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?