静谧中,沉静心灵


一、论文核心贡献

  1. 创新点

    • 提出首个实时Navier-Stokes方程求解器,专为游戏引擎优化(非物理精确解)。
    • 核心突破:无条件稳定性(任意时间步长不崩溃),牺牲物理精度换取视觉合理性与速度。
    • 完整C代码实现仅需100+行,支持2D/3D网格实时运算(标准PC硬件)。
  2. 关键技术

    • 分层求解思想:先解密度场(线性),再扩展至速度场(非线性)。
    • 半拉格朗日法advect()函数):逆向追踪插值避免数值爆炸。
    • 质量守恒投影project()函数):霍奇分解消除速度场散度,生成漩涡效果。

二、算法流程详解

1. 密度场求解(基础层)

void dens_step(...) {
  add_source(...);   // 注入源(如烟雾)
  SWAP; diffuse(...); // 扩散(稳定高斯-赛德尔迭代) 
  SWAP; advect(...);  // 平流(半拉格朗日法)
}
  • 扩散
    使用20次高斯-赛德尔松弛求解线性系统,避免显式差分不稳定性:

    x[IX(i,j)] = (x0[...] + a*(相邻四项和)) / (1+4*a); 
  • 平流
    逆向追踪网格点来源位置,双线性插值获取密度:

    x = i - dt0*u[IX(i,j)];  // 逆向追踪
    d[IX(i,j)] = 相邻四点插值; // 双线性插值

2. 速度场求解(进阶层)

void vel_step(...) {
  add_source(...);     // 注入外力
  diffuse(...);        // 粘性扩散
  project(...);        // 质量守恒投影
  advect(...);         // 自平流(速度场跟随自身运动)
  project(...);        // 二次投影增强稳定性
}
  • 关键创新:质量守恒投影
    通过霍奇分解将速度场拆解为无散度场(漩涡) + 梯度场(非物理振荡):
    // 步骤1:计算散度场
    div[IX(i,j)] = -0.5h*(u右-u左 + v上-v下); 
    
    // 步骤2:解泊松方程求高度场p
    p[IX(i,j)] = (div[...] + 相邻四项和)/4; 
    
    // 步骤3:从速度场减去梯度场
    u[IX(i,j)] -= 0.5*(p右-p左)/h; 

3. 边界处理(set_bnd())

  • 固体边界:速度场法向分量归零(墙面无穿透)
    x[IX(0,i)] = (b==1) ? -x[IX(1,i)] : x[IX(1,i)]; // 水平墙反射
  • 角点特殊处理:避免数值歧义
    x[IX(0,0)] = 0.5*(x[IX(1,0)] + x[IX(0,1)]); 

三、NS方程原始形式与算法项对应

论文图1给出NS方程的核心形式(简化向量表示):

右边三项分别对应

  1. → **advect()**(自平流项)

    • 物理意义:流体自身惯性导致的动量输运(非线性项)
    • 算法实现:通过半拉格朗日法逆向追踪速度场(详见§4.3):
      x = i - dt0*u[IX(i,j)];  // 逆向追踪质点位置
      u_new = 双线性插值(u_old) // 用前一时刻速度场插值
  2. → **diffuse()**(粘性扩散项)

    • 物理意义:流体粘性导致的动量扩散(耗散项)
    • 算法实现:隐式高斯-赛德尔迭代求解扩散方程(§4.2):
      // 解方程:u - dt*ν∇²u = u_old
      u[IX(i,j)] = (u_old + a*(相邻速度之和)) / (1+4*a);
  3. → **add_source()**(外力项)

    • 物理意义:外部力场(如重力、风扇)的输入
    • 算法实现:直接叠加外力到速度场(§4.1):
      u[i] += dt * f_ext[i]; 

四、压力项的隐藏角色与project()

NS方程在不可压缩流体中需补充质量守恒约束

压力项由此衍生

  • 物理本质:压力是维持流体不可压缩的约束力(非独立外力)
  • 算法对应 → **project()**(质量守恒投影)
    • 通过霍奇分解(图7)将速度场拆解:
    • 核心操作(§4.4):
      1. 计算速度场散度 → 质量亏损量
        div = -0.5h*(u_right - u_left + v_top - v_bottom); 
      2. 解泊松方程压力场
        p[IX(i,j)] = (div + 相邻p之和)/4; // 高斯-赛德尔迭代
      3. 从速度场中减去压力梯度 → 无散速度场
        u -= 0.5*(p_right - p_left)/h; // 消除质量亏损

