I just started using shaders with PyOpenGL, and I managed to draw one 3d object, but I'm not able to draw a second one. I tried making another init() function and doing all the vao and vbo stuff twice, and then between drawing objects I switched to using the second shader program, but I can't draw a second object.
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
import numpy as np
from math import sin, cos, tan, radians
# Vertex Shader
vertex_shader = """
#version 330
in vec3 position;
in vec3 color;
out vec3 newColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0);
newColor = color;
}
"""
# Fragment Shader
fragment_shader = """
#version 330
in vec3 newColor;
out vec4 outColor;
void main()
{
outColor = vec4(newColor, 1.0);
}
"""
# Cube vertices and colors
vertices = np.array([
# Vertices Colors
[-.1, -.1, -.1], [1, 0, 0],
[.1, -.1, -.1], [0, 1, 0],
[.1, .1, -.1], [0, 0, 1],
[-.1, .1, -.1], [1, 1, 0],
[-.1, -.1, .1], [0, 1, 1],
[.1, -.1, .1], [1, 1, 1],
[.1, .1, .1], [0.5, 0.5, 0.5],
[-.1, .1, .1], [0.5, 0.5, 0.5],
], dtype=np.float32)
indices = np.array([
0, 1, 2, 2, 3, 0, # Front face
1, 5, 6, 6, 2, 1, # Right face
5, 4, 7, 7, 6, 5, # Back face
4, 0, 3, 3, 7, 4, # Left face
3, 2, 6, 6, 7, 3, # Top face
4, 5, 1, 1, 0, 4, # Bottom face
], dtype=np.uint32)
# Cube vertices and colors
vertices2 = np.array([
# Vertices Colors
[.2, -.1, -.1], [1, 0, 0],
[.4, -.1, -.1], [0, 1, 0],
[.4, .1, -.1], [0, 0, 1],
[.2, .1, -.1], [1, 1, 0],
[.2, -.1, .1], [0, 1, 1],
[.4, -.1, .1], [1, 1, 1],
[.4, .1, .1], [0.5, 0.5, 0.5],
[.2, .1, .1], [0.5, 0.5, 0.5],
], dtype=np.float32)
indices2 = np.array([
0, 1, 2, 2, 3, 0, # Front face
1, 5, 6, 6, 2, 1, # Right face
5, 4, 7, 7, 6, 5, # Back face
4, 0, 3, 3, 7, 4, # Left face
3, 2, 6, 6, 7, 3, # Top face
4, 5, 1, 1, 0, 4, # Bottom face
], dtype=np.uint32)
def init1():
# Compile shaders and create shader program
shader_program = compileProgram(compileShader(vertex_shader, GL_VERTEX_SHADER),
compileShader(fragment_shader, GL_FRAGMENT_SHADER))
# Create Vertex Array Object (VAO)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
# Create Vertex Buffer Object (VBO) and Element Buffer Object (EBO)
vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
ebo = glGenBuffers(1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW)
# Set attribute pointers
stride = 6 * vertices.itemsize
position_offset = ctypes.c_void_p(0)
color_offset = ctypes.c_void_p(3 * vertices.itemsize)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, position_offset)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, color_offset)
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
return shader_program
def init2():
# Compile shaders and create shader program
shader_program2 = compileProgram(compileShader(vertex_shader, GL_VERTEX_SHADER),
compileShader(fragment_shader, GL_FRAGMENT_SHADER))
# Create Vertex Array Object (VAO)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
# Create Vertex Buffer Object (VBO) and Element Buffer Object (EBO)
vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, vertices2.nbytes, vertices2, GL_STATIC_DRAW)
ebo = glGenBuffers(1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices2.nbytes, indices2, GL_STATIC_DRAW)
# Set attribute pointers
stride = 6 * vertices.itemsize
position_offset = ctypes.c_void_p(0)
color_offset = ctypes.c_void_p(3 * vertices2.itemsize)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, position_offset)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, color_offset)
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
return shader_program2
def main():
# Initialize Pygame
pygame.init()
width, height = 800, 600
pygame.display.set_mode((width, height), pygame.OPENGL | pygame.DOUBLEBUF)
pygame.display.set_caption("Shader Testing")
glViewport(0, 0, width, height)
width, height = 800, 600
shader_program = init1()
shader_program2 = init2()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
glClearColor(0.0, 0.0, 0.0, 0.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
# Define the view and projection matrices (unchanged)
view = [1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, -3.0, 1.0]
aspect_ratio = width / height
near = 0.1
far = 100.0
fov = 45.0
top = near * tan(radians(fov) / 2.0)
right = top * aspect_ratio
projection = ([near / right, 0.0, 0.0, 0.0],
[0.0, near / top, 0.0, 0.0],
[0.0, 0.0, -(far + near) / (far - near), -1.0],
[0.0, 0.0, -(2 * far * near) / (far - near), 0.0])
# Define the model matrix (rotate the cube)
angle = pygame.time.get_ticks() / 1000.0 # Rotate the cube over time
model = ([cos(angle), -sin(angle), 0, 0],
[sin(angle), cos(angle), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1])
# Use the shader program
glUseProgram(shader_program)
# Set the model, view, and projection matrices in the shader
model_loc = glGetUniformLocation(shader_program, "model")
view_loc = glGetUniformLocation(shader_program, "view")
projection_loc = glGetUniformLocation(shader_program, "projection")
glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view)
glUniformMatrix4fv(projection_loc, 1, GL_FALSE, projection)
# Draw the cube
glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, None)
# Use the shader program
glUseProgram(shader_program2)
# Set the model, view, and projection matrices in the shader
model_loc = glGetUniformLocation(shader_program, "model")
view_loc = glGetUniformLocation(shader_program, "view")
projection_loc = glGetUniformLocation(shader_program, "projection")
glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view)
glUniformMatrix4fv(projection_loc, 1, GL_FALSE, projection)
# Draw the cube
glDrawElements(GL_TRIANGLES, len(indices2), GL_UNSIGNED_INT, None)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()```
The vertex array object and the shader program are not tied. You must bind the vertex array object before drawing the mesh. OpenGL is a state engine. The currently bound vertex array object is a global state and the draw call (glDraw*
) uses the specification stored in the currently bound vertex array object to draw the mesh.
Also note that it is absolutely pointless to compile and link the same shader twice. You can use the same shader program to draw multiple meshes. All you have to do is set the individual uniforms before the drawing call. Normally, you have an individual model matrix for each mesh:
def init1():
# [...]
return vao
def init2():
# [...]
return vao
def main():
# [...]
shader_program = compileProgram(compileShader(vertex_shader, GL_VERTEX_SHADER),
compileShader(fragment_shader, GL_FRAGMENT_SHADER))
model_loc = glGetUniformLocation(shader_program, "model")
view_loc = glGetUniformLocation(shader_program, "view")
projection_loc = glGetUniformLocation(shader_program, "projection")
vao = init1()
vao2 = init2()
running = True
while running:
# [...]
glUseProgram(shader_program)
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view)
glUniformMatrix4fv(projection_loc, 1, GL_FALSE, projection)
glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)
glBindVertexArray(vao)
glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, None)
glUniformMatrix4fv(model_loc, 1, GL_FALSE, model2)
glBindVertexArray(vao2)
glDrawElements(GL_TRIANGLES, len(indices2), GL_UNSIGNED_INT, None)
# [...]
In your case, you don't really need 2 different meshes, just different model matrices. See the example based on your code:
import pygame
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram, compileShader
import numpy as np
from math import sin, cos, tan, radians
# Vertex Shader
vertex_shader = """
#version 330
in vec3 position;
in vec3 color;
out vec3 newColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0);
newColor = color;
}
"""
# Fragment Shader
fragment_shader = """
#version 330
in vec3 newColor;
out vec4 outColor;
void main()
{
outColor = vec4(newColor, 1.0);
}
"""
# Cube vertices and colors
vertices = np.array([
# Vertices Colors
[-.1, -.1, -.1], [1, 0, 0],
[.1, -.1, -.1], [0, 1, 0],
[.1, .1, -.1], [0, 0, 1],
[-.1, .1, -.1], [1, 1, 0],
[-.1, -.1, .1], [0, 1, 1],
[.1, -.1, .1], [1, 1, 1],
[.1, .1, .1], [0.5, 0.5, 0.5],
[-.1, .1, .1], [0.5, 0.5, 0.5],
], dtype=np.float32)
indices = np.array([
0, 1, 2, 2, 3, 0, # Front face
1, 5, 6, 6, 2, 1, # Right face
5, 4, 7, 7, 6, 5, # Back face
4, 0, 3, 3, 7, 4, # Left face
3, 2, 6, 6, 7, 3, # Top face
4, 5, 1, 1, 0, 4, # Bottom face
], dtype=np.uint32)
def init():
# Create Vertex Array Object (VAO)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
# Create Vertex Buffer Object (VBO) and Element Buffer Object (EBO)
vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)
ebo = glGenBuffers(1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW)
# Set attribute pointers
stride = 6 * vertices.itemsize
position_offset = ctypes.c_void_p(0)
color_offset = ctypes.c_void_p(3 * vertices.itemsize)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, position_offset)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, color_offset)
glEnableVertexAttribArray(0)
glEnableVertexAttribArray(1)
return vao
def main():
# Initialize Pygame
pygame.init()
width, height = 800, 600
pygame.display.set_mode((width, height), pygame.OPENGL | pygame.DOUBLEBUF)
pygame.display.set_caption("Shader Testing")
glViewport(0, 0, width, height)
width, height = 800, 600
# Compile shaders and create shader program
shader_program = compileProgram(compileShader(vertex_shader, GL_VERTEX_SHADER),
compileShader(fragment_shader, GL_FRAGMENT_SHADER))
model_loc = glGetUniformLocation(shader_program, "model")
view_loc = glGetUniformLocation(shader_program, "view")
projection_loc = glGetUniformLocation(shader_program, "projection")
vao = init()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
glClearColor(0.0, 0.0, 0.0, 0.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glEnable(GL_DEPTH_TEST)
# Define the view and projection matrices (unchanged)
view = [1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, -3.0, 1.0]
aspect_ratio = width / height
near = 0.1
far = 100.0
fov = 45.0
top = near * tan(radians(fov) / 2.0)
right = top * aspect_ratio
projection = ([near / right, 0.0, 0.0, 0.0],
[0.0, near / top, 0.0, 0.0],
[0.0, 0.0, -(far + near) / (far - near), -1.0],
[0.0, 0.0, -(2 * far * near) / (far - near), 0.0])
# Define the model matrix (rotate the cube)
angle = pygame.time.get_ticks() / 1000.0 # Rotate the cube over time
model = ([cos(angle), -sin(angle), 0, 0],
[sin(angle), cos(angle), 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1])
model2 = ([cos(angle), -sin(angle), 0, 0],
[sin(angle), cos(angle), 0, 0],
[0, 0, 1, 0],
[0.5, 0, 0, 1])
model3 = ([cos(angle), -sin(angle), 0, 0],
[sin(angle), cos(angle), 0, 0],
[0, 0, 1, 0],
[-0.5, 0, 0, 1])
# Use the shader program
glUseProgram(shader_program)
# Set the model, view, and projection matrices in the shader
glUniformMatrix4fv(view_loc, 1, GL_FALSE, view)
glUniformMatrix4fv(projection_loc, 1, GL_FALSE, projection)
glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)
glBindVertexArray(vao)
glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, None)
glUniformMatrix4fv(model_loc, 1, GL_FALSE, model2)
glBindVertexArray(vao)
glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, None)
glUniformMatrix4fv(model_loc, 1, GL_FALSE, model3)
glBindVertexArray(vao)
glDrawElements(GL_TRIANGLES, len(indices), GL_UNSIGNED_INT, None)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()