games101-1 光栅化与光线追踪中的空间变换
在学习了一些games101的课程之后,我还是有点困惑,对于计算机图形学的基础知识,总感觉还是缺乏一些更加全面的认识,幸而最*在做games101的第五次作业时,查询资料找到了scratchpixel这个网站,看了一些文章,终于把脑子里的一团乱麻组织起来了,也就有了这篇关于图形学的第一篇博客。
想要更好的理解这篇博客,强烈推荐先学习games101中关于transformation,rasterization和ray tracing的第一部分
以下内容参考:https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/perspective-projection.html
https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points.html
https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-generating-camera-rays/generating-camera-rays.html
如果内容有误,欢迎指出
光栅化与光线追踪的理解
在图形学中一个很重要的问题就是,我们如何把一个三维空间中的物体,去展示到一个二维*面上。
这件事其实我们可以分成两步去做,第一步是解决visibility的问题,第二步是解决shading的问题。
怎么理解这两种方法呢?我们先想象,现在在一个无穷大的场景里面,有很多物体,还有一个摄像机,摄像机面前一定距离有个*面,现在我们想知道通过摄像机在它面前这个二维*面上看到的所有三维物体的形象。
光栅化方法,就是把场景内的所有物体先都投影到这个*面上,然后进行着色shading,这里就是光栅化比较麻烦的地方,我们需要设计着色模型(games101中的bling-phone模型),考虑到物体的空间先后顺序我们需要z-buffer,考虑到阴影效果我们需要shadow map,以及纹理贴图来帮助我们。
而光线追踪,模拟了光线在场景中的传播,在光线追踪中,从观察点(或相机)出发的光线被跟踪以确定它们与场景中的物体相交点,然后计算反射、透射和光照等效果,是一种基于物理的渲染方法。
通过上述描述,我们可以发现光栅化可以短时间内处理大量物体,但在光照效果等方面不如光线追踪,而光线追踪速度较慢,因为需要考虑到光线的种种复杂的传播情况,这也就导致光栅化主要用于实时渲染,而光线追踪主要用于离线渲染
我们再来考虑针对这两种方法的空间变换,我们会发现这两种方法其实在干相反的事情,光栅化的坐标变换是把三维变二维,光线追踪是需要摄像机到*面上每个像素点连接的光线,然后求光线与物体的交点,实际上需要我们把二维的点转换为三维,具体的过程我们在接下来的部分阐述
不同空间的介绍
要理解光栅化与光线追踪的空间变换,我们首先要搞清楚几个空间的定义
世界空间:世界空间就是我们之前提到的无穷大的场景,所有的点的定义最初都是在这个三维空间中,与之对应的是世界坐标系
相机空间:相机空间就是以相机为坐标原点的坐标系建立的空间,我们可以使用games101中提到的camera transformation对应的矩阵,实现世界坐标系到相机坐标系之间的转换,一般来说,我们的相机的位置在(0,0,0)这个坐标原点,朝向世界坐标系的-z方向。
屏幕空间:屏幕空间是一个二维空间,相机空间中的点经过透视变换可以到image plane上,然后我们根据画布(canvas)的范围,可以在image plane这个无穷大的*面上划分出屏幕空间
NDC空间:把屏幕空间坐标系标准化到【0-1】的空间中
栅格空间:NDC空间中的2D点被转换为2D像素坐标,为此,我们将标准化点的 x 和 y 坐标乘以像素宽度和高度,从 NDC 到栅格空间还需要反转该点的 y 坐标。 由于像素坐标是整数,因此最终坐标需要四舍五入到最接*的以下整数。
下面的图很形象地展示了上面的三个空间的区别:
其实我们电脑的屏幕就相当于栅格空间,不同的分辨率代表着不同的水*像素数与竖直像素数,也就代表着不同的像素宽度与高度,代表着不同的栅格空间计算方法
注意我们这里的讨论实际上简化了屏幕坐标系与NDC坐标系,games101中的投影变换实际上就是直接从相机空间变换到了NDC空间或者说变换到了裁剪空间然后经过齐次除法到NDC空间,空间是三维的,因为我们不仅要完成投影,还需要记录点的z坐标信息来进行深度缓存等等,确定物体的先后关系:
可以看到二维空间与三维空间的区别就是二维空间舍弃了z坐标,它们在xy坐标方面做的都是投影,三维空间相当于多做了z坐标的投影
注意games101其实并没有过多的讨论裁剪空间,而是推导了一个矩阵直接转换到NDC空间,这也是未来博客中要探讨的内容,同时z-fighting的现象也未提及(*远*面距离过大导致的问题)
在我们这里的讨论屏幕坐标系与NDC坐标系是二维的,这样可以把光栅化与光线追踪的visibility处理统一起来,因为光线追踪中我们并不需要使用投影矩阵,就不存在光栅化中的视锥体与立方体,只需要二维的屏幕空间。
光栅化做的就是从世界空间到栅格空间,实现每个物体的visibility,然后着色
而光线追踪做的是实现每个光线的可视化,然后着色,需要我们从栅格空间转换到世界空间。
光栅化中的空间变换
世界空间到观察空间或者说是摄像机空间:
这一步很简单,和games100课程中的一样,实际上就是进行坐标系变换,进行旋转与*移,值得注意的是默认,相机正对的是z的负半轴,所以我们可见的物体的z坐标也是负的
那么在进行从观察空间到屏幕空间的过程中,我们要进行投影,将3维的点投影到2维的屏幕上:
这样我们根据相似关系,可以得到屏幕空间与观察空间的xy坐标映射,注意这里我们假设和**面的距离是1,可以得到:
\(\begin{array}{l} P'.x = \dfrac{P_{camera}.x}{P_{camera}.z}\\ P'.y = \dfrac{P_{camera}.y}{P_{camera}.z}. \end{array}\)
但是注意我们之前提到过物体的z坐标是负的,所以这样进行除法会导致xy发生颠倒,因此,我们要再加上负号:
\(\begin{array}{l} P'.x = \dfrac{P_{camera}.x}{-P_{camera}.z}\\ P'.y = \dfrac{P_{camera}.y}{-P_{camera}.z}. \end{array}\)
之前提到过屏幕空间是有范围的,由宽度与高度决定,所以我们要加上这个限制
\(\text {visible} = \begin{cases} yes & |P'.x| \le {W \over 2} \text{ or } |P'.y| \le {H \over 2}\\ no & \text{otherwise} \end{cases}\)
从屏幕空间到NDC空间,需要我们把原来在[-width/2]--[width/2]和[-height/2]到[height/2]之间的点转换到[0-1]的点(有的NDC空间采用的是[-1-1]):
\(\begin{array}{l} P'_{normalized}.x = \dfrac{P'.x + width / 2}{ width }\\ P'_{normalised}.y = \dfrac{P'.y + height / 2}{ height } \end{array}\)
从NDC空间到栅格空间,我们需要乘以像素宽度与高度,因为我们认为像素点是一个个小矩形来进行栅格化,并且取整,这一步也被称为viewport变换
\(\begin{array}{l} P'_{raster}.x = \lfloor{ P'_{normalized}.x * \text{ Pixel Width} }\rfloor\\ P'_{raster}.y = \lfloor{ P'_{normalized}.y * \text{Pixel Height} }\rfloor \end{array}\)
同时我们注意到栅格空间的y是向下的,所以改变方向取反:
\(\begin{array}{l} P'_{raster}.x = \lfloor{ P'_{normalized}.x * \text{ Pixel Width} }\rfloor\\ P'_{raster}.y = \lfloor{ (1 - P'_{normalized}.y) * \text{Pixel Height} }\rfloor \end{array}\)
以上我们就将三维空间中的点可视化到了二维空间中
光线追踪中的空间变换
针对光线追踪,我们需要的是可视化我们的光线,因为我们需要实际计算光线与物体相交,所以我们的光线需要世界坐标下的三维表示,简单来说一个光线可以如下定义:
我们已经知道了O的位置,它就是坐标原点(0,0,0)
现在需要知道P的位置
现在考虑下面这张图:
第一步转换到NDC空间:
\(\begin{array}{l}
PixelNDC_x = \dfrac{(Pixel_x + 0.5)}{ImageWidth},\\
PixelNDC_y = \dfrac{(Pixel_y + 0.5)}{ImageHeight}.
\end{array}\)
注意这里我们只考虑光线穿过像素点的中心,所以我们要加上0.5,然后再分别除以栅格空间的宽度与高度
第二步转换到屏幕空间:
注意屏幕空间的y轴发生了反转,同时由于我们是将原本的图像压缩到了-1-1的这个空间中,所以我们现在要将其还原回去,即宽度从[-1--1]转换成[-width/2---width/2],高度同理,我们使用宽高比以及fov角度来表示就可以得到:
\(\begin{array}{l} PixelCamera_x = (2 * {PixelNDC_x } - 1) * ImageAspectRatio * tan(\dfrac{\alpha}{2}),\\ PixelCamera_y = (1 - 2 * {PixelNDC_y }) * tan(\dfrac{\alpha}{2}). \end{array}\)
其中tan(a/2)表示图像高度的一半,因为我们在这里假设**面坐标是-1
最后转换到世界空间中,只需要加入z坐标-1,然后从相机空间转换到世界空间即可
上述过程也就是games101第五次作业求光线的实现