坐标系统详解
将坐标变换为标准化设备坐标,再转换为屏幕坐标,通常是分阶段进行的。整个过程中,物体顶点会依次经过多个过渡坐标系,以便在不同阶段进行更便捷的操作。常见的五种坐标系统如下:
- 局部空间(Local Space / Object Space)
- 世界空间(World Space)
- 观察空间(View Space / Eye Space)
- 裁剪空间(Clip Space)
- 屏幕空间(Screen Space)
要实现坐标系统的转换,需要用到三个核心变换矩阵:模型矩阵(Model)、观察矩阵(View) 和 投影矩阵(Projection)。顶点坐标从局部空间出发,依次变换为世界坐标、观察坐标、裁剪坐标,最终成为屏幕坐标。下图展示了整个变换流程及各步骤的作用:
坐标系统转换流程
- 局部坐标
-
- 物体相对于自身原点的坐标,是模型的起始坐标。
- 世界坐标
-
- 局部坐标经过模型矩阵变换后,变为相对于世界原点的坐标。用于在全局空间中定位物体。
- 观察坐标
-
- 世界坐标经过观察矩阵变换后,转换为以摄像机(观察者)为中心的坐标系。
- 裁剪坐标
-
- 观察坐标经过投影矩阵变换,落入裁剪空间。此时坐标被限定在-1.0到1.0范围,超出范围的顶点会被裁剪。
- 屏幕坐标
-
- 裁剪坐标通过视口变换(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);
- 参数说明:
-
- 前两个参数:左右边界
- 第三、四个参数:底部和顶部
- 第五、六个参数:近平面和远平面
2. 透视投影(Perspective Projection)
- 定义一个锥台形状的平截头体,模拟真实的视觉透视效果。
- 使用GLM函数示例:
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
- 参数说明:
-
- 第一个参数:视野角度(fov)
- 第二个参数:宽高比
- 第三、四个参数:近平面和远平面
坐标变换公式
将顶点坐标从局部空间变换到裁剪空间的过程如下:
注意: 矩阵乘法顺序为从右到左,即 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轴指向观察者(穿过屏幕朝向你)。
可用右手法则理解三个坐标轴的方向关系。
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自动完成,确保前面的片段覆盖后面的片段。