面向图形学与实时流体模拟。本文从烟雾搬运出发,解释回溯采样、插值、GPU 实现和误差来源,帮助你读懂代码并整理自己的笔记。
核心结论: 数据保存在固定网格上;更新某个格点时,沿流体轨迹回到上一时刻,插值得到来源处的旧值,再写入这个格点。

图 1 三种观察方式。半拉格朗日结合固定网格与轨迹追踪。
1 它解决的是平流
风吹着烟雾走,水流带着染料走,这种随速度场搬运物理量的过程叫平流(Advection,也常称对流)。被搬运的量可以是烟雾浓度、温度,也可以是速度本身。
本文用不可压缩流中的烟雾浓度作例子,先假设只发生搬运,没有新的烟雾注入、燃烧或扩散。于是,同一团空气沿途携带的浓度保持不变;变化的是它所在的位置。12
2 名称之间的关系
欧拉描述关注固定空间位置;拉格朗日描述跟随流体微团。半拉格朗日属于欧拉与拉格朗日思想结合的方法,但"欧拉--拉格朗日方法"范围更广,二者不能在所有语境中当作同义词。
注意:"欧拉--拉格朗日方程"通常指变分法中的方程,与这里的数值平流方法是不同概念。
记忆句: 固定网格存数据,沿轨迹回找来源,插值旧值写新格。
从目标位置回找来源
下一帧的位置 x 是已知的。我们想求这里的新浓度,于是问:"到达这里的流体微团,在上一帧在哪里?"图中的虚线箭头表示计算时的回溯方向。

图 2 恒定速度的一步示例。真实运动向右上,回溯向左下;坐标单位为格。
3 从平流方程到采样公式

q 是被搬运的标量,u 是速度场,∇q 表示 q 的空间变化。沿满足 dx/dt = u 的流体轨迹,上式可写为 dq/dt = 0,也就是沿轨迹的值不变。

n 表示旧时刻,n+1 表示新时刻,Δt 为模拟步长,xd 为来源点。第一式是最简单的一阶回溯近似;速度随位置变化时,真实轨迹可能弯曲。第二式从旧网格重建来源处的值。12
为什么回溯: 每个目标格点都主动寻找自己的值,天然适合 GPU 并行写入。若直接把旧格点向前推再写网格,需要额外处理空洞、重叠和权重分配。
来源落在格点之间怎么办
网格只存储离散位置的数据,而回溯得到的来源通常不是整数格点。插值的作用是用周围已有值估计来源位置的值。

图 3 先看一维,再推广到二维。橙色点是需要采样的来源。
4 一维线性插值

α 是来源在左右格点之间的相对位置,范围为 0 到 1。来源越靠近右边,右边的权重越大。图中 α = 0.6,所以左、右权重分别是 0.4 和 0.6,结果为 0.56。
5 二维双线性插值

可以先沿水平方向分别插值两条边,再沿竖直方向插值。图中 α = 0.6,β = 0.4,最终得到 0.6 × 0.56 + 0.4 × 0.76 = 0.64。四个权重非负且总和为 1。
图中的角点代表旧场相邻的存储样本,不一定是物理单元的顶点。三维使用八个样本做三线性插值。MAC 交错网格把速度分量存在不同的面中心;采样每个分量时,需要使用各自正确的位置。2
在实时流体和 GPU 中的位置
半拉格朗日是模拟中的平流步骤。不可压缩流体还需要外力和压力求解等环节;渲染则读取最终的密度、温度或表面信息来形成图像。

