方法实时Voxel Cone Tracing based Global Illumination

废话就不多说了,开始。。。

    之前很早就看到了UE4中的基于Sparse Voxel Octree的RTGI,效果很酷,始终尝试作些研讨与实现,但苦于没机会。前段无暇,抽时间学习了一下,这里小结一下备忘。

    整个算法重要分类几个进程:体素化、Mipmap OCTree、Cone Tracing。

    

1. Voxelization

    体素化整个GI算法的基本。这里体素化可以采取的方法也比较多,重要有以下几种:

    

  1. 直接将体素与场景停止碰撞检测。该方法比较原始,效率也较低,虽然也可以借助于GPU停止加速,但是在该特定场所中使用起来还是诸多不便;
  2. 基于CUDA停止体素操纵,这也是使用GPU停止加速的一种方法。比如这里的一个使用CUDA停止光栅化方法,稍作修改就可以同样用来实现体素化;
  3. 基于GPU Rasterization的方法,使用这类方法的利益就是不需要特殊的Pipeline,很容易将它往现有的游戏或渲染引擎中停止整合;

    

由于整个算法要搞定动态场景以及动态光源,因而这里的体素化操纵也是需要实时更新的,因而就需要效率尽可能高的体素化操纵算法;而且就尽可以地与现在的引擎紧密结合,如此一来基于CUDA的其实也是不太方便。对于第三种方法,结合以后最新的DX11或OGL4.0以上的一些新属性,可以实现基于Rasterization和DirectCompute的体素化算法。这里有一篇文章就是Cone Tracing算法的作者宣布在OpenGL Insight中分析使用OpenGL来停止光栅化的方法,值得学习。

    

体素化的进程重要如下图所示:

    

方法和实时

    

其重要流程是:

    

  1. 使用与体素化细分分辨率相同的正交投影窗口来渲染三维网格中的每一个三角形;
  2. 对于每一个三角形计算出一个投影面积最大的投影矩阵,然在在此位置上做光栅化,这样使得光栅化效率最大化;光栅化出的每一个像素对应一个该方向上的体素;
  3. 在光栅化出的每一个像素中执行Shader,利用OGL imageStore或DX11的RWTexture3D方法将像素对应的体素信息写入到3D Texture中;
  4. 对六个投影轴方向分离停止上述操纵之后失失落6张3D Texture;之后对其停止合并失失落最终的3D Texture,其中就包含了整个场景的完整体素化结果。
使用上述方法停止体素化之后的结果如下:

    

方法和实时

    

注意场景上空中的柱子,其结果其实不完全正确,在旁边会遗漏相应的体素。这重要是因为上述体素化方法使用了守旧的判断操纵,因而可能会遗漏多少体素。对于这类情况,一个可行的解决方法是对三角形向周围停止扩展,如下:

    

方法和实时

    

在扩展后的三角形基本上再停止同样守旧的判断操纵就会失失落原始被裁失落的那些体素,这样一来体素化的效果就会提高不少。

    

方法和实时

    


    

另外,这里简略也说一下对于体素场景的渲染(Voxel rendering)。如果想实现上述类似于Minecraft的块状体素渲染效果,也有几种可选的方案:

    

  • 最简略的一个方法是直接对于每一集体素生成一个6个面的Cube,然后再绘制,但是这样发生的冗余面就太多了,绘制效率极低;
  • 另外一个方法是表面生成(Surface Extraction):其实体素绘制时的有效表面只会发生在相邻两集体素的状态是由非空 变换为 的位置上。基于些,对于场景中的体素停止遍历,通过每一集体素及其相邻体素之间的状态切换关系,判断相关位置上是否有体素表面,如果有的话就将其添加表面列表中以待绘制。这可以使体素内部的表面都被省略。不过对于256x256x256的体素分辨率来讲,在上述场景中停止表面生成后也失失落了多达658240集体素表面,数量仍然不小。
当然,这两种方法都是在体素化之后、实时渲染之前停止一次离线的操纵完成的,其实不能实时停止。为了达到体素化完成后实时绘制的目的, 本人尝试使用多个基本分割面来停止体素渲染(类似于slice based) 。方法就是在每一个轴向上放置分辨率(比如256)个Quad,三个轴一共就需要256 * 3个Quad,或是256 * 6个三角形。当绘制体素时,全部的体素表面其实都是落在这些个基本表面上的;如此一来就只需要在Shader中判断每一个基本表面上的像素是否是属于一个不空的体素的(需要一次由像素位置到3D Texture的读取),如果是的话则将以后像素作为体素表面的像素来绘制,如果不是的话是discard失落。这类方法本身没什么问题,效率也要比表面发生后再绘制高,可以通过较少的表面绘制来完成最终的体素渲染,不过旁边遇的一个情况就是3D Texture的采样问题:微小的采样误差对应的结果是像素状态判断的高频fliking,最终导致全屏的闪烁,尝试失败。。。

    

2. Mipmap based OCTree

    

做过场景管理的童鞋应当会对八叉树很了解,这是一种很常用且简略的空间分割方法,可以用来停止碰撞检测等的加速操纵,实用性很强。这里的Cone Tracing算法也是基于一个类似的八叉树结构上停止的,这里的重要问题是如何倏地地创立出基于体素信息的空间八叉树结构。

    

在第一步完成了体素操纵之后,即可以失失落存储于3D Texture中的场景离散信息,此时其相当于保存了八叉树的最底层信息,也即每一个叶子上的信息。接下来需要通过这些叶子来失失落整个树的结构,这里使用的方法等于自底向上的OCTree创立。通常,自顶向下的方法是对以后的每一个结点停止八个子结点的分割;而自底向上则是对每八个子结点停止合并来失失落它们对应的父结点,这样始终到最顶层的结点结束即可。这里利用了Mipmap的原理来对3D Texture停止不同级别的Mipamp的发生,也就相当于失失落不同深度下的八叉树结构,如下:
    每日一道理
