可视性问题
- 光线追踪需要消耗更多的内存和时间,但是相比栅格化的模拟效果更好,而在如今计算机硬件发展后,几乎所有非实时渲染器都采用光线追踪技术,或者采用混合技术.
如何用栅格化解决可视性问题:如何做到?
从屏幕空间转换到标准化设备坐标空间-NDC(normalized device Coordinates),再转换到栅格化坐标.
- 所有的点最初定义的坐标系是屏幕空间(screen space),由位于画布中心的原点定义.坐标系所有的轴都是单位长度的,因此坐标可能为负数——如果该点位于画布左侧.
- 栅格空间(raster space)是用像素网格来定义坐标系的。其坐标原点位于左上角,其一个像素点为一个单位长度,所以其实际画布大小由图片的宽高决定。
转换方法如下:
- 由于在栅格空间的坐标都为正数,所以需要先把P点的原始坐标标准化。即将它们的坐标转换为0-1范围内,这时候我们称它们转换到了NDC空间中,这样再转换到栅格空间就很简单了。
- 当转换到NDC空间后,将标准化后的坐标乘以图像的大小,并将结果取整。
原始坐标中P点的范围取决于屏幕空间中画布的大小,但是为了简便,我们假设画布的大小为[-1,1],下面是伪代码
int width = 64, height = 64; // dimension of the image in pixels
Vec3f P = Vec3f(-1, 2, 10);
Vec2f P_proj;
P_proj.x = P.x / P.z; // -0.1
P_proj.y = P.y / P.z; // 0.2
// convert from screen space coordinates to normalized coordinates
Vec2f P_proj_nor;
P_proj_nor.x = (P_proj.x + 1) / 2; // (-0.1 + 1) / 2 = 0.45
P_proj_nor.y = (1 - P_proj.y ) / 2; // (1 - 0.2) / 2 = 0.4
// finally, convert to raster space
Vec2i P_proj_raster;
P_proj_raster.x = (int)(P_proj_nor.x * width);
P_proj_raster.y = (int)(P_proj_nor.y * height);
if (P_proj_raster.x == width) P_proj_raster.x = width - 1;
if (P_proj_raster.y == height) P_proj_raster.y = height - 1;
有几点需要在代码中注意:
- 在屏幕空间和NDC空间中点使用的坐标为Vec3f或Vec2f类型,其中点都为小数,而在栅格空间中坐标是用vec2i类型整数表示。
- 数组中从下标0开始,所以栅格空间中的点坐标不能大于图像宽度-1,而当屏幕空间中的点正好位于1时有可能发生这种情况。所以代码需要检测这种特殊情况。
- NDC空间的原点位于左下角,而栅格空间的原点位于左上角,所以进行转换时需要反转x轴。
但是我们为什么需要这种转换呢?我们需要以下的方法来解决可视性问题:
- 将所有的点投射到屏幕上
-
对每个点将其坐标从屏幕空间转换到栅格空间中
首先,要如何找到这些点?我们将组成多边形物体的三角形拆分为更小的几何体, 在实时API中这被称为碎片(fragment)
-
找到点映射到的像素,并存储点到眼睛的距离(存放于该像素点的深度表中)
-
- 过程结束后,升序排列每个像素的深度表,那么深度表的第一个点就是该像素需要显示的点。
表需要被排序,因为在投射时并不是按照深度顺序去进行的。
这个方法有一个叫深度排序的算法。根据物体深度排序的概念是所有栅格化算法的基础。有许多知名的算法例如:
- z-缓冲区算法(z-buffering),这是最常用的算法。
- 画家算法(the Painter algorithm)
- Newell's algorithm
当考虑透明度时
为什么我们需要维护一个列表的点呢?只保存最短距离的点就行了?因为上述算法示例只考虑了非透明物体。当几个半透明点投射到同一个像素时,我们需要通过深度表记录,并以特定的算法进行合成,这样才能正常地渲染它们。
光线追踪解决可视性问题
我们可以从像素点开始,将其转换为屏幕空间内的点(以像素中心计算坐标)。然后我们可以从眼睛追踪一条光线,让其经过P',射向场景中。如果射线与物体相交,那我们就知道了P点就是对该像素点可见的点。
注意:光线追踪和栅格化是殊途同归的,它们都基于相同的原理。不同的是前者是从眼部到物体,后者是从物体到眼部。
光线追踪的技术更为复杂,因为其需要解决光线和物体的相交问题。对于不同的表面和几何体属性都有不同的方法。
许多年来,大量的研究用于寻找最有效的方法来计算光线和最简单的三角形的相交,以及NURBS,隐式平面(implicit surfaces)的相交。然而,一个可行的做法是,在渲染过程开始前,将所有的几何体转换为某一种代表性的几何体。这样渲染时只需要计算光线和这种代表性几何体的相交。由于三角形是一种理想的。
有许多优点:
- 三角形有许多几何特性非常适合作为几何原型。
- 由于上一点,有许多研究用于测试最佳的光线-三角相交检测的算法。最好的算法应该是速度最快的,使用内存最少的,并且十分健壮的。
- 从代码角度来看,支持一个过程比编程处理许多几何体更具有优势。这样可以专门设计代码去更好地处理三角形,尤其是对加速结构来说。计算相交的时间随着场景中的几何体数量增加而线性递增。同时,针对光线-三角相交的硬件支持也在发展。
比较栅格化和光线追踪
利用加速结构(acceleration structure),其将空间分割成一个个小的子空间网格。不同的物体就可以根据它们落入的子空间来分类。
如果子空间的大小大于物体的平均大小,那么一个子空间可能包含多个物体。我们不需要去测试所有的物体,我们可以先测试光线是否经过给定的子空间。如果经过,我们再测试光线是否和子空间内的物体相交;如果没有经过,那么我们就可以跳过对该子空间内的物体的相交测试。这样我们只需要测试场景内几何体的一个子集,这大大节省了时间。
如果加速结构可以用于加速光线追踪,那么光线追踪是否比栅格化更优秀呢?可以说是也可以说不是。使用加速结构引起了许多新问题
- 构建该结构需要时间,这意味着渲染器需要等待构建完才能启动。这大概用不到几秒钟,但是对于应用到实时软件来说,这几秒就太久了。
- 加速需要大量的内存,这取决于场景的复杂程度。但是由于相当一部分内存被用在加速结构上,用于其他功能的内存就更少了,尤其是用于存储几何体的。在实际工作中,这意味着使用光线追踪相比较之下只能渲染更少的几何体。
- 找到一个合适的加速结构是很难的。加速结构的效率取决于场景中物体的分布和物体的各种属性。
实际上,光线追踪由于某些原因还是相当流行的。首先是因为它的概念相当简单,虽然栅格化的代码也长不到哪里去。但是光线追踪的概念更容易去编程实现,并且更贴近于实现3D场景的自然的想法。而且在栅格化中难以模拟的阴影和反射等在真实图像中很重要的特性,在光线追踪中可以直接进行模拟。
栅格化很快,但是需要动脑去支持复杂的视觉特效,光线追踪支持复杂的视觉特效,但是需要动脑去让其变快。(David Luebke - NVIDIA)
总结
栅格化和光线追踪各有千秋,没有哪个有压倒性的优势。
网友评论