AI 触达不到的交互质感:深入拆解 FluidNinja LIVE 2 的 Niagara 实时流体管线

在 AGI 浪潮席卷内容生产管线的当下,游戏特效的生成边界正被广泛讨论。然而,目前生成式 AI 在游戏资产与特效领域的探索,绝大多数仍局限于 2D 图像与预渲染序列帧;即使前沿模型具备了直接调度 Blender 等 DCC 工具进行自动化建模与节点搭建的能力,面对高要求的 3D 实时动态交互特效,在微调控制精度、空间物理一致性以及瞬态响应上依然显得捉襟见肘。

当画面从离线生成走向 60 帧的实时交互场景——无论是随角色骨骼动作撕扯翻卷的烈焰,还是在涉水踏步时层层推开的湍流与涟漪——真正决定画面可信度与控制上限的,依然深植于程序化图形学与物理模拟的根基之中。计算流体力学凭借不可压缩连续流场、涡旋拓扑以及对外界扰动的自然耦合,天然赋予了特效极其高级的动态质感与视觉张力。在生成式模型尚难跨越实时动态交互鸿沟的当下,程序化流体模拟依然是一条极具壁垒与深耕价值的硬核路径。

FluidNinja LIVE 2 彻底重构了 1.x 时代的蓝图与纯材质解算链,将整套 2D 纳维-斯托克斯(Navier-Stokes)流体求解管线完整搬进了 Unreal Engine 的 Niagara GPU Simulation Stage 体系。这不仅是一次架构升级,更为开发者提供了一个极佳的实时计算流体力学(CFD)工程范本。

本文基于 FluidNinja LIVE 2.0.0.56UE 5.8.1,以官方最具代表性的两个案例——全身着火的奔跑角色(Burning Man)与顺流而下的地表溪流(Water Creek)为线索,从基础物理方程的离散化推导出发,逐步切入 Niagara 的底层 Pass 组织、空间加速结构以及最终材质渲染管线。

角色着火示例的实际游戏画面

Fire_Smoke_Explosive 关卡中的 Stage 13「Burning Man 2」:角色骨骼运动动态注入速度与密度,火焰在局部流场驱动下撕扯翻卷。


1. 2D 网格流体求解:从物理方程到 GPU 离散计算

实时欧拉流体模拟的核心逻辑,是在固定的网格空间中,基于上一帧的状态与当前外界扰动,推演下一时刻整个流场的物理状态。整个单步求解更新的宏观数据流如下:

%%{init: {"theme":"base","themeVariables":{"background":"#141414","primaryColor":"#25201d","primaryTextColor":"#f1ece4","primaryBorderColor":"#ef8950","lineColor":"#79cbd5","secondaryColor":"#182b30","tertiaryColor":"#1d1d1d","clusterBkg":"#1b1b1b","clusterBorder":"#68584b","edgeLabelBackground":"#202020","fontFamily":"sans-serif"}}}%%
flowchart TB
    OLD["前一帧网格状态<br/>速度场 u、标量场 q"]
    FORCE["外力驱动<br/>风力、浮力、重力、碰撞交互"]
    SOURCE["源项注入<br/>骨骼轨迹、粒子、材质笔刷"]
    PREDICT["速度预测 u*<br/>平流、黏性扩散与外力合成"]
    Q["标量源项合成<br/>密度/温度累积与衰减"]
    subgraph PROJECTION["压力投影阶段 (Pressure Projection)"]
        DIV["计算速度散度<br/>评估各单元净流入/流出"]
        PRESSURE["求解泊松方程<br/>多尺度分离卷积 / Jacobi 迭代"]
        CORRECT["减去压力梯度<br/>强制速度场满足无散约束"]
        DIV --> PRESSURE --> CORRECT
    end
    ADVECT["标量平流 (Advection)<br/>沿修正速度场回溯采样"]
    FADE["消散衰减<br/>物理衰减与色相映射"]
    NEW["下一帧物理网格状态<br/>速度缓冲、密度缓冲"]
    DRAW["渲染管线<br/>Flow Map、体积采样、折射与光照"]
    OLD -->|"速度场"| PREDICT
    FORCE --> PREDICT
    OLD -->|"标量场"| Q
    SOURCE --> Q
    PREDICT --> DIV
    PREDICT -->|"待修正速度"| CORRECT
    CORRECT -->|"速度引导"| ADVECT
    Q -->|"待搬运标量"| ADVECT
    ADVECT --> FADE --> NEW
    CORRECT -->|"速度写回"| NEW
    NEW --> DRAW
    NEW -.->|"推进至下一时间步"| OLD
    classDef state fill:#182b30,stroke:#79cbd5,color:#f1ece4;
    classDef solver fill:#30231d,stroke:#ef8950,color:#f1ece4;
    class OLD,NEW state;
    class PREDICT,DIV,PRESSURE,CORRECT,ADVECT solver;

