目标
把顶点从三维世界空间变换至二维屏幕空间,绘制顶点(如银河星系数据),操控摄像机旋转模型。
在渲染3D场景之前,先得做一些数学工作。上面提到的“顶点”、“空间变换”、“旋转”等术语,都需要依赖3D数学库来实现。
至少要包括:
- 三维向量(Vector3)
- 四元数(Quaternion)
- 矩阵(Matrix)
- 空间变换(Transform)
这一阶段的主要目标就是实现这些数学库。
在实现的过程中,我的主要参考资料是 jMonkeyEngine 数学库的源代码,以及《3D数学技术:图形与游戏开发》这本书。
实现
约定
在做数学计算之前,应该先约定坐标系和计算规则:
- 使用OpenGL的右手坐标系
- 使用列向量和列矩阵进行计算
行向量和列向量的区别,可以参考这篇文章:矩阵和向量的乘法顺序。
除此之外,还有另外一个条件:
- 尽量使用float精度进行运算。
这么做的原因,是想和OpenGL保持一致。同时,我在学习jMonkeyEngine时已经习惯了这种规则。
数学库是较为基础、也较为复杂的内容,我将用几章来分别实现。网上有很多关于3D数学的资料,如果读者对我下面写的东西不感兴趣,可以直接跳过一这一部分。
三维向量
Vector3f 可以用来表示三维空间中的一个“点”,这时 (x, y, z) 描述了该“点”在自身坐标系统中的相对位置。
Vector3f 也可以用来表示方向,看作是从原点 (0, 0, 0) 出发,指向 (x, y, z) 的箭头。这个“箭头”具有长度,也称为向量的模。
- 文章:Java软光栅渲染器-三维向量
- 代码:Vector3f
- 扩展实现:Vector2f
- 扩展实现:Vector4f
四元数
四元数包含一个标量分量和一个3D向量分量。经常记标量分量为 w,记向量分量为单一 v 或分开的 x, y ,z。两种记法的分别如下:
- [w, v]
- [w, x, y, z]
四元数的主要用来描述三维空间中的旋转,它可以解决“万象锁”的问题。当今3D数学中四元数存在的主要理由是一种称为 slerp 的运算,它是球面线性插值的缩写(Spherical Linear Interpolation)。slerp 运算非常有用,因为它可以在两个四元数之间平滑插值。slerp 运算避免了欧拉角插值的所有问题。
- 文章:Java软光栅渲染器-四元数
- 代码:Quaternion
矩阵
矩阵用来对向量进行线性变换。它可以用来变换物体,比如旋转、缩放、投影、镜像、仿射;
矩阵也可以用来变换坐标系,比如从模型空间(model space)到世界空间(world space)、从世界空间转换(world space)到眼睛空间(Eye space)。
这两种变换在某种意义上是等价的。
- 文章:Java软光栅渲染器-矩阵
- 代码:Matrix4f
- 扩展实现:Matrix3f
空间变换
在3D程序中,模型一般有三种空间变换:
- 缩放,用一个三维向量来表示(scale)
- 旋转,用一个四元数或3*3矩阵来表示(rotation)
- 平移,用一个三维向量来表示(translation)
对于一个三维向量vec来说,原本需要通过上述三个变换,才能得到最终结果。注意这个变换的顺序不能改变,否则可能会变换错误。
// 先缩放,再旋转,再平移
Vector3f dest = position.add(rotation.mult(scale.mult(vec)));
现在可以用一个4*4矩阵来代表这三种变换,一次性完成空间变换。
Matrix4f mat = ...;// 计算矩阵
// 空间变换
Vector3f dest = mat.mult(vec);
一般在开发中,我们会定义一个Transform类来记录三种空间变换,编程时可以单独计算这三种变换。同时还应该提供一个转换为矩阵的方法,这样在变换物体时就可以一步到位。
总结
建立数学库的基本目标已经达成,下面可以继续做渲染管线了。