【Unity Shader学习笔记】Unity中使用的坐标系及变换过程

Unity中使用的坐标系及变换过程

概述

一个点从三维空间到被绘制到二维空间大致需要经历以下几个步骤:

  1. 模型空间(局部空间)
  2. 世界空间
  3. 摄影机 观察空间
  4. 剪裁空间
  5. NDC
  6. 屏幕空间

看书的时候虽然觉得看懂了,但是后来发现还是没有记住,因此特地写下来,当是为书作注,也加上一些自己的理解。主要参考冯乐乐的Unity Shader入门精要第四章第六节和第九节。
另外还参考了:
https://blog.****.net/ad88282284/article/details/78245719
https://learnopengl-cn.github.io/01 Getting started/08 Coordinate Systems/

模型空间坐标,世界空间坐标,观察空间坐标

我们知道所谓坐标不过是相对于参考系而言的一种定位方式,因此上述三种坐标的本质是一样的,都是三维空间内某个物体相对于另一个物体的向量表达。
具体的细节不过赘述,这里只表达一下个人的理解,帮助读者(包括我自己)梳理一下。
这三个坐标是不触及真正的“把物体转化到屏幕上”的过程的。
这三个空间唯一的不同就是原点坐标以及原点坐标轴方向有所不同,但本质是一样的。因此三个坐标系之间两两转换无非是这个过程:

已知B坐标系下某点C的坐标Cb
已知A坐标系下B坐标系原点
已知A坐标系下B坐标三个轴的向量表示
得到A坐标系下C点坐标Ca

在模型空间到世界空间时,模型空间是坐标B,世界空间是坐标A
在世界空间到观察空间时,世界空间是坐标B,观察空间是坐标A

从观察空间到剪裁空间

概述

这一步利用视锥体对观察空间内的坐标进行剪切,这里讲解比较复杂而常用的透视投影,正交投影过程较为简单。
为了达到这个目的,首先通过透视投影,将观察空间下的点坐标进行一个线性的变换。
这时,通过一个简单的式子:用某点坐标的XYZ分量和W分量作比较,就能决定图元是否被绘制

透视投影

视锥是一个四棱台,包含六个平面,其中两个平行面分别是近剪裁平面和远剪裁平面:【Unity Shader学习笔记】Unity中使用的坐标系及变换过程【Unity Shader学习笔记】Unity中使用的坐标系及变换过程
注意到这里Z轴是指向摄像机的反方向的,这里采用的是右手系。
观察空间的视锥体根据不同的视场(field of view,FOV),宽高比(Aspect),近剪裁平面距离(Near),远剪裁平面距离(Far),形态可以多种多样,根据这些值,我们可以算出视锥内某个点在观察空间下的坐标范围:
x(ztan(FOV2)Aspect,ztan(FOV2)Aspect)x\in(z*tan(\frac{FOV}{2})*Aspect,-z*tan(\frac{FOV}{2})*Aspect)
y(ztan(FOV2),ztan(FOV2))y\in(z*tan(\frac{FOV}{2}),-z*tan(\frac{FOV}{2}))
z(Far,Near)z\in(-Far,-Near)
w=1w=1
由于采取的是右手系,这里z是负值

为了剪裁方便,进行如下变换:
{cot(FOV2)/Aspect0000cot(FOV2)0000Far+NearFarNear2FarNearFarNear0010} \left\{ \begin{matrix} cot(\frac{FOV}{2})/Aspect & 0 & 0& 0\\ 0 & cot(\frac{FOV}{2}) & 0 &0\\ 0& 0 & -\frac{Far+Near}{Far-Near}&-\frac{2*Far*Near}{Far-Near}\\ 0& 0 & -1&0 \end{matrix} \right\}
这一步矩阵变换可视为两次缩放:
【Unity Shader学习笔记】Unity中使用的坐标系及变换过程
第一次缩放是针对xy分量:
使得xy分量不受FOV和Aspect变量的影响,直观上看起来就是x和y分量都只跟与摄像机的距离有关系,即这一步使得视锥的边缘跟z轴是成45度角的。
因此这时候FOV看起来是90度
第二次缩放是针对z深度的缩放:
首先将z轴整体翻转了过来,并且将整个视锥的深度范围变成(-Near,Far)的范围。然后再将坐标整体后移,使得视锥包含摄像机。并且用w分量保存原本变换之前的z值的负值。
不难得到变化后,该点在剪裁空间下的坐标范围:
x(w,w)x\in(-w,w)
y(w,w)y\in(-w,w)
z(Near,Far)z\in(-Near,Far)
w(Near,Far)w\in(Near,Far)

剪裁

到这里已经很直观了,如果这时候吧xyz分量除以一个w分量,这个视锥就自然而然地变成了一个立方体。
而剪裁的时候也非常简单:
只要把每个分量取绝对值跟w分量比较,一旦超出,则该点位置的图元就要被舍弃。
这里再解释一下z分量吧,在xyz三个分两种,我们可以看到xy的坐标范围都是实际上直接跟w值已经挂钩了,超出这个范围就不是视锥内的,因此也很好理解,但是z分量则不同。
简单分类讨论一下为什么z分量绝对值超过w就意味着超出视锥范围:
首先前提是,这里的w分量实际上就是原本的z值,而当前的z值是由原本z值做线性变换而来的,这意味着当前zw分量有如下关系:z=aw+bz=aw+b
因此,z和w必然是一一对应的,那么当|z|>w,即意味着z>0时,z>w或者z<0时,-z>w。而上述范围实际上已经对应了这个关系:【Unity Shader学习笔记】Unity中使用的坐标系及变换过程

从剪裁空间到屏幕空间

概述

剪裁空间得到的视锥,已经能方便的剪裁不需要的图元,但是为了转化到屏幕上,还要先经过齐次除法得到NDC(Normalized Device Coordinate,归一化的设备坐标),然后使用NDC下的xy分量,根据实际屏幕大小来进行缩放。得到实际屏幕的2D坐标。

齐次除法得到NDC

上面其实已经提到,用剪裁空间下的坐标中的xyz分量除以w分量就得到了NDC坐标。

NDC或剪裁空间到屏幕空间的转换

可以看到NDC下有x,y(1,1)x,y\in(-1,1),因此,为了映射到x(0,screenWidth),y(0,screenHeight)x\in(0,screenWidth),y\in(0,screenHeight)需要做如下映射:xscreen=(xNDC+1)/0.5screenWidth,yscreen=(yNDC+1)/0.5screenHeightx_{screen}=(x_{NDC}+1)/0.5*screenWidth,y_{screen}=(y_{NDC}+1)/0.5*screenHeight
如果是直接从剪裁空间要得到屏幕空间的话:xscreen=(xclipwclip+1)/0.5screenWidth,yscreen=(yclipwclip+1)/0.5screenHeightx_{screen}=(\frac{x_{clip}}{w_{clip}}+1)/0.5*screenWidth,y_{screen}=(\frac{y_{clip}}{w_{clip}}+1)/0.5*screenHeight