如何在 Minecraft 中加入元素视野效果(三)

八、扫描
本章为较难部分,如果不想了解相关原理,可以直接跳到最后查看代码,这取决于你自己。
我们知道,在原神的元素视野开启时,除了画面色彩会发生变化,还有一条亮色带从玩家的位置出现,产生一种正在探测周围的效果。
为此我们可以简单实现一个由近到远的光亮带,但比较遗憾的是,Minecraft 并没有将光影加载到现在的时间发送至着色器,我们无法实现一个从开启光影时立即从玩家脚下开始的光带。
幸运的是,Minecraft 会将当前游戏时间传递到着色器中,保存在一个叫 worldTime 的整数类型中。在 Minecraft 中,一天有 20 分钟,也就是 24000 个 tick,所以 worldTime 的取值区间为 [0, 23999]。
我们可以稍微利用一下,对元素视野的持续时间估计,大概在 8 秒左右,所以将 worldTime 对 8 秒,也就是 160 tick 取模,得到一个范围在 [0, 160) 的周期变化的量。我们在 final.fsh 中添加 worldTime,并加入函数 depth_offset:

很明显,这样做只能在屏幕上产生一条光带,而并非想象中以一种“翻山越岭”的方式从世界的地形表面扫过,无论我们怎么样弯曲以使其以弧形立体的方式出现,它依然拥有很大的违和感,这并不是我们想要的,为此我们需要引入一个叫深度(depth)的东西。
九、深度缓冲区
在世界中,存在很多的事物,它们通常会一前一后出现在画面上,如果我们没有一个数据可以判断它们谁在前面,很多光影的计算将变的非常复杂。为此,游戏引擎提供了深度测试功能,它能够提供一个深度缓冲区,其中存储了每个颜色与玩家摄像机的距离。
有意思的是,在 Minecraft Wiki 中记录到,游戏在 20w22a 版本修改了渲染相关,并添加了光影对深度缓冲区的支持。不过无所谓,如果你是按第一章选择的游戏版本,应该不会出现无法使用的情况。
但即使我们拥有了深度缓冲区,我们依然需要一点小小的改动才能利用它,因为对于里面的深度值,在设计上并非是线性的。因为对玩家来说,他们大部分时间都在关心发生在他们身边的事情,而远处往往是不必要的。所以为了让近处拥有较高的精度,当近处的距离发生变化时,深度值会变化的非常大,而到远处则会拥有比较小的变化,其大致为这样的图像:

这不是我们希望的,如果使用这样的深度值,扫描的光带将从近处缓慢移动,并在远处进行前进四瞬移。所以我们增加函数 linearize_depth 用于将它转变为线性变化的值。由于这样的非线性深度值还与玩家的摄像机参数有关,为了还原深度值,我们还需要增加玩家摄像机的近平面位置(near)和远平面位置 (far)进行反运算:
我们依然使用 texture2D 函数检索当前颜色的深度值,并使用 linearize_depth 函数线性化:
像先前一样使用时间进行周期性平移,以达到深度值零点向前移动的效果,并上下拉伸深度值,使光带宽度变小:
最后将下移至三四象限的深度值沿 X 轴翻转至一二象限,并整体翻转一次,最终将需要的部分移动至 [0, 1] 区间内。
此时我们将得到一个这样的函数图像,它将随着时间周期性移动。

我们只需要将深度值进行一个简单的判断,再加到当前颜色上,就能实现一个从近到远的扫描效果了。
现在的扫描范围是 [0, 1],此时光带会从脚下延申至视野最远处,但我们还要考虑到,在世界的最远处是天空,我们并不希望这个光带最后闪烁一下天空,所以最后我们还需要将周期区间减少至 [0, 0.99),所以我们修改 depth_offset 函数:
到此,你就得到了一个元素视野效果的光影,恭喜。

以下是 final.fsh 的完整代码:
以及 final.vsh 的完整代码: