UE3 中的 GI
UE3中的lightmass是基于irradiance volume,98年的老古董:irradiance_volume_ieee
需要预处理,这里提示下和光子映射是两码事,即便采样阶段都用了Monte-Carlo。irradiance volume貌似是第一次引入SH思想,论文不看也罢,重点是里面的一张图:
一句话:按照某种定制规则生成采样点位置,用SH保存光通量分布,将SH参数信息保存到一个体纹理中(具体的数据形式随便搞)以便查询。
缺点就是:
- 需要预处理;
- 采样点的排布有学问(多少及位置);
- 限制很大,场景光线变动GI就废了(如可破坏场景,天气系统导致太阳光方向乱变);
RSM(Reflective Shadow Map)
下面介绍的GI都是基于RSM( 反射阴影贴图)这个中间结构,可以简单将其理解为GI中的G-Buf。
-
shadow map的升级版,在shadow map的时候,我们会获取light空间的深度图,以此为基础判断阴影遮蔽。但是RSM除了记录depth以外,还需要记录PN(Position & Normal)和辐射通量(radiant flux),这个辐射通量计算起来比较麻烦。辐射通量即是intensity* solid angle,对于平行光源来说,可以认为每个像素都占有了相同的solid angle,因此辐射通量是一个常数。而对于聚光灯来说,越偏离主方向的像素占有的solid angle 越小,因此需要乘上一个 cosine 因子(如果考虑intensity的衰减,那么还要乘上相应的衰减因子)。最后需要存储的反射辐射通量,因此还要乘上一个反射
-
在渲染的时候怎么利用Reflective Shadow Map得到场景中任意一点x的E_1呢,首先将x投影到light space,得到在RSM中的参数坐标(s, t),在这里近似的采样RSM中位于点 (s, t) 附近的像素点作为pixel light set,若x点的法线为n,那么每个 pixel light (设为p)对点 x 贡献的辐射度为:
a> SII (Splatting Indirect Illumination 泼洒间接光照): RSM的最大缺点就是每个像素都需要采样好几个light point,然后计算光照强度,这样会引入大量的计算。为此,SII的思路是: 1) 首先在 RSM 中采样一定数量的 pixel lights,采样不一定是均匀分布的,比如在整体辐射通量比较高的地方可以采样更多的点(importance sampling)。 2) 然后每一个 pixel lights被当作一个 virtual point light(VPL),并投影到screen space。每一个VPL跟据它的flux和normal可以得到一个影响区域(比如可表现为screen space 中的一个矩形区域),并假设仅所有位于这个区域内的点受到此VPL的照明(也就是相当于将VPL的flux溅射(splat)在其整个影响区域中) 3) 最后就是用 deferred shading的那一套进行光照计算即可。
这种方式,其实就是统一进行采样,然后把采样的light point当做点光源,进行延迟光照。
b> MSII(Multiresolution splatting for indirect illumination): MSII在性能上应该没有太大的体现,只是在效果上更接近全局光照,它的理论基础是间接的这种低频光照,对相邻像素引擎的变化不会太大。如果使用SII的话,由于虚拟点光源是局部的,很容易造成,那种不同点光源区域的像素之间差异比较大。 1) 首先将screen划分成为一个较低分辨率(比如16*16)的网格,每个格子称为一个subsplat 2) 然后分别考查每一个subsplat,对于几何变化较小的subsplat来说,可以直接在这个低分辨率粒度下进行splatting,当计算完成后整个subsplat将得到相同的辐照度,这个结果已经足够。 3) 但是对于几何不连续的subsplat,则还需要进行进一步的划分(这就是为什么称为multiresolution splatting)。对于是否还进行进一步划分可以通过min-max mipmap来判断,这里需要建立四张mipmap,一张存depth,另外三张分别存normal的三个分量。每次划分将一个subsplat分成四个sub-subsplats, 然后再分成16个…就这样通过层次性细化来完成整个splatting过程。
从算法的复杂度来讲,这种方式的算法是比较复杂的,但是它相对于光线追踪优秀的是,光线追踪的效率不会收敛反而会因为三角形面片的增加而俱增,而MSII的效率比较稳定。
LPV(Light Propagation Volumes)
是VPL的进化版本:VPL
其是09年cryengine提出的一种基于点云的思路,全实时没有任何预处理的GI,也是基于延迟渲染,原始论文:Light_Propagation_Volumes
大神ccanan总结的很到位:Light Propagation Volumes in CryEngine 3, 作为新世纪的产物:
- 场景是基于deferred lighting的,不是deferred shading两个很像,只是有比较小的差别。
- 首先画一个reflective shadow map
- down sample reflective shadow map
- 把这个down sample的RSM(reflective shadow map) 每个pixel做为一个surfel(参见前面的point based lighting), 每个surfel就是一个光源了,也叫虚拟光源(virtual point light, VPL for short)。
- 接下来是虚拟光源注入radiance volume阶段,在point based lighting里面radiance volume是在八叉树里面的cell做的,这里我们用的GPu计算,所以用的是3D volume texture, 八叉树的cell就对应到volume tex的texel。做的时候VPL就把SH系数保存在最近的texel里面,这里还没结束,因为正常光源需要传播比较远,而现在只是保存在最近的texel(也就是cell)里,其他的也应该有光照信息的cell还需要处理。就涉及到接下来radiance传播了。
- radiance 传播 : 接下来把radiance在volume texture里面传播,保持能量守恒定律,像周围几个方向传。
- 最后得出的volume texture就是动态记录了场景里面低频简介光照的信息,然后我们用position(文章里说是world space的position)来sample,进而得到间接光照信息。
UE4实现了LPV,这也是前些天翻看延迟渲染代码时的新发现。lpv和ds两个这么相似,放一起整合架构也是合理的,下面是界面上的勾勾叉叉:LightPropagationVolumes
结构逻辑后续再花时间整理,最近时间也不是很多。
另外物理材质的不在延迟渲染里,在BRDF的公共shader头文件里(上次搞错了)。
SVOGI 和 VXGI
lpv是传统实时全局光照实现方案。性能优越效果还行,如果要用实时全局光照,自然首先考虑这个。
上述两个是近几年的GI,可以参考知乎口水贴
参考nv的ppt,可以细看:SB134-Voxel-Cone-Tracing-Octree-Real-Time-Illumination
UE4最初打算使用svo(虚幻引擎4中的实时GI技术 ,又见ccanan的踪影),但是由于性能问题没有加上,估计这个原因改成lpv。后来该技术由nvdia接手,诞生了vxgi。vxgi的效果自然是非常棒的,但是性能不好。 当然,还是要卖它的GameWorks(UE4可以直接集成):vxgi
神谕之于我等浅薄之人往往难以参透,可以参考这个详细点的:Voxel Cone Tracing based Global Illumination
svogi的主要流程:
1. 体素化, 方法不是唯一的,思想是一样的,都是投影再反投影,其实就是当年在实验室搞的MarchingCubes算法的逆过程,但思路是不同的:
a> 使用与体素化细分分辨率相同的正交投影窗口来渲染三维网格中的每个三角形; b> 对于每个三角形计算出一个投影面积最大的投影矩阵,然在在此位置上做光栅化,这样使得光栅化效率最大化;光栅化出的每个像素对应一个该方向上的体素; c> 在光栅化出的每个像素中执行Shader,利用OGL imageStore或DX11的RWTexture3D方法将像素对应的体素信息写入到3D Texture中; d> 对六个投影轴方向分别进行上述操作之后得到6张3D Texture;之后对其进行合并得到最终的3D Texture,其中就包含了整个场景的完整体素化结果。
2. 八叉树表示, 看着这种构图很容易让人联想到ML里的拉普拉斯金字塔,乱入了:
3. Cone Tracing, 共三个Pass 首先,在每个表面的每个点上,将传统做GI计算时的半球积分空间给分割成多个独立的Cones,用这些Cones组合得到的空间(中间会有重叠或裂隙)来近似原始的半球空间,并在其上做Irridiance的采积。
继续一张图:
a> Light pass
从lightview渲染场景,把光照信息填入svo。
b> Filtering pass
对svo中的光照信息进行filter,开始存进去的光照信息实在最底层的level,那么要将这些信息也通过filter放入父节点,那么在cone tracing的时候,就可以直接读父节点(if possible & necessary)来获得足够的光照信息,这个过程颇像构建mipmap,存的信息是光照的方向分布。
c> Camera pass cone tracing,在我们看到的每一个像素这里,使用一组可以覆盖这个点的半球的cone来遍历svo,进而获得这个点上的光照信息 获得的方式有使用较大的cone,来获得indirect diffuse lighting。较小的cone,获得indirect specular lighting。
vxgi 考虑到vxogi中的o的渣性能,vxgi采用了一种叫Clip Map的数据结构。Clip Map就是类似mip map的一种存储方式,只不过在最低LOD的几个level只保存了中心的信息,在算法里Map的中心当然是你的相机视点,也就是说离视点越远的地方场景的体素信息越粗糙。VXGI因为可以调整追踪的Cone的角度的大小,可以通过追踪非常细的Cone来近似Glossy的反射。VXGI的问题当然第一是在于每一帧都要做场景体素化(当然可以只体素化动态的部分)比较费时间,而且3d的文理会费比较多的显存,而且和LPV一样基于场景体素化的精度决定了光照的精度,所以也会有漏光等artifact存在。
ao 可以做GI,那么再有足够的voxel信息的情况下,做AO简直就是顺理成章的事情,而且这个真3d的高品质ao,不是会受限的ssao。
小结
总的来说这些算法都是将场景和光照信息通过某种prefilter的方式保存然后渲染的时候reconstruct回来的尝试。中间具体如何filter和reconstruct我认为现在都不能说是最佳的和精确的,还有探索的空间。同时它们也都是很expensive的技术,所以暂时并不普及。我这里的介绍也比较high level, 许多细节感兴趣还得自己看paper和code。
再回到UE4放出的实时渲染影像,由于这种技术,天气和可破坏场景已经表现的相当好了: 虚幻引擎4演示影像(12.06.08)
无意中又翻到当初的UE4特性贴:unreal engine4效果很不错
附:GI 和 DS 的分分合合
这里以KlayGE引擎的讨论为例: 分:klayge-4-3中渲染的改进(一):gi和deferred的分离
主要是由于G-buf的膨胀,
合,其实也只是在前端解耦:klayge-4-0中deferred-rendering的改进(四):gi的神话
附:Pure run-time methods VS Hybrid methods
写到了GI,这里顺便也对自己之前所了解的GI情况作一简单总结。目前,主流游戏引擎中对实时GI的支持逐渐变得普遍,总体来看,主要使用的实时GI算法应该大体分为这两类:
纯实时方法: 这类方法的特点是全部GI相关的计算都是在实时更新时完成(这里主要是指核心的计算),其面临的最大挑战是效率与效果的兼顾。该类方法中比较典型的有:Reflective Shadow map(以及各种改进版本)、Light Propgation Volume(CE中使用的方法),SVO GI(UE4中使用的方法)(新版本UE4也换成LPV了); 预处理&实时结合方法: 这类方法的特点是将实时计算与场景预处理进行结合,在预处理阶段中生成一些额外的信息来加速实时GI更新时的计算,这样的话可以将部分实时更新计算提前,进而减少动态更新部分的压力。该类方法中比较典型的主要是:Enlighten(Frostbite中使用的方法);
对上述两大类方法,主要作如下的对比: 效果和效率: Hybrid类的方法在GI的效率和质量上都更胜一筹(Enlighten的效果与效率比SVO与LPV都要好),毕竟其将一些复杂的计算都移到预处理阶段,然后在实时更新中直接使用就可以。 易用性: 这个主要是从制作人员角度考虑,Hybrid方法需要前期美术资源制作时的支出一些人力成本用来协助预处理过程,该部分的工作量也会视情况而变;而纯实时的就不需要该部分工作。 使用代价: 效果较好的Enlighten意味着不菲的使用授权;而上述实时GI算法则是免费使用。
其实,RTGI对于一款游戏或引擎的意义也要视情况而定,对某些引擎或项目而言这些东西可能毫无意义;但对其它的来说,这些可能就是众多亮点之一,需要辩证地看待吧。