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

使用PyGame+OpenGL加载OBJ模型后无法显示网格的问题

3D网格渲染空白问题排查

我使用PyGame、OpenGL、glm及pyassimp编写代码,预期加载并渲染res/teapot.obj中的3D网格,但运行后仅显示空白界面,无法看到目标网格。代码如下:

import pygame as pg
import numpy as np

import pyassimp
import glm

from OpenGL.GL import *

vertex_shader = """
#version 330 core

layout (location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 projection;
uniform mat4 view;

void main(){
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}

"""

fragment_shader = """
#version 330 core

out vec4 fragColor;

void main(){
    fragColor = vec4(1.0, 0.0, 1.0, 1.0);
}

"""


class Camera3d():
    def __init__(self, position=glm.vec3(0.0), rotation=glm.vec3(0.0)):
        self.position = position
        self.rotation = rotation
        self.projection_matrix = glm.perspective(glm.radians(75), 800 / 600, 0.1, 1024)

    @property
    def forward(self):
        forward = glm.vec3()
        forward.x = glm.cos(glm.radians(self.rotation.y)) * glm.cos(glm.radians(self.rotation.x))
        forward.y = glm.sin(glm.radians(self.rotation.x))
        forward.z = glm.sin(glm.radians(self.rotation.y)) * glm.cos(glm.radians(self.rotation.x))
        return glm.normalize(forward)

    @property
    def right(self):
        return glm.normalize(glm.cross(self.forward, glm.vec3(0, 1, 0)))

    @property
    def up(self):
        return glm.normalize(glm.cross(self.right, self.forward))

    def update(self):
        keys = pg.key.get_pressed()

        if keys[pg.K_w]:
            self.position += glm.vec3(0, 0, 1)
        elif keys[pg.K_s]:
            self.position += glm.vec3(0, 0, -1)
        if keys[pg.K_a]:
            self.position += glm.vec3(-1, 0, 0)
        elif keys[pg.K_d]:
            self.position += glm.vec3(1, 0, 0)

    def get_projection_matrix(self):
        return self.projection_matrix

    def get_view_matrix(self):
        return glm.lookAt(self.position, self.position + self.forward, self.up)


class Mesh3d():
    def __init__(self):
        self.position = glm.vec3(0.0)
        self.rotation = glm.vec3(0.0)

        self.vertices = np.array([], dtype=np.float32)
        self.texcoords = np.array([], dtype=np.uint32)
        self.normals = np.array([], dtype=np.float32)
        self.faces = np.array([], dtype=np.uint32)

        self.vao, self.vbo, self.ebo = 0, 0, 0
        self.shader = self.create_shader_program(vertex_shader, fragment_shader)

    def compile_shader(self, source: str, shader_type: GL_SHADER_TYPE):
        shader = glCreateShader(shader_type)
        glShaderSource(shader, source)
        glCompileShader(shader)
        return shader

    def create_shader_program(self, vertex_source: str, fragment_source: str):
        vertex_shader = self.compile_shader(vertex_source, GL_VERTEX_SHADER)
        fragment_shader = self.compile_shader(fragment_source, GL_FRAGMENT_SHADER)

        shader_program = glCreateProgram()
        glAttachShader(shader_program, vertex_shader)
        glAttachShader(shader_program, fragment_shader)
        glLinkProgram(shader_program)

        glDeleteShader(vertex_shader)
        glDeleteShader(fragment_shader)

        return shader_program

    def get_model_matrix(self):
        model = glm.mat4(1.0)
        model = glm.translate(model, self.position)
        return model

    def setup_buffers(self):
        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)

        self.vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
        glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes,
                     self.vertices, GL_STATIC_DRAW)

        self.ebo = glGenBuffers(1)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ebo)
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.faces.nbytes,
                     self.faces, GL_STATIC_DRAW)

        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
                              3 * self.vertices.dtype.itemsize, None)
        glEnableVertexAttribArray(0)

        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
                              2 * self.faces.dtype.itemsize, None)
        glEnableVertexAttribArray(1)

        glBindVertexArray(0)

    def render(self, camera: Camera3d):
        if not self.vao:
            return
        glUseProgram(self.shader)

        model_loc = glGetUniformLocation(self.shader, "model")
        glUniformMatrix4fv(model_loc, 1, GL_FALSE,
                           glm.value_ptr(self.get_model_matrix()))

        projection_loc = glGetUniformLocation(self.shader, "projection")
        glUniformMatrix4fv(projection_loc, 1, GL_FALSE,
                           glm.value_ptr(camera.get_projection_matrix()))

        view_loc = glGetUniformLocation(self.shader, "view")
        glUniformMatrix4fv(view_loc, 1, GL_FALSE,
                           glm.value_ptr(camera.get_view_matrix()))

        glBindVertexArray(self.vao)

        glDrawElements(GL_TRIANGLES, len(self.faces) * 3, GL_UNSIGNED_INT, None)

        glBindVertexArray(0)
        glUseProgram(0)

    def load_from(self, filename: str):
        with pyassimp.load(filename) as scene:
            mesh = scene.meshes[0]
            self.vertices = mesh.vertices
            self.texcoords = mesh.texturecoords
            self.normals = mesh.normals
            self.faces = mesh.faces
        self.setup_buffers()
        return self


