版权声明:本文为CSDN博主「wodownload2」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
正文
本文之前一直纠结的问题是 Unity 里面的 正交矩阵 和 透视矩阵 中的 z 到底是映射到 [-1,1] 还是 [0,1] 区间。
经过验证之后,得出 结论:
Unity 里的正交和透视矩阵是将 z 映射到 [-1,1] 区间。
经过查询,知道了,OpenGL 把 z 映射到 [-1,1] 之间;而 DX 则将 z 映射到 [0,1] 区间。
可以查看:
有上面也知道了,DX 是 左手坐标系,而 OpenGL 是 右手坐标系,而 Unity 呢?我们会发现它使用的是 左手坐标系。
左手坐标系、右手坐标系,下图可以直观的给出:
ok,了解了这么多,我们还要看下 OpenGL 的正交透视投影,以及透视投影的矩阵公式,推导这里就不要再叙述了,网上很多,我之前的博客也都写过,这里给出几个参考网址:
这里还有一个 不明白的地方 就是在推导的时候,使用 -N 和 -F 。
还有一个 地方不明 的是,Unity 里面的 摄像机的正方向,指向的是世界坐标系的 -z 轴。仿佛所有摄像机能看到的物体,都是在摄像机的后面。
- 有图说明
上图,Camera 的位置是 (0,0,0),其旋转为 (0,0,0),那么打印其 worldToCameraMatrix,也就是 世界坐标 转换为 视口坐标 的矩阵。
Debug.Log(Camera.main.worldToCameraMatrix);
- 输出
我们知道,凡是坐标系的矩阵的前三维代表的是 xyz 轴向量。
也就是摄像机的视口坐标系的三个轴是:
(1,0,0)(0,1,0)(0,0,-1)
由此知道,其 z 方向,和世界坐标系居然是相反的。
ok,这个只要知道就可以了,对我们的要验证的问题没有关系。
接着我们回到正交矩阵的验证问题上来
验证正交投影矩阵
首先,把 z 坐标映射到 -1 到 1 的正交投影矩阵公式如下:
我们来验证下:
这里的摄像机其 近平面 设置为 1,远平面 设置为 4,也就是 n=1,f=4
此时看下输出:
Debug.Log(Camera.main.projectionMatrix);
可以看到矩阵对 第三行 是验证正确。
再来看下矩阵的 第一行 和 第二行。
当我们把另 r=-l ,以及 t=-b 的时候,
矩阵变为:
也就是:
1/r = 0.16667
1/t = 0.3333
此时 r = 6,t = 3
这个是反推出 r 和 t 的。
ok,验证成立。
那么反思下,这个 r 和 t 的代表了什么,我们知道 width=r-l=2r=12;而 height=t-b=2t=6
那么此时宽高比是 12:6=2:1 ,
那么此时我们的屏幕尺寸是多少呢?
Debug.Log(Screen.width + " " + Screen.height);
Debug.LogError(Screen.height * 1.0f / Screen.width);
Debug.LogError(Screen.width * 1.0f / Screen.height);
是的,我们屏幕的宽高比正好是 2:1。
你可以选择,其他的分别测试:
如屏幕是
600x800,那么此时的比例是3:4=0.75,而0.3333/0.4444=0.75。验证是正确的。
我们再反思,我们的摄像机是正交的,那么屏幕的的比例,就是摄像机的宽高比。
下面要讲解的是如何将一个点映射到 ndc 空间了
我们知道了给出了正交投影矩阵了,任何一个摄像机空间内的点p(x,y,z),只要经过这个矩阵的变换,则最终都被映射到了 [-1,1] 的空间了。其中包括 x 和 y 和 z。上面已经提到 Unity 里面采用的是 OpenGL 的映射方式,将 z 映射到 -1 到 1 之间。
下面我们准备两个立方体:
- 立方体1
- 立方体2
- 摄像机
立方体1 的坐标是 (0,0,1),立方体2 的坐标是 (0,0,4),他们都是摄像机的子节点,而摄像机的坐标是 (0,0,0)。
也就是 立方体1 的最终世界坐标是 (0,0,1),立方体2 的最终坐标是 (0,0,4)。
我们推测下,由于,摄像机的近平面是 1,远平面是 4,所以可以推测,立方体1 的 z=1,最终被映射到了-1,而 立方体2 的 z=4,最终被映射到了 1。
映射代码如下:
Debug.LogError(Camera.main.projectionMatrix.MultiplyPoint(Camera.main.worldToCameraMatrix.MultiplyPoint(cube.transform.position)));
Debug.LogError(Camera.main.projectionMatrix.MultiplyPoint(Camera.main.worldToCameraMatrix.MultiplyPoint(cube2.transform.position)));
其输出是:
推测正确。
下面,再来,我们试图将 立方体1 的 y 变为 2,而 立方体2 的 y 变为 -1,看其 y 轴映射对不对。
我们上面知道了摄像机的 t=3,b=-3。
所以,推测 立方体1 的 y,映射应该是 2/3=0.6667
而 立方体2 的 y,映射为 -1/3=-0.3333
验证正确。
ok,至此 Unity 里面的正交投影矩阵验证正确。
验证透视投影矩阵
我们把摄像机变为 透视模式:
其属性设置为:
摄像机的近平面为 1,远平面为 4。
其投影矩阵为:
我们这里的 n=1,f=4,r=-l, t=-b 带入上面的矩阵:
输出:
可以看到 第三行 验证相同。
再来反推:t 和 r。
1/r = 3.21895,得到r=0.311/t = 2.41421,得到t=0.414θ = 45度aspect = w/h = r/t
带入下面的公式,结果也是正确的。
下面就是验证 z 的映射是否正确了,我们令 立方体1 的 z=1,立方体2 的 z=4,验证其投影映射之后的 z 值。
也就是说 立方体1 的 z 在近平面被映射为 -1,而 立方体2 的 z 在远平面被映射为了 1,结果正确。
其他关于 x 和 y 的映射带入也是满足的。
最后
至此,Unity 中正交投影和透视投影的矩阵验证正确,网上推导的矩阵是真实可以用的。