2D GI-基于Voronoi图和ray marching
给之前的DOTS软体加上2D GI,并尝试了一些优化方案。
基于ray marching的2D GI的方案大同小异,我是从这里抄作业的:https://samuelbigos.github.io/posts/2dgi1-2d-global-illumination-in-godot.html#jump-flooding-part-i—the-seed
这里有web端的演示效果:https://akari.lusion.co/#mayhem
总体来说步骤如下:
Ray marching的优化策略就是减少步进次数。此方案先基于发光物体生成Voronoi Diagram(维诺图)。和距离场类似,表示的是该像素到最近目标点的距离。然后进行ray marching时每次步进的长度来源于采样Voronoi图的值(即到最近发光体的距离)
Voronoi图的生成方式采用了性能开销最小的Jump Flooding Algorithm。其原理类似二分法,每次偏移半分辨率采样当前像素周围的8个像素,查询是否有更靠近光源像素的点,并记录给下一次循环。代码很简短。其好处是迭代次数和光源数量无关,只跟屏幕分辨率有关(例如1024像素的RT,需要迭代10次,即2的十次方)。
可参考这里:https://github.com/alpacasking/JumpFloodingAlgorithm
之后的ray marching过程和降噪方式与一般SSGI大同小异。
具体实现:
上述参考中Voronoi图的生成来源于光源Seeds,但在我的demo里其实光源来自于任意的发光像素,或者说是流明大于阈值的像素。而Jump Flooding Algorithm的迭代次数和光源数量无关,跟屏幕尺寸相关。所以不必用compute shader处理,直接用shader从Render feature里读取Color Buffer来生成最初的Seeds图。
为了优化,这里可以用版分辨率,或者1/4分辨率。但低分辨率会在光源边缘产生锯齿,所以可以在生成Seed图的同时进行一次描边修补,让光源内缩一圈像素,之后RayMarching的时候就不会停止在光源边缘,不会有明显的锯齿瑕疵。Seeds图边缘优化方式就是采样周围上下左右四个像素,一旦有非Seed值就丢弃。(上图修改前边缘有黑影抖动;下图修改后GI很均匀)
Ray Marching之后可以用Temporal Filter + AABB Clamp(时序过滤和相邻像素亮度过滤)来降噪,并消除运动时的拖尾残影。
此外还可以再进行一次高斯模糊,让GI噪点更加均匀。这里用的是Dual Kawase Blur。整体流程图如下:
以下是运行效果