You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

  1. 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.
  2. VAO/VBO/EBO: These objects let the GPU store and access vertex data directly, eliminating the constant CPU-GPU chatter from glBegin/glEnd.
  3. Bulk Rendering: We bind the VAO once per frame, then use glPushMatrix/glPopMatrix to position each cube and render it with a single draw call per cube type (surfaces + edges).
  4. Depth Testing: Enabled GL_DEPTH_TEST to 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:36:41