Position Based Fluids — 把水也塞进 PBD 同一套框架
是什么
Position Based Fluids(PBF) 是把水做成”一堆粒子”的方法。它不像传统流体先算压力再积分,而是直接挪粒子位置让它们满足”密度等于水的密度”这条约束。
日常类比:想象一池子玻璃珠表示水。传统做法(monaghan-1992-sph 风格的 SPH)是给每颗珠子算一个压力推力,让它和邻居互相弹开;PBF 是看一眼”这块珠子挤太密了”,直接把多余的珠子推开一点点,挪完再看下一帧。
挪位置这个套路就是 mueller-2007-pbd 的 PBD 思想——PBF 等于给 PBD 加了一条新约束:“粒子局部密度必须等于 ρ₀”。
为什么重要
游戏要做实时液体一直很难:
- 走 SPH 路线:压力来自一个刚硬的状态方程,刚度一调高,时间步必须缩到 1/2000 秒,跑不动
- 走网格法(stam-1999-stable-fluids 那种 Eulerian):能吃大时间步,但加自由表面、加碰撞复杂度暴涨
PBF 把流体也变成”位置约束”,于是有三个直接收益:
- 大时间步稳定:dt 可以拉到 16ms(每秒 60 帧),比 WCSPH 大一到两个数量级
- 统一求解器:和布料、刚体、绳索共享同一套 PBD 主循环,工程上能合并代码
- 实时百万粒子:原论文 GeForce GTX 680 上百万粒子接近实时
NVIDIA Flex 引擎的水内核就是 PBF;Macklin 后续的 unified particle physics 和 XPBD 都从这里长出来。游戏行业实时液体的事实标准。
核心要点
PBF 一帧的五步循环(在 mueller-2007-pbd 五步上做的扩展):
-
预测位置:
x* = x + dt · v + dt² · g(重力是唯一外力) -
邻域查找:对每个粒子 i,用空间哈希找半径 h 内的所有邻居
-
Jacobi 迭代解密度约束(PBF 的灵魂):
- 用 Poly6 核估计密度
ρᵢ = Σⱼ mⱼ · W(xᵢ-xⱼ, h) - 约束
Cᵢ(x) = ρᵢ/ρ₀ - 1 = 0 - 算拉格朗日乘子
λᵢ = -Cᵢ / (Σ‖∇Cᵢ‖² + ε) - 加人工张力压力
s_corr防止粒子聚团 - 修正位置
Δxᵢ = (1/ρ₀) Σⱼ (λᵢ + λⱼ + s_corr) ∇W(xᵢ-xⱼ, h) - 整套循环 4 到 10 次
- 用 Poly6 核估计密度
-
涡度增强 + XSPH 粘性:把投影耗散掉的旋度能量打回去,再做一遍速度平滑
-
从位置反推速度:
v = (x_new - x_old) / dt
第 3 步是 PBF 区别于 SPH 的核心。SPH 用压力 → 加速度 → 速度 → 位置,链式四步;PBF 直接”位置 → 位置”,一步到位。
实践案例
案例 1:从 SPH 到 PBF 的心智切换
同样初值的一池粒子:
- SPH:每帧算压力
p = k(ρ/ρ₀ - 1),求加速度a = -∇p/ρ,积分到速度和位置。k 调大水才硬,但 k 大了 dt 必须小 - PBF:每帧投影
Cᵢ = ρᵢ/ρ₀ - 1 = 0,几何上把粒子挪到满足约束的最近点。“硬度”靠迭代次数控制,dt 不受影响
直观区别:SPH 是”用力推”,PBF 是”伸手挪”。一个是动力学路线,一个是几何投影路线。
案例 2:参数调到能看出”水”的样子
PBF 有四个参数最折磨人:
- ρ₀(静态密度):决定”多少粒子算一团水”
- 核半径 h:每个粒子的影响范围,建议让邻居数稳定在 30 到 40
- Δq、k、n(人工张力压力 s_corr):抑制粒子聚团成小液滴,但 k 调过大会挤出虚假张力
- Jacobi 迭代次数:4 次水软软的像橡皮泥,10+ 次显著变慢但仍解不到完全不可压
调参体感:从 4 次迭代加到 10 次,水从”果冻”变成”水”;再加涡度增强,水从”粥”变成”有漩涡的水”。
案例 3:把 PBF 作为统一求解器的一员
游戏引擎要同时跑布料、刚体、绳索、水,最怕”四套求解器互相不认账”。PBF 的工程价值在于:
- 布料用距离约束 + 弯曲约束
- 刚体用形状匹配约束
- 流体用密度约束
- 全部共享 PBD 主循环(预测 → 投影 → 反推速度)
这就是 macklin-2014-unified-particle-physics 的思路:写一套求解器,所有物理对象都是”带不同约束的粒子”。NVIDIA Flex 是这个思想的工业实现。
踩过的坑
-
表面粒子邻域不足:水面的粒子上方没有邻居,密度估计 ρᵢ 系统性偏低,约束误判为”压缩不够”,把表面粒子向内拉,产生 phantom 表面张力。论文用 ε relaxation 缓解但消不掉
-
拉伸不稳定(tensile instability):低密度区粒子容易聚团成小液滴。必须加 s_corr 人工张力压力项(Schechter-Bridson 路线),但 Δq、k、n 是经验调参雷区,不同场景要重调
-
PBD 投影本身耗散动能:几何修正会无差别地把动能磨掉,漩涡很快被抹平,水显得”黏”。必须显式加 vorticity confinement 把丢掉的旋度能量重新打回去,否则视觉上像粥不像水
-
邻居数量阈值:每粒子 30 到 40 个邻居才稳。粒子分辨率太低或核半径设错,密度估计抖动甚至发散
-
Jacobi 迭代刚度上限:迭代再多次也解不到完全不可压——这是 PBD 的根本限制。XPBD 后续才把它形式化为”刚度收敛到无穷大”问题并修复
-
粒子分辨率 vs 视觉质量:PBF 表面是粒子离散化的,要做出连续水面必须额外跑 marching cubes 或 anisotropic kernel 表面重建。粒子越少越能跑实时,但水看起来越”颗粒感”。100 万粒子是工业 demo 常见档位
适用 vs 不适用场景
适用:
- 游戏 / VFX 实时液体(水、岩浆、泥浆视觉效果)
- 与 PBD 布料、刚体、绳索共解的统一框架
- 大时间步、视觉优先的场景(不在乎物理精度,只在乎”看起来像水”)
不适用:
- 需要严格物理精度(薄膜流、毛细现象、精确表面张力)→ 用网格法或更精细 SPH
- 需要无耗散保能量的科学仿真 → PBD 投影本质会耗散动能
- 高粘度流体(蜂蜜、熔岩黏性主导)→ XSPH 粘性是经验后处理,不是真粘性
历史小故事(可跳过)
- 2003 年:Müller 等发了《Particle-Based Fluid Simulation for Interactive Applications》,把 SPH 引进游戏图形,但还是状态方程压力路线
- 2007 年:Müller 等发了 mueller-2007-pbd,确立”约束投影代替力积分”的 PBD 路线,初版只做布料和软体
- 2012 年:Schechter & Bridson 在 Ghost SPH 里提出人工张力压力 s_corr,解决粒子聚团
- 2013 年:Macklin & Müller 在 SIGGRAPH 上发 PBF,把 SPH 的密度约束塞进 PBD 框架。预印本写 2014,正式会议是 2013
- 2014 年起:NVIDIA Flex 引擎正式产品化,PBF 成为游戏行业实时液体事实标准
- 2016 年:Macklin 等发 XPBD,把 PBD 的”迭代次数 = 刚度”这个隐式耦合变成显式的 compliance 参数
学到什么
- 同一个物理量可以有多条求解路线:流体既可以”算压力 → 积分”(SPH),也可以”投影位置”(PBF)。算法选择决定时间步上限
- 几何投影是可压缩性的几何视角:不可压不是”压力大”,而是”密度 = ρ₀ 这条流形上的最近点投影”
- 统一求解器的工程价值:布料、刚体、流体共享 PBD 循环 → 引擎代码减一半,而且不同物理对象自然耦合
- 耗散是几何法的代价:PBD 投影会无差别地磨掉动能,必须显式补偿(vorticity confinement、XPBD compliance)。这是几何路线的固有缺陷
- 工业落地需要算法 + 工程双轮:PBF 在论文层只是”加一条密度约束”,但能跑成 Flex 引擎,靠的是空间哈希、GPU 并行邻域、表面重建一整套配套工程
延伸阅读
- 论文 PDF:Position Based Fluids(mmacklin.com)(8 页,公式密但可读)
- 视频 demo:Macklin SIGGRAPH 2013 talk
- NVIDIA Flex:Flex 文档(PBF 的工业实现)
- mueller-2007-pbd —— PBF 的母方法,先理解 PBD 五步循环
- monaghan-1992-sph —— PBF 借用 SPH 的核函数与邻域,但替换了求解策略
- stam-1999-stable-fluids —— 网格法路线对照,理解 Eulerian vs Lagrangian 流体两条线
关联
- mueller-2007-pbd —— PBD 主框架,PBF 是它加密度约束的扩展
- monaghan-1992-sph —— SPH 提供核函数 W、密度估计公式,PBF 复用这些组件
- stam-1999-stable-fluids —— 另一条流体路线(网格 + 半拉格朗日 advection),与 PBF 互为镜像
一句话总结:PBF 把”流体不可压”从动力学问题翻译成几何投影问题,让水和布料、刚体共享同一套求解器,是游戏行业实时液体的事实标准。
反向链接
- mueller-2007-pbd —— Position Based Dynamics — 跳过力,直接挪位置