想象你正在熬一锅浓汤。传统“网格法”模拟流体,就像给整锅汤盖上密密麻麻的刻度线,然后费劲地追踪每一格汤汁的流速;而 SPH(光滑粒子流体动力学)则相反——它把汤的灵魂交给了汤里的食材颗粒。每一粒胡萝卜末、每一片葱花都是一颗“粒子”,它们各自带着质量、位置、速度、密度和压力,在锅里翻滚碰撞。香料的味道从每粒食材向外一圈圈扩散,越远越淡,直到超过某个半径就完全尝不出来——这正是“核函数”。哪个区域食材扎堆,哪个区域就显得“浓稠”(密度高),于是挤得慌的食材会相互推搡(压力),把同伴推向稀疏处;而你捞汤时只会睁眼看着自己勺子周围那一圈食材,而不会把整锅都数一遍——这是“邻域搜索”。最后,每隔一小段时间搅拌一次,重复“尝浓度—看推挤—挪位置”的循环——这就是“时间积分”。整篇文章,我们就用这锅汤讲完 SPH 的设计思路。
掌握 SPH 原理,你就能理解游戏里逼真的水体、电影里滔天的海浪从何而来,这是进入流体物理模拟、游戏引擎物理与特效开发的必备基础。
目录
可视化演示
动态演示SPH平滑粒子流体模拟原理与的核心机制,通过总览/传输/转换/输出四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
SPH平滑粒子流体模拟原理与:SPH方 SPH方法的基本思想与拉格朗日 核函数与属性插值数学 核心机制可视化 SPH平滑粒子流体模拟原理与整体架构与关键组件
SPH概述:不是网格,而是一锅食材
假设你是大厨,想精确掌握锅里每一滴水的去向。传统网格流体模拟的做法是给整口锅盖上一层密密麻麻的渔网,然后记录每个网眼里的平均速度和水位——这就是“欧拉视角”的网格法。现在换个思路:你不再盯着网格,而是盯住汤里的每一粒食材。胡萝卜丁、葱花、肉末各自以“微小一团流体代言人”的身份在锅里游走,它们带着自己的质量、速度、密度去碰撞、聚散,最终你观察到的仍然是整锅汤,但描述它的语言变成了“粒子”——这便是 SPH 所采用的“拉格朗日视角”。
SPH 全称 Smoothed Particle Hydrodynamics,中文叫“光滑粒子流体动力学”,1977 年由 Gingold 与 Monaghan 在研究天体物理(如恒星形成)时提出,后来被图形学工程师引入流体模拟。它不直接求解抽象的偏微分方程,而是用离散粒子的位置与属性重建速度场、密度场。每个粒子的行为遵循两条规则:一是“受周围粒子影响”(通过核函数加权),二是“贡献自己的质量与压力”影响周围。
为什么可以用一堆离散点代替连续流体?因为 SPH 有一个“插值魔法”:任意一点的物理量 f,都可以由附近粒子携带的值加权拼出来。这个拼贴公式就是 SPH 的基石:
1. 目标物理量 $f(\mathbf{r})$:这是要重建的连续场在位置 r 处的值,密度、压力、速度都适用。
2. 粒子求和 $\sum_j$:遍历 r 附近光滑长度 h 内的所有粒子,逐个累加它们的贡献。
3. 体积权重 $\dfrac{m_j}{\rho_j}$:粒子 j 的质量除以密度等于它占据的“小体积”,体现它在空间里占了多少份额。
4. 粒子属性 $f_j$:粒子 j 自身携带的那个物理量的数值。
5. 核函数 $W(\mathbf{r}-\mathbf{r}_j, h)$:衡量粒子 j 对位置 r 的影响强度,距离越近权重越大,超过 h 就归零。
回到汤里——这个公式就像“按勺子大小决定每人盛多少汤”的分配规则。每粒食材都在向周围宣告自己的存在,而任意一点能“尝到”的味道,正是附近食材贡献的加权平均。把这一思想铺开,就是 SPH 的全部起点。
核函数:香料影响一圈圈变淡
往汤里丢一粒花椒,它的麻味并不会整锅均匀扩散,而是离得越近越冲、越远越淡,超过某一圈距离就彻底尝不到了。这粒花椒的“影响曲线”就是核函数 W 的直观形象。光滑长度 h 就是那圈“尝不到”的边界——它决定了一粒食材能影响的半径,也决定了汤里的“社交圈”大小。
在 SPH 里,核函数是粒子之间传递信息的“规则”。它必须满足三个基本要求:归一化(整条影响曲线围出的面积等于 1,保证总量不凭空增加)、紧致支持(超过 h 就取 0,让计算只发生在局部)、非负且平滑(避免出现负的“反味道”和数值突变)。不同的核函数就像不同香料的扩散方式:有的香气锐利,有的绵长。
最常用的核函数之一是 Poly6,它由 Müller 等人于 1996 年引入图形学,形式优美、计算简单:
1. 核函数 $W_{poly6}$:Poly6 是求密度时最常用的权重函数,名字源于表达式里 $(h^2-r^2)^3$ 的六次结构。
2. 归一化系数 $\dfrac{315}{64\pi h^{9}}$:这个常数让核函数在全空间积分为 1,保证质量在加权求和时不增不减。
3. 光滑长度 $h$:影响半径,r 超过 h 时核函数直接取 0,邻域搜索只关心半径 h 内的粒子。
4. 中心距 $r$:目标点与粒子之间的距离,距离越小核函数值越大。
5. 幂次项 $(h^2-r^2)^3$:随距离增大,影响按三次方平滑坠落到 0,边缘无尖角,数值稳定性好。
花椒放得太多,整锅都是麻味;放得太少,又尝不出来。h 选大了,流体细节糊成一团;选小了,粒子各自为政、互相找不到邻域。核函数与光滑长度,就是这锅汤的“调料尺度”,决定了 SPH 模拟的细腻程度和计算成本。
密度计算:一勺汤的浓稠度
你要判断汤的哪个位置浓稠,最直接的办法就是舀一勺,看这一勺里食材挤了多少。SPH 也是这样——想知道粒子 i 所在处的密度,就把它邻域内的“食材”按质量数一遍,离得越近贡献越大,这就是密度求和公式的来源。
在连续介质力学里,密度通常由连续性方程(质量守恒的偏微分方程)求解。而 SPH 的聪明之处在于:密度是“数”出来的,压根不需要解方程。每个粒子的质量为 m_j,它像一颗小葡萄干,把质量“摊”在核函数的范围内,于是粒子 i 处的密度就等于邻域内所有葡萄干摊到 i 处的质量之和:
1. 目标密度 $\rho_i$:粒子 i 所在位置的流体密度,SPH 里每个粒子每一帧都要重新计算。
2. 邻域求和 $\sum_j$:对光滑长度 h 范围内的所有邻居粒子遍历并累加。
3. 粒子质量 $m_j$:每个邻居粒子贡献多少“质量”,SPH 中质量通常恒定不变。
4. 邻距核权重 $W(\mathbf{r}_i-\mathbf{r}_j, h)$:距离越近质量分摊越多,距离超过 h 的粒子不参与求和。
细节上需要注意两件事。首先,这个求和天然满足质量守恒:所有粒子的质量固定,加在一起总量不变,流体体积变化自然反映为密度升降,无需额外修正。其次,密度必须每帧重算——粒子位置时刻在变,“哪一勺浓、哪一勺稀”也跟着变。实际操作中,初始密度可能偏离目标密度 ρ0,这会在下一节产生压力,推动粒子重新分布。
就像大厨每搅拌一下都要重新判断哪里浓稠一样,SPH 每一帧都要重新“舀一勺”。密度不是一次性设定好的常数,而是粒子分布状态的实时写照——它是压力计算的前置条件,也是整个 SPH 循环的“温度计”。
压力与力:拥挤人群会推人
想象商场里人山人海:你周围挤满了人就会感到被往四面八方推搡,而稀疏的角落却没有人挤你。流体中的压力就是这种“微观推挤”的宏观描述。SPH 里压力不是凭空设定的,它由密度偏差驱动——哪个区域密度超过了理想值 ρ0,那里就产生了“拥挤压力”。
SPH 常用一个简单的状态方程(Tait 方程的近似):p_i = k(ρ_i - ρ0)。k 是刚度系数,ρ0 是静水密度。密度低于 ρ0 时压力为负(轻微拉扯),高于 ρ0 时压力为正(向外推挤)。光有压力还不够,粒子感受到的是压力的“变化率”——压力梯度,就像人群的压力差让你被迫移动。离散化后,粒子 i 受到的压力梯度力为:
1. 压力梯度力 $\mathbf{F}_i^{\text{pressure}}$:作用在粒子 i 上、由邻域压力不均产生的合力,驱动流体从高压流向低压。
2. 负号 $-$:力指向压力减小的方向——密度高的地方把粒子推往稀疏处,对应人群散开的直觉。
3. 对称压力项 $\left( \dfrac{p_i}{\rho_i^2} + \dfrac{p_j}{\rho_j^2} \right)$:同时考虑 i、j 双方的压力,交换下标后数值不变,天然满足牛顿第三定律。
4. 核梯度 $\nabla W$:核函数的梯度给出推力的方向和强弱,指向邻居方向且随距离衰减。
5. 质量加权 $\sum_j m_j$:每个邻域粒子对力的贡献按质量累加,质量越大影响越大。
除了压力梯度力,SPH 还叠加三种力。一是粘性力,相当于汤的“粘稠感”,它取邻域粒子速度差的加权平均,让相邻粒子速度趋向一致,抑制抖动;二是重力,最简单的外力,像地心引力让汤往锅底沉;三是边界力,粒子逼近容器壁时由惩罚函数产生推力,防止穿帮。把所有力求和除以质量,就得到粒子 i 的加速度 a_i = F_i / m_i。
汤太浓的地方,食材互相顶撞,把同伴推向清汤寡水的区域——这是压力的作用;汤太稀的地方,粒子又被“拉”回来填补空隙。压力梯度力、粘性力、重力与边界力四者配合,粒子们就在锅里自动演绎出翻涌、沉降、回旋的流体姿态。
邻域搜索:只数勺子里的食材
前面所有计算都有同一个前缀——“邻域内”。那么问题来了:怎么快速知道每个粒子的邻域是谁?最笨的办法是让每粒食材和其他所有食材两两打个招呼:一大锅汤有 N 粒食材,就要比 N² 次关系,这就是 O(N²) 的朴素遍历。当 N 从几百涨到几万,计算量会爆炸式增长。
聪明的大厨有自己的做法:先把锅底划成整齐的小方格,每个格子大约一个“光滑长度”见方。然后让每粒食材报出自己所在的格子编号,查找邻居时,只需要检查自己所在格子以及紧挨着的相邻格子就够了。这套思路在 SPH 中叫“空间哈希网格”(Spatial Hashing)。粒子的格子坐标可以表示为:
1. 网格单元 $\text{cell}(\mathbf{r})$:空间位置 r 所归属的格子坐标,是整个邻域搜索的地图索引。
2. 向下取整 $\left\lfloor \cdot \right\rfloor$:位置除以 h 后取下整,保证坐标连续变化时格子编号整数跳跃。
3. 三维坐标分量:x、y、z 三个方向各算一次,组成完整的空间格子编号。
实际操作中,空间哈希算法会把三维格子编号压缩成一个一维索引(通过乘法和取模),塞进哈希表。这样每个粒子只需维护一串“邻居列表”,每次查询平均只需检查常数个格子。密度、压力、粘性力的计算全都复用这份邻居列表,整体复杂度从 O(N²) 降到接近 O(N)。选择格子边长时,一般取刚好等于光滑长度 h——既能保证不漏邻居(粒子影响范围不超过 h 个格子),又不会让格子大而无当。
就像大厨不会把整锅食材都倒进碗里来数,SPH 通过空间哈希把自己的“社交圈”锁定在相邻几格内。邻域搜索不是锦上添花,而是让 SPH 从玩具演示走向百万粒子工业模拟的关键一跃。
时间积分:稳稳地一步步推进
掌握了每个粒子受到的力,接下来就是“让汤真正动起来”。物理模拟的本质,是用一个个微小的时刻去逼近连续的时间。我们把时间切成固定的小段 Δt,每段做一次“力→加速度→速度→位移”的推进,这就是时间积分。半隐式欧拉是最常用的起步方法:先用当前力更新速度,再用新速度更新位置——先算速度、再算位置,每一步都踩在前一步的“新鲜”结果上:
1. 位置更新 $\mathbf{x}_i(t+\Delta t)$:新位置 = 旧位置 + 新速度 × 时间步长,速度驱动粒子走向下一帧。
2. 速度更新 $\mathbf{v}_i(t+\Delta t)$:新速度 = 旧速度 + 加速度 × 时间步长,力决定粒子如何加速。
3. 合力与质量 $\dfrac{\mathbf{F}_i(t)}{m_i}$:把上节算出的压力、粘性、重力等合力除以质量,得到加速度。
4. 时间步长 $\Delta t$:每步推进的时间,过大则数值失稳(粒子飞散),过小则计算量剧增,通常受 CFL 类似条件约束,与光滑长度和最大速度相关。
时间步长是模拟稳定的命门。直觉上,Δt 必须小到“粒子在一步之内不会冲过整个光滑长度 h”——否则它会直接穿过邻居的“影响圈”,力还没来得及作用就已经飞走了,这就是数值失稳(模拟爆炸)。实践中常把 Δt 限制为 h 除以最大速度的若干分之一。除此之外还有最后一个环节:边界处理。容器壁就像锅沿,不能放食材跑出去。常用做法是当粒子越过边界时立刻反弹回内部,并附加轻微阻尼。
把整锅汤的周期串起来——每帧先做邻域搜索找到“勺子里的食材”,再算密度判断浓稠,用状态方程算压力、叠加各种力求合力,最后按时间步长推进速度和位置。这个循环就像大厨的节拍:尝一口、搅一搅、等一等,反反复复,一锅活灵活现的流体就在屏幕上沸腾起来了。
AI 应用场景
游戏引擎中的实时水体
阶段:实时物理模拟与交互反馈
解决的问题:传统网格法难以高效处理水花飞溅、泡沫产生和复杂自由表面拓扑变化;SPH 用粒子天然表达“飞出去的液体”(如酒水飞溅、船桨划出的浪花),且易于融入 GPU 渲染管线。
具体案例:在 Unity 或 Unreal 的水体资产(如 FluidNinja、SPHFluid)中,玩家用手或武器扰动水面时,数千个 SPH 粒子从波面飞溅而出,配合 GPU 加速实时渲染。AI 与强化学习甚至可训练控制策略,让角色智能地“避浪”或预判水花形状。
影视特效中的流体艺术
阶段:离线渲染与特效制作
解决的问题:电影特效需要高质量的海浪、水柱、黏稠液体(如血液、泥浆)。SPH 粒子法可以自由控制流体离散程度,并在千万级粒子上配合空间哈希完成离线模拟,天然适合艺术家反复调参。
具体案例:《冰雪奇缘》系列中水与冰的魔法效果,以及商业视效软件 Houdini 中基于粒子的 FLIP/SPH 工具链。特效艺术家通过调整光滑长度、密度刚度等参数,实时预览流体的“性格”(猛烈或温婉)。
工业与医疗中的流动仿真
阶段:工程数值分析与参数寻优
解决的问题:油箱内润滑油飞溅、心脏瓣膜附近血流形态等场景,SPH 无需额外追踪自由表面,天然适合描述“液体在复杂固体边界中自由翻滚”。
具体案例:汽车制造中用 SPH 模拟变速箱润滑油的分布与搅动损失;医疗领域用 SPH 粒子模拟血管中的血液流动,为手术方案提供血流动力学参考,并结合深度学习加速参数寻优。
Python 演示
"""A simplified 2D SPH fluid simulation for teaching purposes."""import mathimport random class SPHSimulation: """Use particles to simulate fluid: density, pressure, force, integrate.""" def __init__(self, count=50): self.count = count self.smooth_len = 1.2 # smoothing length h, also the neighbor radius self.target_density = 2.0 # rest density rho0 self.pressure_scale = 1.5 # stiffness coefficient k self.mass = 1.0 # mass of each particle self.time_step = 0.016 # time step dt self.particles = [] def reset(self): self.particles = [] for _ in range(self.count): self.particles.append({ "x": random.uniform(0.5, 6.5), "y": random.uniform(0.5, 3.5), "vx": 0.0, "vy": 0.0, "density": 0.0, "pressure": 0.0, }) def kernel(self, r, h): """Poly6 kernel: larger weight closer to the center, zero beyond h.""" if 0 <= r <= h: q = h * h - r * r return q * q * q / (4.0 * h * h * h) return 0.0 def kernel_gradient(self, dx, dy, r, h): """Gradient of the kernel, pointing to the neighbor direction.""" if 0 < r <= h: scale = -3.0 * (h * h - r * r) ** 2 / (4.0 * h * h * h) return scale * dx / r, scale * dy / r return 0.0, 0.0 def compute_density(self): """Step 1: sum kernel-weighted masses from all neighbors.""" for p in self.particles: p["density"] = 0.0 for i, p in enumerate(self.particles): for j, q in enumerate(self.particles): if i == j: continue dx = p["x"] - q["x"] dy = p["y"] - q["y"] r = math.sqrt(dx * dx + dy * dy) p["density"] += self.mass * self.kernel(r, self.smooth_len) p["pressure"] = self.pressure_scale * (p["density"] - self.target_density) def apply_forces(self): """Step 2: pressure force and gravity, then update velocity and position.""" for i, p in enumerate(self.particles): fx = 0.0 fy = -9.8 for j, q in enumerate(self.particles): if i == j: continue dx = p["x"] - q["x"] dy = p["y"] - q["y"] r = math.sqrt(dx * dx + dy * dy) + 1e-6 gx, gy = self.kernel_gradient(dx, dy, r, self.smooth_len) scale = p["pressure"] / (p["density"] ** 2) + q["pressure"] / (q["density"] ** 2) fx += -self.mass * scale * gx fy += -self.mass * scale * gy p["vx"] += fx * self.time_step p["vy"] += fy * self.time_step p["x"] += p["vx"] * self.time_step p["y"] += p["vy"] * self.time_step if p["x"] < 0.5 or p["x"] > 6.5: p["x"] = max(0.5, min(6.5, p["x"])) p["vx"] *= -0.5 if p["y"] < 0.5 or p["y"] > 3.5: p["y"] = max(0.5, min(3.5, p["y"])) p["vy"] *= -0.5 def run(self, steps=30): """Main loop: density -> forces -> integration, repeated.""" self.reset() for _ in range(steps): self.compute_density() self.apply_forces() return self.particles if __name__ == "__main__": sim = SPHSimulation(count=40) result = sim.run(steps=30) avg = sum(p["density"] for p in result) / len(result) print("SPH simulation done, particle count:", len(result)) print("Average density:", round(avg, 4))
def kernel(self, r, h):定义 Poly6 核函数,距离越近权重越大,超过 h 返回 0。q = h * h - r * r计算核函数的自变量,这是 Poly6 幂次项的核心。return q * q * q / (4.0 * h * h * h)核函数值,数值平滑且在边界处归零。p["density"] += self.mass * self.kernel(r, self.smooth_len)密度是邻域粒子质量的核加权累加,对应文章第 3 节的公式。p["pressure"] = self.pressure_scale * (p["density"] - self.target_density)状态方程:密度高于静水密度则压力为正。scale = p["pressure"] / (p["density"] ** 2) + q["pressure"] / (q["density"] ** 2)对称压力项,保证牛顿第三定律。fx += -self.mass * scale * gx压力梯度力累加,负号让力指向压力减小方向。p["vx"] += fx * self.time_step半隐式欧拉:先更新速度。p["x"] += p["vx"] * self.time_step再用新速度更新位置。p["vx"] *= -0.5边界反弹并施加阻尼,防止粒子逃出计算区域。self.compute_density(); self.apply_forces()主循环两步走,对应文章第 6 节的模拟节奏。
常见误区
误区初学时以为给每个粒子设好密度值就能一直用,每帧重复计算密度纯属浪费性能,不如用初始值代替。
正解密度是粒子实时分布的结果。粒子每帧都在移动,“哪勺浓、哪勺稀”每帧都在变。密度没更新,压力就会用过期数据,模拟会失真甚至发散。SPH 每一帧都必须从邻域重新求和密度,这是压力的“温度计”,像大厨每次下料后都要重新舀一勺判断浓稠。
为何常见:容易把密度当成“粒子自带的固定属性”,混淆了恒定不变的质量与动态变化的密度场。
误区核函数影响范围越大,粒子看得越远,信息越充分,模拟应该越精细越真实。
正解h 增大会让核函数过度平滑,把小尺度细节(飞溅、涡旋)糊成一锅粥;同时每个粒子的邻居数量变多,计算量暴增。真正决定精细度的是充足的粒子数配合合适的 h(通常取初始平均粒子间距的 1~2 倍)。
为何常见:直觉上“看远一点 = 更精确”,但 SPH 是局部插值方法,插值半径超过物理特征尺度只会损失分辨率和性能。
误区粒子是自由运动的,让它无限下落就好,容器边界不用建模,反正 SPH 是“无网格”方法。
正解不处理边界,粒子会穿透锅底永远掉出去,汤就洒光了。实际实现必然包含边界力、反弹或镜像粒子等处理,边界处理的质量直接影响流体形态是否真实。文章 Python 示例中就加了边界反弹逻辑。
为何常见:看粒子法示意图多半只画了“粒子 + 邻居圆”,很容易忽略边界约束这类实现细节。
误区空间哈希大概和 KD 树一样,排序查找应该把复杂度从 O(N²) 降到 O(N log N)。
正解空间哈希在粒子分布较均匀时可以接近 O(N) 线性复杂度:查表、遍历邻近格子都是常数时间,它不需要全局排序,本质是“把地图分成格子,只数勺子里的食材”。某些实现中引入 Z 序曲线或排序优化才涉及对数项。
为何常见:把空间哈希与树结构(kd-tree、BSP)的 O(N log N) 概念混为一谈。
误区压力在整锅汤里应该处处一样,配一个全局压力值直接参与模拟就够了。
正解压力是局部密度的函数 p = k(ρ - ρ0):浓的地方压力高,稀的地方压力低甚至为负,正是这种“压力差”驱动流体运动。如果全局等压,粒子之间就没有任何驱动力了,汤就不会流动。
为何常见:日常经验里大气压“到处一样”,忽视了流体中压力梯度才是驱动流动的核心。
面试题
Q1. SPH 是哪种流体模拟方法的缩写?
- A.光滑粒子流体动力学← 正确答案
- B.稀疏粒子流体力学
- C.可压缩气体粒子算法
- D.波动方程数值解方法
答案:A。SPH 全称 Smoothed Particle Hydrodynamics,中文译为“光滑粒子流体动力学”。它用离散粒子代替连续网格描述流体,每个粒子像汤里的一粒食材,携带质量、速度、密度等属性。B 把“光滑”误写为“稀疏”,C、D 与粒子法定位不符。
Q2. SPH 方法用何种方式建模流体?
- A.携带属性的离散粒子系统← 正确答案
- B.固定空间坐标的网格单元
- C.一组普通微分方程
- D.体素化的六面体堆叠
答案:A。SPH 属于拉格朗日视角:流体被离散成大量携带质量、位置、速度、密度的粒子,这些粒子随流运动。网格单元对应欧拉视角,体素化是三维图像的表示方式,都与 SPH 的“粒子代言”思路不同。
Q3. SPH 中光滑长度 h 的主要作用是什么?
- A.决定粒子的搜索影响范围← 正确答案
- B.决定粒子颜色的深浅
- C.决定时间步长的快慢
- D.决定网格划分的密度
答案:A。光滑长度 h 是核函数的“影响半径”,超过 h 的粒子互不干扰。它直接决定邻域搜索范围和核函数的衰减尺度,就像香料味道扩散的那一圈边界。它不直接决定颜色或网格密度。
Q4. SPH 密度计算公式 ρi = Σ mj W(ri-rj, h) 的正确解读是?
- A.邻域粒子质量经核函数加权累加← 正确答案
- B.密度只由自身位置单独决定
- C.密度等于粒子数的恒定倍数
- D.密度与质量成反比关系
答案:A。密度等于邻域内所有粒子质量 mj 乘以核函数权重 W 后的总和。距离越近、粒子越多,密度越高。它依赖邻居的分布而非自身位置,也不是简单的粒子数倍数。
Q5. 关于 SPH 中核函数的性质,下列说法正确的是?
- A.具有紧致支持的范围有限← 正确答案
- B.在全空间积分为归一数值← 正确答案
- C.只能用于密度计算场合
- D.值随距离增大而不断增强
答案:AB。核函数必须满足紧致支持(超出 h 取 0)和归一化(全空间积分为 1),保证局部计算且总量守恒。核函数不仅能算密度,还用于压力梯度、粘性力等;其值随距离增大而衰减,而非增强。
Q6. 关于空间哈希网格邻域搜索,下列说法正确的是?
- A.将空间划分为均匀立方格子← 正确答案
- B.只需检查相邻格子内的粒子← 正确答案
- C.优于朴素 O(N²) 的遍历← 正确答案
- D.需要遍历所有全局的粒子
答案:ABC。空间哈希把空间均匀切成格子,每个粒子只与当前格及相邻格内的粒子比较,从而避免全局两两遍历,复杂度接近 O(N)。如果还需要遍历所有粒子,那就退化回朴素方法了。
Q7. SPH 模拟中粒子受到的力通常包括?
- A.压力梯度产生的驱动力← 正确答案
- B.粘性耗散产生的阻力← 正确答案
- C.重力等体积外力作用← 正确答案
- D.洛伦兹电磁力的影响
答案:ABC。SPH 粒子受力主要由压力梯度力、粘性力和重力等体积力组成,还可能包含表面张力、边界力。洛伦兹电磁力只在与电磁场耦合的特殊场景中出现,不属于一般流体模拟的标配受力。
Q8. N 个粒子朴素遍历邻域的时间复杂度是?
- A.O(N²) 两两配对检查← 正确答案
- B.O(N) 线性扫描一遍
- C.O(log N) 折半查找
- D.O(1) 常数时间
答案:A。朴素方法对每个粒子都要与其他 N-1 个粒子比较距离,共约 N²/2 次运算,复杂度为 O(N²)。空间哈希和树结构才能把复杂度降到 O(N) 或 O(N log N)。
Q9. 显式欧拉积分中时间步长过大会导致?
- A.数值失稳粒子飞散爆炸← 正确答案
- B.密度自动趋于收敛值
- C.压力梯度完全归零消失
- D.计算加速且结果更精确
答案:A。Δt 过大时,粒子一步运动距离可能超过光滑长度 h,直接“跳过”邻居的影响圈,力还没作用就飞远了,模拟随即发散。稳定性要求限制 Δt 与 h、最大速度的比值,这也是 CFL 类条件的直觉来源。
Q10. 压力梯度力写成对称形式的核心目的是?
- A.满足动量守恒与牛顿第三定律← 正确答案
- B.降低算法时间复杂度的量级
- C.消除外部重力的影响作用
- D.让粒子数量减少一半以上
答案:A。对称形式(同时包含 p_i/ρ_i² 和 p_j/ρ_j²)保证 i 对 j 的力与 j 对 i 的力大小相等、方向相反,系统内部动量守恒。它不改变复杂度,也无法消除外力和减少粒子数。