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.
A Vertex Array Object (VAO) is an object which contains one or more Vertex Buffer Objects and is designed to store the information for a complete rendered object. In our example this is a diamond consisting of four vertices as well as a color for each vertex.
Indices is defined as a GLubyte array of 48 elements; GLubyte is the OpenGL data type for an unsigned byte ( unsigned char ). You could use any of the following unsigned integral OpenGL data types: GLubyte , GLushort , and GLuint , since indices are never negative (signed) or fractional (float/double).
The index buffer contains integers, three for each triangle in the mesh, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). It's a little bit like in the OBJ file format, with one huge difference : there is only ONE index buffer.
The models used in games are created out of triangles, which the rendering engine uses to draw. A vertex buffer is a list of coordinates used for drawing triangles. The index buffer helps save memory by reducing the need to store shared vertices in the vertex buffer.
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()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With