class App():
    def __init__(self):
        self.win = pg.display.set_mode((800, 600), pg.OPENGL | pg.DOUBLEBUF, vsync=True)
        self.clock = pg.time.Clock()

        self.mesh = Mesh3d().load_from('res/teapot.obj')
        self.camera = Camera3d(glm.vec3(0, 0, -3))

    def __events(self):
        for e in pg.event.get():
            if e.type == pg.QUIT:
                self.__is_running = False

    def __update(self):
        self.camera.update()

    def __render(self):
        glClearColor(0.05, 0.05, 0.05, 1)
        glClear(GL_COLOR_BUFFER_BIT)

        self.mesh.render(self.camera)

        pg.display.flip()

    def run(self):
        self.__is_running = True

        while self.__is_running:
            self.__events()
            self.__update()
            self.__render()
            self.clock.tick(0)

        pg.quit()


if __name__ == '__main__':
    App().run()

问题排查及修复方案

  • 缺少深度测试:OpenGL默认不启用深度测试,导致面片绘制顺序混乱,无法正确显示模型。在App.__init__中添加glEnable(GL_DEPTH_TEST),并将__render中的glClear(GL_COLOR_BUFFER_BIT)改为glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)。

  • 相机移动方向错误:当前Camera3d.update方法使用世界空间轴向移动,相机初始位置在z=-3,按W键会向+Z方向移动,远离模型。应改为基于相机自身前向向量移动,修改代码:

    if keys[pg.K_w]:
        self.position += self.forward * 0.1
    elif keys[pg.K_s]:
        self.position -= self.forward * 0.1
    if keys[pg.K_a]:
        self.position -= self.right * 0.1
    elif keys[pg.K_d]:
        self.position += self.right * 0.1
    

    加入速度系数(如0.1)避免移动过快。

  • VAO设置冗余且错误:顶点着色器仅使用aPos属性(location=0),但setup_buffers中错误地给location=1绑定了EBO缓冲区的属性,这会干扰VAO状态。删除以下代码:

    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
                          2 * self.faces.dtype.itemsize, None)
    glEnableVertexAttribArray(1)
    
  • 绘制元素数量计算错误:glDrawElements的元素数量应使用self.faces.size(pyassimp返回的faces是二维数组,size为总索引数),而非len(self.faces)*3,修改render方法中的调用:

    glDrawElements(GL_TRIANGLES, self.faces.size, GL_UNSIGNED_INT, None)
    
  • Shader编译/链接无错误检查:当前代码未验证Shader编译和Program链接是否成功,若存在语法错误会导致渲染失败。添加错误检查逻辑:
    在compile_shader中加入:

    if not glGetShaderiv(shader, GL_COMPILE_STATUS):
        error = glGetShaderInfoLog(shader).decode()
        glDeleteShader(shader)
        raise RuntimeError(f"Shader编译失败: {error}")
    

    在create_shader_program中加入:

    if not glGetProgramiv(shader_program, GL_LINK_STATUS):
        error = glGetProgramInfoLog(shader_program).decode()
        glDeleteProgram(shader_program)
        raise RuntimeError(f"Program链接失败: {error}")
    
  • Pyassimp数据格式显式转换:确保加载的顶点和索引数据为正确的类型,修改load_from方法:

    self.vertices = np.array(mesh.vertices, dtype=np.float32)
    self.faces = np.array(mesh.faces, dtype=np.uint32).flatten()
    

    同时将faces展平为一维数组,适配EBO的要求。

内容的提问来源于stack exchange,提问作者bin4ry

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 05:15:01