想象你在后厨熬一锅浓汤:木勺搅动,汤面旋起涡流,胡萝卜丁互相推挤又弹开,蒸汽贴着锅沿袅袅上升。如果要把这锅汤的每个动作搬进游戏,让玩家舀起、搅动、甚至泼向怪物,你需要一套叫“流体模拟”的算法。流体没有固定形状,给它一点力就持续变形流动,这既是它迷人的地方,也是计算的噩梦。工程师们想出了两条路:一条把锅切成棋盘格子,记录每一格汤的平均速度,这叫欧拉网格法;另一条给每粒食材装上追踪器,看它如何移动碰撞,这叫拉格朗日粒子法(SPH)。从开放世界的湖面到战斗时的血花,从火山口的浓烟到下水道的污水,流体模拟就是那口看不见的锅。本文不堆公式吓人,而是从一锅汤讲起,带你从零看懂流体模拟的原理、算法与工程优化。

岗位关联

掌握流体模拟的原理与优化手段,你就能在游戏与影视项目中独立设计逼真的水、烟、火效果并解决性能瓶颈——从“会调参数”升级为“懂物理”的核心技术人才。

目录
  1. 可视化演示
  2. 流体特性入门
  3. 控制方程解析
  4. 欧拉网格方法
  5. 粒子方法SPH
  6. 稳定性与边界
  7. 性能优化策略
  8. 游戏实战应用
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示SPH粒子流体、Euler网格方法与性能对比,支持敏感度调节。

本节配有交互动效(进入视口后自动加载,约 3KB)。

SPH粒子流体模拟 青色粒子表示流体单元,受重力与压力影响 蓝色连线表示粒子间邻居关系与密度计算 ρ_i = Σ m_j W(r_i - r_j, h) SPH通过核函数计算粒子密度与压力

流体特性入门

熬汤的第一课,是认清食材的脾性。豆腐是固体,刀切成什么样,下锅还是什么样;汤汁是流体,倒进圆碗成圆,倒进方盒成方。流体没有“记性”,只要持续受力就会不断变形流动,这正是它在游戏中如此迷人又如此难算的原因。游戏里的水花、烟雾、岩浆、墨汁,全是“捏不成形”的东西,统统归流体模拟管。

从技术的眼光看,游戏开发者只关心流体的三个核心特征:一是不可压缩性,水怎么挤体积都不缩水;二是粘性,蜂蜜流动缓慢、清水飞溅四射,差别全在粘性系数;三是涡旋,搅动后的汤会保持旋转,这是视觉上“像不像流体”的关键。围绕这三个特征,我们可以用一个无量纲数给流体“定性”——雷诺数:

由此,游戏流体模拟的目标可以总结为三条:视觉可信(看起来像水)、交互实时(玩家一碰就有反馈)、成本可控(必须在几十帧内算完)。一碗汤是“稀”还是“稠”,在游戏里就是调雷诺数——数字大,水花四溅;数字小,蜂蜜挂壁。

Re=ρULμ\displaystyle Re = \frac{\rho U L}{\mu}
雷诺数 Re 越大,流体越“精神”(湍流四溅);Re 越小,流体越“佛系”(安静爬行)。
  1. 分子·惯性力ρ 是密度,U 是特征流速,L 是特征长度(比如漩涡直径),三者相乘代表“汤有多想保持运动”。
    ρUL\displaystyle \rho U L
  2. 分母·粘性力μ 是动力粘度,代表“汤有多想停下来”;稠汤 μ 大,稀汤 μ 小。
    μ\displaystyle \mu
  3. 比值定性Re 远大于 1 时惯性主导、水花四溅;Re 远小于 1 时粘性主导、蜂蜜挂壁。
    Re≫1 湍流,Re≪1 层流\displaystyle Re \gg 1 \text{ 湍流},\quad Re \ll 1 \text{ 层流}

控制方程解析

认识了食材,就要写菜谱。纳维-斯托克斯方程(Navier-Stokes,简称 NS 方程)就是流体的“牛顿第二定律”,碗里每一小团汤都严格服从 F=ma。这个方程把“速度如何变化”拆成四股力量的博弈:惯性让汤自己带着自己跑,压力把汤从高压处推向低压处,粘性让相邻汤层互相拖拽,外力(重力、搅拌棒)从外部推波助澜。