只有启程,才会到达理想和目的地,只有拼搏,才会获得辉煌的成功,只有播种,才会有收获。只有追求,才会品味堂堂正正的人。

    

方法和实时

    

以后Mipmap图层中每一个Texel就代表了一个场景体素结点,对其停止合并后就失失落了上一级(也即上一层OCTree)的更大的结点,如此停止直到顶层。这样一来,对于一个比如含有10级Mipmap的3D Texture中失失落了对应深度为10的场景分割OCTree,这个作为下一步停止Cone Tracing的基本空间数据结构。在GI场所中使用时, 每一集体素中需要存储的信息只需要RGBA就够了。

    

注意:这里直接使用了3D Texture来停止OCTree的存储,并没有使用Sparse OCTree的存储方法,这个实现起来需要作些修改。

    


    

3. Cone Tracing

    有了场景的基本OCTree结构之后,接下来就到Cone Tracing了,这个是整个方法的核心,其重要是使用多层次的空间近似来下降传统Irridiance计算的庞杂度进而使得实时的倏地计算变得可行。先来看个图示意一下:

    方法和实时

    首先,在每一个表面的每一个点上,将传统做GI计算时的半球积分空间给分割成多个独立的Cones,用这些Cones组合失失落的空间(旁边会有重叠或裂隙)来近似原始的半球空间,并在其上做Irridiance的采积。

    之后,对于每一个独立的Cone,又使用下述方法再停止近似:

    方法和实时

    也等于在每一个Cone的内部又将其用多个密布排列的Cube来停止近似,使用Cube的方法是其会使得OCTree的Tracing变得很方便。 每一个Cube巨细的计算就可以根据详细Cone的属性(比如夹角,最大长度等)来停止计算,一般来讲从每一个Cone内部分割出来的Cube个数不会太多.

    对于每一个Cube在OCTree中的Tracing,使用的方法也比较简略:直接计算出的Cube的Size,然后根据此Size找出与其最适配的那层Mipmap,这里的原则就是Cube的Size要尽可能地与Mipmap层中的结点Size接近。最后,直接使用此Cube的位置信息来采样Mipmap中的相应位置上的结点值,即可完成对此Cube的Tracing。

    对Cone中的每一个Cube完成Tracing之后,以后Cone方向上的Irridiance积累结果就可以认为是Cone中全部Cube采样结果的叠加。这个看起来虽然有些不太公道,但是视觉效果上的近似已很不错了。此外,作者也对该近似方法停止了数学上的分析(step by step pre-integration):将每一个Cube认为是Transparent属性,然后Irridiance会在其中停止不断的传递。详细可以见这论文里边的详细内容。

 下面有个简略的试验效果图,Tracing效果还是挺不错的。

    方法和实时

    整体来讲,基于SVO Cone Tracing的GI是一种蛮好的方法,其算法本身跟CE中的LPV很相似,但增加了对场景的体素化加速结构的实时生成,并且在效率与效果中达到了较为不错的平衡,与现有引擎的集成难度应当不是太大,未来应当还是有很好的发展趋势。

    

后记. Pure run-time methods VS Hybrid methods

    写到了GI,这里顺便也对自己之前所了解的GI情况作一简略总结。目前,主流游戏引擎中对实时GI的支持逐步变得普遍,整体来看,重要使用的实时GI算法应当大体分为这两类:

    

  • 纯实时方法:这类方法的特点是全部GI相关的计算都是在实时更新时完成(这里重要是指核心的计算),其面临的最大挑战是效率与效果的兼顾。该类方法中比较典型的有:Reflective Shadow map(以及各种改进版本)、Light Propgation Volume(CE中使用的方法),SVO GI(UE4中使用的方法);
  • 预处理&实时结合方法:这类方法的特点是将实时计算与场景预处理停止结合,在预处理阶段中生成一些额定的信息来加速实时GI更新时的计算,这样的话可以将部分实时更新计算提早,进而减少动态更新部分的压力。该类方法中比较典型的重要是:Enlighten(Frostbite中使用的方法);

    

对上述两大类方法,重要作如下的比较:

    

  1. 效果和效率:Hybrid类的方法在GI的效率和质量上都更胜一筹(Enlighten的效果与效率比SVO与LPV都要好),毕竟其将一些庞杂的计算都移动到了预处理阶段,然后在实时更新中直接使用就可以。
  2. 易用性:这个重要是从制造人员角度斟酌,Hybrid方法需要后期美术资源制造时的付出一些人力本钱用来协助预处理进程,该部分的任务量也会视情况而变;而纯实时的就不需要该部分任务。
  3. 使用价值:效果较好的Enlighten意味着不菲的使用授权;而上述纯实时的GI算规律都是收费使用(当然,开辟集成也需要少许本钱)。

    其实,RTGI对于一款游戏或引擎的意义也要视情况而定,对某些引擎或项目而言这些东西可能毫无意义;但对其它的来讲,这些可能就是浩繁亮点之一,需要辩证地对待吧。

    

 References

    

    

    

    

文章结束给大家分享下程序员的一些笑话语录: 开发时间
  项目经理: 如果我再给你一个人,那可以什么时候可以完工?程序员: 3个月吧!项目经理: 那给两个呢?程序员: 1个月吧!
项目经理: 那100呢?程序员: 1年吧!
项目经理: 那10000呢?程序员: 那我将永远无法完成任务.

--------------------------------- 原创文章 By 方法和实时 ---------------------------------

posted @ 2013-05-23 20:03  xinyuyuanm  阅读(1704)  评论(0编辑  收藏  举报