I have the following code that currently loads a single mesh from an OBJ file and renders it:
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_size(model: dict, stype='vertex'):
items = model[stype]
return items.size * items.itemsize
def get_transform(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] = -5
return transform
def rand_range(start, stop, step):
return np.random.randint(0, int((stop - start) / step)) * step + start
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 = load_model('sample0.obj')
update_enabled = False
print('Model vertex bytesize:t', get_size(model, 'vertex'))
print('Model face bytesize: t', get_size(model, 'face'))
print('Model color bytesize: t', get_size(model, 'color'))
CUBES_COUNT = 10
VBO_BUFFER_SIZE = CUBES_COUNT * get_size(model, 'vertex')
VBO_SUB_BUFFER_SIZE = get_size(model, 'vertex')
IBO_BUFFER_SIZE = CUBES_COUNT * get_size(model, 'face')
IBO_SUB_BUFFER_SIZE = get_size(model, 'face')
CBO_BUFFER_SIZE = CUBES_COUNT * get_size(model, 'color')
CBO_SUB_BUFFER_SIZE = get_size(model, 'color')
UPDATE_INTERVAL = 10 # Time interval between updates (in frames)
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
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)
glUseProgram(program)
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)
modelToCameraMatrixUnif = glGetUniformLocation(program, "modelToCameraMatrix")
cameraToClipMatrixUnif = glGetUniformLocation(program, "cameraToClipMatrix")
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,
model['vertex'].flatten(),
GL_STATIC_DRAW
)
glBindBuffer(GL_ARRAY_BUFFER, 0)
cbo = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, cbo)
glBufferData(
GL_ARRAY_BUFFER,
model['color'].flatten(),
GL_STATIC_DRAW
)
glBindBuffer(GL_ARRAY_BUFFER, 0)
ibo = glGenBuffers(1)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
glBufferData(
GL_ELEMENT_ARRAY_BUFFER,
model['face'].flatten(),
GL_STATIC_DRAW
)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
vao = glGenVertexArrays(1)
glBindVertexArray(vao)
vertex_dim = model['vertex'].shape[1]
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glEnableVertexAttribArray(0)
glVertexAttribPointer(0, vertex_dim, GL_FLOAT, GL_FALSE, 0, None)
color_dim = model['color'].shape[1]
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(offset_prev, offset_curr):
global vbo
global model
#random_offset_in_range = np.random.choice(np.arange(0, VBO_BUFFER_SIZE - VBO_SUB_BUFFER_SIZE, VBO_SUB_BUFFER_SIZE))
print('(VBO) Removing data at ({}:{})'.format(offset_prev, offset_prev + VBO_SUB_BUFFER_SIZE))
print('(VBO) Adding data at ({}:{})'.format(offset_curr, offset_curr + VBO_SUB_BUFFER_SIZE))
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferSubData(GL_ARRAY_BUFFER, offset_prev, VBO_SUB_BUFFER_SIZE, None)
glBufferSubData(GL_ARRAY_BUFFER, offset_curr, VBO_SUB_BUFFER_SIZE, model['vertex'].flatten())
#glBufferSubData(GL_ARRAY_BUFFER, 0, VBO_SUB_BUFFER_SIZE, model['vertex'].flatten())
glBindBuffer(GL_ARRAY_BUFFER, 0)
def update_cbo(offset_prev, offset_curr, color: np.array = np.array([*np.random.uniform(0.0, 1.0, 3), 1.0], dtype='float32')):
global cbo
global model
model['color'] = np.full((len(model['vertex']), 4), color, dtype='float32')
print('(CBO) Removing data at ({}:{})'.format(offset_prev, offset_prev + CBO_SUB_BUFFER_SIZE))
print('(CBO) Adding data at ({}:{})'.format(offset_curr, offset_curr + CBO_SUB_BUFFER_SIZE))
glBindBuffer(GL_ARRAY_BUFFER, cbo)
glBufferSubData(GL_ARRAY_BUFFER, offset_prev, CBO_SUB_BUFFER_SIZE, None)
glBufferSubData(GL_ARRAY_BUFFER, offset_curr, CBO_SUB_BUFFER_SIZE, model['color'].flatten())
#glBufferSubData(GL_ARRAY_BUFFER, 0, CBO_SUB_BUFFER_SIZE, model['color'].flatten())
glBindBuffer(GL_ARRAY_BUFFER, 0)
def update_ibo(offset_prev, offset_curr):
global ibo
global model
print('(IBO) Removing data at ({}:{})'.format(offset_prev, offset_prev + IBO_SUB_BUFFER_SIZE))
print('(IBO) Adding data at ({}:{})'.format(offset_curr, offset_curr + IBO_SUB_BUFFER_SIZE))
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset_prev, IBO_SUB_BUFFER_SIZE, None)
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset_curr, IBO_SUB_BUFFER_SIZE, model['face'].flatten())
#glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, IBO_SUB_BUFFER_SIZE, model['face'].flatten())
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
def render():
global vao, model
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_transform
transformMatrix = transform_func(elapsed_time=elapsed_time)
glUniformMatrix4fv(modelToCameraMatrixUnif, 1, GL_FALSE, transformMatrix.transpose())
glBindVertexArray(vao)
index_count = model['face'].size
glDrawElements(GL_TRIANGLES, index_count, GL_UNSIGNED_INT, None)
glBindVertexArray(0)
pygame.display.flip()
def main():
global update_enabled
initialize()
frame_count = 0
offsets = {
'vbo' : {
'prev' : 0,
'curr' : 0
},
'cbo' : {
'prev' : 0,
'curr' : 0
},
'ibo' : {
'prev' : 0,
'curr' : 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.key == pygame.K_c:
#update_color()
pass
if event.key == pygame.K_u:
update_enabled = not update_enabled
if update_enabled:
print('Update triggered')
if update_enabled: # and frame_count % UPDATE_INTERVAL == 0:
offsets['vbo']['curr'] = rand_range(start=0, stop=(VBO_BUFFER_SIZE - VBO_SUB_BUFFER_SIZE), step=VBO_SUB_BUFFER_SIZE)
update_vbo(offsets['vbo']['prev'], offsets['vbo']['curr'])
offsets['vbo']['prev'] = offsets['vbo']['curr']
offsets['cbo']['curr'] = rand_range(start=0, stop=(CBO_BUFFER_SIZE - CBO_SUB_BUFFER_SIZE), step=CBO_SUB_BUFFER_SIZE)
color = np.array([*np.random.uniform(0.0, 1.0, 3), 1.0], dtype='float32')
update_cbo(offsets['vbo']['prev'], offsets['cbo']['curr'], color)
offsets['cbo']['prev'] = offsets['cbo']['curr']
offsets['ibo']['curr'] = rand_range(start=0, stop=(IBO_BUFFER_SIZE - IBO_SUB_BUFFER_SIZE), step=IBO_SUB_BUFFER_SIZE)
update_ibo(offsets['ibo']['prev'], offsets['ibo']['curr'])
offsets['ibo']['prev'] = offsets['ibo']['curr']
update_enabled = False
render()
frame_count += 1
if __name__ == '__main__':
main()
Whenever U
key is pressed, the update_*()
functions are called to update each type of buffer accordingly. However, I receive
raise self._errorClass(
OpenGL.error.GLError: GLError(
err = 1281,
description = b'invalid value',
baseOperation = glBufferSubData,
pyArgs = (
GL_ARRAY_BUFFER,
576,
96,
array([ 1., 1., -1., 1., -1., -1., 1., 1., 1., 1., -1., 1., -1.,
1., -1., -1., -1., -1., -1., 1., 1....,
),
cArgs = (
GL_ARRAY_BUFFER,
576,
96,
array([ 1., 1., -1., 1., -1., -1., 1., 1., 1., 1., -1., 1., -1.,
1., -1., -1., -1., -1., -1., 1., 1....,
),
cArguments = (
GL_ARRAY_BUFFER,
576,
96,
array([ 1., 1., -1., 1., -1., -1., 1., 1., 1., 1., -1., 1., -1.,
1., -1., -1., -1., -1., -1., 1., 1....,
)
)
for the VBO (vertices). Disabling the update of the VBO triggers similar error for the CBO (colors) and IBO (faces).
My goal is to use a single buffer per type (vertices, faces and colors) and update it in some manner. For example in the future I will load a large mesh (constant) representing my terrain and then iterate through a bunch of other meshes that will be loaded and rendered one after the other by updating the data of the remaining buffer, thus not needing to re-load the data that is constant.
I do know that invalid value
errors are emitted if the offset or data are incorrectly passed to the glBufferSubData()
and indeed, if I replace the offsets with 0, I can at see that it’s working (at least the colors are changing). I can’t figure out how my calculations are incorrect.
The offset is calculated using the rand_range()
function, which takes
- start – here 0
- end – the total buffer size minus the size of the respective chunk that represents my mesh either as vertices, colors or faces
- step – the size of the respective chunk that represents my mesh either as vertices, colors or faces
Since I am currently working with the same mesh, step size and end are constant for the respective type of buffer.