写成数学形式,就是我们常说的 NS 方程:

再配上一条“铁律”——不可压缩约束:汤的体积既不膨胀也不收缩,数学上要求速度场散度恒为零:

菜谱写好了,可惜 NS 方程没有解析解,必须把锅切成小份(离散化)后用计算机数值求解。切法不同,就分出了两大门派:欧拉网格法和拉格朗日粒子法。下一节我们就分别走进这两间厨房。

∂u∂t=−(u⋅∇)u−1ρ∇p+ν∇2u+f\displaystyle \frac{\partial \mathbf{u}}{\partial t} = -(\mathbf{u} \cdot \nabla)\mathbf{u} - \frac{1}{\rho} \nabla p + \nu \nabla^2 \mathbf{u} + \mathbf{f}
∇⋅u=0\displaystyle \nabla \cdot \mathbf{u} = 0
两个公式合起来就是流体的完整菜谱:第一行是“四股力如何改变速度”,第二行是“无论如何不能把汤压扁”。
  1. 左端时间导数速度场随时间的变化率,即“这一小团汤下一秒怎么动”。
    ∂u∂t\displaystyle \frac{\partial \mathbf{u}}{\partial t}
  2. 平流项汤自己带着自己跑——上一刻的流动把速度“搬运”到新位置,漩涡因此能够自转。
    −(u⋅∇)u\displaystyle -(\mathbf{u} \cdot \nabla)\mathbf{u}
  3. 压力项高压往低压推,让拥挤处疏散;不可压缩性主要依靠它来维持。
    −1ρ∇p\displaystyle -\frac{1}{\rho} \nabla p
  4. 粘性项邻层速度差带来的拖拽,把突变磨平、让能量耗散,蜂蜜的“黏糊感”就来自它。
    ν∇2u\displaystyle \nu \nabla^2 \mathbf{u}
  5. 外力项重力、搅拌棒推力、风力等一切外部作用。
    f\displaystyle \mathbf{f}
  6. 不可压缩约束速度场散度恒为零,等价于“汤的体积不胀不缩”。
    ∇⋅u=0\displaystyle \nabla \cdot \mathbf{u} = 0

欧拉网格方法

做法一:给汤锅盖上一张标好格子的坐标纸,格子固定不动,我们只记录每一格汤的平均速度、密度和压力——这是“守株待兔”式的观察:汤流过格子,我们把读数记下来。网格上的每个格子只存一个“平均值”,所以计算量只取决于格子数量,与汤的细节无关。这就是欧拉网格法(Eulerian Grid Method)。

1999 年 Jos Stam 提出的“稳定流体”(Stable Fluids)把每步计算拆成三个动作:平流(汤带着自己的速度、密度等属性沿流线漂移到新位置)、扩散(粘性让邻格速度互相传染)、投影(修正速度场,让散度归零以满足不可压缩约束)。其中压力投影需要求解一个压力泊松方程:

欧拉网格法的拿手菜是烟、雾、云这类“充满整个空间”的流体——因为格子天然覆盖所有区域,无需关心“表面在哪”。它的短板是数值耗散:细节像被煮烂一样,剧烈的漩涡会被网格的平均效应磨平。想要更锐利的水花,就得请出下一节的粒子法。

∇2p=ρΔt∇⋅u\displaystyle \nabla^2 p = \frac{\rho}{\Delta t} \nabla \cdot \mathbf{u}
这个公式的意思是:速度散度(“胀缩罪证”)越大,就产生越强的压力(“矫正力”)来把流体压回不可压缩状态。
  1. 压力拉普拉斯左端是压力场的二阶空间导数,刻画压力在空间中的“峰谷分布”。
    ∇2p\displaystyle \nabla^2 p
  2. 散度源项右端以当前速度场的散度为“罪状”,散度越大,需要修正的压力越强。
    ρΔt∇⋅u\displaystyle \frac{\rho}{\Delta t} \nabla \cdot \mathbf{u}
  3. 投影修正解出 p 后,从速度中减去压力梯度项,速度场散度归零,流体恢复“不可压”。
    u←u−Δt∇p\displaystyle \mathbf{u} \leftarrow \mathbf{u} - \Delta t \nabla p

粒子方法SPH

做法二:把汤想象成一颗颗小汤圆,每颗汤圆随身携带追踪器,记录自己的位置、速度和压力。汤圆们在锅里互相推挤、彼此拖拽、四处飞溅——这就是拉格朗日粒子法。其中最著名的是光滑粒子流体动力学(Smoothed Particle Hydrodynamics,简称 SPH)。

在 SPH 里,一颗汤圆的密度无法直接称重,只能“目测”:看看自己周围多大范围内聚着多少邻居。这个“视线范围”叫核半径 h,邻居的贡献按距离加权,权重由核函数 W 给出:

密度大意味着被挤得太紧,压力随之升高;压力差把汤圆们推向更空旷的地方。将压力沿核函数梯度方向施加,就得到粒子间的压力合力:

SPH 的天赋在于自由表面和大变形——水花飞溅、浪花翻卷、液体从杯沿溢出来,这些欧拉网格要费很大力气处理的场景,对粒子法来说只是家常便饭。缺点是邻居查找昂贵,压力计算容易抖动,需要小心调参。

ρi=∑jmjW(xi−xj,h)\displaystyle \rho_i = \sum_j m_j W(\mathbf{x}_i - \mathbf{x}_j, h)
压力公式的妙处在于括号里的两项是对称的——你推我,我也推你,天然满足牛顿第三定律。
  1. 对称压力括号内是两粒子压力按各自密度平方归一化后的和,保证作用力对称。
    piρi2+pjρj2\displaystyle \frac{p_i}{\rho_i^2} + \frac{p_j}{\rho_j^2}
  2. 核梯度方向∇W_ij 指向“权重衰减最快”的方向,把力导向正确的作用线。
    ∇Wij\displaystyle \nabla W_{ij}
  3. 整体取负负号让力从高压粒子指向低压粒子,拥挤的汤圆被推开,流体因此“松软”而有弹性。
    −∑jmj(⋯ )∇Wij\displaystyle -\sum_j m_j (\cdots) \nabla W_{ij}

稳定性与边界

游戏里做汤,火候必须拿捏。时间步长是火候的表盘:步长太大,速度信息一个时间步内“穿越”好几个格子,数值直接发散成 NaN——锅炸了。步长太小,计算量翻倍,帧率崩盘。数学家给出了安全线,就是著名的 CFL 条件(柯朗-弗里德里希斯-列维条件):

实际操作中,游戏渲染帧率固定为 60fps,物理往往要拆成 2~4 个更小的子步来满足 CFL;同时还要处理边界:粒子撞上锅壁要反弹并损失速度(法向反转 + 切向摩擦),或者用距离势场把粒子“顶”回容器内部。一句话:不想让汤溢出来,锅沿(边界)和火候(时间步长)都要盯紧。

C=umax⁡ΔtΔx≤1\displaystyle C = \frac{u_{\max} \Delta t}{\Delta x} \le 1
CFL 条件说:每个时间步内,信息传播的最远距离不能超过一个网格尺寸(或核半径),否则数值方法就会失稳爆炸。
  1. 分子·传播距离u_max 是全场最大流速,乘时间步长 Δt,即一个步内信息能跑多远。
    umax⁡Δt\displaystyle u_{\max} \Delta t
  2. 分母·最小单元Δx 是网格宽度或粒子核半径,代表数值方法能分辨的最小尺度。
    Δx\displaystyle \Delta x
  3. 库朗数判定C ≤ 1 时信息一步最多跨一个单元,数值稳定;C > 1 时信息跳过多个单元,解发散。
    C≤1\displaystyle C \le 1

性能优化策略

汤圆成千上万,如果每颗都去遍历全场找邻居,复杂度是 O(N²),再强的 CPU 也会卡成幻灯片。聪明的做法像厨房里按楼层分抽屉存放食材:空间哈希(Spatial Hashing)把空间切成固定大小的桶,找邻居时只搜查中心桶及其周围 9 个桶,复杂度从 O(N²) 降到 O(N·k)——k 是平均邻居数,通常只有几十。

第二板斧是 GPU 并行:每颗汤圆的受力只依赖邻居,粒子之间天然独立,适合在显卡上几千个线程同时结算。不过并行有收益上限,阿姆达尔定律给出了天花板:

第三板斧是多分辨率简化(LOD):远处的水面用粗糙网格糊弄,近处的浪花才上高精度粒子;玩法区域的流体全精度模拟,背景的烟雾用便宜的大网格冒个意思。优化本质是“把力气花在玩家看得见的地方”——汤的精华,在表面那层油花上。

S=1(1−f)+f/N\displaystyle S = \frac{1}{(1 - f) + f / N}
阿姆达尔定律告诉我们:加速比的上限由“无法并行的部分”决定,串行比例哪怕只有 10%,加速天花板也只有 10 倍。
  1. 串行比例1 - f 是程序中无法并行的部分,无论多少核它都照跑一遍。
    1−f\displaystyle 1 - f
  2. 并行摊薄f / N 表示可并行部分被 N 个处理器瓜分后摊薄的开销。
    f/N\displaystyle f / N
  3. 极限加速比N 趋于无穷时,S 最多接近 1/(1 - f),瓶颈永远是串行部分。
    S→11−f\displaystyle S \to \frac{1}{1 - f}

游戏实战应用

最后把汤端上桌。设计上,流体最适合做“搅动式”互动:抹布擦水、刀切西瓜迸汁、角色踩水坑溅起浪花——玩家要的就是即时反馈的快感。系统上,流体需要与刚体物理(木块漂在水面、角色趟水减速)联动,通常的做法是给刚体一个额外的浮力和阻力。

视觉上,单纯一堆粒子穿模飞溅并不好看,需要叠加泡沫、浪花贴花和屏幕湿痕来“化妆”。性能上,每帧留给流体的预算通常只有 2~5 毫秒,成本可以用一个简单的模型估算:

实战经验还有三条:优先缩小核半径而非猛减粒子数(密度保不住会更难看);用固定时间步长躲避随机抖动;让流体系统与刚体物理共享碰撞层。一锅好汤,火候、食材、调料三者平衡——流体模拟的“火候”是时间步长,“食材”是粒子或网格,“调料”是各种参数。调好了,玩家会忍不住伸手搅一搅。

T≈N×k×tpair\displaystyle T \approx N \times k \times t_{\text{pair}}
每帧成本 ≈ 粒子总数 × 平均邻居数 × 单对交互开销。空间哈希把 k 压到常数,GPU 把 t_pair 摊薄,两项都优化到位,才能在大场景里跑起来。
  1. 粒子总数 N汤圆总颗数,决定底盘成本,是最直观的预算项。
    N\displaystyle N
  2. 平均邻居 k核半径内的平均邻居数,空间哈希让它从 N 降到常数 30~60。
    k\displaystyle k
  3. 单对成本t_pair 是计算一对粒子交互的耗时,在 GPU 上可进一步摊薄。
    tpair\displaystyle t_{\text{pair}}

AI 应用场景

强化学习训练场中的“物理汤锅”

阶段:训练环境 / 模拟数据生成

解决的问题:机器人学习倒水、搅拌、注液等操作时,真实实验成本高、重复性差、难以自动复位。用流体模拟搭建虚拟厨房,可以批量生成海量交互数据供强化学习 agent 练习。

具体案例:研究团队在虚拟场景中训练机械臂“倒红酒不洒”,环境内置 SPH 流体模拟,agent 每回合自动重置酒瓶和酒杯,用数千 GPU 小时训练出稳健的倒水策略,再迁移到真实机械臂上。游戏引擎里的流体模拟,在这里成了 AI 的“练兵场”。

流体参数的 AI 自动调参

阶段:模型调优 / 参数搜索

解决的问题:SPH 的核半径、粘度、粒子数、时间步长组合空间巨大,人工试错费时费力,且视觉质量与帧耗时互相牵制,很难手工调出最优配方。

具体案例:技术美术用贝叶斯优化自动搜索参数,目标函数 = 视觉相似度评分 + 帧耗时惩罚。周末让 AI 跑几百组模拟,周一直接拿到兼顾“好看”和“流畅”的参数组合——相当于让 AI 替你反复尝汤、调整咸淡。

流体谜题的智能通关测试

阶段:测试与验证

解决的问题:含流体谜题(堵漏、引流、灭火、排水)的关卡,状态空间巨大,人工测试难以覆盖所有解法和边界状态,流体穿模、粒子飞散等 bug 极难复现。

具体案例:测试 agent 在流体模拟环境中随机探索操作序列,自动发现“水穿透地板”“流速过大导致粒子爆炸”等缺陷,并生成可一键回放的复现录像。AI 测试员像一位不知疲倦的食客,把所有奇怪的喝汤姿势都试了一遍。

Python 演示

python108 行
import mathimport random  class Particle:    """一颗汤圆: 记录位置/速度/密度/压力"""     def __init__(self, x, y):        self.x = x        self.y = y        self.vx = 0.0        self.vy = 0.0        self.density = 1.0        self.pressure = 0.0  class SPHSimulation:    def __init__(self, count=100):        self.particles = [            Particle(random.uniform(0.3, 0.7), random.uniform(0.3, 0.7))            for _ in range(count)        ]        self.h = 0.06             # 核半径: 汤圆的视线范围        self.mass = 0.01          # 每颗汤圆的质量        self.rest_density = 1.0   # 静止密度        self.gas_constant = 3.0   # 压力系数        self.viscosity = 0.15     # 粘性系数        self.dt = 0.0008          # 时间步长     def _kernel(self, r):        """Poly6 核函数: 伞内邻居的权重函数"""        if r >= self.h:            return 0.0        q = self.h * self.h - r * r        return 315.0 / (64.0 * math.pi * self.h ** 9) * q ** 3     def _kernel_grad(self, dx, dy):        """Spiky 核函数的梯度: 压力力的方向来源"""        r = math.hypot(dx, dy)        if r <= 0.0 or r >= self.h:            return 0.0, 0.0        s = self.h - r        factor = -45.0 / (math.pi * self.h ** 6) * s * s / r        return factor * dx, factor * dy     def compute_density(self):        for p in self.particles:            total = 0.0            for q in self.particles:                dx = p.x - q.x                dy = p.y - q.y                total += self.mass * self._kernel(math.hypot(dx, dy))            p.density = total            p.pressure = self.gas_constant * (total - self.rest_density)     def step(self):        self.compute_density()        for p in self.particles:            ax, ay = 0.0, -9.8            for q in self.particles:                if p is q:                    continue                dx = p.x - q.x                dy = p.y - q.y                r = math.hypot(dx, dy)                if r >= self.h or r <= 0.0:                    continue                gx, gy = self._kernel_grad(dx, dy)                pf = -self.mass * (                    p.pressure / p.density ** 2                    + q.pressure / q.density ** 2                )                ax += pf * gx                ay += pf * gy                w = self._kernel(r)                vf = self.viscosity * self.mass * w / q.density                ax += vf * (q.vx - p.vx)                ay += vf * (q.vy - p.vy)            p.vx = max(-30.0, min(30.0, p.vx + ax * self.dt))            p.vy = max(-30.0, min(30.0, p.vy + ay * self.dt))            p.x += p.vx * self.dt            p.y += p.vy * self.dt            if p.x < 0.05:                p.x = 0.05                p.vx = abs(p.vx) * 0.5            if p.x > 0.95:                p.x = 0.95                p.vx = -abs(p.vx) * 0.5            if p.y < 0.05:                p.y = 0.05                p.vy = abs(p.vy) * 0.5            if p.y > 0.95:                p.y = 0.95                p.vy = -abs(p.vy) * 0.5     def run(self, frames=100):        for i in range(frames):            self.step()            if i % 20 == 0:                cx = sum(p.x for p in self.particles) / len(self.particles)                cy = sum(p.y for p in self.particles) / len(self.particles)                print("frame=%d center=(%.3f, %.3f) density=%.3f"                      % (i, cx, cy, self.particles[0].density))  if __name__ == "__main__":    sim = SPHSimulation(count=100)    sim.run(frames=100)
目的:实现了 2D SPH 流体模拟的核心循环——每步先算密度与压力,再累加压力力和粘性力,最后更新位置并处理锅壁反弹,可直接运行观察粒子团的运动轨迹。
  1. total += self.mass * self._kernel(math.hypot(dx, dy))密度是核半径内所有邻居质量的加权和,伞内才算数。
  2. p.pressure = self.gas_constant * (total - self.rest_density)密度超过静止密度越多,压力越大,汤圆被挤得越“不高兴”。
  3. if r >= self.h or r <= 0.0: continue只和伞内邻居交互,这是 SPH 计算的局部性来源。
  4. pf = -self.mass * (p.pressure / p.density ** 2 + q.pressure / q.density ** 2)对称压力项,保证你推我我也推你。
  5. ax += pf * gx; ay += pf * gy压力沿核梯度方向把粒子推向低压区。
  6. vf = self.viscosity * self.mass * w / q.density粘性系数决定邻居之间的“拖拽”强度,蜂蜜就靠它变稠。
  7. ax += vf * (q.vx - p.vx)速度差越大拖拽越强,让相邻汤圆速度趋向一致。
  8. p.x += p.vx * self.dt显式时间积分,步长受稳定性限制,这就是 CFL 条件在代码里的样子。
  9. p.x = 0.05; p.vx = abs(p.vx) * 0.5撞墙反弹:法向速度反转并衰减一半,模拟能量损耗。

常见误区

误区我直接把粒子数从一万一口气加到十万,画面肯定更精致,毕竟是“分辨率”嘛。

正解粒子数只是“食材量”,密度、核半径、粘性和时间步长共同决定“口感”。盲目加粒子而不缩小核半径或调小步长,汤会越煮越稠、压力抖动剧烈,甚至直接数值爆炸。正确的姿势是先用 CFL 条件锁定步长,再平衡粒子数与核半径,最后才谈加量。

为何常见:“分辨率越高越清晰”在图像领域深入人心,大家惯性类推到物理模拟,忽略了数值稳定性这道隐形门槛。

误区游戏里烟雾都用网格法、水花都用粒子法,所以欧拉网格法天生不能做水。

正解欧拉网格模拟水完全可行,业界著名的 FLIP、APIC 方法就是“网格 + 粒子”的混合体——网格负责压力求解,粒子负责输运细节。传统纯网格法的短板只是难以锐利跟踪自由表面,需要用水平集等辅助手段,并非“不能做水”。

为何常见:很多教程把“欧拉=烟雾、SPH=水”当作速记口诀,大家看漏了混合方法这片更广阔的中间地带。

误区老师总说步长太大会发散,那我干脆把时间步长设成 1e-8,稳如老狗。

正解步长变小确实让显式积分更安全,但游戏每帧预算有限,步长减半等于计算量翻倍。聪明的做法是“对症下药”:平流和力用显式积分、老老实实受 CFL 约束;压力投影用隐式求解、允许较大步长;再把步长与渲染帧解耦,用子步推进。

为何常见:教材为了强调显式方法的危险性,经常把“步长必须小”绝对化,忽略了隐式方法这条路。

误区论文里的流体渲染那么逼真,我只要把物理算得足够精确,玩家一定买账。

正解游戏追求的是“可信 + 好玩 + 流畅”。真实水花算得再准,若一帧要 20 毫秒、还难以控制,玩家体验反而糟糕。业界常做夸张、简化、风格化处理:加大粘性防穿模、增强涡旋补细节、碰撞后额外发射溅射粒子——一切都是为了“看起来对”。

为何常见:看到 Siggraph 论文的高保真渲染,误以为工程目标就是逼近真实,忘了游戏是“体验优先”的商品。

面试题

Q1. 游戏流体模拟的两大主流数值方法分别是什么?

单选题 · easy

  • A.粒子追踪法与碰撞检测法
  • B.欧拉网格法与拉格朗日粒子法← 正确答案
  • C.有限状态机与行为树算法
  • D.蒙特卡罗法与贝叶斯估计法

答案:B。欧拉网格法守着固定格子记录“流过格子的汤”(连续介质视角),拉格朗日粒子法(SPH)给每颗汤圆装追踪器。A 不是标准流派名,C 是 AI 行为逻辑,D 是统计方法,均与流体数值方法无关。

Q2. 蜂蜜比清水更难流动,反映的是流体的什么属性?

单选题 · easy

  • A.密度
  • B.压强
  • C.粘性← 正确答案
  • D.温度

答案:C。蜂蜜流动慢是因为层与层之间的“拖拽”强,即粘性大。密度只关乎轻重,压强是挤压效应,温度通过改变粘性间接影响流动,都不是根本属性。

