Search code examples
pythonopenglpyqt5pyopenglopengl-compat

How does PyOpenGL pick the colors to draw each shape?


I am in the middle of a PyQt5 PyOpenGL project. I am trying to draw a white wireframe cube with a bunch of colored solid cubes. Wireframe cubes are drawn from a list of tuple points, and a list of tuple references to those points. Solid cubes are drawn from a list of tuple references to the points. Here is the cube code:

class cube():
    render = True
    solid = False
    color = (1, 1, 1)

    def config(self, x, y, z, size = 0.1, solid = False, color = (1, 1, 1)):
        self.solid = solid
        self.color = color
        self.size = size / 2
        s = self.size
        self.vertices = [
                         (-s + x, s + y, -s + z),
                         (s + x, s + y, -s + z),
                         (s + x, -s + y, -s + z),
                         (-s + x, -s + y, -s + z),
                         (-s + x, s + y, s + z),
                         (s + x, s + y, s + z),
                         (s + x, -s + y, s + z),
                         (-s + x, -s + y, s + z)
                       ]
        self.edges = [
                      (0,1), (0,3), (0,4), (2,1),
                      (2,3), (2,6), (7,3), (7,4),
                      (7,6), (5,1), (5,4), (5,6)
                     ]
        self.facets = [
                       (0, 1, 2, 3), (0, 1, 6, 5),
                       (0, 3, 7, 4), (6, 5, 1, 2),
                       (6, 7, 4, 5), (6, 7, 3, 2)
                      ]
    def show(self):
        self.render = True
    def hide(self):
        self.render = False

To render a cube, I get the size of a list held in my mainWindow class, then append an instance of the cube class to that list. I can then reference that instance by using the size before appending. Here is the code for the render function:

def paintGL(self):
    glLoadIdentity()
    gluPerspective(45, self.width / self.height, 0.1, 110.0)    #set perspective?
    glTranslatef(0, 0, self.zoomLevel)    #I used -10 instead of -2 in the PyGame version.
    glRotatef(self.rotateDegreeV, 1, 0, 0)    #I used 2 instead of 1 in the PyGame version.
    glRotatef(self.rotateDegreeH, 0, 0, 1)
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

    if len(self.shapes) != 0:
        glBegin(GL_LINES)
        for s in self.shapes:
            if s.render and not s.solid:
                for e in s.edges:
                    for v in e:
                        glVertex3fv(s.vertices[v])
        glEnd()

        glBegin(GL_QUADS)
        for s in self.shapes:
            if s.render and s.solid:
                for f in s.facets:
                    for v in f:
                        glColor3fv(s.color)
                        glVertex3fv(s.vertices[v])
        glEnd()

If I render just one cube in wireframe, it renders white. If I add a red solid cube and a blue solid cube after it, the wireframe cube is colored in the last color used, whatever it may be. For example:

    self.shapes.append(self.cube())
    self.shapes.append(self.cube())
    self.shapes.append(self.cube())
    self.shapes[0].config(-1, 0, 0, size = 0.5, solid = False)
    self.shapes[1].config(0, 0, 0, size = 0.5, solid = True, color = (1, 0, 0))
    self.shapes[2].config(1, 0, 0, size = 0.5, solid = True, color = (0, 0, 1))

Result:

cubes

How can I make my wireframes render in the default white or a different color? I would expect glClear() would reset it and draw the wireframe in white, given that it is the first one.

Here is the full code:

import sys
from PyQt5.QtWidgets import (
                             QApplication, QMainWindow, QSlider,
                             QOpenGLWidget, QLabel, QPushButton
                            )
from PyQt5.QtCore import Qt
from OpenGL.GL import (
                       glLoadIdentity, glTranslatef, glRotatef,
                       glClear, glBegin, glEnd,
                       glColor3fv, glVertex3fv,
                       GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT,
                       GL_QUADS, GL_LINES
                      )
from OpenGL.GLU import gluPerspective

