Search code examples
pythonopenglglslvbopyopengl

PyOpenGL not drawing big VAO


I want to render a large scene made up of many cubes. My first take on this was to create a single cube VBO, and then draw it multiple times using a uniform model matrix to translate it. This was slow, as I was calling glDrawArrays many times per frame.

So I decided to instead use a single, giant VBO with every single cube vertex and an additional translation vector. Basically, I took my old generic cube VBO plus the translation vector, and appended that list to my VBO's list for every cube that I want to draw. I then bind a VAO and draw it. I also changed my vertex shader so that it accepts that translation vector as a vertex attribute, and I generate a model matrix inside the shader.

Now, this doesn't return any errors, which is great, but it doesn't actually draw anything, which is not great, even though the process is fairly similar to what I was doing before.

Here's some code:

  • Whopper VBO creator:

    class render:
        def __init__(self, coords_list):
    
            self.render_list = []
            for i in coords_list:
                self.render_list.append([
                    # Cube model       Texture     Translation
                    0.0,  0.0,  0.0,  1.0, 1.0, i[0], i[1], i[2],
                    1.0,  0.0,  0.0,  0.0, 1.0, i[0], i[1], i[2],
                    1.0,  1.0,  0.0,  0.0, 0.0, i[0], i[1], i[2],
                    1.0,  1.0,  0.0,  0.0, 0.0, i[0], i[1], i[2],
                    0.0,  1.0,  0.0,  1.0, 0.0, i[0], i[1], i[2],
                    0.0,  0.0,  0.0,  1.0, 1.0, i[0], i[1], i[2],
    
                    0.0,  0.0,  1.0,  1.0, 1.0, i[0], i[1], i[2],
                    1.0,  0.0,  1.0,  0.0, 1.0, i[0], i[1], i[2],
                    1.0,  1.0,  1.0,  0.0, 0.0, i[0], i[1], i[2],
                    1.0,  1.0,  1.0,  0.0, 0.0, i[0], i[1], i[2],
                    0.0,  1.0,  1.0,  1.0, 0.0, i[0], i[1], i[2],
                    0.0,  0.0,  1.0,  1.0, 1.0, i[0], i[1], i[2],
    
                    0.0,  1.0,  1.0,  1.0, 0.0, i[0], i[1], i[2],
                    0.0,  1.0,  0.0,  0.0, 0.0, i[0], i[1], i[2],
                    0.0,  0.0,  0.0,  0.0, 1.0, i[0], i[1], i[2],
                    0.0,  0.0,  0.0,  0.0, 1.0, i[0], i[1], i[2],
                    0.0,  0.0,  1.0,  1.0, 1.0, i[0], i[1], i[2],
                    0.0,  1.0,  1.0,  1.0, 0.0, i[0], i[1], i[2],
    
                    1.0,  1.0,  1.0,  1.0, 0.0, i[0], i[1], i[2],
                    1.0,  1.0,  0.0,  0.0, 0.0, i[0], i[1], i[2],
                    1.0,  0.0,  0.0,  0.0, 1.0, i[0], i[1], i[2],
                    1.0,  0.0,  0.0,  0.0, 1.0, i[0], i[1], i[2],
                    1.0,  0.0,  1.0,  1.0, 1.0, i[0], i[1], i[2],
                    1.0,  1.0,  1.0,  1.0, 0.0, i[0], i[1], i[2],
    
                    0.0,  0.0,  0.0,  0.0, 1.0, i[0], i[1], i[2],
                    1.0,  0.0,  0.0,  1.0, 1.0, i[0], i[1], i[2],
                    1.0,  0.0,  1.0,  1.0, 0.0, i[0], i[1], i[2],
                    1.0,  0.0,  1.0,  1.0, 0.0, i[0], i[1], i[2],
                    0.0,  0.0,  1.0,  0.0, 0.0, i[0], i[1], i[2],
                    0.0,  0.0,  0.0,  0.0, 1.0, i[0], i[1], i[2],
    
                    0.0,  1.0,  0.0,  0.0, 1.0, i[0], i[1], i[2],
                    1.0,  1.0,  0.0,  1.0, 1.0, i[0], i[1], i[2],
                    1.0,  1.0,  1.0,  1.0, 0.0, i[0], i[1], i[2],
                    1.0,  1.0,  1.0,  1.0, 0.0, i[0], i[1], i[2],
                    0.0,  1.0,  1.0,  0.0, 0.0, i[0], i[1], i[2],
                    0.0,  1.0,  0.0,  0.0, 1.0, i[0], i[1], i[2],
                ])
                print('Cube added!')
    
        def create_buffers(self):
            render_vbo, self.render_vao = glGenBuffers(1), glGenVertexArrays(1)
            glBindVertexArray(self.render_vao)
            glBindBuffer(GL_ARRAY_BUFFER, render_vbo)
            glBufferData(GL_ARRAY_BUFFER, np.array(self.render_list), GL_STATIC_DRAW)
    
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(0))
            glEnableVertexAttribArray(0)
            glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(12))
            glEnableVertexAttribArray(1)
            glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(20))
            glEnableVertexAttribArray(2)
    
        def draw_buffer(self, program, texture):
            program.use()
            glBindTexture(GL_TEXTURE_2D, texture)
            glBindVertexArray(self.render_vao)
            glDrawArrays(GL_TRIANGLES, 0, int(len(self.render_list)/8))
    
  • Initialization and render loop:

    def main():
    
        global delta_time, last_frame
    
        test_chunk = chunk.chunk((0,0,0)) #Creates a 16x16x16 cube of cubes
        test_chunk.fill_layers(0, 16, 1)  #Works fine
    
        window = utilities.window()
        camera.setup_window(window)
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    
        shader_program = utilities.shader(vertex_source_3d, fragment_source_3d, '330')
        shader_program.compile()
    
        #Check which cubes are exposed and should be rendered
        #Works fine
        exposed_list = [i for i, blocktype in np.ndenumerate(test_chunk.data) if test_chunk.return_if_exposed(i) == True and blocktype != 0]
    
        shader_program.use()
        shader_program.set_int('texture0', 0)
    
        camera_direction = glm.vec3()
        yaw = -90.0
        second_counter = 0
        frame_counter = 0
    
        render = cube.render(exposed_list)
        render.create_buffers()
    
        while not window.check_if_closed():
    
            current_frame = glfw.get_time()
            delta_time = current_frame - last_frame
            last_frame = current_frame
            second_counter += delta_time
            frame_counter += 1
    
            window.refresh(0)
    
            camera.process_input(window, delta_time)
            camera.testing_commands(window)
    
            glActiveTexture(GL_TEXTURE0)
    
            shader_program.use()
    
            pos, looking, up = camera.return_vectors()
            view = glm.lookAt(pos, looking, up)
            projection = glm.perspective(glm.radians(45), window.size[0]/window.size[1], 0.1, 100)
            shader_program.set_mat4('view', glm.value_ptr(view))
            shader_program.set_mat4('projection', glm.value_ptr(projection))
    
            render.draw_buffer(shader_program, cobble_tex_ID)
    
            glBindVertexArray(0)
    
            if second_counter >= 1:
                print(frame_counter)
                second_counter, frame_counter = 0, 0
    
            window.refresh(1)
    
        window.close()
    
    if __name__ == '__main__':
        main()
    
  • Vertex shader:

    #version %s core
    
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec2 aTexCoord;
    layout (location = 2) in vec3 cube_coord;
    
    out vec2 TexCoord;
    
    uniform mat4 view;
    uniform mat4 projection;
    
    mat4 model = mat4(1.0);
    
    void main() {
        model[0].w = cube_coord.x;
        model[1].w = cube_coord.y;
        model[2].w = cube_coord.z;
        gl_Position = projection * view * model * vec4(aPos, 1.0);
        TexCoord = aTexCoord;
    }
    
  • Fragment shader:

    version %s core
    
    out vec4 FragColor;
    in vec2 TexCoord;
    
    uniform sampler2D texture0;
    
    void main()
    {
        FragColor = texture(texture0, TexCoord);
    }
    

