使用 PyOpenGL 绘图时出现问题

Trouble plotting with PyOpenGL

我想使用 Qt 和 PyOpenGL 进行一些实时绘图并了解一些 OpenGL,但我什至无法显示我的初始测试数据。

想法是将 x 坐标和 y 坐标存储在不同的缓冲区中,因为 x 坐标很少改变,而 y 坐标几乎每次渲染都会改变。不幸的是,将它们放在不同的缓冲区中给我带来了问题。

现在我没有错误,也没有任何显示,所以我不确定去哪里。

这是我到目前为止的绘图 class:

class GLWidget(QtOpenGL.QGLWidget):
    def __init__(self, parent=None):
        self.parent = parent
        QtOpenGL.QGLWidget.__init__(self, parent)
        self.current_position = 0

    def initializeGL(self):
        self.initGeometry()

        self.vertex_code = """
            #version 120
            attribute vec4 color;
            attribute float x_position;
            attribute float y_position;
            varying vec4 v_color;
            void main()
            {
                gl_Position = vec4(x_position, y_position, 0.0, 1.0);
                v_color = color;
            } """

        self.fragment_code = """
            #version 120
            varying vec4 v_color;
            void main()
            {
                //gl_FragColor = v_color;
                gl_FragColor = vec4(1,1,1,1);
            } """

        ## Build and activate program
        # Request program and shader slots from GPU
        self.program = GL.glCreateProgram()
        self.vertex = GL.glCreateShader(GL.GL_VERTEX_SHADER)
        self.fragment = GL.glCreateShader(GL.GL_FRAGMENT_SHADER)

        # Set shaders source
        GL.glShaderSource(self.vertex, self.vertex_code)
        GL.glShaderSource(self.fragment, self.fragment_code)

        # Compile shaders
        GL.glCompileShader(self.vertex)
        GL.glCompileShader(self.fragment)

        # Attach shader objects to the program
        GL.glAttachShader(self.program, self.vertex)
        GL.glAttachShader(self.program, self.fragment)

        # Build program
        GL.glLinkProgram(self.program)

        # Get rid of shaders (not needed anymore)
        GL.glDetachShader(self.program, self.vertex)
        GL.glDetachShader(self.program, self.fragment)

        # Make program the default program
        GL.glUseProgram(self.program)

        # Create array object
        self.vao = GL.glGenVertexArrays(1)
        GL.glBindVertexArray(self.vao)

        # Request buffer slot from GPU
        self.x_data_buffer = GL.glGenBuffers(1)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.x_data_buffer)
        GL.glBufferData(GL.GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(self.x), self.x, GL.GL_DYNAMIC_DRAW)

        self.y_data_buffer = GL.glGenBuffers(1)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.y_data_buffer)
        GL.glBufferData(GL.GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(self.y), self.y, GL.GL_DYNAMIC_DRAW)




        ## Bind attributes
        #self.stride = self.x.strides[0]
        #self.offset = ctypes.c_void_p(0)
        self.loc = GL.glGetAttribLocation(self.program, "x_position".encode('utf-8'))
        GL.glEnableVertexAttribArray(self.loc)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.x_data_buffer)
        GL.glVertexAttribPointer(self.loc, 1, GL.GL_FLOAT, GL.GL_FALSE, 0, 0)

        #self.stride = self.y.strides[0]
        #self.offset = ctypes.c_void_p(0)
        self.loc = GL.glGetAttribLocation(self.program, "y_position".encode('utf-8'))
        GL.glEnableVertexAttribArray(self.loc)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.y_data_buffer)
        GL.glVertexAttribPointer(self.loc, 1, GL.GL_FLOAT, GL.GL_FALSE, 0, 0)


    def resizeGL(self, width, height):
        if height == 0: height = 1

        GL.glViewport(0, 0, width, height)
        GL.glMatrixMode(GL.GL_PROJECTION)
        GL.glLoadIdentity()
        aspect = width / float(height)

        GLU.gluPerspective(45.0, aspect, 1.0, 100.0)
        GL.glMatrixMode(GL.GL_MODELVIEW)

    def paintGL(self):
        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)

        GL.glDrawArrays(GL.GL_LINE_STRIP, 0, self.bins)


    def initGeometry(self):
        self.bins = 1000

        self.x = np.linspace(-0.5,0.5,self.bins)
        self.y = np.array([np.sin(val*2*np.pi) for val in self.x])
        self.color = np.array([(1,1,1,1) for x in np.arange(self.bins)])


    def addPoint(self, point):
        #print('ADD POINT')
        self.y[self.current_position] = point
        if self.current_position < self.bins-1:
            self.current_position += 1
        else:
            self.current_position = 0

        return True

    def render(self):
        #print('RENDER')
        self.updateGL()
        return True