五、算法流程与NS方程的完整映射

物理过程NS方程项算法函数执行顺序
外力输入add_source()第一步
粘性扩散diffuse()第二步
质量守恒约束(压力)project()第三步
自平流advect()第四步

关键说明

  • 压力项无独立函数:其效果通过project()中间接实现(泊松方程求解)
  • project()双重调用:首次在扩散后修正粘性导致的质量亏损,二次在平流后修正惯性导致的质量亏损(§4.4代码)
  • 粘滞力与扩散项严格对应diffuse()的物理意义

六、边界条件与数值稳定性

  • 固体边界set_bnd()):强制法向速度为零(§4.4):
    u_wall = (边界类型==1) ? -u_adjacent : u_adjacent; // 反射法向分量
  • 稳定性保障
    • 扩散项用隐式格式(无条件稳定)
    • 平流项用半拉格朗日法(逆向追踪避免数值振荡)

此设计确保算法可处理任意时间步长(论文§2强调),突破传统CFL条件限制。

七、参考

1.Navier-Stokes方程式介紹與推導
2.描述流場性質的2種方法 – Eulerian v.s. Lagrange,固定座標系下流動質點的物理性質變化表示

以飞牛photo文件夹挂载群晖目录为例

1、在photo文件夹中新建一个目录,例如DSM

2、右键点击DSM目录, 选择“详细信息”, 复制原始路径/vol1/1000/Photos/DSM

3、进入SSH

sudo -i

输入root密码后获取root权限

4、进入fstab文件

vim /etc/fstab

5、按“i”键进入编辑模式, 在fstab文件最后加入一行

//群晖共享文件夹IP地址/群晖共享文件夹路径 /vol1/1000/Photos/smbpic cifs username=群晖用户名,password=群晖密码,iocharset=utf8,gid=1000,uid=1000,file_mode=0777,dir_mode=0777 0 0 

6、输入完成后, 按下esc, 退出编辑模式, 再输入:wq退出文件

7、输入以下命令执行挂载, 成功

systemctl daemon-reload

mount -a

在现代实时渲染中,处理大量动态光源是一个核心挑战。传统的 Forward Rendering(正向渲染) 在每个物体绘制时遍历所有光源计算光照,其性能开销与 物体数量 × 光源数量 成正比,因此无法高效处理大量光源。

以下是主流的几种方案:


1. Deferred Shading/Deferred Rendering(延迟着色/延迟渲染)

这是最早广泛用于解决多光源问题的方案。

  • 核心思想:将几何处理光照计算分离成两个独立的阶段。

    • 第一阶段(Geometry Pass):遍历所有几何体,但不计算任何光照。 Instead,将表面的各种属性(如位置、法线、漫反射颜色、高光系数等)渲染到一张张单独的缓冲区中,合称为 G-Buffer
    • 第二阶段(Lighting Pass):不再需要场景几何信息。遍历每一个光源,对于每个光源,将其视为一个几何体(如球体、锥体),并在屏幕空间下,根据 G-Buffer 中存储的信息,计算该光源对哪些像素有贡献。每个光源的计算互不影响。
  • 优点

    • 光照计算复杂度与场景复杂度无关,仅与屏幕分辨率光源数量成正比。非常适合处理大量小型光源。
    • 每个像素只计算真正被贡献的光照,效率高。
  • 缺点

    • G-Buffer 占用大量带宽和内存
    • 对透明物体和抗锯齿(MSAA)支持不友好,需要额外方案(如正向渲染透明物体,使用 TAA/FXAA)。
    • 材质类型受限,G-Buffer 的格式固定,难以支持非常复杂、多样的材质(虽然可以通过设计来扩展,但有成本)。

2. Forward+ (Tiled Forward Rendering)(分块正向渲染)

