pythonopenglpyqtpyqt5vao

Draw multiple objects using VBOs with PyQt5 and OpenGL?


I am trying to render multiple objects in pyopengl using pyqt5. After following tutorials I created a 3D mesh which uploads a wavefront obj file and renders it with texture. This worked for me:

class Model:
    def __init__(self, file_name, texture_name):
        self.object = ObjectLoader()
        self.object.load_model(file_name)

        //creating and compiling shaders

        glUseProgram(shader)

        vertex_buffer_object = GLuint(0)
        glGenBuffers(1, vertex_buffer_object)
        glBindBuffer(GL_ARRAY_BUFFER,  vertex_buffer_object)
        glBufferData(GL_ARRAY_BUFFER, len(self.object.model) * 4, self.object.c_model, GL_DYNAMIC_DRAW) 
        glDrawArrays(GL_TRIANGLES, 0, len(self.object.vertex_index))
        # vertices
        vertex_offset = 0
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(vertex_offset))
        glEnableVertexAttribArray(0)

        # textures
        texture_offset = len(self.object.vertex_index) * 12
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 2, ctypes.c_void_p(texture_offset))
        glEnableVertexAttribArray(1)

        # normals
        normal_offset = texture_offset + len(self.object.texture_index) * 8
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(normal_offset))
        glEnableVertexAttribArray(2)

        texture = GLuint(0)
        glGenTextures(1, texture)
        glBindTexture(GL_TEXTURE_2D, texture)
        # texture wrapping
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        # texture filtering
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)

        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        image = Image.open(texture_name)
        flipped_image = image.transpose(Image.FLIP_TOP_BOTTOM)
        image_data = image.convert("RGB").tobytes()
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data)
      // setting the view, projection etc matrix     

Since I am working with PyQt5, I have this class were I load the object:

class Obj(QOpenGLWidget):
    def __init__(self, parent):
        QOpenGLWidget.__init__(self, parent)
           
    def initializeGL(self):
        glClearColor(82/255, 95/255, 107/255, 1.0)
        glEnable(GL_DEPTH_TEST)         
        
        self.model = Model("....obj", "texture.png")     
            
        
    def paintGL(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glDrawArrays(GL_TRIANGLES, 0, len(self.model.object.vertex_index))
        
   
        self.update()
        
        
    def resizeGL(self, width, height):
        glViewport(0, 0, width, height)

What I am struggling with is creating in the Obj class another Model. I tried creating it in initializeGL and rendering in paintGL with another glDrawArrays call, but nothing happened, or nothing expected... Any idea of what I am doing wrong?


Solution

  • You have to use a Vertex Array Object. A Vertex Array Object stores all of the state needed to supply vertex data.

    The constructor of the model only creates all the required OpenGL objects. The shader program is not part of the model. Use the same program for all models:

    class Model:
        def __init__(self, file_name, texture_name):
            self.object = ObjectLoader()
            self.object.load_model(file_name)
    
            # create vertex array object
            self.vao = glGenVertexArrays(1)
            glBindVertexArray(self.vao)
    
            vertex_buffer_object = GLuint(0)
            glGenBuffers(1, vertex_buffer_object)
            glBindBuffer(GL_ARRAY_BUFFER,  vertex_buffer_object)
            glBufferData(GL_ARRAY_BUFFER, len(self.object.model) * 4, self.object.c_model, GL_DYNAMIC_DRAW) 
            glDrawArrays(GL_TRIANGLES, 0, len(self.object.vertex_index))
    
            # vertices
            vertex_offset = 0
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(vertex_offset))
            glEnableVertexAttribArray(0)
    
            # textures
            texture_offset = len(self.object.vertex_index) * 12
            glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 2, ctypes.c_void_p(texture_offset))
            glEnableVertexAttribArray(1)
    
            # normals
            normal_offset = texture_offset + len(self.object.texture_index) * 8
            glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, self.object.model.itemsize * 3, ctypes.c_void_p(normal_offset))
            glEnableVertexAttribArray(2)
    
            # create texture
            self.texture = GLuint(0)
            glGenTextures(1, self.texture)
            glBindTexture(GL_TEXTURE_2D, self.texture)
            # texture wrapping
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
            # texture filtering
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    
            image = Image.open(texture_name)
            flipped_image = image.transpose(Image.FLIP_TOP_BOTTOM)
            image_data = image.convert("RGB").tobytes()
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, image_data)
    

    Add a method to the class which binds the OpenGL objects (VAO and texture) and draws the mesh:

    class Model:
        # [...]
    
        def draw(self):
    
            // bind texture
            glBindTexture(GL_TEXTURE_2D, self.texture)
    
            // create vertex array object
            glBindVertexArray(self.vao)
    
            // draw object
            glDrawArrays(GL_TRIANGLES, 0, len(self.object.vertex_index))
    

    With this setup you can draw multiple models:

    class Obj(QOpenGLWidget):
        def __init__(self, parent):
            QOpenGLWidget.__init__(self, parent)
    
        def initializeGL(self):
            glClearColor(82/255, 95/255, 107/255, 1.0)
            glEnable(GL_DEPTH_TEST)
            glEnable(GL_BLEND)
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    
            self.model1 = Model("....obj", "texture.png") 
            self.model2 = Model("....obj", "texture2.png")  
            # [...] 
    
        def paintGL(self):
            
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
            
            # install program
            glUseProgram(self.shader)
    
            # setting the view, projection etc matrix
            # [...]
            
            self.model1.draw()
            self.model2.draw()
            # [...] 
            
            self.update()
            
        def resizeGL(self, width, height):
            glViewport(0, 0, width, height)
    

    The models can be organized in a list, too:

    class Obj(QOpenGLWidget):
       # [...]
    
        def initializeGL(self):
            # [...]
    
            self.models = [
                Model("....obj", "texture.png"),
                Model("....obj", "texture2.png")  
                # [...]
            ] 
    
        def paintGL(self):
            # [...]
    
            for model in self.models:
                model.draw()
    
            # [...]