我还在这里放了一个完整的程序版本:

import sys
from PyQt5.QtWidgets import QDesktopWidget, QMainWindow, QWidget, QAction, qApp, QApplication, QHBoxLayout, QVBoxLayout, QPushButton
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QIcon
from PyQt5 import QtOpenGL

from OpenGL import GL
from OpenGL import GLU
from OpenGL.arrays.arraydatatype import ArrayDatatype

import ctypes
import numpy as np
from threading import Timer, Thread, Event

class OxySensor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.initActions()
        self.initMenuBar()
        self.initRenderTimer()
        self.start()

    def initUI(self):
        self.resize(800,600)
        self.center()
        self.setWindowTitle('OxySensor')

        okButton = QPushButton("OK")
        cancelButton = QPushButton("Cancel")


        hbox = QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(okButton)
        hbox.addWidget(cancelButton)

        vbox = QVBoxLayout()
        #vbox.addStretch(1)
        self.gl_widget = GLWidget()
        vbox.addWidget(self.gl_widget)
        vbox.addLayout(hbox)

        mainWidget = QWidget(self)
        mainWidget.setLayout(vbox)

        self.setCentralWidget(mainWidget)


        self.show()

    def initActions(self):
        self.exitAction = QAction(QIcon('images/close20.png'), '&Exit', self)
        self.exitAction.setShortcut('Ctrl+W')
        self.exitAction.setStatusTip('Exit application')
        self.exitAction.triggered.connect(self.onExit)

    def initMenuBar(self):
        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(self.exitAction)
        return True

    def initRenderTimer(self):
        self.timer = QTimer()
        self.timer.timeout.connect(self.gl_widget.render)
        self.timer.start(100)
        return True

    def start(self):
        self.stop_flag = Event()
        self.thread = SerialPort(self.onTimerExpired, self.stop_flag)
        self.thread.start()
        self.statusBar().showMessage('Ready')

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())
        return True

    def onTimerExpired(self):
        data = np.random.uniform(-1,1)
        self.gl_widget.addPoint(data)
        return True

    def onExit(self):
        self.close()
        return None

    def closeEvent(self,event):
        self.stop_flag.set()
        event.accept()
        return None

class GLWidget(QtOpenGL.QGLWidget):
    def __init__(self, parent=None):
        self.parent = parent
        QtOpenGL.QGLWidget.__init__(self, parent)
        self.yRotDeg = 0.0
        self.current_position = 0

    def initializeGL(self):
        self.initGeometry()

        self.vertex_code = """
            #version 120
            attribute vec4 color;
            attribute float x_position;
            attribute float y_position;
            varying vec4 v_color;
            void main()
            {
                gl_Position = vec4(x_position, y_position, 0.0, 1.0);
                v_color = color;
            } """

        self.fragment_code = """
            #version 120
            varying vec4 v_color;
            void main()
            {
                //gl_FragColor = v_color;
                gl_FragColor = vec4(1,1,1,1);
            } """

        ## Build and activate program
        # Request program and shader slots from GPU
        self.program = GL.glCreateProgram()
        self.vertex = GL.glCreateShader(GL.GL_VERTEX_SHADER)
        self.fragment = GL.glCreateShader(GL.GL_FRAGMENT_SHADER)

        # Set shaders source
        GL.glShaderSource(self.vertex, self.vertex_code)
        GL.glShaderSource(self.fragment, self.fragment_code)

        # Compile shaders
        GL.glCompileShader(self.vertex)
        GL.glCompileShader(self.fragment)

        # Attach shader objects to the program
        GL.glAttachShader(self.program, self.vertex)
        GL.glAttachShader(self.program, self.fragment)

        # Build program
        GL.glLinkProgram(self.program)

        # Get rid of shaders (not needed anymore)
        GL.glDetachShader(self.program, self.vertex)
        GL.glDetachShader(self.program, self.fragment)

        # Make program the default program
        GL.glUseProgram(self.program)

        # Create array object
        self.vao = GL.glGenVertexArrays(1)
        GL.glBindVertexArray(self.vao)

        # Request buffer slot from GPU
        self.x_data_buffer = GL.glGenBuffers(1)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.x_data_buffer)
        GL.glBufferData(GL.GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(self.x), self.x, GL.GL_DYNAMIC_DRAW)

        self.y_data_buffer = GL.glGenBuffers(1)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.y_data_buffer)
        GL.glBufferData(GL.GL_ARRAY_BUFFER, ArrayDatatype.arrayByteCount(self.y), self.y, GL.GL_DYNAMIC_DRAW)




        ## Bind attributes
        #self.stride = self.x.strides[0]
        #self.offset = ctypes.c_void_p(0)
        self.loc = GL.glGetAttribLocation(self.program, "x_position".encode('utf-8'))
        GL.glEnableVertexAttribArray(self.loc)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.x_data_buffer)
        GL.glVertexAttribPointer(self.loc, 1, GL.GL_FLOAT, GL.GL_FALSE, 0, 0)

        #self.stride = self.y.strides[0]
        #self.offset = ctypes.c_void_p(0)
        self.loc = GL.glGetAttribLocation(self.program, "y_position".encode('utf-8'))
        GL.glEnableVertexAttribArray(self.loc)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.y_data_buffer)
        GL.glVertexAttribPointer(self.loc, 1, GL.GL_FLOAT, GL.GL_FALSE, 0, 0)


    def resizeGL(self, width, height):
        if height == 0: height = 1

        GL.glViewport(0, 0, width, height)
        GL.glMatrixMode(GL.GL_PROJECTION)
        GL.glLoadIdentity()
        aspect = width / float(height)

        GLU.gluPerspective(45.0, aspect, 1.0, 100.0)
        GL.glMatrixMode(GL.GL_MODELVIEW)

    def paintGL(self):
        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)

        GL.glDrawArrays(GL.GL_LINE_STRIP, 0, self.bins)


    def initGeometry(self):
        self.bins = 1000

        self.x = np.linspace(-0.5,0.5,self.bins)
        self.y = np.array([np.sin(val*2*np.pi) for val in self.x])
        self.color = np.array([(1,1,1,1) for x in np.arange(self.bins)])


    def addPoint(self, point):
        #print('ADD POINT')
        self.y[self.current_position] = point
        if self.current_position < self.bins-1:
            self.current_position += 1
        else:
            self.current_position = 0

        return True

    def render(self):
        #print('RENDER')
        self.updateGL()
        return True

class SerialPort(Thread):
    def __init__(self, callback, event):
        Thread.__init__(self)
        self.callback = callback
        self.stopped = event
        return None

    def SetInterval(self, time_in_seconds):
        self.delay_period = time_in_seconds
        return True

    def run(self):
        while not self.stopped.wait(0.1):
            self.callback()
        return True

if __name__ == '__main__':
    app = QApplication(sys.argv)
    oxy_sensor = OxySensor()
    sys.exit(app.exec_())

问题很可能是 'OpenGL.GL' 函数的调用:

GL.glVertexAttribPointer(self.loc, 1, GL.GL_FLOAT, GL.GL_FALSE, 0, 0)

请注意,最后一个值为 0(不幸的是,它不会转换为 C++ API 中的 (void *)0,而是转换为某个高地址)。您很可能是指 "offset 0" 而不是 "the address of the 0 object"。 IE。使用 None 代替(确实转换为 (void *)0):

GL.glVertexAttribPointer(self.loc, 1, GL.GL_FLOAT, GL.GL_FALSE, 0, None)

是的,这有点违反直觉。我花了半天时间才弄清楚这就是我自己的代码中输出黑色的原因。

另请注意,如果您使用 Qt 函数(通过 gl = self.context().versionFunctions() 获得)而不是 OpenGL.GL 函数,它们会提供略微不同的API 当你的意思是 "Offset 0".

时,你 实际上传递了 0

你应该看看程序 Avogadro
http://avogadro.cc/wiki/Main_Page 它基于 OpenGL 函数。

源代码是免费的,可能会有用。