1.1 状态场的拆解:速度向量场与被动标量场

在 2D 网格离散化中,流体状态被严格剥离为两组核心数据:

  • 速度场(u\mathbf{u}:每个网格单元记录流体在该点的宏观运动速度向量 (u,v)(u, v),决定物质在下一刻的迁移方向与位移量。
  • 标量场(qq:随波逐流的物理量,如火焰烟气浓度(Density)、温度(Temperature)或水面染料。标量自身没有动量,其空间分布完全由速度场搬运。

密度、速度与角色火焰的关系

标量场(密度)提供视觉形态,向量场(速度)控制流动演化。两者的耦合驱动了最终的非刚体运动。

若速度为零,标量图样将静止于网格中并随时间自然消散;一旦速度场存在非均匀梯度,标量边缘便会被拉伸、扭曲并形成涡旋。

1.2 欧拉视角与离散网格

流体力学有两种经典观察坐标系:

  • 拉格朗日视角(Lagrangian):追踪具体流体微团的运动轨迹(如经典粒子系统)。
  • 欧拉视角(Eulerian):固定空间观察网格,记录流经每个单元格的速度、压力与标量浓度变化。

固定网格采样与跟随流体观察的区别

上排:欧拉网格,采样点坐标固定,数值随时间变化;下排:拉格朗日粒子,跟随流体轨迹移动。

FluidNinja LIVE 2 属于典型的欧拉网格法。连续空间被划分为离散网格(如 512×512),相邻单元通过有限差分(Finite Difference)估算空间导数,时间维度以固定时间步 Δt\Delta t 步进。

1.3 质量守恒与不可压缩性

很多初学者容易将视觉上的“烟气变浓”误认为“流体被压缩”。在流体力学中,不可压缩性(Incompressibility)是针对流体体积微元而言的。质量守恒连续性方程为:

ρt+(ρu)=0\frac{\partial\rho}{\partial t}+\nabla\cdot(\rho\mathbf u)=0

当流体质量密度 ρ=ρ0\rho = \rho_0 恒定时,方程直接简化为无散度约束(Divergence-Free Condition):

u=0\nabla\cdot\mathbf u = 0

散度算子 u\nabla \cdot \mathbf{u} 衡量一个微元区域内流体的净流出或净流入。无散度意味着流入任一微元的流量必须等于流出量,不可压缩的流体微元在受压时不会凭空缩瘪,而是必须向侧方泄流。

不可压缩流量平衡与烟色变化

左侧:无散度约束强制单元进出流量守恒;右侧:被动标量(烟色/染料)的注入与搬运不影响流体微元本身的体积。

至于可见的烟气标量 qq,其输运方程独立于速度连续性方程:

qt+uq=sqλq\frac{\partial q}{\partial t} + \mathbf{u}\cdot\nabla q = s_q - \lambda q

其中左侧第二项为对流输运,右侧 sqs_q 为外部发射源(如角色骨骼处注入的火焰粒子),λ\lambda 为自然消散常数。

1.4 动量守恒与动量方程各项剖析

流体的速度场演化受纳维-斯托克斯(Navier-Stokes)动量方程主导:

ut+(u)u=1ρ0p+ν2u+f\frac{\partial\mathbf u}{\partial t} + (\mathbf u\cdot\nabla)\mathbf u = -\frac{1}{\rho_0}\nabla p + \nu\nabla^2\mathbf u + \mathbf f

动量方程中的自平流、压力、黏性和外部作用

动量方程中四项对速度场更新的物理贡献示意。

该方程各项的工程实现意义如下:

数学表达 物理本质 实时工程处理方式
物质导数/自平流 (u)u(\mathbf u\cdot\nabla)\mathbf u 流体流动时顺带将动量本身向下游搬运 采用半拉格朗日法(Semi-Lagrangian)对速度场执行回溯采样
压力梯度 1ρ0p-\frac{1}{\rho_0}\nabla p 高压向低压加速,消除局部体积挤压 求解泊松方程得到标量压力场 pp,从中提取梯度修正速度
黏性扩散 ν2u\nu\nabla^2\mathbf u 内部摩擦力使邻域速度趋向均匀 实时管线常借由数值耗散与直接阻尼替代显式扩散求解
外部源项 f\mathbf f 外界加速度(浮力、风力、碰撞冲量) 在 Composite 阶段直接将外力速度向量累加至暂态速度场

1.5 平流(Advection):半拉格朗日回溯采样

平流描述流场中的物理量(标量或速度向量)如何随流动迁移。传统前向积分(Forward Advection)容易导致网格出现空洞或多重覆盖。Jos Stam 在 1999 年的 Stable Fluids 中奠定了半拉格朗日回溯采样法(Semi-Lagrangian Backtracking)在图形学中的核心地位。

其逻辑是“反向寻根”:既然要在当前网格点 x\mathbf{x} 计算下一时刻的新值 qnew(x)q_{new}(\mathbf{x}),只需追溯是上一时刻哪里的流体微团运动到了 x\mathbf{x}

xprev=xu(x)Δt\mathbf{x}_{prev} = \mathbf{x} - \mathbf{u}(\mathbf{x}) \cdot \Delta t

qnew(x)=SampleBilinear(qold,xprev)q_{new}(\mathbf{x}) = \text{SampleBilinear}(q_{old}, \mathbf{x}_{prev})

回溯采样的具体数字演算

以 1D 为例:若格点 4 的速度向右偏移 1.25 格,回溯位置为 2.75。通过对 2 与 3 处的密度(0.2 与 0.8)进行双线性插值,得到格点 4 的新值 0.65。

这种反向插值采样天然具备绝对无条件稳定性(Unconditionally Stable),即使时间步较大也不会发生数值爆炸。其代价在于每次双线性插值都会带来微小的低通平滑,即数值耗散(Numerical Dissipation)。在实时渲染中,这种耗散往往恰好充当了廉价的天然黏性。

1.6 压力投影:通过泊松方程重构无散场

经过平流和外力注入后,预测得到的中间速度场 u\mathbf{u}^* 通常不满足 u=0\nabla \cdot \mathbf{u}^* = 0。如果直接使用它,流体会在汇聚处凭空堆积,在分离处凭空真空。

压力投影(Pressure Projection)的数学目标,是寻找一个标量压力场 pp,其梯度恰好能抵消 u\mathbf{u}^* 中的非零散度部分:

unew=uΔtρ0p\mathbf{u}_{new} = \mathbf{u}^* - \frac{\Delta t}{\rho_0} \nabla p

对两端同时取散度,由于要求 unew=0\nabla \cdot \mathbf{u}_{new} = 0,立即导出压力泊松方程(Poisson Equation):

2p=ρ0Δtu\nabla^2 p = \frac{\rho_0}{\Delta t} \nabla \cdot \mathbf{u}^*

令归一化压力 π=Δtρ0p\pi = \frac{\Delta t}{\rho_0} p,差分展开后,中心网格散度 dCd_C 与四周邻居压力的五点差分格式为:

πL+πR+πB+πT4πCh2=dC\frac{\pi_L + \pi_R + \pi_B + \pi_T - 4\pi_C}{h^2} = d_C

通过多轮 Jacobi 迭代或 Gauss-Seidel 迭代更新,局部被压缩的流体便获得了沿压力梯度向周围泄流的速度修正。

单格压力修正前后的四边流量

对冲流动的修正:左右两侧同时以速度 1 涌入中心(散度 -2)。解出中心压力后,经梯度修正,左右流入降为 0.5,上下各获得 0.5 的流出速度,局部体积重新达到动态平衡。

1.7 涡度与旋度:保持旋转特性的物理边界

流体最具观赏性的结构是漩涡。无散度约束(u=0\nabla \cdot \mathbf{u} = 0)只规定了体积守恒,绝不意味着流体无法旋转。衡量流体微团自身旋转速率的物理量是旋度(Curl),在 2D 中表现为标量涡度(Vorticity):

ω=×u=vxuy\omega = \nabla \times \mathbf{u} = \frac{\partial v}{\partial x} - \frac{\partial u}{\partial y}

散度、压力梯度与旋度的区别

散度约束净流出,梯度指示数值爬升方向,旋度则衡量局部流场旋转态势。

由于压力梯度的旋度恒为零(×p0\nabla \times \nabla p \equiv 0),纯净的压力投影本身不会凭空创造新的旋转,它只负责配平流量。漩涡的产生源于流体剪切、外力注入以及速度边界条件的非均匀响应。


2. FluidNinja LIVE 2 的 Niagara 核心架构

脱离具体引擎架构谈算法没有落地点。FluidNinja LIVE 2 放弃了前代的材质蓝图闭环,将整个解算流重构为基于 Niagara GPU Simulation Stage 的多阶段渲染管线。

其核心对象分层结构非常明确:

1
2
3
NinjaLive (关卡 Actor)
└── NinjaLiveComponent (继承自 UNiagaraComponent,处理世界交互、参数收集与输入管线)
└── NinjaLiveCore (底座 Niagara System,包含核心解算发射器 PainterAndSim)

PainterAndSim 发射器内部,所有 Simulation Stage 均绑定为对 2D Grid 的并行 GPU Dispatch。对于一张 512×512 的解算网格,GPU 会为这 262,144 个单元启动规整的 Compute/Pixel Pass。

%%{init: {"theme":"base","themeVariables":{"background":"#141414","primaryColor":"#25201d","primaryTextColor":"#f1ece4","primaryBorderColor":"#ef8950","lineColor":"#79cbd5","secondaryColor":"#182b30","tertiaryColor":"#1d1d1d","clusterBkg":"#1b1b1b","clusterBorder":"#68584b","edgeLabelBackground":"#202020","fontFamily":"sans-serif"}}}%%
flowchart TB
    IN["空间点散列筛选<br/>Double Hash Pass-1 / Pass-2"]
    FIELD["静态场几何采集<br/>SDF / Landscape / RVT / Spline<br/>写入 FieldCaptureGrid"]
    PAINT["交互输入绘制<br/>TrackPoint Painter 写入 PainterGrid"]
    C["合成与梯度计算<br/>Composite and Gradient 写入 CompositeGrid"]
    A["半拉格朗日平流<br/>Advection 写入 AdvectionGrid"]
    D["散度重算<br/>Divergence 写入 PressureGrid.G"]
    P["多尺度分离压力解算<br/>Pressure 写入 PressureGrid.R"]
    OUT["导出至 RenderTarget<br/>Velocity / Density / Pressure / WetMask"]
    IN --> FIELD --> PAINT --> C --> A --> D --> P --> OUT
    classDef sim fill:#30231d,stroke:#ef8950,color:#f1ece4;
    class C,A,D,P sim;

2.1 骨骼交互与局部坐标变换:世界输入如何落网

Stage 12: Burning Man 1 为例,角色身上挂载的 NinjaLiveComponent 启用了 InteractionWithOwnerActor,并绑定了头、颈、脊柱、四肢共 13 处关键骨骼插槽。

  1. 坐标归一化:Niagara 从骨骼抓取世界坐标后,由变换矩阵映射到当前 2D 模拟平面的局部 UV 空间(例如 10m×10m 的范围,世界坐标 (2, -1) 转换为 UV (0.7, 0.4))。
  2. 连续笔划构造:若单纯在骨骼当前帧坐标处绘制圆点,角色高速运动时就会产生断续的噪斑。Painter 会利用骨骼上一帧与当前帧的世界位移向量,在两点之间进行插值拉伸(Stroke),形成连续的运动带。
  3. 输入衰减与时间步管理:骨骼每帧不仅写入标量密度,还基于骨骼运动速度写入有向动量。

世界坐标映射到模拟UV

骨骼的世界空间运动经由局部投影矩阵,快速折算为网格上的 UV 写入坐标与切向速度。

2.2 核心 Stage 调度流水线

PainterAndSim 内部,解算阶段的执行次序极其严格:

  1. Hash 分桶(Double Hash Pass-1 / 2):将海量动态交互源(物体中心、粒子、Chaos 碎片)快速粗筛并分派到局部网格单元。
  2. FieldCapture 采样:对网格区域内的静态几何体(SDF)与地表高度(HeightField)进行正交采样。
  3. Painter 阶段:将当前帧生效的动态点输入栅格化到 PainterGrid
  4. Composite 阶段(Ninja_CompositeAndGradient:将上一帧残留的流速、压力梯度、地形坡度矢量以及当前帧的新笔刷全面合流,生成本帧的待平流状态。
  5. Advection 阶段(Ninja_Advection:执行回溯采样,将速度场与密度场向前推进。
  6. Divergence 阶段(Ninja_Divergence:对推进后的速度场计算离散散度,写回缓冲区的 Green 通道,同时完整保留 Red 通道的历史压力。
  7. Pressure 阶段(Ninja_Pressure:利用多尺度分离滤波核执行压力迭代。
  8. Export 阶段(CopyGridToRenderTarget:将解算结果写入输出 RT 供渲染材质消费。

在官方教学关卡 Tutorial01_Basics 的 Stage 10 中,作者专门将管线的三组核心缓冲展平排列在视口中,直观呈现了这一演化过程:

Tutorial01_Basics Stage 10:Painter、流体模拟与显示的俯视对照

第一列 Painter:纯几何输入笔刷;第二列 Fluidsim:经对流与压力调整后的物理标量分布;第三列 Visualisation:接驳最终渲染材质后的烟团形态。

2.3 复合输入与有向速度的色彩编码

在进入 Advection 之前,所有外界作用必须在 Composite 阶段完成向量合成。

需要特别指出的是,输入纹理通常使用标准的 RGBA 无符号色彩空间(0 到 1),而速度是具有正负方向的连续向量。LIVE 2 普遍使用经典的有符号映射解码:

u=2c1\mathbf{u} = 2\mathbf{c} - 1

零到一颜色编码与带符号速度

在标准流速编码格式中,色值 (0.5, 0.5) 代表绝对零速;纯黑 (0, 0) 实际代表 (-1, -1) 的强负向速度。

这意味着在制作自定义输入笔刷或调试流速贴图时,中间灰才是“无扰动”,任何偏向纯黑或纯白的色值都会被系统解读为剧烈的定向推力。

2.4 LIVE 2 的分离式多尺度压力求解

传统流体求解器通常使用纯 Jacobi 迭代解泊松方程,为了消除长波长误差往往需要数十甚至上百次迭代,这在 60fps 的实时游戏管线中是不可承受之重。

LIVE 2 采用了一种工程上极其巧妙的折中方案:分离式多尺度滤波与时间维度的阻尼反馈

打开 Ninja_Pressure 模块可以发现,系统预置了 GaussKernelsCustomKernels 两套从 15、13 直至 5 宽度的 1D 卷积核表:

1
Grid2D.SamplePreviousGridVector2DValue<Attribute=Pressure>(sampleUV, value);

压力属性的读取、加权与方向切换

单次压力更新的加权过程:分离开来的横向/纵向 1D 核先采样邻域,使原本 O(K2)O(K^2) 的 2D 卷积复杂度降至 O(2K)O(2K)

这一机制包含三大核心设计:

  1. 可分离卷积(Separable Convolution):通过管线中的 StepIndex 切换横向与纵向采样,仅用两次 1D 卷积即可覆盖大范围 2D 邻域影响半径,极大节约了访存带宽。
  2. 多核异构加权Gaussian 核负责光滑扩散已有压力,而 Custom 核(中心权重更高)负责对新散度项进行即时脉冲响应。
  3. 跨帧压力延续(PressureFeedback:在真实物理中,压力每帧由散度即时求解决定;而在 LIVE 2 中,上一帧的残余压力作为有阻尼的状态场参与下一帧循环,从而在极少数(通常只需 2~4 次)迭代下就能呈现出极具动量延续感的水波推挤效果。

2.5 渲染对接:模拟精度与视觉质感的分层解耦

解算完成的物理网格通过 CopyGridToRenderTarget 阶段打包为三张标准的二维 RenderTarget:

  • Velocity / Density RT:RG 通道存储归一化速度向量,B 通道存储烟气或水流密度,A 通道则常作为湿痕遮罩(WetMask)。
  • Pressure / Divergence RT:R 通道为平滑后的压力分布,G 通道为当前散度(常用于提取泡沫或激波浪花)。
  • Painter RT:纯几何交互输入备份。

一个极为核心的图形学思考在于:我们看到的精细烟雾与火苗细节,究竟有多少来自物理求解?

Tutorial01_Basics Stage 11 中,官方给出了一组极具启发性的实验对照:

Tutorial01_Basics Stage 11:速度、密度、噪声与细节流动材质

左侧:平滑的低频模拟密度与速度图;右侧:材质层注入高频噪声后利用流速 UV 畸变产生的 Flow Detail。

现代实时流体的黄金法则是**“物理管线算大尺度拓扑,着色管线补小尺度高频”**:

  • 物理模拟网格(Simulation Grid):主要决定大尺度的涡流形态、碰撞阻挡和宏观运动趋势。
  • 表面输出材质(Material Shader):读取物理速度场作为 Flow Map,对高分辨率静态噪声贴图进行随时间推进的扭曲采样,利用极低的开销瞬间铺满丝缕分明的高频烟火细节。

3. 大场景与地表流体的进阶机制

当视角从角色的孤立火焰拓展到广袤地表的自然溪流时,模拟系统立即遭遇两大全新工程挑战:高密度交互的几何检索开销,以及开放地形对流体的拓扑塑造

3.1 空间加速结构:两级 Double Hash 网格

在溪流场景中,可能同时存在涉水角色、滚落的碎石(Chaos 物理刚体)、溅落的粒子(Niagara Data Channel)。若让 2048×2048 的网格逐格遍历成百上千个潜在交互点,计算复杂度将飙升至千万次量级。

LIVE 2 引入了两级哈希空间索引机制(HashGrid1HashGrid2):

%%{init: {"theme":"base","themeVariables":{"background":"#141414","primaryColor":"#25201d","primaryTextColor":"#f1ece4","primaryBorderColor":"#ef8950","lineColor":"#79cbd5","secondaryColor":"#182b30","tertiaryColor":"#1d1d1d","clusterBkg":"#1b1b1b","clusterBorder":"#68584b","edgeLabelBackground":"#202020","fontFamily":"sans-serif"}}}%%
flowchart TB
    SRC["动态输入源<br/>骨骼 / 刚体 / 粒子 / 碎片"]
    H1["HashGrid 1<br/>粗筛:判断点或运动跨步线段是否落在模拟体边界内"]
    NG1[("NeighborGrid 1<br/>有效交互源粗列表")]
    H2["HashGrid 2<br/>细分:将交互对象投递至局部空间桶 (Spatial Bucket)"]
    NG2[("NeighborGrid 2<br/>局部单元邻居索引")]
    PAINTER["TrackPoint Painter<br/>每个像素仅检视所属空间桶内的候选对象"]
    PG[("PainterGrid<br/>栅格化速度与密度")]
    SRC --> H1 --> NG1 --> H2 --> NG2 --> PAINTER --> PG
    classDef grid fill:#182b30,stroke:#79cbd5,color:#f1ece4;
    class NG1,NG2,PG grid;

两级 HashGrid 的筛选、分桶和局部绘制

两级空间分桶将全域暴力比对转化为基于局部格子的近邻查询,显著降低了密集交互下的绘制消耗。

特别值得称道的是 HashGrid1 对高速运动物体的穿越检测机制(Stroke Interception):当角色的脚掌在相邻帧之间高速踩踏时,脚掌上一帧可能悬在水面上方,下一帧已深踩至水体下方。若仅做点采样,该交互将被完全漏报。系统通过将位移构造成带半径的 3D 线段(Stroke),直接求交厚度有限的水体碰撞带,完美解决了高速交互穿透水面的物理 Bug。

3.2 动静态场的分级降采样策略

在复杂的自然环境下,并非所有场都必须以最高分辨率运算。LIVE 2 允许为主模拟、输入绘制和环境几何场分配各自独立的分辨率网格:

主模拟、Painter 与 Field 的分辨率分工

溪流实例中的分辨率分配方案:主模拟保持 2048×2048 高精度以保障水纹扩散,输入笔刷与大尺度场数据分别降采样至 1024 与 512。

这种按需降采样使得算力能够精准倾斜至最敏感的物理扩散上,大幅压缩了静态几何采集对显存带宽的占用。

3.3 地形坡度与高度场的双向映射

底层的 2D 流体网格在平直的 UV 空间中迭代,但在宏观上水流却能自然绕过石头、顺着山坡滑落,这依赖于地表高度场的双向桥接:

  1. 从几何高度到流体驱动:通过 Landscape DIRVT DI 读取地形高程 h(x,y)h(x, y)。利用中心差分计算其空间梯度 h=(hx,hy)\nabla h = (\frac{\partial h}{\partial x}, \frac{\partial h}{\partial y})。该梯度的反方向 h-\nabla h 正是地表坡降最陡的下坡路径,将其直接作为外力项加进 Composite 阶段的速度场中,水流便获得了顺坡而下的自然重力分量。
  2. 从平流网格贴回 3D 地表:在最终材质着色阶段,顶点着色器(VS)读取同一张高度图对水体网格进行沿世界 Z 轴的顶点位移抬升,将二维平面演化的动态水纹精准锚定在起伏的地表岩石上。

高度场怎样同时参与模拟与表面显示

地形高度梯度转换为流体倾泻动力;高度采样值则用于最终渲染阶段的水面几何重建。

3.4 动态漫游模拟区与整格量化(Quantizer)

为了以有限的模拟分辨率覆盖广阔的无缝大世界,模拟容器通常必须挂载在主角身上跟随移动。但容器一旦在世界空间平移,网格内的流体状态就面临严重的重采样模糊。

当模拟盒向右平移了 Δx\Delta \mathbf{x} 时,为了让世界中的既有水波保持在原位不动,网格内部的图样必须向反方向偏移等量距离:

uvold=uvnew+xnewxoldLuv_{old} = uv_{new} + \frac{\mathbf{x}_{new} - \mathbf{x}_{old}}{\mathbf{L}}

区域移动时,世界位置与纹理坐标的对应

容器随角色移动时,内部纹理坐标根据世界位移逆向补偿,从而将既有波纹锁死在真实世界坐标点。

然而,如果角色每帧移动微小的小数距离,贴图就会进行连续的双线性重采样,只需几秒钟,原先锐利的水波与涟漪就会被彻底抹平为模糊一片。

官方在 Tutorial01_Basics Stage 7 中给出了极其清晰的实验对照:

Tutorial01_Basics Stage 7:固定容器、连续世界偏移和量化偏移的俯视对比

对比结果:关闭量化的 G 组(中)在角色移动时波纹迅速耗散退化;开启整格量化(Quantizer)的 H 组(右)则长期保留了极其清晰的物理轨迹。

整格量化(Quantizer)的本质:强制移动容器的世界位移步长必须严格与单个模拟纹素在世界中的物理尺寸整倍对齐(例如 10m / 256格 \approx 3.9cm)。容器只在累积位移超过 3.9cm 时跳进一格,此时纹理坐标重映射完全是无损的整数像素点偏移,彻底切断了亚像素插值带来的数值扩散。

3.5 视觉记忆层:WetMask 与 Simple Painter 的独立演化

当角色踏过浅滩跑上岸边,脚印不能随着水花离开而瞬间蒸发,地面需要保持一段时间的水渍湿润。

LIVE 2 在架构上明确将渲染表现划分为三种不同的时序响应机制:

  1. Fluid Sim 动态层:主导毫秒级的高频水花、流速扰动与波纹传播,响应迅速,随速度场扩散。
  2. WetMask 记忆层:由 Composite 阶段对经过此处的流体密度进行积分暂存,并在水体退去后通过独立的极低衰减率维持数秒,供岸边岩石材质采样生成粗糙度降低、色泽加深的湿润外观。
  3. Simple Painter 静态印记层:对于泥地车辙、重度脚印等纯静态痕迹,完全绕过耗费算力的流体演化阶段,直接以全分辨率绘制到印记 RT 并仅做随时间自然淡出,既保证了锐利的笔画轮廓,又兼顾了极高的吞吐性能。

4. 实时流体工程落地的性能思考

纵观 FluidNinja LIVE 2 的整个设计,其处处闪烁着实时游戏工业界在“物理真实度”与“运行帧率”之间走钢丝的工程智慧:

  • 维度降维打击:坚决避开极其昂贵的三维网格(3D Grid),将所有复杂的空间立体效果(如火焰体积、地表贴合、水花四溅)完全拆解为 2D 正交流场演算 + 2.5D 高度映射 + 材质片元视差/体积着色。
  • 算力分级管理:碰撞分级使用空间两级 Hash;物理场按需采用 2x / 4x 降采样;动态波纹靠流体求解,高频肌理直接让位于材质噪波 Flow Map。
  • 避免教条主义:在压力求解中,不盲目追求在单帧内将残差迭代至极致收敛,而是引入可分离 1D 核结合时间维度的压力反馈,用极少的迭代轮次换取了视觉上高度可信且稳定无抖动的交互扰动。

正是这种从物理规律中提取视觉灵魂、再用纯粹工程手段重构执行路径的方法,才让复杂动人的流体特效真正能够稳定跑在 60 帧的现代高品质游戏之中。


参考文献与源码索引