引言

在游戏开发中,水体交互效果是提升沉浸感与交互性的关键设计点之一。一些常见的交互水体制作思路包括:

  1. SPH(光滑粒子流体动力学)水体
    在粒子的基础上实现物理模拟,效果比较真实,但涉及到大量粒子之间相互作用的计算,内存消耗也高,难以在移动设备上实现。

  2. LBM(格子玻尔兹曼方法)水体
    LBM是水体模拟的一种数值解法,在形式上适合并行计算(每次迭代仅考虑格点四周的情况)。虽然并行计算比传统水体模拟的串行计算效率高得多,但对移动端来说依然开销昂贵(每个时间步骤需要完成碰撞/迁移计算等)、难以实现。

  3. SWE(浅水方程)水体
    SWE同样也是水体模拟的一种数值解法,该方法相对 LBM 来说开销更小,仅需求解水深和水平速度分量,被 UE5 的水体系统所集成,在移动端上也能用(但存在分辨率要求)。可以参考:https://zhuanlan.zhihu.com/p/649003961

上面的三种方式虽然能够得到真实(相对真实)的物理水体交互效果,但有可能造成用户设备发烫/帧率下降的问题。对于移动端游戏来说,水体模拟必须找到一种低开销、能够适配绝大多数机型的实现方式,因此我想介绍一种俗称“动画水”的基于粒子系统+Render Texture的可交互水实现思路。

粒子+RT

粒子+RT可以实现一种伪物理动画水,其基本原理是:

  1. 利用绑定在角色模型上的粒子系统制作交互效果(涟漪)
  2. 将交互效果存放到一张RT上
  3. 将该RT作为纹理输入到水体的 shader 的法线中
  4. 得到交互效果。

涟漪shader

以下演示基于Unity 2021.3.21 URP

涟漪shader的目标是得到单个水波涟漪的法线贴图,这个贴图会被用在粒子系统上,最后加到水体shader的法线输出上。可以通过下面的思路制作三个涟漪都法线:

image.png
  1. 找一张径向扩散的贴图,中间是1边缘是0
image.png
  1. 乘以间隔系数 X(例如下图为37.3),主要作用在于将[0, 1]映射到[0, X]
  1. 钳制(Clamp),用以限制映射的范围,可以影响涟漪层数
  1. 通过Sine处理钳制后的结果,经由NormalFromHeight由高度图生成法线,输出到材质的BaseColor上,用在后续的涟漪粒子系统上

涟漪粒子系统

该粒子系统应当被绑定在角色模型身上,在角色移动时留下逐渐扩大的涟漪痕迹。下图是通过Unity的粒子系统实现的涟漪粒子demo。

粒子的render选项中,render mode记得设置成horizontal billboard

此时涟漪的消失很“硬”,一般来说希望交互产生的涟漪在扩散的过程中逐渐变淡、消失。在ShaderGraph中,VertexColor节点可以影响粒子系统所使用的材质的颜色,因此我们可以通过将VertexColor的aplha通道与先前Sine节点所处理的结果相乘并输出到材质的alpha通道上,以实现对涟漪的透明度控制。

在粒子系统中,通过Color over Lifetime控制透明度的变化,就可以实现涟漪渐隐的效果了。

涟漪相机RT

我们最终的目标是将这个涟漪法线粒子加到水体材质的法线上,因此需要一个媒介对粒子进行记录和传递,这个媒介就是RenderTexture(RT)。

新建一个用来记录涟漪粒子的正交相机,将RT赋给该相机,并将背景类型设置成纯黑。该相机仅渲染粒子层(比如Water),并在主相机中取消对该层的渲染,这样正常的游戏画面就看不到涟漪粒子,但粒子可以被记录到涟漪相机的RT上了:

接下来让涟漪相机完全罩住水体,这样RT与水体表面的位置关系就可以很好的对应到:

水体shader效果叠加

我做了一个非常简单(丑)的水面shader,把RT中的内容与水体shader原本的法线相加,就可以得到涟漪+水面的法线。反映到游戏中,当拖动游戏对象移动时,涟漪粒子的效果就会作用到水面上了。

期间可能涉及到一些调试,可以看我之前做的一期视频(建议静音播放),视频里的演示效果更好一些: