学习OpenGL——第七天

30 阅读5分钟

坐标系统详解

将坐标变换为标准化设备坐标,再转换为屏幕坐标,通常是分阶段进行的。整个过程中,物体顶点会依次经过多个过渡坐标系,以便在不同阶段进行更便捷的操作。常见的五种坐标系统如下:

  • 局部空间(Local Space / Object Space)
  • 世界空间(World Space)
  • 观察空间(View Space / Eye Space)
  • 裁剪空间(Clip Space)
  • 屏幕空间(Screen Space)

要实现坐标系统的转换,需要用到三个核心变换矩阵:模型矩阵(Model)、观察矩阵(View) 和 投影矩阵(Projection)。顶点坐标从局部空间出发,依次变换为世界坐标、观察坐标、裁剪坐标,最终成为屏幕坐标。下图展示了整个变换流程及各步骤的作用:

image.png


坐标系统转换流程

  1. 局部坐标
    • 物体相对于自身原点的坐标,是模型的起始坐标。
  1. 世界坐标
    • 局部坐标经过模型矩阵变换后,变为相对于世界原点的坐标。用于在全局空间中定位物体。
  1. 观察坐标
    • 世界坐标经过观察矩阵变换后,转换为以摄像机(观察者)为中心的坐标系。
  1. 裁剪坐标
    • 观察坐标经过投影矩阵变换,落入裁剪空间。此时坐标被限定在-1.0到1.0范围,超出范围的顶点会被裁剪。
  1. 屏幕坐标
    • 裁剪坐标通过视口变换(Viewport Transform),映射到屏幕像素坐标,最终用于渲染。

各坐标系详解

局部坐标

局部空间是物体创建时的坐标空间。比如,立方体的原点可能在(0, 0, 0)。所有模型的顶点最初都在局部空间中,描述的是物体自身的结构。


世界坐标

当多个物体加入场景时,需要为它们分别指定在世界空间中的位置。通过模型矩阵(Model Matrix),将局部坐标变换为世界坐标,实现物体在场景中的摆放。


裁剪空间

在顶点着色器的最后阶段,OpenGL要求所有顶点坐标都落在特定范围内(-1.0到1.0),超出范围的顶点会被裁剪。这个变换由**投影矩阵(Projection Matrix)**完成,将观察空间坐标映射到标准化设备坐标(NDC)。

  • 投影矩阵定义了一个“观察箱”(Viewing Box),也称平截头体(Frustum)。
  • 处于平截头体内的顶点最终会被渲染到屏幕上。
透视除法

裁剪空间坐标会自动进行透视除法,即将x、y、z分量分别除以w分量,得到标准化设备坐标。


投影矩阵的两种形式

1. 正射投影(Orthographic Projection)
  • 定义一个立方体形状的平截头体,所有在其内的顶点都不会被裁剪。
  • 使用GLM函数示例:

glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);

  • 参数说明:
    • 前两个参数:左右边界
    • 第三、四个参数:底部和顶部
    • 第五、六个参数:近平面和远平面

image.png


2. 透视投影(Perspective Projection)
  • 定义一个锥台形状的平截头体,模拟真实的视觉透视效果。
  • 使用GLM函数示例:

glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);

  • 参数说明:
    • 第一个参数:视野角度(fov)
    • 第二个参数:宽高比
    • 第三、四个参数:近平面和远平面

image.png


坐标变换公式

将顶点坐标从局部空间变换到裁剪空间的过程如下:

Vclip=MprojectionMviewMmodelVlocal\mathbf{V}_{\text{clip}} = \mathbf{M}_{\text{projection}} \cdot \mathbf{M}_{\text{view}} \cdot \mathbf{M}_{\text{model}} \cdot \mathbf{V}_{\text{local}}

注意: 矩阵乘法顺序为从右到左,即 projection * view * model * position。

最终结果赋值给顶点着色器中的 gl_Position,OpenGL会自动执行透视除法和裁剪。


3D绘图实操指南

1. 创建模型矩阵

  • 模型矩阵包含位移、缩放和旋转,用于把物体从局部空间变换到世界空间。

变换函数

平移变换:glm::translate

glm::mat4 glm::translate(glm::mat4 const& m, glm::vec3 const& v);

  • 参数1(m):原始变换矩阵,类型为 glm::mat4 或 glm::mat3
  • 参数2(v):平移向量,类型为 glm::vec3(3D)或 glm::vec2(2D)

旋转变换:glm::rotate

glm::mat4 glm::rotate(glm::mat4 const& m, float angle, glm::vec3 const& axis);

  • 参数1(m):原始变换矩阵
  • 参数2(angle):旋转角度(弧度制,需用 glm::radians() 转换)
  • 参数3(axis):旋转轴,glm::vec3

缩放变换:glm::scale

glm::mat4 glm::scale(glm::mat4 const& m, glm::vec3 const& v);

  • 参数1(m):原始变换矩阵
  • 参数2(v):缩放向量,glm::vec3 或 glm::vec2

2. 创建观察矩阵

  • 观察矩阵用于模拟摄像机视角。通常通过将场景沿z轴负方向平移,实现在视野中观察物体。

右手坐标系(Right-handed System)说明:

x轴向右,y轴向上,z轴指向观察者(穿过屏幕朝向你)。

可用右手法则理解三个坐标轴的方向关系。

image.png


3. 创建投影矩阵

  • 透视投影常用于3D场景,声明方式如下:
glm::mat4 projection; 
projection = glm::perspective(glm::radians(45.0f), screenWidth / screenHeight, 0.1f, 100.0f);
  • 通常将变换矩阵通过uniform变量传递到顶点着色器,并在主函数中进行坐标变换:
#version 330 core 
layout (location = 0) 
in vec3 aPos; 
... 
uniform mat4 model; 
uniform mat4 view; 
uniform mat4 projection; 
void main() { 
    // 注意乘法顺序 
    gl_Position = projection * view * model * vec4(aPos, 1.0); 
    ... 
}

4. Z缓冲(深度缓冲)

  • Z缓冲(Z-buffer)/ 深度缓冲(Depth Buffer) 用于存储每个片段的深度信息,确保正确的遮挡关系。
  • 启用深度测试:

glEnable(GL_DEPTH_TEST);

  • 每次绘制前需清除深度缓冲:

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  • 深度测试由OpenGL自动完成,确保前面的片段覆盖后面的片段。