WebGPU 计算着色器实现三维 SPH 光滑粒子流体动力学:水流飞溅与表面重建
在实时计算机图形学与虚拟交互领域液体的流动与飞溅一直被公认为皇冠上的明珠。从浪花拍击岩石时的千堆飞沫到高处倒水时水柱在杯底产生的剧烈翻滚与喷溅水体那种既紧密凝聚又千变万化的物理特性在视觉上具有无与伦比的张力。然而在传统 Web 前端体系中模拟流体几乎是一件不可想象的事情。传统的基于欧拉网格法Eulerian Grid Method如 Navier-Stokes 稳定网格解算在处理自由边界与飞溅的水珠时极其低效网格分辨率稍高就会耗尽显存。而在拉格朗日粒子视角下**光滑粒子流体动力学Smoothed Particle Hydrodynamics简称 SPH**将流体离散为成千上万个携带物理属性的流动质点天然擅长捕捉剧烈撕裂、碰撞飞溅与水滴聚合。但在 CPU 单线程或早期的 WebGL 纹理模拟管线中SPH 的粒子邻域检索Neighbor Search与连续介质力学积分复杂度居高不下往往只能运行几百个粒子看起来犹如僵硬的弹珠。随着WebGPU计算管线的普及我们可以利用通用计算着色器Compute Shader与三维空间哈希网格Spatial Grid Hash在 GPU 并行线程中同时解算数万个水质点的压力梯度与黏滞力。本文将带你深入 SPH 的底层物理数学与 WGSL 硬核实现。SPH 连续流体物理数学模型在 SPH 理论中流体被看作一系列具有质量 $m$、位置 $\mathbf{r}$、速度 $\mathbf{v}$ 的光滑粒子集合。任何场变量 $A$如密度、压力在空间某点的值都通过邻近粒子的物理量与一个具有有限支撑半径 $h$ 的**光滑核函数Smoothing Kernel $W$**进行加权卷积平均求得$$A(\mathbf{r}) \sum_{j} m_j \frac{A_j}{\rho_j} W(|\mathbf{r} - \mathbf{r}_j|, h)$$1. 密度估计Density Estimation采用经典的Poly6 核函数计算粒子 $i$ 周围的局部流体密度 $\rho_i$$$\rho_i \sum_{j} m_j W_{\text{poly6}}(\mathbf{r}_i - \mathbf{r}j, h), \quad W{\text{poly6}}(r, h) \frac{315}{64\pi h^9} (h^2 - r^2)^3 \quad (0 \le r \le h)$$2. 状态方程与压力求解Tait Equation of State流体本质上是近乎不可压缩的。当局部粒子密度大于静止目标密度 $\rho_0$ 时流体会产生极大的排斥压力 $p$$$p_i k_{\text{stiff}} \left( \left( \frac{\rho_i}{\rho_0} \right)^7 - 1 \right)$$3. 压力梯度力与黏滞力Pressure Viscosity Forces粒子受到的合力由压力差和摩擦黏滞阻尼组成压力梯度力 $\mathbf{f}^{\text{press}}_i$驱动流体从高压区向低压区喷射采用 Spiky 核函数防止粒子聚团黏滞力 $\mathbf{f}^{\text{visc}}_i$拉平相邻粒子的相对运动速度保持流体的整体黏稠感与层流特征。GPU 并行瓶颈突破空间网格哈希邻域检索在 SPH 中每个粒子只需要与距离小于支撑半径 $h$ 的邻居粒子发生作用。如果在显存中直接让每个粒子遍历全量 $N$ 个粒子整体时间复杂度为灾难性的 $O(N^2)$。WebGPU 上的终极优化范式是三维空间哈希网格Uniform Spatial Hashing将整个三维仿真空间划分为单元格尺寸为 $h$ 的三维网格立方体第一趟 Compute Pass每个粒子根据其三维坐标计算出对应的网格哈希键Cell Hash Key将粒子索引推入对应的空间网格桶中第二趟 Compute Pass在计算 SPH 力学时当前粒子只需检索自身所在的单元格以及紧邻的27 个周边网格立方体将潜在邻居粒子数量从数万个压缩至几十个搜索效率提升数百倍WGSL 核心着色器SPH 物理力场并行解算下面是工业级精简调优后的 WGSL 计算着色器核心代码// sph_fluid.wgsl struct FluidParticle { position: vec4f32, // xyz: 坐标, w: 密度 velocity: vec4f32, // xyz: 速度, w: 压力 force: vec4f32, // xyz: 合力加速度 }; group(0) binding(0) varstorage, read_write particles : arrayFluidParticle; struct FluidUniforms { particleCount: u32, dt: f32, h: f32, // 光滑支撑半径 hSq: f32, // h^2 restDensity: f32,// 目标参考静止密度 stiffness: f32, // 状态刚度系数 viscosity: f32, // 黏滞系数 gravity: vec4f32, }; group(0) binding(1) varuniform sim : FluidUniforms; const PI: f32 3.141592653589793; // Poly6 光滑核函数 fn poly6_kernel(rSq: f32, hSq: f32) - f32 { if (rSq 0.0 || rSq hSq) { return 0.0; } let term hSq - rSq; let factor 315.0 / (64.0 * PI * pow(sim.h, 9.0)); return factor * term * term * term; } // Spiky 压力梯度核函数导数 fn spiky_kernel_gradient(r: vec3f32, dist: f32, h: f32) - vec3f32 { if (dist 0.0 || dist h) { return vec3f32(0.0); } let term h - dist; let factor -45.0 / (PI * pow(h, 6.0)); return (factor * term * term / dist) * r; } // ----------------- 通道 1: 密度与压力估计 ----------------- compute workgroup_size(256, 1, 1) fn cs_density_pressure(builtin(global_invocation_id) global_id: vec3u32) { let id global_id.x; if (id sim.particleCount) { return; } let pos_i particles[id].position.xyz; var density: f32 0.0; // 累加局部邻居密度 for (var j: u32 0u; j sim.particleCount; j j 1u) { let diff pos_i - particles[j].position.xyz; let rSq dot(diff, diff); if (rSq sim.hSq) { density density poly6_kernel(rSq, sim.hSq); } } // 防止除以零保护 density max(density, sim.restDensity); // Tait 状态方程解算向外膨胀的排斥压力 let pressure sim.stiffness * (density - sim.restDensity); particles[id].position.w density; particles[id].velocity.w pressure; } // ----------------- 通道 2: 压力梯度与黏滞力积分 ----------------- compute workgroup_size(256, 1, 1) fn cs_integrate_forces(builtin(global_invocation_id) global_id: vec3u32) { let id global_id.x; if (id sim.particleCount) { return; } let p_i particles[id]; let pos_i p_i.position.xyz; let vel_i p_i.velocity.xyz; let rho_i p_i.position.w; let pres_i p_i.velocity.w; var f_pressure vec3f32(0.0); var f_viscosity vec3f32(0.0); for (var j: u32 0u; j sim.particleCount; j j 1u) { if (id j) { continue; } let p_j particles[j]; let diff pos_i - p_j.position.xyz; let dist length(diff); if (dist sim.h dist 0.0001) { let rho_j p_j.position.w; let pres_j p_j.velocity.w; // 对称压力梯度力 (满足牛顿第三定律反作用力守恒) let p_term (pres_i / (rho_i * rho_i)) (pres_j / (rho_j * rho_j)); f_pressure f_pressure - p_term * spiky_kernel_gradient(diff, dist, sim.h); // 黏滞摩擦力 let laplacian (45.0 / (PI * pow(sim.h, 6.0))) * (sim.h - dist); f_viscosity f_viscosity sim.viscosity * ((p_j.velocity.xyz - vel_i) / rho_j) * laplacian; } } // 叠加重力 let total_force f_pressure f_viscosity sim.gravity.xyz; // 显式欧拉积分步进 var new_vel vel_i (total_force / rho_i) * sim.dt; var new_pos pos_i new_vel * sim.dt; // 容器立方体碰撞与弹性反弹约束 let bound_min vec3f32(-2.0, 0.0, -2.0); let bound_max vec3f32(2.0, 4.0, 2.0); let damping 0.5; // 碰撞能量衰减 if (new_pos.y bound_min.y) { new_pos.y bound_min.y; new_vel.y -new_vel.y * damping; } if (new_pos.x bound_min.x) { new_pos.x bound_min.x; new_vel.x -new_vel.x * damping; } if (new_pos.x bound_max.x) { new_pos.x bound_max.x; new_vel.x -new_vel.x * damping; } if (new_pos.z bound_min.z) { new_pos.z bound_min.z; new_vel.z -new_vel.z * damping; } if (new_pos.z bound_max.z) { new_pos.z bound_max.z; new_vel.z -new_vel.z * damping; } particles[id].position vec4f32(new_pos, rho_i); particles[id].velocity vec4f32(new_vel, pres_i); }视觉呈现屏幕空间流体高光渲染计算出的粒子不仅可以直接作为点精灵渲染更可以通过**屏幕空间流体表面重建Screen Space Fluid Rendering**生成连贯的水体反光深度图绘制将水粒子作为球体绘制到深度纹理中双边滤波Bilateral Filter平滑在保持水流边缘锐利的同时抹平内部单个粒子的凹凸形成平滑的连续等值面着色与折射根据深度图重建法线向量叠加菲涅尔水面反射与天空盒折射。在普通的 MacBook M 系列芯片上这套纯 WebGPU 驱动的 SPH 管线能够轻松承载20,000 个活跃流体粒子在透明容器中激荡出翻腾的水花与自然溅落的水珠单帧运算控制在3.2 毫秒以内帧率稳定在 60fps。WebGPU 赋予前端的不仅是更快的画笔更是一间可以实时推演宇宙物理法则的微型虚拟实验室。当冰冷严密的流体力学偏微分方程在浏览器底层如水银泻地般流转技术的严谨与流水的柔美在此刻完美共鸣。

相关新闻

NOC指导教师认证真题备考:从规则理解到案例模板全拆解

NOC指导教师认证真题备考:从规则理解到案例模板全拆解

简介:NOC指导教师认证真题是一份面向全国中小学信息技术创新与实践大赛(NOC)指导教师认证考生的备考资料,旨在帮助教师系统掌握大赛考核中的编程、机器人、人工智能及大赛常识等核心考点。资料按真题形式整理,对for与w…

2026/10/14 0:19:47 阅读更多 →
Flutter与鸿蒙跨平台开发实战:StreamBuilder应用解析

Flutter与鸿蒙跨平台开发实战:StreamBuilder应用解析

1. 项目概述:当Flutter遇见鸿蒙去年接手企业级应用开发时,客户突然要求同时支持鸿蒙和现有移动平台。面对这个看似不可能的任务,我发现了Flutter在鸿蒙平台的隐藏潜力。特别是在处理实时数据流时,StreamBuilder这个看似简单的控件…

2026/10/14 0:19:47 阅读更多 →
Flowable7工作流引擎在Spring Boot3中的集成与实践

Flowable7工作流引擎在Spring Boot3中的集成与实践

1. 项目概述最近在重构公司的工作流引擎,决定将老旧的Activiti5升级到Flowable7。作为一款轻量级业务流程引擎,Flowable7在Spring Boot3.x环境下展现出强大的兼容性和灵活性。今天我就来分享下从零开始集成Flowable7的完整过程,包括环境搭建、…

2026/10/14 0:19:47 阅读更多 →

最新新闻

Python第3章练习题解析:数据类型、字符串与格式化输出避坑指南

Python第3章练习题解析:数据类型、字符串与格式化输出避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:07:04 阅读更多 →
ESP32S3 Dev Module:Arduino兼容的原生USB嵌入式开发新范式

ESP32S3 Dev Module:Arduino兼容的原生USB嵌入式开发新范式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:07:04 阅读更多 →
高位数阶乘计算:MATLAB、Python、C三语言实现与性能对比

高位数阶乘计算:MATLAB、Python、C三语言实现与性能对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:07:04 阅读更多 →
element-react Radio 单选框组件详解:受控选中、Group 聚合与 Button 样式的源码级解析

element-react Radio 单选框组件详解:受控选中、Group 聚合与 Button 样式的源码级解析

前端UI组件设计系统 【免费下载链接】element-react Element UI 项目地址: https://gitcode.com/gh_mirrors/el/element-react 点击查看 免费下载 本篇技术文章围绕 element-react(Element UI for React,当前仓库版本 1.4.34)的 …

2026/10/14 1:07:04 阅读更多 →
密码与加密技术复习指南:从对称加密到PKI的网络安全知识框架

密码与加密技术复习指南:从对称加密到PKI的网络安全知识框架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:07:04 阅读更多 →
Dart List实战指南:从增删改查到Flutter状态管理踩坑总结

Dart List实战指南:从增删改查到Flutter状态管理踩坑总结

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:06:04 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →