I want to render a large scene made up of many cubes. My first take on this was to create a single cube VBO, and then draw it multiple times using a uniform model matrix to translate it. This was slow, as I was calling glDrawArrays
many times per frame.
So I decided to instead use a single, giant VBO with every single cube vertex and an additional translation vector. Basically, I took my old generic cube VBO plus the translation vector, and appended that list to my VBO's list for every cube that I want to draw. I then bind a VAO and draw it. I also changed my vertex shader so that it accepts that translation vector as a vertex attribute, and I generate a model matrix inside the shader.
Now, this doesn't return any errors, which is great, but it doesn't actually draw anything, which is not great, even though the process is fairly similar to what I was doing before.
Here's some code:
Whopper VBO creator:
class render:
def __init__(self, coords_list):
self.render_list = []
for i in coords_list:
self.render_list.append([
# Cube model Texture Translation
0.0, 0.0, 0.0, 1.0, 1.0, i[0], i[1], i[2],
1.0, 0.0, 0.0, 0.0, 1.0, i[0], i[1], i[2],
1.0, 1.0, 0.0, 0.0, 0.0, i[0], i[1], i[2],
1.0, 1.0, 0.0, 0.0, 0.0, i[0], i[1], i[2],
0.0, 1.0, 0.0, 1.0, 0.0, i[0], i[1], i[2],
0.0, 0.0, 0.0, 1.0, 1.0, i[0], i[1], i[2],
0.0, 0.0, 1.0, 1.0, 1.0, i[0], i[1], i[2],
1.0, 0.0, 1.0, 0.0, 1.0, i[0], i[1], i[2],
1.0, 1.0, 1.0, 0.0, 0.0, i[0], i[1], i[2],
1.0, 1.0, 1.0, 0.0, 0.0, i[0], i[1], i[2],
0.0, 1.0, 1.0, 1.0, 0.0, i[0], i[1], i[2],
0.0, 0.0, 1.0, 1.0, 1.0, i[0], i[1], i[2],
0.0, 1.0, 1.0, 1.0, 0.0, i[0], i[1], i[2],
0.0, 1.0, 0.0, 0.0, 0.0, i[0], i[1], i[2],
0.0, 0.0, 0.0, 0.0, 1.0, i[0], i[1], i[2],
0.0, 0.0, 0.0, 0.0, 1.0, i[0], i[1], i[2],
0.0, 0.0, 1.0, 1.0, 1.0, i[0], i[1], i[2],
0.0, 1.0, 1.0, 1.0, 0.0, i[0], i[1], i[2],
1.0, 1.0, 1.0, 1.0, 0.0, i[0], i[1], i[2],
1.0, 1.0, 0.0, 0.0, 0.0, i[0], i[1], i[2],
1.0, 0.0, 0.0, 0.0, 1.0, i[0], i[1], i[2],
1.0, 0.0, 0.0, 0.0, 1.0, i[0], i[1], i[2],
1.0, 0.0, 1.0, 1.0, 1.0, i[0], i[1], i[2],
1.0, 1.0, 1.0, 1.0, 0.0, i[0], i[1], i[2],
0.0, 0.0, 0.0, 0.0, 1.0, i[0], i[1], i[2],
1.0, 0.0, 0.0, 1.0, 1.0, i[0], i[1], i[2],
1.0, 0.0, 1.0, 1.0, 0.0, i[0], i[1], i[2],
1.0, 0.0, 1.0, 1.0, 0.0, i[0], i[1], i[2],
0.0, 0.0, 1.0, 0.0, 0.0, i[0], i[1], i[2],
0.0, 0.0, 0.0, 0.0, 1.0, i[0], i[1], i[2],
0.0, 1.0, 0.0, 0.0, 1.0, i[0], i[1], i[2],
1.0, 1.0, 0.0, 1.0, 1.0, i[0], i[1], i[2],
1.0, 1.0, 1.0, 1.0, 0.0, i[0], i[1], i[2],
1.0, 1.0, 1.0, 1.0, 0.0, i[0], i[1], i[2],
0.0, 1.0, 1.0, 0.0, 0.0, i[0], i[1], i[2],
0.0, 1.0, 0.0, 0.0, 1.0, i[0], i[1], i[2],
])
print('Cube added!')
def create_buffers(self):
render_vbo, self.render_vao = glGenBuffers(1), glGenVertexArrays(1)
glBindVertexArray(self.render_vao)
glBindBuffer(GL_ARRAY_BUFFER, render_vbo)
glBufferData(GL_ARRAY_BUFFER, np.array(self.render_list), GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(0))
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(12))
glEnableVertexAttribArray(1)
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 32, ctypes.c_void_p(20))
glEnableVertexAttribArray(2)
def draw_buffer(self, program, texture):
program.use()
glBindTexture(GL_TEXTURE_2D, texture)
glBindVertexArray(self.render_vao)
glDrawArrays(GL_TRIANGLES, 0, int(len(self.render_list)/8))
Initialization and render loop:
def main():
global delta_time, last_frame
test_chunk = chunk.chunk((0,0,0)) #Creates a 16x16x16 cube of cubes
test_chunk.fill_layers(0, 16, 1) #Works fine
window = utilities.window()
camera.setup_window(window)
glEnable(GL_DEPTH_TEST)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
shader_program = utilities.shader(vertex_source_3d, fragment_source_3d, '330')
shader_program.compile()
#Check which cubes are exposed and should be rendered
#Works fine
exposed_list = [i for i, blocktype in np.ndenumerate(test_chunk.data) if test_chunk.return_if_exposed(i) == True and blocktype != 0]
shader_program.use()
shader_program.set_int('texture0', 0)
camera_direction = glm.vec3()
yaw = -90.0
second_counter = 0
frame_counter = 0
render = cube.render(exposed_list)
render.create_buffers()
while not window.check_if_closed():
current_frame = glfw.get_time()
delta_time = current_frame - last_frame
last_frame = current_frame
second_counter += delta_time
frame_counter += 1
window.refresh(0)
camera.process_input(window, delta_time)
camera.testing_commands(window)
glActiveTexture(GL_TEXTURE0)
shader_program.use()
pos, looking, up = camera.return_vectors()
view = glm.lookAt(pos, looking, up)
projection = glm.perspective(glm.radians(45), window.size[0]/window.size[1], 0.1, 100)
shader_program.set_mat4('view', glm.value_ptr(view))
shader_program.set_mat4('projection', glm.value_ptr(projection))
render.draw_buffer(shader_program, cobble_tex_ID)
glBindVertexArray(0)
if second_counter >= 1:
print(frame_counter)
second_counter, frame_counter = 0, 0
window.refresh(1)
window.close()
if __name__ == '__main__':
main()
Vertex shader:
#version %s core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
layout (location = 2) in vec3 cube_coord;
out vec2 TexCoord;
uniform mat4 view;
uniform mat4 projection;
mat4 model = mat4(1.0);
void main() {
model[0].w = cube_coord.x;
model[1].w = cube_coord.y;
model[2].w = cube_coord.z;
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = aTexCoord;
}
Fragment shader:
version %s core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D texture0;
void main()
{
FragColor = texture(texture0, TexCoord);
}
The assignment of the translation to the model matrix is wrong. A glsl matrix is stored in column major order. The translation is the 4th column. A typical model matrix looks as follows:
mat4 m44 = mat4(
vec4( Xx, Xy, Xz, 0.0),
vec4( Yx, Xy, Yz, 0.0),
vec4( Zx Zy Zz, 0.0),
vec4( Tx, Ty, Tz, 1.0) );
You have to change the model matrix initialization:
mat4 model = mat4(1.0);
void main() {
model[3] = vec4(cube_coord.xyz, 1.0);
// [...]
}
Further more, you have to specify the data type for the numpy.array
(numpy.float32
):
glBufferData(GL_ARRAY_BUFFER, np.array(self.render_list), GL_STATIC_DRAW)
glBufferData(GL_ARRAY_BUFFER, np.array(self.render_list, np.float32), GL_STATIC_DRAW)
The vertex array is 2 dimensional, thus len(self.render_list)/8
is not the number of vertices:
glDrawArrays(GL_TRIANGLES, 0, int(len(self.render_list)/8))
no_of_verices = len(self.render_list) * 36
glDrawArrays(GL_TRIANGLES, 0, no_of_verices)
respectively
no_of_verices = len(self.render_list) * len(self.render_list[0]) // 8
glDrawArrays(GL_TRIANGLES, 0, no_of_verices)