Pygame OpenGL 3D立方体卡顿:glBegin/glEnd替代方案咨询
Hey there! Let's tackle that PyOpenGL lag issue you're facing. The root cause here is definitely the old-school glBegin/glEnd immediate mode you're using—combined with rendering 75 cubes this way, you're forcing constant, inefficient back-and-forth between your CPU and GPU. The U盘 storage might add a tiny bit of IO overhead, but the real bottleneck is the rendering API.
The Solution: Switch to VAOs & VBOs (Modern OpenGL)
Instead of sending vertex data one piece at a time with glBegin/glEnd, we'll use Vertex Array Objects (VAOs) and Vertex Buffer Objects (VBOs) to upload all our cube data to the GPU once, then render everything in bulk. This cuts down on CPU-GPU communication drastically and will fix that lag.
Full Modified Code
Here's the updated version of your code using VAOs/VBOs, with comments explaining key changes:
import pygame from pygame.locals import * from OpenGL.GL import * from OpenGL.GLU import * import random import ctypes # Needed for proper pointer handling in OpenGL # Cube base data: vertices, surfaces, edges, and colors vertices = ( (1, -1, -1), (1, 1, -1), (-1, 1, -1), (-1, -1, -1), (1, -1, 1), (1, 1, 1), (-1, -1, 1), (-1, 1, 1), ) surfaces = ( (0,1,2,3), (3,2,7,6), (6,7,5,4), (4,5,1,0), (1,5,7,2), (4,0,3,6), ) edges = ( (0,1), (0,3), (0,4), (2,1), (2,3), (2,7), (6,3), (6,4), (6,7), (5,1), (5,4), (5,7), ) colors = ( (1,0,0), (0,1,0), (0,0,1), (0,0,0), (1,1,1), (0,1,1), (1,0,0), (0,1,0), (0,0,1), (0,0,0), (1,1,1), (0,1,1), ) # Pre-combine vertex positions and colors into a single array (GPU-friendly) base_vertex_data = [] for surface_idx, surface in enumerate(surfaces): for vert_idx in surface: # Add position data (3 floats) base_vertex_data.extend(vertices[vert_idx]) # Add color data (3 floats) for this vertex base_vertex_data.extend(colors[surface_idx * 4]) # Pre-combine edge indices for efficient line rendering edge_index_data = [] for edge in edges: edge_index_data.extend(edge) def generate_cube_position(max_distance): # Generate random position for each cube x = random.randrange(-10, 10) y = random.randrange(-10, 10) z = random.randrange(-max_distance, -20) return (x, y, z) def init_gpu_buffers(): # Create VAO (Vertex Array Object) to store vertex attribute configs vao = glGenVertexArrays(1) glBindVertexArray(vao) # Create VBO (Vertex Buffer Object) to store our vertex data on GPU vbo = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, vbo) # Upload data to GPU: use GL_STATIC_DRAW since data doesn't change glBufferData( GL_ARRAY_BUFFER, len(base_vertex_data) * 4, # Each float is 4 bytes (GLfloat * len(base_vertex_data))(*base_vertex_data), GL_STATIC_DRAW ) # Configure position attribute (index 0: 3 floats, stride 6*4 bytes) glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * 4, ctypes.c_void_p(0)) glEnableVertexAttribArray(0) # Configure color attribute (index 1: 3 floats, offset 3*4 bytes from position) glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * 4, ctypes.c_void_p(3 * 4)) glEnableVertexAttribArray(1) # Create EBO (Element Buffer Object) for edge indices ebo = glGenBuffers(1) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) glBufferData( GL_ELEMENT_ARRAY_BUFFER, len(edge_index_data) * 4, # Each index is 4 bytes (GLuint) (GLuint * len(edge_index_data))(*edge_index_data), GL_STATIC_DRAW ) # Return VAO handle, number of vertices, and number of edge indices return vao, len(base_vertex_data) // 6, len(edge_index_data) def main(): pygame.init() display = (1000, 800) pygame.display.set_mode(display, DOUBLEBUF | OPENGL) # Set up perspective projection (increased far clip to 500 for larger distances) gluPerspective(45, (display[0]/display[1]), 0.1, 500.0) glTranslatef(random.randrange(-5,5), random.randrange(-5,5), -40) # Initialize GPU buffers (VAO/VBO/EBO) cube_vao, num_cube_verts, num_edge_indices = init_gpu_buffers() # Enable depth testing to fix object occlusion glEnable(GL_DEPTH_TEST) # Generate positions for all 75 cubes max_distance = 300 cube_positions = [generate_cube_position(max_distance) for _ in range(75)] x_move = 0 y_move = 0 while True: for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() quit() # Handle key presses for movement if event.type == pygame.KEYDOWN: if event.key == pygame.K_LEFT: x_move = 0.3 elif event.key == pygame.K_RIGHT: x_move = -0.3 elif event.key == pygame.K_UP: y_move = -0.3 elif event.key == pygame.K_DOWN: y_move = 0.3 elif event.type == pygame.KEYUP: if event.key in (pygame.K_LEFT, pygame.K_RIGHT): x_move = 0 elif event.key in (pygame.K_UP, pygame.K_DOWN): y_move = 0 # Clear screen and depth buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) # Apply camera movement glTranslatef(x_move, y_move, 0.5) # Bind the VAO once, then render all cubes in bulk glBindVertexArray(cube_vao) for tx, ty, tz in cube_positions: # Save current matrix state, translate to cube position glPushMatrix() glTranslatef(tx, ty, tz) # Draw cube surfaces using pre-loaded vertex data glDrawArrays(GL_QUADS, 0, num_cube_verts) # Draw cube edges using pre-loaded index data glDrawElements(GL_LINES, num_edge_indices, GL_UNSIGNED_INT, None) # Restore matrix state for next cube glPopMatrix() # Update display pygame.display.flip() # Reduced wait time since performance is improved pygame.time.wait(5) if __name__ == "__main__": main()
Key Improvements Explained
- Preprocessed GPU Data: We combined vertex positions and colors into a single array, so we only upload data to the GPU once, not 75 times.
- VAO/VBO/EBO: These objects let the GPU store and access vertex data directly, eliminating the constant CPU-GPU chatter from
glBegin/glEnd. - Bulk Rendering: We bind the VAO once per frame, then use
glPushMatrix/glPopMatrixto position each cube and render it with a single draw call per cube type (surfaces + edges). - Depth Testing: Enabled
GL_DEPTH_TESTto fix visual glitches where cubes would render incorrectly over each other.
Bonus Tips
- Move your project from the U盘 to your local hard drive—this will eliminate any potential IO lag from slow USB transfer speeds.
- If you ever need to render hundreds/thousands of cubes, look into instanced rendering (
glDrawArraysInstanced) to render all cubes with a single draw call, which is even more efficient.
内容的提问来源于stack exchange,提问作者Moses Wescombe

