如何在PyOpenGL中修改相机位置以从不同角度观察立方体
解决PyOpenGL立方体仅显示正面的相机视角问题
问题背景
使用PyOpenGL绘制立方体后,仅显示出正方形(立方体的正面视图),需要将相机移动至(1,2,3)位置并看向原点以实现3D视角观察,但不清楚如何用Python实现类似C++ glm库的矩阵相机控制逻辑。
解决方案
要实现3D视角,需引入MVP(模型-视图-投影)矩阵变换顶点坐标,同时开启深度测试避免遮挡错误。以下是具体步骤:
1. 修改顶点着色器
在顶点着色器中添加MVP矩阵uniform变量,并用它变换顶点位置:
#version 330 core layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aColor; out vec3 fragmentColor; // 添加MVP矩阵uniform uniform mat4 MVP; void main() { // 使用MVP矩阵变换顶点位置 gl_Position = MVP * vec4(aPos, 1); fragmentColor = aColor; }
2. 计算MVP矩阵(Python代码中)
利用glm库计算模型、视图、投影矩阵并组合:
- 模型矩阵:立方体位于原点,直接使用单位矩阵
- 视图矩阵:通过
glm.lookAt创建,参数为相机位置、观察目标点、上方向向量 - 投影矩阵:通过
glm.perspective创建透视投影矩阵
3. 传递MVP矩阵给着色器
获取MVPuniform的位置,在渲染循环中更新并传递矩阵数据。
4. 开启深度测试
在初始化代码中添加gl.glEnable(gl.GL_DEPTH_TEST),确保被遮挡的面不会被绘制在前面。
修改后的完整代码
from OpenGL import GL as gl import glfw import ctypes import sys import glm # 截图函数 def dump_framebuffer_to_ppm(ppm_name, fb_width, fb_height): pixelChannel = 3 pixels = gl.glReadPixels(0, 0, fb_width, fb_height, gl.GL_RGB, gl.GL_UNSIGNED_BYTE) fout = open(ppm_name, "w") fout.write('P3\n{} {}\n255\n'.format(int(fb_width), int(fb_height))) for i in range(0, fb_height): for j in range(0, fb_width): cur = pixelChannel * ((fb_height - i - 1) * fb_width + j) fout.write('{} {} {} '.format(int(pixels[cur]), int(pixels[cur+1]), int(pixels[cur+2]))) fout.write('\n') fout.flush() fout.close() screen_width, screen_height = 512, 512 ss_id = 0 # 截图ID # 创建窗口 if not glfw.init(): sys.exit(1) glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True) glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) glfw.window_hint(glfw.RESIZABLE, glfw.FALSE) title = 'Assignment0' window = glfw.create_window(screen_width, screen_height, title, None, None) if not window: print('GLFW Window Failed') sys.exit(2) glfw.make_context_current(window) gl.glClearColor(0.3, 0.4, 0.5, 0) # 开启深度测试 gl.glEnable(gl.GL_DEPTH_TEST) # 创建顶点数组和缓冲区 vertex_array_id = gl.glGenVertexArrays(1) gl.glBindVertexArray(vertex_array_id) vertex_data = [-0.5,-0.5,-0.5, -0.5,-0.5, 0.5, -0.5, 0.5, 0.5, 0.5, 0.5,-0.5, -0.5,-0.5,-0.5, -0.5, 0.5,-0.5, 0.5,-0.5, 0.5, -0.5,-0.5,-0.5, 0.5,-0.5,-0.5, 0.5, 0.5,-0.5, 0.5,-0.5,-0.5, -0.5,-0.5,-0.5, -0.5,-0.5,-0.5, -0.5, 0.5, 0.5, -0.5, 0.5,-0.5, 0.5,-0.5, 0.5, -0.5,-0.5, 0.5, -0.5,-0.5,-0.5, -0.5, 0.5, 0.5, -0.5,-0.5, 0.5, 0.5,-0.5, 0.5, 0.5, 0.5, 0.5, 0.5,-0.5,-0.5, 0.5, 0.5,-0.5, 0.5,-0.5,-0.5, 0.5, 0.5, 0.5, 0.5,-0.5, 0.5, 0.5, 0.5, 0.5, 0.5,0.5,-0.5, -0.5, 0.5,-0.5, 0.5, 0.5, 0.5, -0.5, 0.5,-0.5, -0.5, 0.5, 0.5, 0.5, 0.5, 0.5, -0.5, 0.5, 0.5, 0.5,-0.5, 0.5] # top mid attr_id = 0 vertex_buffer = gl.glGenBuffers(1) gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vertex_buffer) array_type = (gl.GLfloat * len(vertex_data)) sizeof_float = ctypes.sizeof(ctypes.c_float) gl.glBufferData(gl.GL_ARRAY_BUFFER, len(vertex_data) * sizeof_float, array_type(*vertex_data), gl.GL_STATIC_DRAW) gl.glVertexAttribPointer( attr_id, # attribute 0. 3, # components per vertex attribute gl.GL_FLOAT, # type False, # to be normalized? 0, # stride None # array buffer offset ) gl.glEnableVertexAttribArray(attr_id) color_data = [ 0.583, 0.771, 0.014, 0.609, 0.115, 0.436, 0.327, 0.483, 0.844, 0.822, 0.569, 0.201, 0.435, 0.602, 0.223, 0.310, 0.747, 0.185, 0.597, 0.770, 0.761, 0.559, 0.436, 0.730, 0.359, 0.583, 0.152, 0.483, 0.596, 0.789, 0.559, 0.861, 0.639, 0.195, 0.548, 0.859, 0.014, 0.184, 0.576, 0.771, 0.328, 0.970, 0.406, 0.615, 0.116, 0.676, 0.977, 0.133, 0.971, 0.572, 0.833, 0.140, 0.616, 0.489, 0.997, 0.513, 0.064, 0.945, 0.719, 0.592, 0.543, 0.021, 0.978, 0.279, 0.317, 0.505, 0.167, 0.620, 0.077, 0.347, 0.857, 0.137, 0.055, 0.953, 0.042, 0.714, 0.505, 0.345, 0.783, 0.290, 0.734, 0.722, 0.645, 0.174, 0.302, 0.455, 0.848, 0.225, 0.587, 0.040, 0.517, 0.713, 0.338, 0.053, 0.959, 0.120, 0.393, 0.621, 0.362, 0.673, 0.211, 0.457, 0.820, 0.883, 0.371, 0.982, 0.099, 0.879] attr_id = 1 color_buffer = gl.glGenBuffers(1) gl.glBindBuffer(gl.GL_ARRAY_BUFFER, color_buffer) array_type = (gl.GLfloat * len(color_data)) sizeof_float = ctypes.sizeof(ctypes.c_float) gl.glBufferData(gl.GL_ARRAY_BUFFER, len(color_data) * sizeof_float, array_type(*color_data), gl.GL_STATIC_DRAW) gl.glVertexAttribPointer( attr_id, # attribute 0. 3, # components per vertex attribute gl.GL_FLOAT, # type False, # to be normalized? 0, # stride None # array buffer offset ) gl.glEnableVertexAttribArray(attr_id) shaders = { gl.GL_VERTEX_SHADER: '''\ #version 330 core layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aColor; out vec3 fragmentColor; uniform mat4 MVP; void main() { gl_Position = MVP * vec4(aPos, 1); fragmentColor = aColor; } ''', gl.GL_FRAGMENT_SHADER: '''\ #version 330 core in vec3 fragmentColor; out vec3 color; void main() { color = fragmentColor; } '''} program_id = gl.glCreateProgram() shader_ids = [] for shader_type, shader_src in shaders.items(): shader_id = gl.glCreateShader(shader_type) gl.glShaderSource(shader_id, shader_src) gl.glCompileShader(shader_id) # 检查编译是否成功 result = gl.glGetShaderiv(shader_id, gl.GL_COMPILE_STATUS) nlog = gl.glGetShaderiv(shader_id, gl.GL_INFO_LOG_LENGTH) if nlog: logmsg = gl.glGetShaderInfoLog(shader_id) print("Shader Error", logmsg) sys.exit(1) gl.glAttachShader(program_id, shader_id) shader_ids.append(shader_id) gl.glLinkProgram(program_id) result = gl.glGetProgramiv(program_id, gl.GL_LINK_STATUS) nlog = gl.glGetProgramiv(program_id, gl.GL_INFO_LOG_LENGTH) if nlog: logmsg = gl.glGetProgramInfoLog(program_id) print("Link Error", logmsg) sys.exit(1) gl.glUseProgram(program_id) # 获取MVP矩阵的uniform位置 mvp_uniform_id = gl.glGetUniformLocation(program_id, "MVP") # 计算MVP矩阵相关参数 projection = glm.perspective(glm.radians(45.0), screen_width/screen_height, 0.1, 100.0) view = glm.lookAt(glm.vec3(1,2,3), glm.vec3(0,0,0), glm.vec3(0,1,0)) model = glm.mat4(1.0) mvp = projection * view * model while ( glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS and not glfw.window_should_close(window) ): # 按P键截图 if glfw.get_key(window, glfw.KEY_P) == glfw.PRESS: print ("Capture Window ", ss_id) buffer_width, buffer_height = glfw.get_framebuffer_size(window) ppm_name = "Assignment0-ss" + str(ss_id) + ".ppm" dump_framebuffer_to_ppm(ppm_name, buffer_width, buffer_height) ss_id += 1 # 绘制立方体 gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) # 传递MVP矩阵给着色器 gl.glUniformMatrix4fv(mvp_uniform_id, 1, gl.GL_FALSE, glm.value_ptr(mvp)) gl.glDrawArrays(gl.GL_TRIANGLES, 0, 12*3) glfw.swap_buffers(window) glfw.poll_events() # 释放资源 for shader_id in shader_ids: gl.glDetachShader(program_id, shader_id) gl.glDeleteShader(shader_id) gl.glUseProgram(0) gl.glDeleteProgram(program_id)
关键修改说明
- 深度测试:添加
gl.glEnable(gl.GL_DEPTH_TEST),确保远处的面不会遮挡近处的面 - 顶点着色器:新增
uniform mat4 MVP,并使用该矩阵变换顶点位置 - MVP矩阵计算:
glm.lookAt(glm.vec3(1,2,3), glm.vec3(0,0,0), glm.vec3(0,1,0)):创建相机视图矩阵,相机在(1,2,3)看向原点,Y轴为上方向glm.perspective(glm.radians(45.0), screen_width/screen_height, 0.1, 100.0):创建透视投影矩阵,45度视场角,适配窗口宽高比
- 矩阵传递:通过
gl.glUniformMatrix4fv将MVP矩阵传递给着色器
内容的提问来源于stack exchange,提问作者Victoria Robinson
相关产品推荐
相关产品推荐