图 4 一个简化流程。压力投影用于减小速度场散度,平流并不能代替它。
6 每个线程更新一个目标格点
// p: cell index; positions and velocity use world units
float2 x = CellCenterWorld(p);
float2 u = SampleVelocity(oldVelocity, x);
float2 xd = x - dt * u;
xd = ApplyBoundaryPolicy(x, xd);
newDensityp = SampleLinear(oldDensity, xd);
// Swap oldDensity and newDensity after this pass.
这是说明思路的伪代码。CellCenterWorld、采样与边界策略需要由实际求解器实现。更新速度时,把最后一行的被采样场换成旧速度场,并处理好分量位置。
7 实现时最容易出错的四件事
坐标单位一致。如果位置使用归一化 UV,而速度使用米/秒,应先按模拟域各轴的实际长度转换为 UV/秒,再乘 Δt;不能直接把世界空间速度从 UV 中相减。
旧数据和新数据分开。使用两份场进行 ping-pong。若在同一纹理里边读边写,就可能读到其他线程已经更新的数据,使结果依赖执行顺序。
边界属于物理模型。周期边界、固体墙和开放边界需要不同处理。简单 Clamp 纹理坐标不等于正确处理障碍物,长轨迹还可能穿过薄墙。
模拟步长与显示帧分开考虑。一帧可以执行多个模拟子步。平流不发散并不能保证大步长下的轨迹、碰撞或压力求解足够准确。12
为什么稳定又为什么变糊
线性、双线性或三线性插值的权重非负且总和为 1,因此每次更新都在来源周围旧值的范围内。只考虑纯平流且边界处理保持有界时,新值不会凭空超过已有的最大最小值。

这解释了常见的"半拉格朗日平流无条件稳定":即使一次移动跨过多个格点,也不必像某些显式欧拉平流格式那样,为避免发散而把 CFL 数限制在约 1 以内。它不是任意步长都准确的保证。12
一维时 CFL 数 C = |u|Δt / h,其中 h 为网格间距。C = 2 表示一步移动两个网格间距。完整求解器的外力、压力和边界处理仍需分别考虑。

图 5 一维周期网格,恒速每步移动 0.5 格,连续更新 40 步。虚线为精确平移,实线为线性插值结果。
8 数值耗散与质量误差要分开理解
数值耗散: 重复加权平均会抹平浓度的突变,削弱高频细节。图中的方形烟团逐渐变圆滑;速度场也会出现小涡旋衰减。即使没有设置物理扩散,也可能发生这种模糊。
质量守恒: 普通半拉格朗日按点采样,没有按格子边界交换通量,因此一般不保证总量严格守恒。总质量误差可能增加也可能减少,取决于流场、边界和离散方式。
图 5 的恒速、均匀周期网格是一个特殊例子:这时权重平移具有守恒性质,曲线变糊而总和不变。因此不能仅凭"变糊"就断定"烟雾总质量减少"。
改进方向与复习提纲
9 根据误差来源选择改进
轨迹不够准确:用中点法或更高阶 Runge--Kutta 法回溯。冻结旧速度场时,可先估计半步位置,再用该位置的速度完成一步回溯。它改善轨迹误差,但不会消除插值耗散。2

图像过于模糊:考虑更高阶重建、BFECC 或半拉格朗日 MacCormack 类误差补偿。它们通常需要更多采样或计算,也可能引入超调和负浓度,需要限幅或回退处理。23
总量必须严格可靠:考虑守恒型半拉格朗日或基于通量的有限体积方法。提高回溯阶数、提高分辨率,并不会自动获得严格质量守恒。
10 总结
① 半拉格朗日:固定网格存储,沿流体轨迹回溯,插值旧场得到新场。
② 回溯从新时刻的目标点出发,方向与实际流动相反。
③ 基础算法只有两件事:估计来源坐标,再采样旧数据。
④ 线性插值的有界性解释平流稳定性;稳定不等于准确。
⑤ 重复插值带来数值耗散;有界、低耗散和质量守恒是不同性质。
⑥ 它负责平流;压力投影负责不可压约束,渲染负责把结果显示出来。
参考资料
1 Mark J. Harris. Fast Fluid Dynamics Simulation on the GPU. GPU Gems, Chapter 38. 重点阅读 Advection 与 GPU Implementation。NVIDIA 官方章节
2 Robert Bridson and Matthias Müller-Fischer. Fluid Simulation for Computer Animation. SIGGRAPH 2007 Course Notes. 重点阅读 Semi-Lagrangian Advection、Accuracy 与边界处理。UBC 课程讲义
3 Keenan Crane, Ignacio Llamas and Sarah Tariq. Real-Time Simulation and Rendering of 3D Fluids. GPU Gems 3, Chapter 30. 重点阅读 MacCormack 平流。NVIDIA 官方章节