Draw multiple objects using VBOs with PyQt5 and OpenGL?

Question:

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?

Asked By: user1234352

||

Answers:

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()

        # [...] 
Answered By: Rabbid76
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.