Q3. 下列关于流体特性的说法,正确的有?

多选题 · easy

  • A.水可近似视为不可压缩← 正确答案
  • B.粘性越大流体越易流动
  • C.涡旋是受搅动后的旋转← 正确答案
  • D.雷诺数越大越趋近层流

答案:AC。水在常规受力下体积几乎不变,可近似不可压缩(A 对);搅动产生涡旋(C 对)。B 说反了——粘性大反而更难流动;D 也说反了——雷诺数大对应湍流而非层流。

Q4. 不可压缩流体的连续性约束在数学上等价于?

单选题 · mid

  • A.速度场的散度等于零← 正确答案
  • B.速度场的旋度等于零
  • C.压力场的梯度等于零
  • D.密度场的拉普拉斯为零

答案:A。不可压缩即“体积不缩水”,数学上要求速度场散度为零 ∇·u=0。旋度为零是“无旋”,压力梯度为零意味着无压强差,拉普拉斯为零是调和场,都与不可压缩不等价。

Q5. 关于欧拉网格法中的压力投影步骤,正确的说法是?

单选题 · mid

  • A.通过解泊松方程求压力场← 正确答案
  • B.用来给粒子累加碰撞推力
  • C.负责把流体渲染成高光
  • D.用来删除网格中的空单元

答案:A。压力投影先求解压力泊松方程,再用压力梯度修正速度场,让散度归零。B 是粒子受力累加,属于 SPH 流程;C 是渲染技巧;D 与投影无关。

Q6. CFL 条件要求在每个时间步内?

单选题 · mid

  • A.信息传播距离不超过网格尺寸← 正确答案
  • B.粒子数量保持恒定不变
  • C.压力场必须完全收敛稳定
  • D.渲染帧率至少达到六十帧

答案:A。CFL 条件的本质是“一步的传播距离不大于网格尺度”,像火候过大汤会炸锅。B 是粒子数守恒,C 是压力收敛,D 是渲染帧率,都不是数值稳定性判据。

Q7. SPH 中某粒子的密度是如何获得的?

单选题 · mid

  • A.用核函数对邻居粒子质量加权求和← 正确答案
  • B.直接读取网格节点上的插值压力
  • C.对速度场做拉普拉斯算子运算
  • D.用有限差分求解压力泊松方程

答案:A。SPH 密度 = 邻居质量 × 核函数的加权和,核函数像一把“伞”,伞内邻居算、伞外不算。B 是网格法思路,C 是粘性算子,D 是压力投影,都不是 SPH 求密度的方式。

Q8. SPH 相对于欧拉网格法的优点包括?

多选题 · mid

  • A.天然支持自由表面与大变形← 正确答案
  • B.质量守恒由粒子恒存自然保证← 正确答案
  • C.不需要任何邻居遍历运算
  • D.完全不受时间步长稳定限制

答案:AB。粒子恒存自然守恒质量,自由表面也无需额外追踪(A、B 对)。SPH 恰恰需要邻居遍历(C 错),显式时间积分同样受 CFL 稳定性限制(D 错)。

Q9. 空间哈希加速 SPH 的基本思想是?

单选题 · hard

  • A.把空间切桶只查邻居桶中的粒子← 正确答案
  • B.用四叉树存储每个粒子的速度值
  • C.每帧重排粒子数组并做基数排序
  • D.让粒子只与固定编号的粒子作用

答案:A。空间哈希把空间切成固定桶,找邻居只查中心桶及周围 9 个桶,把 O(N²) 降为 O(N·k),正如按抽屉找食材。B 是四叉树维护速度,C 是排序优化而非哈希核心,D 的“固定编号”不符合物理规律。

Q10. 关于流体稳定性与边界处理,正确的说法有?

多选题 · extreme

  • A.显式平流满足 CFL 才避免数值发散← 正确答案
  • B.隐式压力投影可放宽时间步长限制← 正确答案
  • C.边界碰撞反弹需反转法向速度分量← 正确答案
  • D.核半径越大邻居数量必然越少

答案:ABC。显式平流受 CFL 约束(A 对);隐式压力投影无条件稳定、可放宽步长(B 对);边界碰撞需反转法向速度模拟反弹(C 对)。核半径越大覆盖的邻居越多而非越少,D 恰好说反。