使用PyGame+OpenGL加载OBJ模型后无法显示网格的问题
我使用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

