【转载】Unity 里的正交投影和透视投影

630 阅读5分钟

版权声明:本文为CSDN博主「wodownload2」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:blog.csdn.net/wodownload2…

正文

本文之前一直纠结的问题是 Unity 里面的 正交矩阵透视矩阵 中的 z 到底是映射到 [-1,1] 还是 [0,1] 区间。

经过验证之后,得出 结论: Unity 里的正交和透视矩阵是将 z 映射到 [-1,1] 区间。

经过查询,知道了,OpenGL 把 z 映射到 [-1,1] 之间;而 DX 则将 z 映射到 [0,1] 区间。 可以查看:

image.png

有上面也知道了,DX 是 左手坐标系,而 OpenGL 是 右手坐标系,而 Unity 呢?我们会发现它使用的是 左手坐标系

image.png

左手坐标系、右手坐标系,下图可以直观的给出:

image.png

ok,了解了这么多,我们还要看下 OpenGL 的正交透视投影,以及透视投影的矩阵公式,推导这里就不要再叙述了,网上很多,我之前的博客也都写过,这里给出几个参考网址:

  1. 深入探索透视投影变换
  2. OpenGL学习脚印: 投影矩阵和视口变换矩阵(math-projection and viewport matrix)
  3. 投影矩阵的推导

这里还有一个 不明白的地方 就是在推导的时候,使用 -N-F 。 还有一个 地方不明 的是,Unity 里面的 摄像机的正方向,指向的是世界坐标系的 -z 轴。仿佛所有摄像机能看到的物体,都是在摄像机的后面。

  • 有图说明
    image.png

上图,Camera 的位置是 (0,0,0),其旋转为 (0,0,0),那么打印其 worldToCameraMatrix,也就是 世界坐标 转换为 视口坐标 的矩阵。

Debug.Log(Camera.main.worldToCameraMatrix);
  • 输出
    image.png

我们知道,凡是坐标系的矩阵的前三维代表的是 xyz 轴向量。 也就是摄像机的视口坐标系的三个轴是:

  1. (1,0,0)
  2. (0,1,0)
  3. (0,0,-1)

由此知道,其 z 方向,和世界坐标系居然是相反的

ok,这个只要知道就可以了,对我们的要验证的问题没有关系。

接着我们回到正交矩阵的验证问题上来

验证正交投影矩阵

首先,把 z 坐标映射到 -11 的正交投影矩阵公式如下:

image.png

我们来验证下:

image.png

这里的摄像机其 近平面 设置为 1远平面 设置为 4,也就是 n=1,f=4

image.png

此时看下输出:

Debug.Log(Camera.main.projectionMatrix);

image.png

可以看到矩阵对 第三行 是验证正确。 再来看下矩阵的 第一行第二行。 当我们把另 r=-l ,以及 t=-b 的时候, 矩阵变为:

image.png

也就是:

1/r = 0.16667
1/t = 0.3333

此时 r = 6t = 3 这个是反推出 rt 的。 ok,验证成立。

那么反思下,这个 rt 的代表了什么,我们知道 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);

image.png

是的,我们屏幕的宽高比正好是 2:1。 你可以选择,其他的分别测试:

image.png

如屏幕是 600x800,那么此时的比例是 3:4=0.75,而 0.3333/0.4444=0.75 。验证是正确的。

我们再反思,我们的摄像机是正交的,那么屏幕的的比例,就是摄像机的宽高比

下面要讲解的是如何将一个点映射到 ndc 空间了

我们知道了给出了正交投影矩阵了,任何一个摄像机空间内的点p(x,y,z),只要经过这个矩阵的变换,则最终都被映射到了 [-1,1] 的空间了。其中包括 xyz。上面已经提到 Unity 里面采用的是 OpenGL 的映射方式,将 z 映射到 -11 之间。

下面我们准备两个立方体:

  • 立方体1
    image.png
  • 立方体2
    image.png
  • 摄像机
    image.png

立方体1 的坐标是 (0,0,1)立方体2 的坐标是 (0,0,4),他们都是摄像机的子节点,而摄像机的坐标是 (0,0,0)。 也就是 立方体1 的最终世界坐标是 (0,0,1)立方体2 的最终坐标是 (0,0,4)

我们推测下,由于,摄像机的近平面是 1,远平面是 4,所以可以推测,立方体1z=1,最终被映射到了-1,而 立方体2z=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)));

其输出是:

image.png

推测正确。

下面,再来,我们试图将 立方体1y 变为 2,而 立方体2y 变为 -1,看其 y 轴映射对不对。 我们上面知道了摄像机的 t=3,b=-3。 所以,推测 立方体1y,映射应该是 2/3=0.6667立方体2y,映射为 -1/3=-0.3333

image.png

验证正确。

ok,至此 Unity 里面的正交投影矩阵验证正确。

验证透视投影矩阵

我们把摄像机变为 透视模式

image.png

其属性设置为:

image.png

摄像机的近平面为 1,远平面为 4。 其投影矩阵为:

image.png

我们这里的 n=1f=4r=-lt=-b 带入上面的矩阵:

image.png

输出:

image.png

可以看到 第三行 验证相同。

再来反推:tr

  • 1/r = 3.21895,得到 r=0.31
  • 1/t = 2.41421,得到 t=0.414
  • θ = 45度
  • aspect = w/h = r/t

带入下面的公式,结果也是正确的。

image.png

下面就是验证 z 的映射是否正确了,我们令 立方体1z=1立方体2z=4,验证其投影映射之后的 z 值。

image.png

也就是说 立方体1z 在近平面被映射为 -1,而 立方体2z 在远平面被映射为了 1,结果正确。 其他关于 xy 的映射带入也是满足的。

最后

至此,Unity 中正交投影和透视投影的矩阵验证正确,网上推导的矩阵是真实可以用的。