PyOpenGL如何将VAO重新绑定到另一个VBO以便快速切换需要绘制的数据
PyOpenGL how to rebind VAO to another VBO in order to quickly switch data that needs to be drawn
我正在使用 PyQt5 QOpenGLWidget 简单地在屏幕上用不同的颜色绘制一些点。我准备了两个数据集,每个都包含它们的坐标和颜色,总共 4 个数组,我希望程序每 100 毫秒绘制一个数据集(通过使用 QTimer)。我将这 4 个数组放入 4 个 VBO,对于每个 paintGL() 调用,我想从一个数据集切换到另一个数据集,也就是说,如果此帧使用 VBO0(坐标)、VBO1(颜色),那么下一帧将使用 VBO2(坐标)、VBO3(颜色),反之亦然。
我的想法是使用 VAO,并且由于 VBO0/1 与 VBO2/3 具有完全相同的结构,对于每个新帧,我可以将 VAO 重新绑定到另一个 VBO。然而这并不像预期的那样有效,那么实现我的目标的正确方法是什么?
最小示例:
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_()
预期输出:
两个数据集每 100 毫秒在屏幕上切换一次(在 QTimer 中设置),例如:
实际输出:
第一帧是正确的,但是第一次切换后屏幕上出现了一个白色三角形而不是点,并且没有进一步的效果。
在 paintGL
中每次重新绑定顶点数组对象 (self.VAO),而不是在 initBuffers
中重新绑定一次。官方 doco 不是很清楚,但是这些早期的答案表明更改 VAO 上的属性可能不会对当前绑定生效。
When is What bound to a VAO?
我正在使用 PyQt5 QOpenGLWidget 简单地在屏幕上用不同的颜色绘制一些点。我准备了两个数据集,每个都包含它们的坐标和颜色,总共 4 个数组,我希望程序每 100 毫秒绘制一个数据集(通过使用 QTimer)。我将这 4 个数组放入 4 个 VBO,对于每个 paintGL() 调用,我想从一个数据集切换到另一个数据集,也就是说,如果此帧使用 VBO0(坐标)、VBO1(颜色),那么下一帧将使用 VBO2(坐标)、VBO3(颜色),反之亦然。
我的想法是使用 VAO,并且由于 VBO0/1 与 VBO2/3 具有完全相同的结构,对于每个新帧,我可以将 VAO 重新绑定到另一个 VBO。然而这并不像预期的那样有效,那么实现我的目标的正确方法是什么?
最小示例:
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_()
预期输出:
两个数据集每 100 毫秒在屏幕上切换一次(在 QTimer 中设置),例如:
实际输出:
第一帧是正确的,但是第一次切换后屏幕上出现了一个白色三角形而不是点,并且没有进一步的效果。
在 paintGL
中每次重新绑定顶点数组对象 (self.VAO),而不是在 initBuffers
中重新绑定一次。官方 doco 不是很清楚,但是这些早期的答案表明更改 VAO 上的属性可能不会对当前绑定生效。
When is What bound to a VAO?