class mainWindow(QMainWindow):    #Main class.
    shapes = []    #this will hold instances of the following classes: 
    zoomLevel = -10
    rotateDegreeV = -90
    rotateDegreeH = 0

    class cube():
        render = True
        solid = False
        color = (1, 1, 1)

        def config(self, x, y, z, size = 0.1, solid = False, color = (1, 1, 1)):
            self.solid = solid
            self.color = color
            self.size = size
            s = self.size / 2
            self.vertices = [
                             (-s + x, s + y, -s + z),
                             (s + x, s + y, -s + z),
                             (s + x, -s + y, -s + z),
                             (-s + x, -s + y, -s + z),
                             (-s + x, s + y, s + z),
                             (s + x, s + y, s + z),
                             (s + x, -s + y, s + z),
                             (-s + x, -s + y, s + z)
                            ]
            self.edges = [
                          (0,1), (0,3), (0,4), (2,1),
                          (2,3), (2,6), (7,3), (7,4),
                          (7,6), (5,1), (5,4), (5,6)
                         ]
            self.facets = [
                           (0, 1, 2, 3), (0, 1, 6, 5),
                           (0, 3, 7, 4), (6, 5, 1, 2),
                           (6, 7, 4, 5), (6, 7, 3, 2)
                          ]
        def show(self):
            self.render = True
        def hide(self):
            self.render = False

    def keyPressEvent(self, event):    #This is the keypress detector. I use this to determine input to edit grids.
        try:
            key = event.key()
            if key == 87:
                self.rotateV(5)
            elif key == 65:
                self.rotateH(5)
            elif key == 83:
                self.rotateV(-5)
            elif key == 68:
                self.rotateH(-5)
            elif key == 67:
                self.zoom(1)
            elif key == 88:
                self.zoom(-1)
        except:
            pass

    def __init__(self):
        super(mainWindow, self).__init__()
        self.width = 700    #Variables used for the setting of the size of everything
        self.height = 600
        self.setGeometry(0, 0, self.width, self.height)    #Set the window size
        self.shapes.append(self.cube())
        self.shapes.append(self.cube())
        self.shapes.append(self.cube())
        self.shapes[0].config(-1, 0, 0, size = 0.5, solid = False)
        self.shapes[1].config(0, 0, 0, size = 0.5, solid = True, color = (1, 0, 0))
        self.shapes[2].config(1, 0, 0, size = 0.5, solid = True, color = (0, 0, 1))

    def setupUI(self):
        self.openGLWidget = QOpenGLWidget(self)    #Create the GLWidget
        self.openGLWidget.setGeometry(0, 0, self.width, self.height)    #Size it the same as the window.
        self.openGLWidget.initializeGL()
        self.openGLWidget.resizeGL(self.width, self.height)    #Resize GL's knowledge of the window to match the physical size?
        self.openGLWidget.paintGL = self.paintGL    #override the default function with my own?


    def zoom(self, value):
        self.zoomLevel += value
        self.openGLWidget.update()

    def rotateV(self, value):
        self.rotateDegreeV += value
        self.openGLWidget.update()

    def rotateH(self, value):
        self.rotateDegreeH += value
        self.openGLWidget.update()

    def paintGL(self):
        glLoadIdentity()
        gluPerspective(45, self.width / self.height, 0.1, 110.0)    #set perspective?
        glTranslatef(0, 0, self.zoomLevel)    #I used -10 instead of -2 in the PyGame version.
        glRotatef(self.rotateDegreeV, 1, 0, 0)    #I used 2 instead of 1 in the PyGame version.
        glRotatef(self.rotateDegreeH, 0, 0, 1)
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)

        if len(self.shapes) != 0:
            glBegin(GL_LINES)
            for s in self.shapes:
                if s.render and not s.solid:
                    for e in s.edges:
                        for v in e:
                            glVertex3fv(s.vertices[v])
            glEnd()

            glBegin(GL_QUADS)
            for s in self.shapes:
                if s.render and s.solid:
                    for f in s.facets:
                        for v in f:
                            glColor3fv(s.color)
                            glVertex3fv(s.vertices[v])
            glEnd()


app = QApplication([])
window = mainWindow()
window.setupUI()
window.show()
sys.exit(app.exec_())

Solution

  • OpenGL is a state engine. Once a state is set, it is kept until it is changed again, even beyond frames. The current color is a global state. When glColor* is called, then the current color is set.
    When glVertex* is called, then the current color, normal and texture coordinates are associated with the vertex.

    That means, the proper color has to be set before the vertices are specified. You missed to set the color attribute, before you draw the wireframe cube:

    class mainWindow(QMainWindow):    #Main class.
        # [...]
    
        def paintGL(self):
            # [...]
    
            if len(self.shapes) != 0:
                glBegin(GL_LINES)
                for s in self.shapes:
                    glColor3fv(s.color)  # <------------------------
                    if s.render and not s.solid:
                        for e in s.edges:
                            for v in e:
                                glVertex3fv(s.vertices[v])
                glEnd()
    
                glBegin(GL_QUADS)
                for s in self.shapes:
                    glColor3fv(s.color)
                    if s.render and s.solid:
                        for f in s.facets:
                            for v in f:
                                glVertex3fv(s.vertices[v])
                glEnd()
    

    Note, it is not necessary to set the current color before each call to glVertex3fv. It is sufficient to set the current color once, when it is changed. The new color is associated to all the following vertices.