Forward+ 是对传统正向渲染的增强,旨在保留其优点的同时解决多光源问题。

  • 核心思想:它也分为两个阶段,但比延迟渲染更“轻量”。

    • 第一阶段(Depth Pre-Pass):首先,渲染整个场景的深度信息,得到一个深度缓冲区(Depth Buffer)。
    • 第二阶段(Light Culling & Shading)
      1. 分块(Tiling):将屏幕分割成许多个小块(Tiles),例如 16x16 或 32x32 像素的块。
      2. 光源剔除(Light Culling):对每个屏幕块,根据其在世界空间中的视锥体(由块的像素区域和深度范围计算得出),遍历所有光源,找出哪些光源会影响到这个块。为每个块生成一个光源索引列表
      3. 着色(Shading):使用传统的正向渲染流程绘制每个物体。在像素着色器中,不再是遍历所有光源,而是只需加载当前像素所在屏幕块对应的光源索引列表,并只计算这些少数光源的贡献。
  • 优点

    • 克服了传统正向渲染 O(n*m) 的计算复杂度。
    • 支持 MSAA 和任意材质,因为着色阶段仍然是正向渲染,着色器可以非常灵活。
    • 比延迟渲染占用更少的内存带宽(没有大的 G-Buffer)。
  • 缺点

    • 光源剔除阶段需要一定的 CPU 或 GPU 计算开销。
    • 每个像素最终计算的光源数量可能不均匀(有的块光源多,有的块光源少),可能导致性能波动。

3. Clustered Shading(集群着色)

这是对 Forward+ 的进一步优化,解决了其一个主要痛点:在深度方向上划分不够精细。

  • 核心思想:Forward+ 只在屏幕的 2D 空间上分块,而在深度方向上是“一刀切”的。Clustered Shading 在 3D 空间中对视图视锥体进行划分。

    • 不仅沿着屏幕的 X、Y 轴分块,还沿着 Z 轴(深度轴) 进行切片(Slice)。这样形成了一个个 3D 的“集群”(Cluster)。
    • 光源剔除过程是为每个 3D 集群计算其影响的光源列表。
  • 优点

    • 解决了 Forward+ 在深度方向上的浪费问题。例如,一个靠近相机的小块和一个很远的小块,如果包含的光源数量差异很大,在 2D Tiling 中会被同等对待,而在 3D Clustering 中会被区分开,光照列表更精确。
    • 尤其擅长处理光源分布不均匀的复杂场景(如有很多小灯的长走廊)。
  • 缺点

    • 算法实现比 Forward+ 更复杂。
    • 剔除阶段的计算开销更高。

4. Tiled Deferred / Clustered Deferred

这只是上述思想的组合。延迟渲染同样可以受益于分块/集群的光源剔除技术。

  • 核心思想:在延迟渲染的 Lighting Pass 中,不再为每个光源绘制一个全屏的 Light Volume,而是使用计算着色器,对每个 Tile 或 Cluster 进行光源剔除,然后逐块进行光照计算。
  • 优点:进一步提升了延迟渲染在处理极大量光源时的性能,因为它避免了为每个光源绘制其影响范围(Overdraw),而是更精确地计算每个像素受哪些光源影响。

总结与对比

方案核心思想优点缺点适用场景
传统正向渲染逐个物体,遍历所有光源计算材质灵活,支持MSAA,透明物体简单性能与物体×光源数成正比,无法处理多光源光源数量很少的场景(<10个)
延迟渲染将几何和光照分离,使用G-Buffer高效处理大量小型光源,与场景复杂度无关占用带宽大,透明和MSAA支持差,材质不灵活大量小型动态光源的场景(如科幻、霓虹灯)
Forward+屏幕分块,为每个块剔除光源支持MSAA和复杂材质,内存带宽占用低深度方向剔除不精确,性能可能不均需要MSAA、复杂材质且光源数量较多的场景
Clustered在3D视锥体内分块,更精确剔除光源剔除最精确,尤其擅长深度变化大的场景实现最复杂,计算开销最大最通用、高效处理极大量动态光源的方案

现代游戏引擎的选择

  • **Unity (URP/HDRP)**:
    • URP:默认使用 Forward+ (Tiled) 渲染器。因为它需要在移动端和PC端有良好的平衡,并支持多种材质。
    • HDRP:默认使用 Deferred。因为它面向高端平台,需要处理极其复杂的光照和材质(通过多通道G-Buffer实现),并且其透明物体通常用正向渲染进行叠加。
  • Unreal Engine:传统上以延迟渲染为核心,并且其光照计算也使用了分块/集群优化。UE5 的 Lumen 等全局光照系统也构建在延迟管线之上。

结论:
目前,Clustered Shading 被认为是处理大量动态光源最强大和通用的解决方案,无论是与正向渲染结合(Clustered Forward)还是与延迟渲染结合(Clustered Deferred),它都提供了最佳的性能和灵活性,因此被许多3A游戏和现代游戏引擎所采用。Forward+ 是一个优秀的折中方案,而延迟渲染在特定需求下依然非常强大。

