前言
由于webGL
存在着几个不同的坐标系:模型坐标系、世界坐标系、相机坐标系和裁剪坐标系;因此将物体的同一坐标表示在不同的坐标系时,就需要对坐标进行转换,而根据不同坐标系之间的特性转换的具体参数可能有不同之处。
坐标系变换矩阵
将坐标从原坐标系变换到新坐标系中,有以下通用变换矩阵:
其中,分别是新坐标系的基向量及原点在原坐标系中的表示。只需要将变换矩阵左乘一个列向量,即可得到该向量在新坐标系中的表示。
通过通用的坐标系变换矩阵可以推导出其它变换(平移、旋转、缩放等)的矩阵;
注:左乘是因为得到的变换矩阵是列主序的,为啥矩阵要列主序?因为glsl
中的矩阵就是列主序的!
思考:为啥矩阵要取逆矩阵?
模型变换矩阵
模型变换指的是将坐标从模型坐标系转换到世界坐标系;一个模型内的所有顶点坐标都是相对于该模型的中心点(也就是模型坐标系的原点)而确定的,在实际应用中,一般都只能确定模型中心点在世界坐标系中的坐标,以及平移、旋转、缩放等操作,除此之外,一般模型的中心点的初始位置就是世界坐标系的原点位置(0, 0, 0)
,所以模型变换矩阵可以简化为:
思考:为什么能够简化?
注意顺序一定不能弄错,因为矩阵不满足交换律……那么为啥矩阵顺序必须是这样的?原因如下:
- 平移后会影响旋转,因此这里的旋转指的是是绕模型中心进行的旋转操作;所以平移要后于旋转进行。
- 同样,旋转后再进行缩放时,可能会造成缩放的效果不对(主要是旋转后
x, y, z
坐标发生改变,而缩放x, y, z
系数不一致时,就可能对应不上);因此缩放要先于旋转进行。
视图变换矩阵
视图变换指的是将坐标从世界坐标系转换到相机坐标系(也叫观察坐标系);根据通用坐标系变换矩阵公式可知,只需要得到相机坐标系的原点及三个基向量在世界坐标系中的表示,即可得到视图变换矩阵。
需要注意的是相机坐标系是右手系!!!
相机空间
- VRP(View Reference Point):观察参考点,也叫视点(eyepoint);相机坐标系的原点。
- VPN(View Plane Normal):观察平面法向量,也叫观察/相机方向;相机坐标系的
z
轴。 - VUV(View Up Vector):相机顶部正朝向;相机坐标系的
y
轴大概方向。
推导过程
- 首先,相机坐标系原点(即人眼或相机位置)在世界坐标系中的位置是已知的;相机的关注点(
lookat
)的位置也是已知的;VUV
也是已知的; - 根据关注点和相机位置可以得到
VPN
(即相机坐标系的z
轴基向量,); - 根据
VUV
及VPN
的叉乘可以得到相机坐标系的x
轴基向量,; - 同理,根据和的叉乘可以得到真正的
y
轴基向量,; - 将,,以及
VRP
的坐标代入到通用变换公式即可得到视图变换矩阵:
注:当一个矩阵为正交矩阵时(即行向量和列向量两两正交),其转置矩阵等于其逆矩阵[1]!
投影变换矩阵
投影变换指的是将坐标从相机坐标系转换到裁剪坐标系,最终投影变换将得到顶点投影后的标准化设备坐标(Normalized Device Coordinate, NDC
),可以输入到顶点着色器中进行使用。
所谓的标准化设备坐标指的是
x, y , z
坐标值的范围都在[-1, 1]
之间。
投影变换跟前面的坐标系之间的变换方法有些不同,裁剪坐标系并不是指定一个坐标原点和三个基向量而形成的,而是通过一些观察参数得到的平截头体(Frustum
,一个空间范围)形成的,这个空间范围除了跟观察参数有关以外还和投影方式有关!
相关概念
实际上在设定观察参数时,用到的参考坐标系仍然是相机坐标系,也就是相当于平截头体是相机空间的子空间,只不过变换后坐标进行了转换。
- 投影点:相机坐标系的原点。
- 近平面:通过设置近平面距离可得,所以近平面指的是相机坐标系平面。
- 远平面:同理,设置一个远平面距离可得,近平面指的是相机坐标系平面。
- 平截头体的宽高:指的是平截头体在近平面一端上的矩形宽高(远平面一端矩形的宽高可以根据参数推算)。
正交投影
正交投影是平行投影的一种,其投影线是垂直于投影平面(这里的投影平面值的就是近平面)的。因此可以得到正交投影的平截头体为一个长方体:
注:正交投影平截头体的宽高一般是通过设置、、和得到的。
由于投影平面垂直于相机坐标系的z
轴,因此相机坐标系内的一点在投影平面的xyz
坐标只需要对原坐标做一个范围缩放处理即可:
从上面x
坐标的变换可以看到,这种变换实际上就是到的变换。同理,y
坐标就是;而z
坐标则是。
注:由于相机坐标系是右手系,因此!
从投影变换结果可以得到正交投影的投影变换矩阵:
透视投影
投射投影则是模拟真实世界中看见物体的模式进行的投影,具有立体效果。其平截头体如下所示:
可以看到点和投影点有一组三角相似关系:
进而根据三角相似原理可以得到一组等式:
需要注意的是,这里的和并不是最终我们想要的标准化设备坐标,而是在近平面的投影坐标,实际上还是处于相机坐标系中;因此还需要进行一下范围变换:
不过由于投影坐标系是左手系的,因此z
轴方向与相机坐标系刚好相反,需要取负值;其次,由于分量为,所以各分量需要先乘以:
由于投影到平面的点是2D
的(因为投影后所有点的都相同),不过由于深度测试需要用到值来判断点在空间中前后顺序,因此需要对原有的坐标进行线性插值得到一个符合标准化设备坐标系的坐标值;不过,由于齐次坐标的分量为,因此实际上应该对进行插值[2]:
思考:我在想如果使,那么是不是可以用普通的一次线性函数插值?还有就是为啥一定要利用的特性来除以坐标,而不是直接让其为目标值?这样看起来有点多此一举……
事实上,使用普通一次线性函数得到的参数似乎对不上公认的那个透视投影矩阵。
同时,在近平面和远平面处有确定的投影关系:
经过这么多步转换后,可以得到透视投影矩阵为:
不过上述参数只是从数学上假设的,实际应用中计算透视投影矩阵时通常使用视场角(fovy
)、宽高比(aspect
)、近平面和远平面这四个值来计算透视投影矩阵:
很明显,通过视场角、近平面和宽高比能够算出近平面的宽高;
相关文档
- WebGL 入门与实践:18 - lucefer - 掘金小册
- 视图矩阵的推导-云栖社区-阿里云
- 三维投影 - 维基百科,自由的百科全书
- 坐标系统 - LearnOpenGL-CN
- 线性代数之透视矩阵Perspective Matrix – Wyman的原创技术博客