Transform Hierarchies 变换的层次结构
介绍 local space、world space 以及层级变换累积的基本概念。
Transform Hierarchies 变换的层次结构
在图形引擎里,如果想让物体正确地跟随父级节点做平移、旋转、缩放,就需要把每个节点的 位置 position,旋转 rotation,和 缩放 scale 组织为一颗层级树,并在渲染前将它们积累成 矩阵
struct Transform {
Transform parent; // 父节点(引用或指针)
Vector position; // 平移
Quaternion rotation;// 旋转
Vector scale; // 缩放
};每个节点记录自己的 位移、旋转四元数 和 缩放向量,并持有一个到 父节点的引用 这样层层向上就能追溯到根节点
由于 GPU / 图形 API 最终只认 4×4 矩阵,所以要将 Transform 转成矩阵
-
先用 旋转四元数 把三个单位轴
(1,0,0),(0,1,0),(0,0,1)旋转出物体在世界空间中的 基向量,这一步也可以直接使用我们在 四元数 笔记中介绍过的 公式展开法 直接展开四元数为对应的 旋转矩阵 -
然后分别对 基向量 执行
scale.x / scale.y / scale.z的缩放,这一步可以构造缩放矩阵 -
最后把平移 position 放到矩阵最后一列,得到
| x.x y.x z.x 0 | | x.y y.y z.y 0 | | x.z y.z z.z 0 | | t.x t.y t.z 1 |这样一个矩阵就同时编码了平移、旋转、缩放
Matrix ToMatrix(Transform transform) {
// First, extract the rotation basis of the transform
// 也可以直接使用 四元数公式展开法 直接得到旋转矩阵
Vector x = Vector(1, 0, 0) * transform.rotation; // Vec3 * Quat (right vector)
Vector y = Vector(0, 1, 0) * transform.rotation; // Vec3 * Quat (up vector)
Vector z = Vector(0, 0, 1) * transform.rotation; // Vec3 * Quat (forward vector)
// Next, scale the basis vectors
// 如果上一步使用了 公式展开 此处应该构造缩放矩阵
x = x * transform.scale.x; // Vector * float
y = y * transform.scale.y; // Vector * float
z = z * transform.scale.z; // Vector * float
// Extract the position of the transform
Vector t = transform.position;
// Create matrix
return Matrix(
x.x, x.y, x.z, 0, // X basis (& Scale)
y.x, y.y, y.z, 0, // Y basis (& scale)
z.x, z.y, z.z, 0, // Z basis (& scale)
t.x, t.y, t.z, 1 // Position
);
}层级累积 Hierarchy Accumulation
由于
子节点的世界矩阵 = 父节点的世界矩阵 × 子节点的局部矩阵所以顺序正确与否、是否把非统一缩放混进旋转,都会带来各种“奇怪的插值、体积剪切”等副作用,我, 后面将会继续讨论这些细节,例如
- 如何高效维护“局部 / 世界”两套变换并懒更新(dirty flag)
- 如何避免缩放-旋转耦合导致的非正交基向量
- 获取全局 / 局部位置、旋转、缩放时的注意事项