几何变换是计算机图形学的核心基础之一,它决定了我们如何在二维和三维空间中移动、旋转、缩放物体,以及如何将三维世界投影到二维屏幕上。本章将深入探讨变换的数学原理、实现细节和优化技巧,为后续的渲染管线学习打下坚实基础。
在计算机图形学中,我们使用齐次坐标(Homogeneous Coordinates)来统一表示点和向量,并使得仿射变换可以用矩阵乘法来表示。齐次坐标是投影几何的核心概念,它优雅地解决了平移变换无法用矩阵乘法表示的问题。
对于二维空间:
对于三维空间:
齐次坐标本质上是将 $n$ 维空间嵌入到 $n+1$ 维投影空间中:
这种区分不仅是数学上的优雅,更具有深刻的几何意义:
glm::perspective 等库函数时注意精度设置gl_Position 是四维齐次坐标二维变换构成了计算机图形学的基础,它们可以组合产生复杂的效果。每种变换都有其独特的几何意义和代数性质。
平移将所有点沿着指定方向移动相同的距离:
\[\mathbf{T}(t_x, t_y) = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y \\ 0 & 0 & 1 \end{bmatrix}\]数学性质:
几何意义:
实现细节:
点的平移:[x', y', 1]^T = T(tx, ty) × [x, y, 1]^T = [x+tx, y+ty, 1]^T
向量不变:[x', y', 0]^T = T(tx, ty) × [x, y, 0]^T = [x, y, 0]^T
绕原点逆时针旋转 $\theta$ 角度的变换矩阵:
\[\mathbf{R}(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 \\ \sin\theta & \cos\theta & 0 \\ 0 & 0 & 1 \end{bmatrix}\]推导过程: 考虑单位圆上的点 $(r\cos\phi, r\sin\phi)$ 旋转 $\theta$ 后: \(\begin{align} x' &= r\cos(\phi + \theta) = r\cos\phi\cos\theta - r\sin\phi\sin\theta \\ y' &= r\sin(\phi + \theta) = r\cos\phi\sin\theta + r\sin\phi\cos\theta \end{align}\)
这给出了旋转矩阵的形式。
重要性质:
与复数的联系: 二维旋转可用复数乘法表示: \(z' = e^{i\theta}z = (\cos\theta + i\sin\theta)(x + iy)\)
这揭示了旋转的深层数学结构。
小角度近似: 当 $\theta$ 很小时: \(\mathbf{R}(\theta) \approx \begin{bmatrix} 1 & -\theta & 0 \\ \theta & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\)
这在增量旋转计算中很有用。
缩放变换改变物体的大小:
\[\mathbf{S}(s_x, s_y) = \begin{bmatrix} s_x & 0 & 0 \\ 0 & s_y & 0 \\ 0 & 0 & 1 \end{bmatrix}\]分类与性质:
特征分解: 缩放矩阵是对角矩阵,其特征向量是坐标轴方向:
缩放中心: 绕任意点 $(c_x, c_y)$ 缩放: \(\mathbf{S}_c = \mathbf{T}(c_x, c_y) \mathbf{S}(s_x, s_y) \mathbf{T}(-c_x, -c_y)\)
展开后: \(\mathbf{S}_c = \begin{bmatrix} s_x & 0 & c_x(1-s_x) \\ 0 & s_y & c_y(1-s_y) \\ 0 & 0 & 1 \end{bmatrix}\)
错切变换使物体发生倾斜:
水平错切: \(\mathbf{H}_x(s) = \begin{bmatrix} 1 & s & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\)
垂直错切: \(\mathbf{H}_y(s) = \begin{bmatrix} 1 & 0 & 0 \\ s & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}\)
几何解释:
重要性质:
应用场景:
反射产生镜像效果:
基本反射:
关于任意直线的反射: 对于直线 $\mathbf{n} \cdot \mathbf{p} = d$($\mathbf{n}$ 是单位法向量):
\[\mathbf{F} = \mathbf{I} - 2\mathbf{n}\mathbf{n}^T\]对于直线 $ax + by + c = 0$:
性质:
组合反射:
保持性质的层次结构:
三维变换将二维概念扩展到三维空间,但引入了更多的复杂性,特别是在旋转表示和组合方面。
三维平移扩展了二维情况:
\[\mathbf{T}(t_x, t_y, t_z) = \begin{bmatrix} 1 & 0 & 0 & t_x \\ 0 & 1 & 0 & t_y \\ 0 & 0 & 1 & t_z \\ 0 & 0 & 0 & 1 \end{bmatrix}\]数学性质:
应用场景:
三维缩放影响物体的体积和比例:
\[\mathbf{S}(s_x, s_y, s_z) = \begin{bmatrix} s_x & 0 & 0 & 0 \\ 0 & s_y & 0 & 0 \\ 0 & 0 & s_z & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}\]几何意义:
| 体积变化:$V’ = | s_x \cdot s_y \cdot s_z | \cdot V$ |
特殊缩放类型:
缩放中心的选择:
绕点P缩放 = T(P) × S(sx,sy,sz) × T(-P)
常见中心:物体中心、包围盒中心、质心
三维旋转是最复杂的基本变换,有多种表示方法。
基本轴旋转
绕 x 轴旋转(俯仰 Pitch): \(\mathbf{R}_x(\theta) = \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & \cos\theta & -\sin\theta & 0 \\ 0 & \sin\theta & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}\)
绕 y 轴旋转(偏航 Yaw): \(\mathbf{R}_y(\theta) = \begin{bmatrix} \cos\theta & 0 & \sin\theta & 0 \\ 0 & 1 & 0 & 0 \\ -\sin\theta & 0 & \cos\theta & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}\)
绕 z 轴旋转(滚转 Roll): \(\mathbf{R}_z(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta & 0 & 0 \\ \sin\theta & \cos\theta & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}\)
记忆规则:
旋转矩阵的性质:
欧拉角表示
任意旋转可分解为三次基本旋转:
ZYX顺序(偏航-俯仰-滚转): \(\mathbf{R} = \mathbf{R}_z(\psi)\mathbf{R}_y(\theta)\mathbf{R}_x(\phi)\)
XYZ顺序(滚转-俯仰-偏航): \(\mathbf{R} = \mathbf{R}_x(\phi)\mathbf{R}_y(\theta)\mathbf{R}_z(\psi)\)
万向锁(Gimbal Lock)问题:
解决方案:
三维反射扩展了二维情况:
基本平面反射:
任意平面反射: 对于平面 $\mathbf{n} \cdot \mathbf{p} = d$($\mathbf{n}$ 是单位法向量):
\[\mathbf{F} = \mathbf{I} - 2\mathbf{n}\mathbf{n}^T\]展开为4×4矩阵: \(\mathbf{F} = \begin{bmatrix} 1-2n_x^2 & -2n_xn_y & -2n_xn_z & -2dn_x \\ -2n_yn_x & 1-2n_y^2 & -2n_yn_z & -2dn_y \\ -2n_zn_x & -2n_zn_y & 1-2n_z^2 & -2dn_z \\ 0 & 0 & 0 & 1 \end{bmatrix}\)
应用:
三维错切比二维复杂,有多种形式:
XY错切(沿z变化): \(\mathbf{H}_{xy}(h_x, h_y) = \begin{bmatrix} 1 & 0 & h_x & 0 \\ 0 & 1 & h_y & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}\)
一般错切矩阵: \(\mathbf{H} = \begin{bmatrix} 1 & h_{xy} & h_{xz} & 0 \\ h_{yx} & 1 & h_{yz} & 0 \\ h_{zx} & h_{zy} & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}\)
性质:
组合顺序的重要性:
局部到世界:Scale → Rotate → Translate
世界到局部:Translate^(-1) → Rotate^(-1) → Scale^(-1)
常见组合模式:
性能优化考虑:
绕任意单位向量 $\mathbf{n} = (n_x, n_y, n_z)$ 旋转 $\theta$ 角度是三维图形学的核心操作。Rodrigues公式提供了优雅的解决方案:
\[\mathbf{R}(\mathbf{n}, \theta) = \cos\theta \mathbf{I} + (1-\cos\theta)\mathbf{n}\mathbf{n}^T + \sin\theta \mathbf{N}\]其中 $\mathbf{N}$ 是 $\mathbf{n}$ 的反对称矩阵(叉积矩阵):
\[\mathbf{N} = \begin{bmatrix} 0 & -n_z & n_y \\ n_z & 0 & -n_x \\ -n_y & n_x & 0 \end{bmatrix}\]核心思想:将任意向量分解为平行和垂直于旋转轴的分量,分别处理。
对于向量 $\mathbf{v}$:
| $\mathbf{e}1 = \mathbf{v}\perp / | \mathbf{v}_\perp | $ |
旋转后的向量: \(\mathbf{v}' = \mathbf{v}_\parallel + \cos\theta \mathbf{v}_\perp + \sin\theta (\mathbf{n} \times \mathbf{v}_\perp)\)
注意:$\mathbf{n} \times \mathbf{v}\perp = \mathbf{n} \times \mathbf{v}$(因为 $\mathbf{n} \times \mathbf{v}\parallel = \mathbf{0}$)
将向量形式转换为矩阵形式,我们得到:
\[\mathbf{R}(\mathbf{n}, \theta) = \begin{bmatrix} c + n_x^2(1-c) & n_xn_y(1-c) - n_zs & n_xn_z(1-c) + n_ys \\ n_yn_x(1-c) + n_zs & c + n_y^2(1-c) & n_yn_z(1-c) - n_xs \\ n_zn_x(1-c) - n_ys & n_zn_y(1-c) + n_xs & c + n_z^2(1-c) \end{bmatrix}\]其中 $c = \cos\theta$,$s = \sin\theta$。
验证矩阵性质:
$\theta = 0$:$\mathbf{R} = \mathbf{I}$(恒等变换)
$\theta = 90°$: \(\mathbf{R}(\mathbf{n}, 90°) = \mathbf{n}\mathbf{n}^T + \mathbf{N}\) 纯叉积形式,计算简化
$\theta = 180°$: \(\mathbf{R}(\mathbf{n}, 180°) = 2\mathbf{n}\mathbf{n}^T - \mathbf{I}\) 关于轴的反射组合
小角度近似($\theta \ll 1$): \(\mathbf{R}(\mathbf{n}, \theta) \approx \mathbf{I} + \theta\mathbf{N}\) 线性化旋转,用于微分运动
1. 轴角到旋转矩阵: 使用 Rodrigues 公式直接计算。
2. 旋转矩阵到轴角:
3. 四元数表示: \(q = \left[\cos\frac{\theta}{2}, \sin\frac{\theta}{2}\mathbf{n}\right]\)
四元数到旋转矩阵: \(\mathbf{R} = \mathbf{I} + 2s\mathbf{Q} + 2\mathbf{Q}^2\) 其中 $q = [s, \mathbf{v}]$,$\mathbf{Q}$ 是 $\mathbf{v}$ 的叉积矩阵。
4. 指数映射: \(\mathbf{R} = \exp(\theta\mathbf{N}) = \sum_{k=0}^{\infty} \frac{(\theta\mathbf{N})^k}{k!}\)
利用 $\mathbf{N}^3 = -\mathbf{N}$ 的性质,可得到 Rodrigues 公式。
1. 接近 0° 的情况:
if (theta < epsilon):
return I + theta * N // 线性近似
2. 接近 180° 的情况:
if (theta > pi - epsilon):
// 使用对称矩阵形式
// 从最大对角元素提取轴
3. 归一化保证:
1. 减少三角函数调用:
c = cos(theta)
s = sin(theta)
t = 1 - c
2. 利用对称性:
// 只计算上三角,然后复制
R[0][1] = t*n.x*n.y - s*n.z
R[1][0] = t*n.x*n.y + s*n.z
3. SIMD 向量化:
4. 预计算常用旋转:
| 角速度积分:$\mathbf{R}(t+dt) = \mathbf{R}(\boldsymbol{\omega}, | \boldsymbol{\omega} | dt) \cdot \mathbf{R}(t)$ |
旋转的李群结构:
旋转的不变量:
螺旋运动: 结合旋转和沿轴平移: \(\mathbf{T} = \begin{bmatrix} \mathbf{R}(\mathbf{n}, \theta) & \mathbf{t}\mathbf{n} \\ \mathbf{0}^T & 1 \end{bmatrix}\)
形成螺旋线轨迹,广泛应用于:
理解如何分解和组合变换是掌握计算机图形学的关键。每个复杂变换都可以分解为基本变换的组合,而正确的组合顺序决定了最终效果。
任意仿射变换可表示为: \(\mathbf{A} = \begin{bmatrix} \mathbf{M}_{3\times3} & \mathbf{t} \\ \mathbf{0}^T & 1 \end{bmatrix}\)
其中:
1. 极分解(Polar Decomposition): \(\mathbf{M} = \mathbf{R} \cdot \mathbf{S}\)
其中:
计算方法: \(\mathbf{S} = \sqrt{\mathbf{M}^T\mathbf{M}}, \quad \mathbf{R} = \mathbf{M}\mathbf{S}^{-1}\)
2. 奇异值分解(SVD): \(\mathbf{M} = \mathbf{U} \boldsymbol{\Sigma} \mathbf{V}^T\)
几何意义:
3. QR 分解: \(\mathbf{M} = \mathbf{Q} \cdot \mathbf{R}\)
其中:
使用 Gram-Schmidt 正交化计算。
变换按照保持的几何性质形成严格的层次结构:
1. 刚体变换(Rigid/Euclidean Transform)
定义:保持距离和角度的变换
| 点间距离:$ | \mathbf{p}_1’ - \mathbf{p}_2’ | = | \mathbf{p}_1 - \mathbf{p}_2 | $ |
应用:物体姿态、相机运动、刚体动力学
2. 相似变换(Similarity Transform)
定义:保持角度和形状比例的变换
| 距离比:$\frac{ | \mathbf{p}_1’ - \mathbf{p}_2’ | }{ | \mathbf{p}_3’ - \mathbf{p}_4’ | } = \frac{ | \mathbf{p}_1 - \mathbf{p}_2 | }{ | \mathbf{p}_3 - \mathbf{p}_4 | }$ |
应用:LOD系统、UI缩放、地图投影
3. 仿射变换(Affine Transform)
定义:保持平行性和直线性的变换
| 面积比:$\frac{\text{Area}(\triangle A’B’C’)}{\text{Area}(\triangle ABC)} = | \det(\mathbf{A}) | $ |
应用:纹理映射、图像校正、形变动画
4. 投影变换(Projective Transform)
定义:最一般的线性变换
应用:透视投影、图像纠正、增强现实
变换群的包含关系:
刚体 ⊂ 相似 ⊂ 仿射 ⊂ 投影
每一级都是上一级的特殊情况。
变换的不交换性是图形学中最容易犯错的地方之一。让我们通过具体例子深入理解:
示例1:平移与旋转的顺序
先平移后旋转: \(\mathbf{M}_1 = \mathbf{R} \cdot \mathbf{T} = \begin{bmatrix} \mathbf{R}_{3\times3} & \mathbf{R}_{3\times3}\mathbf{t} \\ \mathbf{0}^T & 1 \end{bmatrix}\)
先旋转后平移: \(\mathbf{M}_2 = \mathbf{T} \cdot \mathbf{R} = \begin{bmatrix} \mathbf{R}_{3\times3} & \mathbf{t} \\ \mathbf{0}^T & 1 \end{bmatrix}\)
几何解释:
示例2:缩放与旋转的顺序
考虑非均匀缩放 $\mathbf{S}(2, 1, 1)$ 和旋转 $\mathbf{R}_z(45°)$:
结果完全不同!
交换条件: 两个变换 $\mathbf{A}$ 和 $\mathbf{B}$ 可交换当且仅当:
实用指南:
局部到世界:Scale → Rotate → Translate
世界到局部:Translate^(-1) → Rotate^(-1) → Scale^(-1)
1. 绕任意点旋转
绕点 $\mathbf{c}$ 旋转 $\theta$ 角度: \(\mathbf{M} = \mathbf{T}(\mathbf{c}) \cdot \mathbf{R}(\theta) \cdot \mathbf{T}(-\mathbf{c})\)
展开后: \(\mathbf{M} = \begin{bmatrix} \mathbf{R} & \mathbf{c} - \mathbf{R}\mathbf{c} \\ \mathbf{0}^T & 1 \end{bmatrix}\)
这揭示了“移动到原点-变换-移回”模式的普遍性。
2. 绕任意轴缩放
给定轴方向 $\mathbf{u}$ 和缩放因子 $s$:
直接公式: \(\mathbf{M} = \mathbf{I} + (s-1)\mathbf{u}\mathbf{u}^T\)
3. 镜像与旋转的组合
滑移反射(Glide Reflection): \(\mathbf{G} = \mathbf{T}(\mathbf{d}) \cdot \mathbf{F}\)
其中 $\mathbf{d}$ 平行于反射面。
两个反射的复合产生旋转: \(\mathbf{F}_1 \cdot \mathbf{F}_2 = \mathbf{R}(2\theta)\)
其中 $\theta$ 是两个反射面的夹角。
4. 螺旋变换
绕轴 $\mathbf{n}$ 旋转 $\theta$ 同时沿轴平移 $d$: \(\mathbf{H} = \begin{bmatrix} \mathbf{R}(\mathbf{n}, \theta) & d\mathbf{n} \\ \mathbf{0}^T & 1 \end{bmatrix}\)
应用:DNA双螺旋、螺丝运动、弹簧形变。
在动画和过渡效果中,我们经常需要在两个变换之间平滑插值。直接插值矩阵元素通常会产生错误结果。
1. 平移插值
最简单的情况,直接线性插值: \(\mathbf{t}(\alpha) = (1-\alpha)\mathbf{t}_0 + \alpha\mathbf{t}_1\)
对于曲线路径,可使用:
2. 旋转插值
错误方法:直接插值旋转矩阵
正确方法:
a) 四元数 SLERP(球面线性插值): \(q(t) = \frac{\sin((1-t)\theta)}{\sin\theta}q_0 + \frac{\sin(t\theta)}{\sin\theta}q_1\) 其中 $\cos\theta = q_0 \cdot q_1$
b) 指数映射插值: \(\mathbf{R}(t) = \mathbf{R}_0 \exp(t \log(\mathbf{R}_0^{-1}\mathbf{R}_1))\)
c) 轴角插值:
3. 缩放插值
为避免负值和保持平滑性: \(s(t) = s_0^{1-t} \cdot s_1^t = \exp((1-t)\log s_0 + t\log s_1)\)
对于各向异性缩放,对每个分量独立处理。
4. 完整变换插值
对于一般的 TRS 变换:
M(t) = T(t) × R(t) × S(t)
5. 高级插值技术
双四元数(Dual Quaternion):
矩阵对数插值: \(\mathbf{M}(t) = \exp(t \log(\mathbf{M}_0^{-1}\mathbf{M}_1)) \mathbf{M}_0\)
适用于任意可逆变换。
图形渲染管线的核心是将三维世界投影到二维屏幕。这个过程通过一系列精心设计的变换完成,每个变换都有其特定的目的和数学基础。
图形渲染管线中的坐标系统:
完整的变换管线: \(\text{顶点} \xrightarrow{\mathbf{M}} \text{世界} \xrightarrow{\mathbf{V}} \text{视图} \xrightarrow{\mathbf{P}} \text{裁剪} \xrightarrow{\text{÷w}} \text{NDC} \xrightarrow{\mathbf{VP}} \text{屏幕}\)
坐标系的手性(Handedness):
模型变换将物体从模型空间变换到世界空间: \(\mathbf{M}_{\text{model}} = \mathbf{T}_{\text{world}} \cdot \mathbf{R}_{\text{world}} \cdot \mathbf{S}_{\text{local}}\)
模型变换的组成:
层次化变换(Hierarchical Transforms): 场景图中的父子关系: \(\mathbf{M}_{\text{child}} = \mathbf{M}_{\text{parent}} \cdot \mathbf{M}_{\text{local}}\)
例如,机械臂的变换链:
实例化渲染(Instanced Rendering): 对于大量相同模型(如森林中的树木):
基础模型矩阵 + 每实例变换数据 = 最终变换
优化技巧:
骨骼动画的模型变换: 顶点最终位置由多个骨骼影响: \(\mathbf{v}' = \sum_{i} w_i \mathbf{M}_{\text{bone},i} \mathbf{M}_{\text{bind},i}^{-1} \mathbf{v}\)
其中:
视图变换将世界空间变换到相机空间,是实现“第一人称视角”的关键。它的本质是将相机放置在原点,朝向-Z轴,同时将整个世界做相应变换。
给定相机参数:
步骤1:构建相机坐标系
使用右手坐标系(OpenGL约定): \(\begin{align} \mathbf{g} &= \mathbf{c} - \mathbf{e} \quad \text{(观察方向)} \\ \mathbf{w} &= -\frac{\mathbf{g}}{||\mathbf{g}||} \quad \text{(相机-Z轴,指向后方)} \\ \mathbf{u} &= \frac{\mathbf{t} \times \mathbf{w}}{||\mathbf{t} \times \mathbf{w}||} \quad \text{(相机X轴,指向右方)} \\ \mathbf{v} &= \mathbf{w} \times \mathbf{u} \quad \text{(相机Y轴,指向上方)} \end{align}\)
注意:$\mathbf{t}$ 不需要与 $\mathbf{g}$ 垂直,只需不平行。
步骤2:构造视图矩阵
视图变换分两步:
旋转矩阵将相机坐标轴对齐到世界坐标轴: \(\mathbf{R} = \begin{bmatrix} \mathbf{u}^T \\ \mathbf{v}^T \\ \mathbf{w}^T \end{bmatrix}\)
这是正交矩阵,因为 $(\mathbf{u}, \mathbf{v}, \mathbf{w})$ 是标准正交基。
最终视图矩阵: \(\mathbf{V} = \mathbf{R} \cdot \mathbf{T}(-\mathbf{e}) = \begin{bmatrix} \mathbf{u}^T & -\mathbf{u} \cdot \mathbf{e} \\ \mathbf{v}^T & -\mathbf{v} \cdot \mathbf{e} \\ \mathbf{w}^T & -\mathbf{w} \cdot \mathbf{e} \\ 0 & 1 \end{bmatrix}\)
1. FPS(第一人称射击)相机
使用欧拉角(yaw, pitch, roll):
yaw: 水平旋转(绕Y轴)
pitch: 俯仰(绕X轴)
roll: 滚转(绕Z轴,通常为0)
前向量计算: \(\mathbf{forward} = \begin{bmatrix} \sin(yaw) \cdot \cos(pitch) \\ \sin(pitch) \\ \cos(yaw) \cdot \cos(pitch) \end{bmatrix}\)
2. 轨道相机(Orbit Camera)
绕目标点旋转,使用球坐标:
相机位置: \(\mathbf{e} = \mathbf{c} + r\begin{bmatrix} \sin\theta \cos\phi \\ \sin\phi \\ \cos\theta \cos\phi \end{bmatrix}\)
3. Arcball 相机
将鼠标移动映射到球面旋转:
1. 避免重复计算
// 缓存视图矩阵
if (camera.hasChanged()) {
viewMatrix = computeViewMatrix();
camera.clearChangeFlag();
}
2. 增量更新
对于小幅度移动: \(\mathbf{V}_{new} = \mathbf{T}(\Delta\mathbf{p}) \cdot \mathbf{V}_{old}\)
对于小幅度旋转: \(\mathbf{V}_{new} = \mathbf{V}_{old} \cdot \mathbf{R}(\Delta\theta)\)
3. 双精度计算
对于大场景,相机远离原点时:
// 使用双精度计算视图矩阵
// 单精度传递给GPU
1. 垂直观察问题
当 $\mathbf{g}$ 与 $\mathbf{t}$ 平行时:
if (|g × t| < epsilon) {
// 使用备用up向量
t = (|g.y| < 0.9) ? vec3(0,1,0) : vec3(1,0,0);
}
2. 旋转奇异性
避免万向锁:
pitch = clamp(pitch, -89°, 89°);
1. 立体视觉(Stereoscopic)
左右眼分离: \(\mathbf{V}_{left} = \mathbf{T}(-IPD/2, 0, 0) \cdot \mathbf{V}\) \(\mathbf{V}_{right} = \mathbf{T}(IPD/2, 0, 0) \cdot \mathbf{V}\)
其中 IPD 是瞳距(约 6.5cm)。
2. 立方体贴图(Cubemap)渲染
从中心点渲染6个方向:
+X: (1,0,0), up=(0,1,0)
-X: (-1,0,0), up=(0,1,0)
+Y: (0,1,0), up=(0,0,-1)
-Y: (0,-1,0), up=(0,0,1)
+Z: (0,0,1), up=(0,1,0)
-Z: (0,0,-1), up=(0,1,0)
投影变换是图形管线中最关键的步骤之一,它决定了我们如何将三维世界“拍平”到二维平面。不同的投影方式产生不同的视觉效果和应用场景。
透视投影模拟人眼和相机的成像原理,产生“近大远小”的效果。
几何原理
基于小孔成像模型:
视锥体参数
透视投影矩阵推导
步骤1:将视锥体压缩成立方体
步骤2:应用正交投影
最终矩阵(OpenGL约定): \(\mathbf{P}_{\text{persp}} = \begin{bmatrix} \frac{1}{\text{aspect} \cdot \tan(\text{fov}_y/2)} & 0 & 0 & 0 \\ 0 & \frac{1}{\tan(\text{fov}_y/2)} & 0 & 0 \\ 0 & 0 & \frac{f+n}{n-f} & \frac{2fn}{n-f} \\ 0 & 0 & -1 & 0 \end{bmatrix}\)
透视除法的意义
投影后的齐次坐标:$(x’, y’, z’, w’)$
非线性深度映射
NDC 深度值: \(z_{NDC} = \frac{f+n}{f-n} + \frac{2fn}{(f-n)z}\)
特点:
正交投影保持平行线平行,没有透视效果。
几何意义
正交投影矩阵
给定视景体 $[l,r] \times [b,t] \times [n,f]$:
\[\mathbf{P}_{\text{ortho}} = \begin{bmatrix} \frac{2}{r-l} & 0 & 0 & -\frac{r+l}{r-l} \\ 0 & \frac{2}{t-b} & 0 & -\frac{t+b}{t-b} \\ 0 & 0 & \frac{2}{n-f} & -\frac{n+f}{n-f} \\ 0 & 0 & 0 & 1 \end{bmatrix}\]这实际上是:
1. 反向 Z(Reverse-Z)
传统方法的问题:
解决方案:
近平面映射到 1
远平面映射到 0
深度测试改为 GREATER
修改后的投影矩阵: \(P_{33} = \frac{n}{n-f}, \quad P_{34} = \frac{fn}{n-f}\)
2. 对数深度(Logarithmic Depth)
在片元着色器中:
gl_FragDepth = log(C * w + 1) / log(C * f + 1);
其中 C 是调节参数(如 1.0)。
3. 级联阴影贴图(CSM)
将视锥体分割成多个区间:
近景:[0.1, 10] - 高精度
中景:[10, 100] - 中精度
远景:[100, 1000] - 低精度
1. 斜投影(Oblique Projection)
用于水面反射等效果: \(\mathbf{P}_{oblique} = \mathbf{P} \cdot \begin{bmatrix} 1 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 1 \\ 0 & 0 & 0 & 0 \end{bmatrix} \cdot \begin{bmatrix} 1 & 0 & a & 0 \\ 0 & 1 & b & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & c & 1 \end{bmatrix}\)
2. 非对称投影
VR/AR 中的应用:
left = -0.5 * IPD - 0.5 * w
right = 0.5 * IPD + 0.5 * w
3. 无限远平面
设置 $f \to \infty$: \(P_{33} = -1, \quad P_{34} = -2n\)
适用于开放世界场景。
透视投影适用于:
正交投影适用于:
1. 齐次裁剪
在裁剪空间执行:
-w ≤ x ≤ w
-w ≤ y ≤ w
0 ≤ z ≤ w (D3D)
-w ≤ z ≤ w (OpenGL)
2. 透视正确插值
屏幕空间属性插值: \(attr = \frac{\frac{attr_0}{w_0}\lambda_0 + \frac{attr_1}{w_1}\lambda_1 + \frac{attr_2}{w_2}\lambda_2}{\frac{1}{w_0}\lambda_0 + \frac{1}{w_1}\lambda_1 + \frac{1}{w_2}\lambda_2}\)
其中 $\lambda_i$ 是重心坐标。
视口变换是渲染管线的最后一个几何变换,它将标准化设备坐标(NDC)映射到屏幕像素坐标。
将 NDC 坐标 $[-1,1]^3$ 映射到屏幕坐标 $[0,w] \times [0,h] \times [0,1]$:
\[\mathbf{M}_{\text{viewport}} = \begin{bmatrix} \frac{w}{2} & 0 & 0 & \frac{w}{2} \\ 0 & \frac{h}{2} & 0 & \frac{h}{2} \\ 0 & 0 & \frac{1}{2} & \frac{1}{2} \\ 0 & 0 & 0 & 1 \end{bmatrix}\]注意:
对于部分屏幕渲染(如分屏、小地图):
\[\mathbf{M}_{\text{custom}} = \begin{bmatrix} \frac{w_{vp}}{2} & 0 & 0 & x_{vp} + \frac{w_{vp}}{2} \\ 0 & \frac{h_{vp}}{2} & 0 & y_{vp} + \frac{h_{vp}}{2} \\ 0 & 0 & \frac{f_{vp}-n_{vp}}{2} & \frac{f_{vp}+n_{vp}}{2} \\ 0 & 0 & 0 & 1 \end{bmatrix}\]其中:
像素中心偏移
Direct3D 约定:像素中心在 $(0.5, 0.5)$
pixel_center = floor(ndc_coord * viewport_size) + 0.5
OpenGL 约定:可配置,默认也是 $(0.5, 0.5)$
多重采样考虑
MSAA 时的子像素位置:
2x2 pattern:
(0.25, 0.25), (0.75, 0.25)
(0.25, 0.75), (0.75, 0.75)
设备像素比(Device Pixel Ratio)
逻辑像素 vs 物理像素
Retina: 2x或 3x
需要调整视口变换:
w_physical = w_logical * devicePixelRatio
h_physical = h_logical * devicePixelRatio
1. 裁剪视口(Scissor Test)
限制渲染区域:
glScissor(x, y, width, height);
glEnable(GL_SCISSOR_TEST);
应用:分屏渲染、UI 裁剪、性能优化
2. 分屏渲染
左右分屏:
// 左半屏
glViewport(0, 0, width/2, height);
renderPlayer1();
// 右半屏
glViewport(width/2, 0, width/2, height);
renderPlayer2();
3. 画中画(Picture-in-Picture)
主视图 + 小地图:
// 主视图
glViewport(0, 0, width, height);
renderMainView();
// 小地图
glViewport(width-200, height-150, 200, 150);
renderMinimap();
动态分辨率调整
根据性能调整渲染分辨率:
render_width = screen_width * resolution_scale;
render_height = screen_height * resolution_scale;
// 渲染到较小的 framebuffer
// 然后上采样到屏幕
超采样与欠采样
resolution_scale > 1.0resolution_scale < 1.0(提高性能)完整的变换链:
模型空间 → 世界空间 → 视图空间 → 裁剪空间 → NDC → 屏幕空间
(M) (V) (P) (÷w) (VP)
逆变换(屏幕到世界):
常用于:
结合律:$(AB)C = A(BC)$
不满足交换律:$AB \neq BA$(一般情况)
逆变换:
1. 矩阵预计算
MVP = Projection × View × Model // 预计算,每个物体只算一次
2. 矩阵分解存储
3. 特殊矩阵的快速计算
4. SIMD优化
法向量的变换不能直接使用模型矩阵,需要使用逆转置矩阵:
设模型变换矩阵为 $\mathbf{M}$,法向量变换矩阵为: \(\mathbf{G} = (\mathbf{M}^{-1})^T\)
推导:保持法向量与切向量垂直
对于仅包含旋转的变换,$\mathbf{G} = \mathbf{M}$(因为 $\mathbf{R}^{-1} = \mathbf{R}^T$)
浮点精度损失
深度精度(Z-fighting)
矩阵正交化 使用 Gram-Schmidt 过程保持旋转矩阵的正交性:
重新正交化旋转矩阵的前三列
场景图优化
实例化渲染
练习 2.1 证明二维旋转矩阵 $\mathbf{R}(\theta)$ 是正交矩阵,即 $\mathbf{R}^T\mathbf{R} = \mathbf{I}$。
Hint: 计算 $\mathbf{R}^T(\theta)\mathbf{R}(\theta)$ 并利用三角恒等式。
练习 2.2 给定点 $P = (2, 3, 1)$,先绕原点旋转 $45°$,再平移 $(1, -1, 0)$。写出组合变换矩阵并计算变换后的点坐标。
Hint: 注意变换顺序,先旋转后平移意味着 $\mathbf{T} \cdot \mathbf{R}$。
练习 2.3 推导透视投影后,为什么需要进行透视除法(除以 $w$ 分量)?
Hint: 考虑相似三角形原理和齐次坐标的意义。
练习 2.4 设计一个算法,将任意 $4 \times 4$ 仿射变换矩阵分解为平移、旋转和缩放的组合。假设没有错切变换。
Hint: 先提取平移部分,然后对 $3 \times 3$ 子矩阵进行极分解或QR分解。
练习 2.5 证明 Rodrigues 旋转公式,并说明当 $\theta = 180°$ 时的特殊情况。
Hint: 将向量分解为平行和垂直于旋转轴的分量。
练习 2.6 给定视锥体的六个平面,推导如何在齐次裁剪空间进行视锥体裁剪。
Hint: 在裁剪空间中,视锥体变成了 $[-w, w]$ 的立方体。
练习 2.7(开放题)讨论在虚拟现实(VR)渲染中,如何修改投影矩阵以适应双目渲染和镜头畸变校正。
Hint: 考虑非对称视锥体和逆畸变预处理。
错误:Model × View × Projection
正确:Projection × View × Model
记忆技巧:从右到左读,”先模型变换,再视图变换,最后投影”
错误:n' = M × n
正确:n' = (M^(-1))^T × n
只有在仅包含旋转和均匀缩放时才能直接使用模型矩阵