I have a scene that I need to render quickly. So I successfully generate the vertices of the triangles as numpy, upload to GPU and render it very fast. So far so good.
The problem starts when some of the elements in the scene need to disappear from the scene. What I do is traverse the entire array and filter out the vertices the scene elements that no longer need to be drawn, upload it again to GPU and then render it. This obviously takes a few seconds which is unacceptable. I'm looking for a quick way to modify the array and redraw the scene.
This a simplified version of the code I use: (Would also appreciate any comments about the code itself)
from OpenGL.GL import *
from OpenGL.GLU import *
import numpy as np
import ctypes
vertex_dtype = [("position", np.float32, 3), ("color", np.float32, 4)]
vertex_array_object = None
vertex_buffer = None
shader_program = .......
def upload_to_gpu(vertices_np):
global vertex_array_object, vertex_buffer
if vertex_array_object is None:
vertex_array_object = glGenVertexArrays(1)
if vertex_buffer is None:
vertex_buffer = glGenBuffers(1)
glBindVertexArray(vertex_array_object)
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer)
glBufferData(GL_ARRAY_BUFFER, vertices_np.nbytes, vertices_np, GL_STATIC_DRAW)
stride = vertices_np.strides[0]
offset = ctypes.c_void_p(0)
position_location = glGetAttribLocation(shader_program, 'a_position')
glEnableVertexAttribArray(position_location)
glVertexAttribPointer(position_location, 3, GL_FLOAT, False, stride, offset)
offset = ctypes.c_void_p(vertices_np.dtype["position"].itemsize)
color_location = glGetAttribLocation(shader_program, "a_color")
glEnableVertexAttribArray(color_location)
glVertexAttribPointer(color_location, 4, GL_FLOAT, False, stride, offset)
glBindVertexArray(0)
glBindBuffer(GL_ARRAY_BUFFER, 0)
def create_np_array(all_vertices):
vertices_np = np.zeros(len(all_vertices), dtype=vertex_dtype)
cur_idx = 0
for vertex in all_vertices:
if ....should_ignore_triangle....:
continue
vertices_np[cur_idx]["position"] = [vertex[0], vertex[1], vertex[2]]
vertices_np[cur_idx]["color"] = ......
cur_idx += 1
vertices_np = vertices_np[:cur_idx]
return vertices_np
def draw_scene(vertices_np):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glUseProgram(shader_program)
# Set view and projection matrix
glBindVertexArray(vertex_array_object)
glDrawArrays(GL_TRIANGLES, 0, vertices_np.shape[0])
def main():
all_vertices = [[0, 0, 0], [1, 0, 0], [0, 1, 0], ......] # long list of ~million vertices
vertices_np = create_np_array(all_vertices)
upload_to_gpu(vertices_np)
# Now we are ready to start rendering
draw_scene(vertices_np) # works fine
# Now something changes and we need to regenerate the vertices_np and upload it again
vertices_np = create_np_array(all_vertices) # <-- THIS TAKES TOO MUCH TIME
upload_to_gpu(vertices_np)
draw_scene(vertices_np) # works fine
If you only want to omit vertices, but you don't want to add new ones, then I recommend just to draw the subset of vertices you want. Don't change any data on the GPU or recreate any vertex array.
Create an array of tuples, which contains the index ranges of the vertices, to be drawn. For this find the first vertex which has to be omitted and append a "range" tuple to the tuple list. Find the next vertex which should be drawn and repeat the process till the end of the array:
def draw_np_array(all_vertices):
idx_list = []
cur_start_idx = 0
cur_end_idx = 0
for vertex in all_vertices:
if ....should_ignore_triangle....:
if cur_end_idx > cur_start_idx:
idx_list.append( (cur_start_idx, cur_end_idx) );
cur_end_idx += 1
cur_start_idx = cur_end_idx
continue
cur_end_idx += 1
if cur_end_idx > cur_start_idx:
idx_list.append( (cur_start_idx, cur_end_idx) );
return idx_list
Draw the scene with an "intelligent" draw call:
def draw_scene(idx_list):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glUseProgram(shader_program)
# Set view and projection matrix
glBindVertexArray(vertex_array_object)
for idx_range in idx_list:
start = idx_range[0]
count = idx_range[1]-idx_range[0]
glDrawArrays(GL_TRIANGLES, start, count)
You can further improve this by using glMultiDrawArrays
, which can draw multiple ranges of an array by one draw call, instead of drawing the arrays in a loop.