Solution

  • The assignment of the translation to the model matrix is wrong. A glsl matrix is stored in column major order. The translation is the 4th column. A typical model matrix looks as follows:

    mat4 m44 = mat4(
        vec4( Xx, Xy, Xz, 0.0),
        vec4( Yx, Xy, Yz, 0.0),
        vec4( Zx  Zy  Zz, 0.0),
        vec4( Tx, Ty, Tz, 1.0) );
    

    You have to change the model matrix initialization:

    mat4 model = mat4(1.0);
    
    void main() {
        model[3] = vec4(cube_coord.xyz, 1.0);
    
        // [...]
    }
    

    Further more, you have to specify the data type for the numpy.array (numpy.float32):

    glBufferData(GL_ARRAY_BUFFER, np.array(self.render_list), GL_STATIC_DRAW)

    glBufferData(GL_ARRAY_BUFFER, np.array(self.render_list, np.float32), GL_STATIC_DRAW)
    

    The vertex array is 2 dimensional, thus len(self.render_list)/8 is not the number of vertices:

    glDrawArrays(GL_TRIANGLES, 0, int(len(self.render_list)/8))

    no_of_verices = len(self.render_list) * 36
    glDrawArrays(GL_TRIANGLES, 0, no_of_verices)
    

    respectively

    no_of_verices = len(self.render_list) * len(self.render_list[0]) // 8
    glDrawArrays(GL_TRIANGLES, 0, no_of_verices)