重要性采样(Importance Sampling, IS)多重重要性采样(Multiple Importance Sampling, MIS) 技术在蒙特卡洛积分中至关重要。

它们主要用于计算机图形学(如光线追踪渲染)、强化学习等领域,以高效地计算复杂积分。


1. 蒙特卡洛积分基础

首先,快速回顾一下蒙特卡洛积分。我们要计算一个积分:
$$I = \int_a^b f(x) dx$$

蒙特卡洛积分的基本思想是随机采样。它通过从定义域内随机抽取样本 $x_i$,计算 $f(x_i)$,然后取平均值来近似积分:
$$I \approx \frac{1}{N} \sum_{i=1}^N f(x_i)$$

但这里有一个问题:如果 $f(x)$ 在某些区域值很大,在另一些区域值很小(例如,一个尖锐的峰值),均匀随机采样会非常低效。大多数样本会落在 $f(x)$ 值很小的“不重要”区域,对最终结果的贡献微乎其微,从而造成方差很大,收敛速度很慢。


2. 重要性采样 (Importance Sampling, IS)

核心思想

重要性采样的核心思想是:我们应该把更多的样本放在函数值大(重要)的区域,而不是均匀地放置样本。

换句话说,我们不再使用均匀分布进行采样,而是使用一个与我们想要积分的函数 $f(x)$ 形状相似的概率分布 $p(x)$ 来生成样本。

数学表达

为了修正采样分布带来的偏差,我们需要对每个样本进行加权。蒙特卡洛积分公式变为:
$$I = \int f(x) dx = \int \frac{f(x)}{p(x)} p(x) dx \approx \frac{1}{N} \sum_{i=1}^N \frac{f(x_i)}{p(x_i)}$$
其中:

  • $x_i$ 是从分布 $p(x)$ 中抽取的样本。
  • $\frac{f(x_i)}{p(x_i)}$ 被称为重要性权重

关键要求与目标

  1. $p(x)$ 必须满足:$p(x) > 0$ wherever $f(x) \neq 0$。即,在 $f(x)$ 有值的任何地方,$p(x)$ 都不能为零,否则我们会忽略掉一些本应有贡献的区域。
  2. 理想情况:如果 $p(x)$ 正比于 $f(x)$,即 $p(x) = c|f(x)|$,那么方差会变为 0,每个样本的贡献都是常数。这是重要性采样的终极目标。
  3. 现实情况:我们很难找到完美的 $p(x)$,但只要 $p(x)$ 的形状大致与 $f(x)$ 相似,就能显著降低方差。

例子:渲染中的直接光照

在计算一个点接收到来自环境的光照时,积分函数 $f(x)$ 是光的强度乘以BRDF(材质属性)。如果光来自一个很小的窗户(一个很小的区域贡献了大部分的光),均匀地在所有方向采样会非常低效。重要性采样会选择一个与光强分布相似的 $p(x)$,让我们更多地朝窗户的方向采样,从而快速收敛。

局限性

重要性采样有一个致命弱点:它严重依赖于所选采样分布 $p(x)$ 的质量
如果 $p(x)$ 选得不好,比如:

  • $p(x)$ 的形状与 $f(x)$ 相差甚远。
  • $f(x)$ 本身由多个不同特征的函数相乘组成(例如,$f(x) = g(x)h(x)$),很难找到一个 $p(x)$ 同时匹配 $g(x)$ 和 $h(x)$。

这时,重要性采样的效果可能甚至不如简单采样,方差会变得非常大。


3. 多重重要性采样 (Multiple Importance Sampling, MIS)

核心思想

既然很难找到一个完美的采样分布,那我们为什么不结合多个不同的采样分布呢?

MIS 的核心智慧在于:它允许我们使用 多种采样策略(例如,从一个分布 $p_1$ 中取 $n_1$ 个样本,从另一个分布 $p_2$ 中取 $n_2$ 个样本),然后通过一个聪明的加权函数将这些样本结合起来,从而充分利用每种策略的优势,避免其劣势。

数学表达

假设我们有 $m$ 种不同的采样分布 $p_1(x), p_2(x), …, p_m(x)$。
从每个分布 $p_i(x)$ 中采集 $n_i$ 个样本,总样本数 $N = \sum n_i$。

