top of page
  • Naijia Jin

ShadingViewer with PySide2 and PyOpenGL - (I)

Introduction

The goal is to build a 3d Shading Viewer with Pyside2 and PyOpenGL. This is a project combines my interest in python and graphics. :)

User can use UI to interact with the 3d rendering. They can choose which model to use, controls the translation of this model, and select different shaders to view different effects.

The image shows an early stage work - proof of concept of this project.

QGLWidget

Up to this point, the UI part is all done by pyside2. The rendering window is a self-defined class called GLGeometry, and it is a derived class from QGLWidget.

QGLWidget is a class in the Qt library that provides a widget for rendering OpenGL graphics. It is a subclass of QWidget and can be used in the same way as any other widget in Qt. QGLWidget provides three methods for rendering OpenGL graphics: paintGL, resizeGL, and initializeGL. The paintGL method is called whenever the widget needs to be repainted, the resizeGL method is called whenever the widget is resized, and the initializeGL method is called once before the widget is first displayed. QGLWidget also provides a set of functions for interacting with OpenGL, such as glDrawPixels and glReadPixels, which can be used to draw and read pixel data respectively.


def initializeGL(self):
    print('gl initial')
    glClearColor(0, 0, 0, 0)
    glColor(1, 0, 0, 1)
    glEnable(GL_DEPTH_TEST)

    self.initCube()

def resizeGL(self, w: int, h: int):
    glViewport(0, 0, w, h)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(45, w / float(h), 1, 100)
    glMatrixMode(GL_MODELVIEW)

def paintGL(self):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glPushMatrix()  # push the current matrix to the current stack

    glTranslate(0.0, 0.0, -50.0)  # third, translate cube to specified depth
    glScale(20.0, 20.0, 20.0)  # second, scale cube
    glRotate(60, 1, 0, 0)
    glTranslate(-0.5, -0.5, -0.5)  # first, translate cube center to origin

    glEnableClientState(GL_VERTEX_ARRAY)
    glEnableClientState(GL_COLOR_ARRAY)  # enable the color and vertex array

    glVertexPointer(3, GL_FLOAT, 0, self.vertVBO)
    glColorPointer(3, GL_FLOAT, 0, self.colorVBO)  # set the pointer to the vbo objects

    glDrawElements(GL_QUADS,
                   len(self.cubeIdxArray),
                   GL_UNSIGNED_INT,
                   self.cubeIdxArray)

    glDisableClientState(GL_VERTEX_ARRAY)
    glDisableClientState(GL_COLOR_ARRAY)

    glPopMatrix()  # restore the previous model view matrix

The initializeGL method is called once before the widget is first displayed and is used to set up the OpenGL context for rendering. It sets the background color to black, sets the current drawing color to red, and enables depth testing. The initCube method is called to initialize the data for the cube, but it is not shown in this code.

The resizeGL method is called whenever the widget is resized and is used to set the viewport and projection matrix for rendering. It sets the viewport to cover the entire widget, sets the projection matrix to a perspective projection with a 45 degree field of view, and sets the modelview matrix to the identity matrix.

The paintGL method is called whenever the widget needs to be repainted and is used to render the 3D cube. It clears the color and depth buffers, pushes the current matrix to the matrix stack, translates the cube to a specified depth, scales it, rotates it, and translates the cube center to the origin. It then enables the vertex and color arrays, sets the pointer to the vertex buffer object (VBO) and color buffer object (CBO), and draws the cube using the index array. Finally, it disables the vertex and color arrays, and restores the previous modelview matrix.


To initialize Cube, I learned that I define vertex data, color data and face - vertex relationship as arrays. Learning Reference: https://nrotella.github.io/journal/first-steps-python-qt-opengl.html

def initCube(self):
    self.cubeVertexArray = np.array(
        [[0.0, 0.0, 0.0],
         [1.0, 0.0, 0.0],
         [1.0, 1.0, 0.0],
         [0.0, 1.0, 0.0],
         [0.0, 0.0, 1.0],
         [1.0, 0.0, 1.0],
         [1.0, 1.0, 1.0],
         [0.0, 1.0, 1.0]])  # cube position vertex
    self.vertVBO = vbo.VBO(
        np.reshape(self.cubeVertexArray, (1, -1)).astype(
            np.float32))  # by default the target is GLBUFFER_ARRAY already
    self.vertVBO.bind()  # bind it to be used as the vertex buffer

    # vertex color info
    self.cubeColorArray = np.array(
        [[0.0, 0.0, 0.0],
         [1.0, 0.0, 0.0],
         [1.0, 1.0, 0.0],
         [0.0, 1.0, 0.0],
         [0.0, 0.0, 1.0],
         [1.0, 0.0, 1.0],
         [1.0, 1.0, 1.0],
         [0.0, 1.0, 1.0]])
    self.colorVBO = vbo.VBO(np.reshape(self.cubeColorArray,
                                       (1, -1)).astype(np.float32))
    self.colorVBO.bind()

    # cube id
    self.cubeIdxArray = np.array(
        [0, 1, 2, 3,
         3, 2, 6, 7,
         1, 0, 4, 5,
         2, 1, 5, 6,
         0, 3, 7, 4,
         7, 6, 5, 4])

The cubeVertexArray variable is an array of eight 3D vertices that define the corners of the cube. These vertices are then used to create a vertex buffer object (VBO) using the vbo library. The VBO is created by reshaping the vertex array into a single row and converting it to a 32-bit float array. The VBO is then bound using the bind method, which allows it to be used as a vertex buffer.

The cubeColorArray variable is an array of eight 3D colors that define the color of each vertex. These colors are used to create a color buffer object (CBO) in the same way as the VBO. The CBO is also bound using the bind method.

Finally, the cubeIdxArray variable is an array of indices that define the order in which the vertices are connected to form the faces of the cube. These indices are used to draw the cube using the glDrawElements function in the paintGL method.




1 view
bottom of page