How to draw with Vertex Array Objects and glDrawElements in PyOpenGL
Asked Answered
P

1

9

I have the following code which should simply draw a green triangle to the screen. It is using Vertex Array Objects and index buffers to draw and has the simplest shader I could make.

At first I was not using index buffers and was simply making the draw call with glDrawArrays which worked fine but when I change it to use glDrawElements then nothing is drawn to the screen (it is entirely black).

from OpenGL.GL import shaders
from OpenGL.arrays import vbo
from OpenGL.GL import *
from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, \
                                                  glBindVertexArray

import pygame

import numpy as np

def run():
    pygame.init()
    screen = pygame.display.set_mode((800,600), pygame.OPENGL)

    #Create the Vertex Array Object
    vertexArrayObject = GLuint(0)
    glGenVertexArrays(1, vertexArrayObject)
    glBindVertexArray(vertexArrayObject)

    #Create the VBO
    vertices = np.array([[0,1,0],[-1,-1,0],[1,-1,0]], dtype='f')
    vertexPositions = vbo.VBO(vertices)

    #Create the index buffer object
    indices = np.array([0,1,2], dtype='uint16')
    indexPositions = vbo.VBO(indices, target=GL_ELEMENT_ARRAY_BUFFER)

    indexPositions.bind()
    vertexPositions.bind()

    glEnableVertexAttribArray(0) # from 'location = 0' in shader
    glVertexAttribPointer(0, 3, GL_FLOAT, False, 0, None)

    glBindVertexArray(0)
    vertexPositions.unbind()
    indexPositions.unbind()

    #Now create the shaders
    VERTEX_SHADER = shaders.compileShader("""
    #version 330
    layout(location = 0) in vec4 position;
    void main()
    {
        gl_Position = position;
    }
    """, GL_VERTEX_SHADER)

    FRAGMENT_SHADER = shaders.compileShader("""
    #version 330
    out vec4 outputColor;
    void main()
    {
        outputColor = vec4(0.0f, 1.0f, 0.0f, 1.0f);
    }
    """, GL_FRAGMENT_SHADER)

    shader = shaders.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER)

    #The draw loop
    while True:
        glUseProgram(shader)
        glBindVertexArray(vertexArrayObject)

        #glDrawArrays(GL_TRIANGLES, 0, 3) #This line works
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, 0) #This line does not

        glBindVertexArray(0)
        glUseProgram(0)

        # Show the screen
        pygame.display.flip()

run()

If I simply comment out the glDrawElements and uncomment the glDrawArrays line then it works correctly so at least the vertex VBO is being input correctly.

What am I doing wrong here? All the OpenGL documentation I have been able to find suggests that I am doing this correctly so I am obviously misunderstanding something either about OpenGL itself or the PyOpenGL wrapper.

EDIT

Changing the VAO setup to use more direct OpenGL function rather than the VBO wrapper like:

vertices = np.array([[0,1,0],[-1,-1,0],[1,-1,0]], dtype='f')
vertexPositions = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vertexPositions)
glBufferData(GL_ARRAY_BUFFER, vertices, GL_STATIC_DRAW)

#Create the index buffer object
indices = np.array([0,1,2], dtype='uint16')
indexPositions = glGenBuffers(1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexPositions)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexPositions)
glBindBuffer(GL_ARRAY_BUFFER, vertexPositions)

glEnableVertexAttribArray(0) # from 'location = 0' in shader
glVertexAttribPointer(0, 3, GL_FLOAT, False, 0, None)

glBindVertexArray(0)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
glBindBuffer(GL_ARRAY_BUFFER, 0)

makes no difference. glDrawArrays still works and glDrawElements still doesn't.

Paraguay answered 16/1, 2013 at 18:46 Comment(3)
It's unclear what these functions do: indexPositions.bind().Mohamedmohammad
@NicolBolas Sorry, they're PyOpenGL things. All they do inside is a single call to glBindBuffer (and unbind respectively). Similarly, vbo.VBO() calls glGenBuffers, glBindBuffer and glBufferData.Paraguay
@NicolBolas I've tried exchanging those calls with the actual OpenGL commands (as shown in the question edit) and it doesn't appear to make a difference.Paraguay
S
15

Thanks @NicolBolas. He motivated me to actually take this code and make it work. Instead of theoritizing:) I have removed vertexArrayObject(it's redundand as we already have VBOs for vertices and indices). So you just bind index and vertex buffers(along with attributes) prior to glDraw* call. And of course very important to pass None(null pointer) to glDrawElements indices instead of 0!

from OpenGL.GL import shaders
from OpenGL.arrays import vbo
from OpenGL.GL import *
from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, \
                                                  glBindVertexArray

import pygame

import numpy as np

def run():
    pygame.init()
    screen = pygame.display.set_mode((800,600), pygame.OPENGL|pygame.DOUBLEBUF)

    #Create the VBO
    vertices = np.array([[0,1,0],[-1,-1,0],[1,-1,0]], dtype='f')
    vertexPositions = vbo.VBO(vertices)

    #Create the index buffer object
    indices = np.array([[0,1,2]], dtype=np.int32)
    indexPositions = vbo.VBO(indices, target=GL_ELEMENT_ARRAY_BUFFER)

    #Now create the shaders
    VERTEX_SHADER = shaders.compileShader("""
    #version 330
    layout(location = 0) in vec4 position;
    void main()
    {
        gl_Position = position;
    }
    """, GL_VERTEX_SHADER)

    FRAGMENT_SHADER = shaders.compileShader("""
    #version 330
    out vec4 outputColor;
    void main()
    {
        outputColor = vec4(0.0f, 1.0f, 0.0f, 1.0f);
    }
    """, GL_FRAGMENT_SHADER)

    shader = shaders.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER)

    #The draw loop
    while True:
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        glUseProgram(shader)

        indexPositions.bind()

        vertexPositions.bind()
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, False, 0, None)

        #glDrawArrays(GL_TRIANGLES, 0, 3) #This line still works
        glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, None) #This line does work too!

        # Show the screen
        pygame.display.flip()

run()
Surrey answered 16/1, 2013 at 19:1 Comment(9)
It does not appear to change anything. All the docs I've seen (such as the wiki) suggest that count is the number if indices to use. Also, I thought that I didn't have to specify indices if using a VAO with GL_ELEMENT_ARRAY_BUFFER set?Paraguay
-1: For being wrong. glDrawElements takes the number of indices to pull from the indices array.Mohamedmohammad
@NicolBolas - ok. Seems you're right. Could be something like this then glDrawElements(GL_TRIANGLES, indices)?Surrey
Fantastic. That works now. It seems the two major errors I had were the type of indices being uint16 instead of uint32 and that I was passing 0 instead of None to glDrawElements. I'm able to use your code also with VAO (as they are what I am planning on using going forward).Paraguay
Sure, VAO is the way to go. I assumed that vbo.VBO already uses it. I was wrong...again!Surrey
Yeah, PyOpenGL lacks a Pythonic VAO wrapper. As I learn more about their intricacies I might might provide them with a patch for one.Paraguay
Thanks so much!!! I suffered from this problem for days. Finally I just pass None instead of 0, and glDrawElements worked! I dont know why they made thing so inconsistent... ... ...Malherbe
Is it really necessary to compile the shader and program during the code? PyOpenGL doesn't give you any other way?Draconic
Thanks! I spent many time making my code works, even write C-version (and it works!). The solution was passing None to the glDrawElements instead of 0. And this still works with VAO.Advice

© 2022 - 2024 — McMap. All rights reserved.