MIS 的估计量表示为:
$$I \approx \sum_{i=1}^m \frac{1}{n_i} \sum_{j=1}^{n_i} w_i(x_{i,j}) \frac{f(x_{i,j})}{p_i(x_{i,j})}$$
其中:

  • $x_{i,j}$ 是从第 $i$ 个分布 $p_i$ 中抽取的第 $j$ 个样本。
  • $w_i(x)$ 是权重函数,其关键作用是平衡不同采样方法在某个特定点 $x$ 上的贡献。所有权重函数必须满足 $\sum_{i=1}^m w_i(x) = 1$(当 $f(x) \neq 0$)。

关键:权重函数 $w_i(x)$

权重函数的设计是 MIS 的精髓。一个好的权重函数应该:

  • 在某种采样策略表现好的地方,给予该策略产生的样本更高的权重。
  • 在某种采样策略表现差的地方(即该策略的概率密度函数值很小),降低其权重,以避免产生巨大的重要性权重($f(x)/p_i(x)$)。

最著名和常用的权重函数是平衡启发式(Balance Heuristic)
$$w_i(x) = \frac{n_i p_i(x)}{\sum_k n_k p_k(x)}$$

将其代入 MIS 公式,得到:
$$I \approx \sum_{i=1}^m \frac{1}{n_i} \sum_{j=1}^{n_i} \frac{f(x_{i,j})}{\sum_k n_k p_k(x_{i,j})} \approx \frac{1}{N} \sum_{i=1}^m \sum_{j=1}^{n_i} \frac{f(x_{i,j})}{\sum_k (n_k / N) p_k(x_{i,j})}$$

平衡启发式在实践中效果非常好,它几乎总是比使用任何一种单一采样策略的方差要低。

例子:渲染中的直接光照(再次)

计算光照积分 $L = \int L_i(light) * BRDF * \cos\theta d\omega$ 时,这个函数是两部分相乘:

  1. **光强分布 $L_i$**:可能是一个小窗户,适合用光源采样。
  2. BRDF 分布:可能是镜面反射,适合用 BRDF 采样。

单一的重要性采样会陷入两难:

  • 如果用光源采样,在BRDF值很小(非镜面反射方向)的地方采样会浪费样本。
  • 如果用BRDF采样,可能会错过重要的光源。

MIS 的解决方案
同时使用两种采样策略。

  • 策略1($p_1$):根据光源分布采样(多朝窗户方向采样)。
  • 策略2($p_2$):根据BRDF分布采样(多朝镜面反射方向采样)。

然后使用平衡启发式权重函数将这两组样本结合起来。结果是:

  • 当采样方向既指向光源又是镜面反射方向时(“好”样本),两种策略的 $p_i(x)$ 都较高,权重被合理分配。
  • 当采样方向指向光源但不是镜面反射方向时,BRDF采样策略的 $p_{brdf}(x)$ 会很小,因此其样本权重 $w_{brdf}(x)$ 会被降低,避免产生噪声。
  • 反之亦然。

这样,MIS 巧妙地结合了两种策略的优点,得到了一个方差更低、更稳定的估计结果。


总结与对比

特性重要性采样 (IS)多重重要性采样 (MIS)
核心思想用一个好的分布 $p(x)$ 来指导采样,聚焦重要区域。组合多个采样分布,用加权函数智能地结合它们的样本。
采样分布单一分布 $p(x)$多个分布 $p_1(x), p_2(x), …, p_m(x)$
权重简单的重要性权重 $1/p(x)$复杂的权重函数 $w_i(x)$(如平衡启发式)
优点概念简单,在 $p(x)$ 匹配 $f(x)$ 时极其高效。非常鲁棒。能有效处理由多个因素相乘组成的复杂被积函数 $f(x)$,方差显著低于任何单一策略。
缺点极度依赖 $p(x)$ 的质量。选错 $p(x)$ 会导致方差极大。计算开销更大,需要计算多个 PDF 的值。
适用场景被积函数 $f(x)$ 特征明显且单一,容易找到匹配的 $p(x)$。被积函数是多个函数的乘积(如渲染中的 $Lighting * BRDF$),难以用单一分布描述。

简单来说,重要性采样是“把所有鸡蛋放在一个篮子里,但希望这个篮子足够结实”,而多重重要性采样是“把鸡蛋分开放到几个篮子里,并且有一个智能系统来决定从哪个篮子拿鸡蛋最安全”。MIS 是重要性采样思想的重大飞跃,使其在实践中变得更加实用和强大。

0%