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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 12:05:33