在荒野大镖客2分享在SIGGRAPH2019的文章里面有几张PPT是关于云雾是怎么优化Raymarch效率的,我先按照我的理解说一遍到底这几页PPT在讲什么。

首先是告诉我们因为体素不能表现很多高频细节,所以从体素的远端开始要进行视口的Raymarch,然而全分辨率Raymarch消耗太高,所以需要在半分辨率下(长宽各除以2)进行RayMarch。然而这样的消耗仍然很高啊,那就把这Raymarch的操作分散到4帧里面执行吧。考虑到后期需要UpSample + TAA,那就把射线的起始位置延视线方向用BlueNoise抖动一下前后位置以便得到更好的效果。具体实现就是用两个半视口分辨率的RT,前后帧交替一个做历史帧云雾Raymarch结果,一个做当前帧云雾融合结果并最后渲染到SceneColor上。当前帧Raymarch云雾的操作执行在一个四分之一分辨率(长宽各除以4)的小RT上,用于和历史帧云雾融合。

然后下一张PPT就是在讲,因为Raymarch分散到4帧里面执行了,那么就需要一个好的方法将以前几帧但是当前帧跳过的像素Raymarch的结果融合到当前帧来。怎么做呢?
1、用围绕跳过像素的的所对应的3x3大小的RayMarch像素块形成的云雾颜色的AABB包围盒来裁剪历史帧恢复出来的云雾信息,从而丢弃过时的云雾信息。
2、在3x3的Raymarch像素内用一个尽可能大的深度包裹范围来进行Raymarch,以便更好的处理深度边缘云层出现抖动的问题。(PS:深度边缘就是在一个区域内深度发生剧烈变化的位置。比如场景中挡住天空的东西,这些东西的边缘出就会因为天空深度是无穷远而导致和东西的深度出现巨大的反差。这个时候分帧处理后的Raymarch的云雾遇到这种边缘就会因为某一帧只看到远处的云层下一帧只看到近处的雾而导致,历史帧云雾信息恢复到当前帧因为进行了Color AABB 裁剪,就会出现颜色抖动,即闪烁。所以在深度变化剧烈的地方需要特殊处理。)
3、如果没有历史云雾信息,那么就直接使用周遭Raymarch像素中距离此融合位置LinearEyeDepth最近的像素的云雾信息替代。(比如转动视角时的视口边缘部分)
那么就需要考虑一种在深度差异大的地方有关联性的分布射线的方法:

先准备好半分辨率的深度MinMax棋盘格图,即像国际象棋棋盘格一样,上图黄格子存2x2像素的深度最小值,灰格子存2x2像素的深度最大值。这样处理过后我们可以看到深度边缘会出现棋盘格样的亮度变化,并且每个Raymarch像素都会涉及2x2大小的MinMax棋盘格图。我们叫这样一个2x2大小的区域为一个Tile,分帧就是分4帧Raymarch出整个Tile,每个Tile会有2个Min深度,记录2x2个视口全分辨率像素的深度的最小值,2个Max深度,记录2x2个视口全分辨率像素深度的最大值。

比如上图这样,树木边缘呈现出棋盘格的样式。
当然仅仅做到这一步并不算完成,深度差异太大的地方历史帧信息是很难还原的。

每一帧在2x2的Tile里面的哪个格子发射射线就成了问题,如果我们就按照在一个2x2的Tile里左上右上右下左下的顺序依次发射射线,在3x3的Raymarch像素中很多时候会出现射线没有覆盖整个12x12像素的深度范围的情况,上图中第0、1、3号帧都有比较好的深度覆盖范围,但是第2号帧就只覆盖了近处的深度,那么云雾Raymarch的历史信息还原就会出先闪烁。因为远处可能是大朵云,近处是稀薄的雾。这样融合出来的像素就会在每4帧中跑到第二号帧就闪烁一次。

然后考虑下一种方法:我们把射线对应的2x2的Tile,按照Tile做一个棋盘格状的TileIndex偏移即:

然后我们就能得到PPT上的第二种偏移模式了。这时候发现对于刚才的深度情况,3x3的Raymarch像素已经能够比较好的覆盖所属区域的深度的最大最小值了,但是这就完事了吗?然而并没有:

像这种深度MinMax分布,刚才的偏移模式也会出现不能覆盖的情况。所以需要引入一种机制,在这种情况下也能覆盖深度的MinMax。这个时候我们就先假定周围8个像素都是第二种偏移模式,看看中间Tile中距离最近的深度的LinearEyeDepth和周围8个像素Raymarch位置的LinearEyeDepth比较是不是相差很大,如果8个像素都相差很大(用周围8个像素的LinearEyeDepth的25%作为阈值)那么说明需要将Raymarch的Tile内的Index设置到这个Tile中距离最近的像素上去。如果不是,那么就看这个Tile中距离最远的像素深度是不是和周围8个Tile的Raymarch位置上的LinearEyeDepth相差是不是都很大,如果是,就把Raymarch的Tile内的Index设置到这个Tile中距离最远的像素上去。否则就依然按照第二种偏移模式设置Raymarch在Tile内的Index。

上面这一段很绕,但是目的很明确,就是要让每个Tile都能用周围3x3的Tile在Raymarch的时候覆盖住这3x3个Tile所属的12x12全分辨率像素的最大和最小深度。从而得到一个有纵深不会抖动的云雾颜色的AABB盒子。
这样射线在每个Tile中应该怎么分布就介绍完了,所以在实际执行的时候用了一张RG16F的图的红通道来记录Raymarch的时候每个Ray所属Tile的Index。这张图的绿通道记录的是Ray所属位置的MinMax图上的LinearEyeDepth除以10000的结果,很意外吧,这里没有直接使用深度也没有使用LinearEyeDepth。如果DeviceZ == 0,也就是深度是远裁剪面,就在绿通道上记录2,然后我们就可以看到遮掩一张图:

放大一个区域:

红通道的值是3、2交替ÿ