I am trying to render multiple models, loaded from OBJ files using a single VBO using a VAO. Along with the VBO I have two other buffers – CBO (colors) and IBO (indices).
import pygame
from pathlib import Path
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GL import shaders
import numpy as np
import pywavefront
from math import sin, cos, tan, atan2
def calcFrustumScale(fFovDeg):
degToRad = np.pi * 2.0 / 360.0
fFovRad = fFovDeg * degToRad
return 1.0 / tan(fFovRad / 2.0)
def calcLerpFactor(fElapsedTime, fLoopDuration):
fValue = (fElapsedTime % fLoopDuration) / fLoopDuration
if fValue > 0.5:
fValue = 1.0 - fValue
return fValue * 2.0
def computeAngleRad(fElapsedTime, fLoopDuration):
fScale = np.pi * 2.0 / fLoopDuration
fCurrTimeThroughLoop = fElapsedTime % fLoopDuration
return fCurrTimeThroughLoop * fScale
def load_model(single_model_path: Path, color: np.array = np.array([*np.random.uniform(0.0, 1.0, 3), 1.0], dtype='float32')):
scene = pywavefront.Wavefront(single_model_path, collect_faces=True)
model = {
'vertex' : np.array(scene.vertices, dtype='float32'),
'face' : np.array(scene.mesh_list[0].faces, dtype='uint32')
}
model['color'] = np.full((len(model['vertex']), 4), color, dtype='float32')
return model
def get_rotate_y(elapsed_time):
angle_rad = computeAngleRad(elapsed_time, 2.0)
_cos = cos(angle_rad)
_sin = sin(angle_rad)
transform = np.identity(4, dtype='float32')
transform[0][0] = _cos
transform[2][0] = _sin
transform[0][2] = -_sin
transform[2][2] = _cos
# offset
transform[0][3] = 0.0 #-5.0
transform[1][3] = 0.0 #5.0
transform[2][3] = -10
return transform
def rand_range(start, stop, step):
r = np.random.randint(0, int((stop - start) / step))
return r, r * step + start
def model_info(model: dict):
print('Model (static) vertex bytesize:t', model['vertex'].nbytes)
print('Model (static) face bytesize: t', model['face'].nbytes)
print('Model (static) color bytesize: t', model['color'].nbytes)
color_rnd = np.array([*np.random.uniform(0.0, 1.0, 3), 1.0], dtype='float32')
print(color_rnd)
modelToCameraMatrixUnif = None
cameraToClipMatrixUnif = None
# Global display variables
cameraToClipMatrix = np.zeros((4,4), dtype='float32')
fFrustumScale = calcFrustumScale(45.0)
model_static = load_model(Path('cube_static.obj'))
model_dynamic = [
load_model(_m, color=np.array([*np.random.uniform(0.0, 1.0, 3), 1.0], dtype='float32'))
for _m in list(Path('.').glob('cube_dynamic_*.obj'))
]
model_info(model_static)
VBO_BUFFER_SIZE = 1000 * np.array([0, 0, 0], dtype='float32').nbytes
print('VBO total bytesize:', VBO_BUFFER_SIZE)
IBO_BUFFER_SIZE = 1000 * np.array([0, 0, 0, 1], dtype='float32').nbytes
print('IBO total bytesize:', IBO_BUFFER_SIZE)
CBO_BUFFER_SIZE = 1000 * np.array([0, 0, 0], dtype='uint32').nbytes
print('CBO total bytesize:', CBO_BUFFER_SIZE)
vertex_shader = '''
#version 330
layout(location = 0) in vec4 position;
layout(location = 1) in vec4 color;
smooth out vec4 theColor;
uniform mat4 cameraToClipMatrix;
uniform mat4 modelToCameraMatrix;
void main()
{
vec4 cameraPos = modelToCameraMatrix * position;
gl_Position = cameraToClipMatrix * cameraPos;
theColor = color;
}
'''
fragment_shader = '''
#version 330
smooth in vec4 theColor;
out vec4 outputColor;
void main()
{
outputColor = theColor;
}
'''
vbo = None
cbo = None
ibo = None
vao = None
program = None
def initialize():
global model_static
global vbo, cbo, ibo, vao
global program
global modelToCameraMatrixUnif, cameraToClipMatrixUnif, cameraToClipMatrix
pygame.init()
display = (800, 800)
pygame.display.set_mode(display, DOUBLEBUF | OPENGL)
vertex_shader_id = shaders.compileShader(vertex_shader, GL_VERTEX_SHADER)
fragment_shader_id = shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER)
program = shaders.compileProgram(vertex_shader_id, fragment_shader_id)
glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
glFrontFace(GL_CW)
glEnable(GL_DEPTH_TEST)
glDepthMask(GL_TRUE)
glDepthFunc(GL_LEQUAL)
glDepthRange(0.0, 1.0)
glUseProgram(program)
modelToCameraMatrixUnif = glGetUniformLocation(program, "modelToCameraMatrix")
cameraToClipMatrixUnif = glGetUniformLocation(program, "cameraToClipMatrix")
glUseProgram(0)
fzNear = 1.0
fzFar = 100.0
# Note that this and the transformation matrix below are both
# ROW-MAJOR ordered. Thus, it is necessary to pass a transpose
# of the matrix to the glUniform assignment function.
cameraToClipMatrix[0][0] = fFrustumScale
cameraToClipMatrix[1][1] = fFrustumScale
cameraToClipMatrix[2][2] = (fzFar + fzNear) / (fzNear - fzFar)
cameraToClipMatrix[2][3] = -1.0
cameraToClipMatrix[3][2] = (2 * fzFar * fzNear) / (fzNear - fzFar)
glUseProgram(program)
glUniformMatrix4fv(cameraToClipMatrixUnif, 1, GL_FALSE, cameraToClipMatrix.transpose())
glUseProgram(0)
vbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(
GL_ARRAY_BUFFER,
VBO_BUFFER_SIZE,
None,
GL_STATIC_DRAW)
glBufferSubData(
GL_ARRAY_BUFFER,
0,
model_static['vertex'].nbytes,
model_static['vertex'].ravel()
)
glBindBuffer(GL_ARRAY_BUFFER, 0)
cbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, cbo)
glBufferData(
GL_ARRAY_BUFFER,
CBO_BUFFER_SIZE,
None,
GL_STATIC_DRAW)
glBufferSubData(
GL_ARRAY_BUFFER,
0,
model_static['color'].nbytes,
model_static['color'].ravel()
)
glBindBuffer(GL_ARRAY_BUFFER, 0)
ibo = glGenBuffers(1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
IBO_BUFFER_SIZE,
None,
GL_STATIC_DRAW)
glBufferSubData(
GL_ELEMENT_ARRAY_BUFFER,
0,
model_static['face'].nbytes,
model_static['face'].ravel()
)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
vertex_dim = 3
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, vertex_dim, GL_FLOAT, GL_FALSE, 0, None)
color_dim = 4
glBindBuffer(GL_ARRAY_BUFFER, cbo)
glEnableVertexAttribArray(1)
glVertexAttribPointer(1, color_dim, GL_FLOAT, GL_FALSE, 0, None)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
glBindVertexArray(0)
def update_vbo(idx):
global vbo
global model_static
print('VBO 1st vertex', model_dynamic[idx]['vertex'][0])
offset = model_static['vertex'].nbytes
print('VBO offset', offset)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferSubData(
GL_ARRAY_BUFFER,
offset,
model_dynamic[idx]['vertex'].nbytes,
model_dynamic[idx]['vertex'].ravel()
)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def update_cbo(idx):
global cbo
global model_static
print('CBO 1st color', model_dynamic[idx]['color'][0])
offset = model_static['color'].nbytes
print('CBO offset', offset)
glBindBuffer(GL_ARRAY_BUFFER, cbo)
glBufferSubData(
GL_ARRAY_BUFFER,
offset,
model_dynamic[idx]['color'].nbytes,
model_dynamic[idx]['color'].ravel()
)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def update_ibo(idx):
global ibo
global model_static
print('IBO 1st face', model_dynamic[idx]['face'][0])
offset = model_static['face'].nbytes
print('IBO offset', offset)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
glBufferSubData(
GL_ELEMENT_ARRAY_BUFFER,
offset,
model_dynamic[idx]['face'].nbytes,
model_dynamic[idx]['face'].ravel()
)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
def render():
global idx
global model_static, model_dynamic
global vao
global modelToCameraMatrixUnif
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glUseProgram(program)
elapsed_time = pygame.time.get_ticks() / 1000.0
transform_func = get_rotate_y
transformMatrix = transform_func(elapsed_time=elapsed_time)
glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, transformMatrix.transpose())
glBindVertexArray(vao)
index_count = model_static['face'].size + model_dynamic[idx]['face'].size
#print(model_static['face'].size, model_dynamic[idx]['face'].size, index_count)
glDrawElements(GL_TRIANGLES, index_count, GL_UNSIGNED_INT, None)
glBindVertexArray(0)
glUseProgram(0)
pygame.display.flip()
idx = 0
def main():
global idx
initialize()
frame_count = 0
idx_prev = 0
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
quit()
if event.type == pygame.KEYUP:
if event.key == pygame.K_u:
idx = (idx + 1) % 2
print(idx)
if idx != idx_prev:
print('Switching dynamic content ({})'.format(idx))
update_vbo(idx)
update_cbo(idx)
update_ibo(idx)
idx_prev = idx
render()
frame_count += 1
if __name__ == '__main__':
main()
All models that I am loading in the code below are simple cubes (8 vertices, 12 primitive faces) with some changes in scaling, rotation and translation so that I can visualize the differences.
The memory footprint for each type of buffer is as follows:
- VBO – 1000 vertices, each consisting of 3 float32 components (so
3*4 = 12 bytes
per vertex) - CBO – 1000 colors, each consisting of 4 float32 components (RGBA, so
4*4 = 16 bytes
per color) - IBO – 1000 faces, each consisting of 3 uint32 components (so
3*4 = 12 bytes
per primitive face)
Initially I allocated the full size and right away I use glBufferSubData()
to load my static model. Unlike the dynamic one, the static model is something I will not change until the program ends. A dynamic model is something I iterate through. Each dynamic model currently has the same bytesize (incl. number of vertices, colors and faces) as the static one.
Whenever the user presses a specific key, I increase an index, which selects the next dynamic model (all loaded using PyWavefront).
When I run my code, I get
as the initial view. However, while pressing the key does show that I am switching the dynamic models (print()
statements), I cannot get any visual change to render onto the screen. I expect to see
followed by
near the initially rendered model. The image below should show what I imagine (somewhat) my final render would look like for one of the dynamic models: