I am using PyQt5 QOpenGLWidget to simply draw some points on the screen with different colors. I prepared two datasets, each containing their coordinates and their colors, 4 arrays on total, and I want the program to paint one of the datasets for each 100ms (by using a QTimer). I put these 4 arrays into 4 VBOs, and for each paintGL() call I want to switch from a dataset to another, that is, if this frame uses VBO0(coordinate), VBO1(color), then next frame will use VBO2(coordinate), VBO3(color), and vice versa.
My idea is to use a VAO, and since VBO0/1 has exactly same structure with VBO2/3, for each new frame I can just rebind the VAO to another VBO. However this does not work as expected, so what is the correct way of achieving my goal?
Minimum Example:
import numpy as np
from PyQt5.QtWidgets import QOpenGLWidget, QApplication, QWidget, QHBoxLayout
from PyQt5.QtCore import QTimer
from OpenGL.GL import *
def compileShader(vsSource, fsSource):
vertexShader = glCreateShader(GL_VERTEX_SHADER)
glShaderSource(vertexShader, vsSource)
glCompileShader(vertexShader)
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER)
glShaderSource(fragmentShader, fsSource)
glCompileShader(fragmentShader)
program = glCreateProgram()
glAttachShader(program, vertexShader)
glAttachShader(program, fragmentShader)
glLinkProgram(program)
glDeleteShader(vertexShader)
glDeleteShader(fragmentShader)
return program
class MyOpenGLWidget(QOpenGLWidget):
def __init__(self):
super().__init__()
def initializeGL(self):
# with open('./shaders/vertexShader.shader', 'r') as f:
# vsSource = f.read()
# with open('./shaders/fragmentShader.shader', 'r') as f:
# fsSource = f.read()
vsSource = """
#version 450 core
layout (location = 0) in vec4 position;
layout (location = 1) in vec4 color;
out vec4 vs_Color;
void main() {
gl_Position = position;
gl_PointSize = 5.;
vs_Color = color;
}
"""
fsSource = """
#version 450 core
out vec4 FragColor;
in vec4 vs_Color;
void main() {
FragColor = vs_Color;
}
"""
self.program = compileShader(vsSource, fsSource)
self.initBuffers()
self.timer = QTimer(self)
self.timer.timeout.connect(self.onTimeout)
self.timer.start(100)
self.dataset = 1
def paintGL(self):
bgColor = np.array([0, 0, 0, 1], dtype=np.float32)
glClearBufferfv(GL_COLOR, 0, bgColor)
glUseProgram(self.program)
glEnable(GL_PROGRAM_POINT_SIZE)
glDrawArrays(GL_POINTS, 0, self.n)
def generateRandomData(self, n, mode='vertex'):
if mode == 'vertex':
dx = np.random.rand(n) * 2 - 1
dy = np.random.rand(n) * 2 - 1
zeros = np.zeros(n)
ones = np.ones(n)
data = np.vstack([dx, dy, zeros, ones]).T.astype(np.float32)
return data.flatten()
elif mode == 'color':
r = np.random.rand(n)
g = np.random.rand(n)
b = np.random.rand(n)
ones = np.ones(n)
data = np.vstack([r, g, b, ones]).T.astype(np.float32)
return data.flatten()
def initBuffers(self):
self.n = 100
self.vertexData1 = self.generateRandomData(self.n, mode='vertex')
self.colorData1 = self.generateRandomData(self.n, mode='color')
self.vertexData2 = self.generateRandomData(self.n, mode='vertex')
self.colorData2 = self.generateRandomData(self.n, mode='color')
self.buffers = np.empty(4, dtype=np.uint32)
glCreateBuffers(4, self.buffers)
for buffer in self.buffers:
glBindBuffer(GL_ARRAY_BUFFER, buffer)
glNamedBufferStorage(buffer, self.vertexData1.nbytes, None,
GL_DYNAMIC_STORAGE_BIT)
glNamedBufferSubData(self.buffers[0], 0, self.vertexData1.nbytes, self.vertexData1)
glNamedBufferSubData(self.buffers[1], 0, self.colorData1.nbytes, self.colorData1)
glNamedBufferSubData(self.buffers[2], 0, self.vertexData2.nbytes, self.vertexData2)
glNamedBufferSubData(self.buffers[3], 0, self.colorData2.nbytes, self.colorData2)
self.VAO = GLuint(0)
glCreateVertexArrays(1, self.VAO)
glEnableVertexArrayAttrib(self.VAO, 0)
glEnableVertexArrayAttrib(self.VAO, 1)
glVertexArrayAttribFormat(self.VAO, 0, 4, GL_FLOAT, GL_FALSE, 0)
glVertexArrayAttribFormat(self.VAO, 1, 4, GL_FLOAT, GL_FALSE, 0)
glVertexArrayAttribBinding(self.VAO, 0, 0)
glVertexArrayAttribBinding(self.VAO, 1, 1)
glVertexArrayVertexBuffer(self.VAO, 0, self.buffers[0], 0, 4 * 4)
glVertexArrayVertexBuffer(self.VAO, 1, self.buffers[1], 0, 4 * 4)
glBindVertexArray(self.VAO)
def onTimeout(self):
if self.dataset == 1:
glVertexArrayVertexBuffer(self.VAO, 0, self.buffers[2], 0, 4 * 4)
glVertexArrayVertexBuffer(self.VAO, 1, self.buffers[3], 0, 4 * 4)
self.dataset = 2
elif self.dataset == 2:
glVertexArrayVertexBuffer(self.VAO, 0, self.buffers[0], 0, 4 * 4)
glVertexArrayVertexBuffer(self.VAO, 1, self.buffers[1], 0, 4 * 4)
self.dataset = 1
self.update()
app = QApplication([])
win = QWidget()
win.showMaximized()
layout = QHBoxLayout()
win.setLayout(layout)
layout.addWidget(MyOpenGLWidget())
win.show()
app.exec_()
Expected Output:
Two datsets switches on the screen every 100ms (as set in QTimer) like:
Real Output:
First frame is correct, but after first switching there is a white triangle on the screen instead of points, and there's no further effect.
Rebind your vertex array object (self.VAO) each time in paintGL
rather than just once in initBuffers
. The official doco is not very clear, but these earlier answers suggest that changing attribs on a VAO probably won't take effect on the current binding.