You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt6调用glBufferSubData更新数组后paintGL无法正常刷新问题

问题描述

按下左右方向键让4个点绕原点旋转时,屏幕无法正确更新,渲染出现破碎。顶点数组已正确传入缓冲区,但找不到异常原因。
破碎渲染效果


相关代码

GLWidget类代码

import Shader
from Math_3d import Vector3f
from OpenGL.GL import *
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from PyQt6.QtCore import Qt


class GLWidget(QOpenGLWidget):
    def __init__(self):
        super().__init__()
        # Vertex Buffer Object
        # Create point vertex data
        self.v3f = Vector3f([[0.5, 0.5, 0.0],
                            [-0.5, -0.5, 0.0],
                            [0.5, -0.5, 0.0],
                            [-0.5, 0.5, 0.0]])
        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)

    def initializeGL(self):
               program = glCreateProgram()
        buffer = glGenBuffers(1)
        # Request program and shader slots from the GPU
        vertex = glCreateShader(GL_VERTEX_SHADER)
        fragment = glCreateShader(GL_FRAGMENT_SHADER)

        # Set shader sources
        glShaderSource(vertex, Shader.vertex_code)
        glShaderSource(fragment, Shader.fragment_code)
        # Compile shaders and check that they have compiled
        glCompileShader(vertex)
        glCompileShader(fragment)
        if not glGetShaderiv(vertex, GL_COMPILE_STATUS):
            report_shader = glGetShaderInfoLog(vertex)
            print(report_shader)
            raise RuntimeError("Vertex shader compilation error")

        if not glGetShaderiv(fragment, GL_COMPILE_STATUS):
            report_frag = glGetShaderInfoLog(fragment)
            print(report_frag)
            raise RuntimeError("Fragment shader compilation error")

        # Link shaders to program
        glAttachShader(program, vertex)
        glAttachShader(program, fragment)
        glLinkProgram(program)

        if not glGetProgramiv(program, GL_LINK_STATUS):
            print(glGetProgramInfoLog(program))
            raise RuntimeError('Linking error')
        # Get rid of shaders
        glDetachShader(program, vertex)
        glDetachShader(program, fragment)
        # Make default program to run
        glUseProgram(program)

        # Make this buffer the default one
        glBindBuffer(GL_ARRAY_BUFFER, buffer)
        # Vertex Array Buffer
        vao = glGenVertexArrays(1)
        glBindVertexArray(vao)
        strides = int(self.v3f.buffdata.nbytes/self.v3f.buffdata.itemsize)
        loc = glGetAttribLocation(program, 'position')
        glEnableVertexAttribArray(loc)
        glVertexAttribPointer(loc, 3, GL_FLOAT, False, strides, None)
        glBufferData(GL_ARRAY_BUFFER, self.v3f.buffdata.nbytes, self.v3f.buffdata, GL_DYNAMIC_DRAW)

   def paintGL(self):
        glClear(GL_COLOR_BUFFER_BIT)
        glPointSize(20)
        glDrawArrays(GL_POINTS, 0, self.v3f.buffdata.itemsize)

    def keyPressEvent(self, e):
        if e.key() == Qt.Key.Key_Left:
            self.v3f.Rotate(-0.1)
            print("Left")
        elif e.key() == Qt.Key.Key_Right:
            self.v3f.Rotate(0.1)
            print("Right")
        elif e.key() == Qt.Key.Key_Escape or Qt.Key.Key_Q:
            exit()
        self.update()

Vector3f模块代码

from OpenGL.GL import *
import numpy as np
np.set_printoptions(precision=3, suppress=True)


class Vector3f:
    def __init__(self, ndarray):
        self.data = np.array([ndarray], dtype=np.float32)
        # Convert n-dimension arrays to one long array
        self.buffdata = self.data.ravel()
        self.orig = self.data
        self.__rval = 0.0
        self.__sval = 0.0

    def Rotate(self, val):  # Rotation Matrix
        self.__rval += val  # Create a memory value and add to that value
        m1 = np.asmatrix(self.data)  # Convert n-dimension array to matrix
        rot2 = np.matrix([[np.cos(self.__rval), np.sin(-self.__rval), 0.0],  # Create rotation matrix
                         [np.sin(self.__rval), np.cos(self.__rval), 0.0],
                         [0.0, 0.0, 0.0]], dtype=np.float32)
        rotator = m1 * rot2  # Matrix multiplication
        self.buffdata = rotator.ravel()  # Convert result to array to be used on GPU buffer
        glBufferSubData(GL_ARRAY_BUFFER, 0, self.buffdata.data.nbytes, self.buffdata.data)

问题修复

以下是导致渲染破碎的关键问题及修复方案:

1. 缓冲区未持久绑定

initializeGL中buffer是局部变量,方法执行后销毁,后续调用glBufferSubData无法确保操作目标是正确的顶点缓冲区。

  • 修复:将buffer保存为GLWidget的实例变量self.buffer,并在旋转时传入Vector3f的Rotate方法,操作前绑定该缓冲区。

2. 顶点数量参数逻辑错误

paintGL中glDrawArrays的第三个参数使用self.v3f.buffdata.itemsize(数组元素字节数),虽数值巧合等于顶点数,但逻辑完全错误。

  • 修复:直接传入顶点总数4,或通过self.v3f.data.shape[0]动态获取。

3. 旋转矩阵规范错误

原旋转矩阵第三行第三列为0.0,会强制z坐标清零,不符合2D旋转矩阵规范(z轴应保持不变)。

  • 修复:将旋转矩阵第三行改为[0.0, 0.0, 1.0]。

4. 旋转状态未累积更新

Rotate方法始终基于初始数据self.data计算旋转,导致每次旋转都是重置而非累积。

  • 修复:旋转后更新self.data为最新结果,确保下一次旋转基于当前状态。

修复后的代码

修复后的GLWidget类

import Shader
from Math_3d import Vector3f
from OpenGL.GL import *
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
from PyQt6.QtCore import Qt


class GLWidget(QOpenGLWidget):
    def __init__(self):
        super().__init__()
        self.v3f = Vector3f([[0.5, 0.5, 0.0],
                            [-0.5, -0.5, 0.0],
                            [0.5, -0.5, 0.0],
                            [-0.5, 0.5, 0.0]])
        self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
        self.buffer = None
        self.program = None

    def initializeGL(self):
        self.program = glCreateProgram()
        self.buffer = glGenBuffers(1)
        vertex = glCreateShader(GL_VERTEX_SHADER)
        fragment = glCreateShader(GL_FRAGMENT_SHADER)

        glShaderSource(vertex, Shader.vertex_code)
        glShaderSource(fragment, Shader.fragment_code)
        glCompileShader(vertex)
        glCompileShader(fragment)

        if not glGetShaderiv(vertex, GL_COMPILE_STATUS):
            print(glGetShaderInfoLog(vertex))
            raise RuntimeError("Vertex shader compilation error")
        if not glGetShaderiv(fragment, GL_COMPILE_STATUS):
            print(glGetShaderInfoLog(fragment))
            raise RuntimeError("Fragment shader compilation error")

        glAttachShader(self.program, vertex)
        glAttachShader(self.program, fragment)
        glLinkProgram(self.program)

        if not glGetProgramiv(self.program, GL_LINK_STATUS):
            print(glGetProgramInfoLog(self.program))
            raise RuntimeError('Linking error')

        glDetachShader(self.program, vertex)
        glDetachShader(self.program, fragment)
        glUseProgram(self.program)

        glBindBuffer(GL_ARRAY_BUFFER, self.buffer)
        vao = glGenVertexArrays(1)
        glBindVertexArray(vao)
        stride = 3 * 4  # 3个float × 4字节/浮点数
        loc = glGetAttribLocation(self.program, 'position')
        glEnableVertexAttribArray(loc)
        glVertexAttribPointer(loc, 3, GL_FLOAT, False, stride, None)
        glBufferData(GL_ARRAY_BUFFER, self.v3f.buffdata.nbytes, self.v3f.buffdata, GL_DYNAMIC_DRAW)

    def paintGL(self):
        glClear(GL_COLOR_BUFFER_BIT)
        glPointSize(20)
        glDrawArrays(GL_POINTS, 0, 4)  # 传入正确的顶点数量

    def keyPressEvent(self, e):
        if e.key() == Qt.Key.Key_Left:
            self.v3f.Rotate(-0.1, self.buffer)
            print("Left")
        elif e.key() == Qt.Key.Key_Right:
            self.v3f.Rotate(0.1, self.buffer)
            print("Right")
        elif e.key() in (Qt.Key.Key_Escape, Qt.Key.Key_Q):
            exit()
        self.update()

修复后的Vector3f类

from OpenGL.GL import *
import numpy as np
np.set_printoptions(precision=3, suppress=True)


class Vector3f:
    def __init__(self, ndarray):
        self.data = np.array(ndarray, dtype=np.float32)  # 移除外层多余[],形状变为(4,3)
        self.buffdata = self.data.ravel()
        self.orig = self.data.copy()  # 保存原始数据副本
        self.__rval = 0.0
        self.__sval = 0.0

    def Rotate(self, val, buffer):
        self.__rval += val
        m1 = np.asmatrix(self.data)
        # 修正旋转矩阵的z轴分量
        rot2 = np.matrix([[np.cos(self.__rval), np.sin(-self.__rval), 0.0],
                         [np.sin(self.__rval), np.cos(self.__rval), 0.0],
                         [0.0, 0.0, 1.0]], dtype=np.float32)
        rotator = m1 * rot2
        # 更新当前数据为旋转后的结果
        self.data = np.array(rotator)
        self.buffdata = rotator.ravel()
        # 绑定正确缓冲区后更新数据
        glBindBuffer(GL_ARRAY_BUFFER, buffer)
        glBufferSubData(GL_ARRAY_BUFFER, 0, self.buffdata.nbytes, self.buffdata)

内容的提问来源于stack exchange,提问作者Treslav

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 09:50:40