简介:这份资源是面向Unity3D开发者、尤其是流体特效初学者的Obi Fluid插件配套教程与可运行源码包,帮助读者掌握基于粒子技术的流体模拟方案,解决水、烟雾、火焰等特效在项目中的落地问题。压缩包共6个文件,约11KB,以js脚本、json配置、html页面、md说明及inscode工程文件为主,分别承担核心模拟逻辑、依赖与参数配置、可视化预览、学习笔记和项目结构说明等用途,体积轻量便于快速导入与二次修改。目前已有88人学习下载。内容围绕粒子产生与运动、重力粘度表面张力等物理属性、编辑器参数调节、脚本API自定义控制,以及项目规划、开发调试与性能优化展开,并附拓展学习资源与社区交流指引,可帮助读者从入门逐步过渡到独立开发流体特效。1. 流体模拟为什么总在“最后一帧”翻车做过流体特效的人大概都有过这种体验:预览时水流顺滑得像广告片,一按渲染,粒子突然炸开、液体穿模、帧率断崖式下跌。问题往往不在渲染器,而在解算器参数和发射体设置之间的耦合关系。Obi Fluid 是一套基于粒子(PBD/XPBD 思路)的实时流体解算方案,常见于 Unity 环境下的液体、粘稠物、气体近似模拟。它和传统 SPH 网格法不同,核心是把流体离散成一堆带约束的粒子,靠迭代求解密度约束和碰撞约束来表现不可压缩性。这套方案适合谁?做游戏内交互液体、短视频特效、工业演示动画的开发者,尤其是需要“可运行源码”直接改参数、看效果的人。这篇笔记就围绕 Obi Fluid 插件教程和可运行源码,把发射器、解算器、碰撞、渲染这条链路拆开讲,让你拿到工程后知道每个参数动了会发生什么。2. Obi Fluid 的解算链路与最小可运行工程2.1 粒子、约束与子步进:先搞懂它在算什么Obi Fluid 的每一帧大致走这么几步:发射器按速率生成粒子,解算器收集所有粒子并构建邻居列表,然后对密度约束、碰撞约束、摩擦约束做多次迭代,最后把粒子位置同步给渲染网格。这里的关键词是“子步进”(substeps)和“迭代次数”(iterations)。子步进是把一帧切成若干小时间段分别解算,迭代次数是在每个子步里反复修正约束。两者都调高,稳定性上升,但计算量近似线性增长。很多人第一次用会把迭代次数拉到很高,结果发现液体变得“发僵”,像果冻而不是水。原因是密度约束被过度满足,粒子间排斥过强,宏观上就失去了流动性。我一般会先把子步进设为 2 到 4,迭代次数 3 到 6,再根据粘度和流速微调。另一个容易忽略的是粒子半径(particle radius),它直接决定邻居搜索范围和密度约束的刚度。半径设大,液体显得“胖”且稳定;设小,细节多但容易穿透。提示:粒子半径和解算器里的“平滑半径”不是一回事,前者影响碰撞体积,后者影响密度核函数的作用范围,改一个要回头看另一个。2.2 从空场景到可运行:最小工程搭建步骤下面这套步骤是我在多个 Demo 里复现过的,不依赖特定版本号,只讲操作顺序和参数含义。假设你已经有一个 Unity 工程,并且拿到了 Obi Fluid 的可运行源码包。第一步,在场景里新建一个空物体,挂上 Obi Solver 组件。这个组件是解算中枢,所有发射器和碰撞体都要注册到它下面。第二步,创建 Obi Fluid Emitter,把它拖到 Solver 的发射器列表里。发射器决定粒子从哪来、以什么速度来。第三步,给发射器指定一个 Obi Fluid Blueprint,Blueprint 里存的是粒子分布和初始属性,相当于“流体模板”。第四步,加一个 Obi Collider 到地面或容器上,让粒子有东西可撞。第五步,加 Obi Fluid Renderer,把粒子渲染成连续表面。// 最小化配置 Obi Solver 的示例,挂在 Solver 物体上 using UnityEngine; using Obi; public class MinimalFluidSetup : MonoBehaviour { public ObiSolver solver; public ObiEmitter emitter; void Start() { // 子步进设为 3,迭代 4 次,兼顾稳定与性能 solver.substeps = 3; solver.iterations = 4; // 开启碰撞,否则粒子会穿过所有 Collider solver.collisionMaterial = new ObiCollisionMaterial(); solver.collisionMaterial.dynamicFriction = 0.4f; solver.collisionMaterial.staticFriction = 0.5f; // 发射器速度不要一上来就给大,先 1~2 单位/秒观察 emitter.speed = 1.5f; emitter.emissionRate = 200f; // 每秒粒子数,按需调 } }这段代码的逻辑说明:substeps和iterations是解算精度和开销的旋钮,先给中间值。collisionMaterial里的摩擦系数影响液体在斜面上的滞留行为,动态摩擦低则流得快,静态摩擦高则不容易启动。emissionRate和speed共同决定单位时间进入系统的粒子量和初速度,速率过高而解算迭代不足时,粒子会挤在一起产生爆炸式排斥。参数说明:substeps常见范围 1 到 6,移动端建议 1 到 2;iterations常见 2 到 8;dynamicFriction0 到 1,水一般 0.1 到 0.3,粘液 0.5 以上;emissionRate取决于粒子半径和容器尺寸,没有万能值,先小后大。2.3 渲染网格与粒子不同步:表面张力参数怎么设粒子解算完,渲染器要把离散点变成连续表面。Obi Fluid Renderer 通常提供两种模式:基于粒子元球(metaball)的平滑,和基于网格重建的平滑。元球模式性能好,但边缘容易“糊”;网格重建细节多,但开销大。表面张力(surface tension)参数在这里起作用:它让粒子在表面处互相吸引,形成液滴和液面收缩效果。我一般这样调:先关掉表面张力,看基础流动是否正常;然后从 0.1 开始加,每次加 0.05,观察液滴是否成形、液面是否过度收缩。表面张力过高,液体会缩成球状,失去铺展性;过低,液面散开像沙堆。渲染平滑半径(smoothing radius)要和粒子半径匹配,一般设为粒子半径的 1.5 到 2 倍,否则会出现“空心”或“过厚”的视觉 bug。注意:渲染参数不影响物理,只影响观感。调物理时先把渲染平滑关掉或调低,避免视觉误导你对解算稳定性的判断。3. 发射器、碰撞与粘度:三个最常改的参数区3.1 发射器形状与方向:别让粒子一出生就打架发射器不是只有“点”和“圆”两种。常见形状包括球形、盒形、环形、网格形,每种都有对应的尺寸和方向参数。新手最容易犯的错是把发射器体积设得很大,同时发射速率很高,结果粒子在出生位置就重叠,密度约束瞬间产生巨大排斥力,表现为“喷发”或“炸开”。正确做法是让发射器体积和粒子半径匹配:如果粒子半径是 0.1,发射器最小维度不要小于 0.2,否则粒子挤在一条线上。方向参数决定初速度矢量。做水龙头效果时,发射器朝下,速度 1 到 3;做喷泉时,速度 5 到 10,但要把子步进调高,否则高速粒子会穿透薄壁碰撞体。我习惯在发射器上挂一个脚本,运行时用滑条调速度和速率,方便找临界点。// 运行时动态调整发射器,方便找参数临界点 using UnityEngine; using Obi; public class EmitterTuner : MonoBehaviour { public ObiEmitter emitter; [Range(0f, 10f)] public float speed = 2f; [Range(0f, 1000f)] public float rate = 300f; void Update() { emitter.speed = speed; emitter.emissionRate = rate; // 当速率超过 600 且速度超过 6 时,打印提醒检查子步进 if (rate > 600f && speed > 6f) { Debug.LogWarning("高发射率+高速度,建议 substeps >= 4"); } } }逻辑说明:把参数暴露到 Inspector,运行时拖动滑条观察粒子行为。警告条件是我在多个场景里总结的经验阈值,超过后穿透和爆炸概率明显上升。参数说明:speed单位是 Unity 单位/秒,rate是粒子/秒,两者乘积近似单位时间进入系统的粒子总数,要和解算器承载能力匹配。3.2 碰撞体厚度与摩擦:液体穿模的根源粒子穿透碰撞体,九成原因是碰撞体太薄或解算子步进不够。Obi Collider 有一个“厚度”概念,实际是碰撞距离阈值。如果容器壁厚度小于粒子半径,粒子在高速下会直接跳过碰撞检测。解决办法不是无限加厚,而是把容器壁厚度设为粒子半径的 1.5 到 2 倍,同时把子步进提到 3 以上。摩擦参数分动态和静态。动态摩擦影响滑动,静态摩擦影响启动。做“液体沿斜面流下”时,动态摩擦 0.1 到 0.2 比较像水;做“蜂蜜缓慢下滑”时,动态摩擦 0.6 以上,静态摩擦 0.8 以上。注意摩擦是成对出现的:碰撞体材质和粒子材质都有摩擦系数,最终效果是两者某种组合,不是简单相加。提示:如果液体在平底容器里“抖”个不停,先检查碰撞体是不是有微小缝隙或重叠面,再检查静态摩擦是否过低。3.3 粘度模型:让水像水,让蜂蜜像蜂蜜Obi Fluid 的粘度通常通过“粘度系数”或“粘性约束”来实现。数值低时,粒子间切向相对运动阻力小,表现像水;数值高时,粒子被“粘”在一起,表现像油或蜂蜜。但粘度不是越高越好:过高会让解算器需要更多迭代才能收敛,否则液体变得僵硬且抖动。我一般把粘度分三档来试:0.01 到 0.05 接近水,0.1 到 0.3 接近食用油,0.5 以上接近糖浆。每调高一档,迭代次数加 1 到 2,子步进加 1。如果发现液体在容器里“爬壁”,那是粘度和表面张力共同作用的结果,适当降低表面张力或提高重力补偿。// 按档位设置粘度与配套解算参数 using UnityEngine; using Obi; public class ViscosityPreset : MonoBehaviour { public ObiSolver solver; public ObiFluidEmitter emitter; public enum FluidType { Water, Oil, Syrup } public FluidType fluidType = FluidType.Water; void ApplyPreset() { switch (fluidType) { case FluidType.Water: emitter.viscosity = 0.02f; solver.iterations = 4; solver.substeps = 3; break; case FluidType.Oil: emitter.viscosity = 0.2f; solver.iterations = 5; solver.substeps = 4; break; case FluidType.Syrup: emitter.viscosity = 0.6f; solver.iterations = 7; solver.substeps = 5; break; } } }逻辑说明:粘度改变后,约束系统的刚度分布变了,需要更多迭代来收敛。这段代码把粘度和解算参数绑定成预设,避免只改粘度导致抖动。参数说明:viscosity是相对值,不同 Obi 版本可能范围不同,以实际插件面板为准;iterations和substeps的配套关系是经验值,移动端可整体减 1 到 2。4. 避坑与排查:五个真实翻车现场4.1 粒子数量暴涨导致帧率骤降现象:运行几秒后帧率从 60 掉到 10,粒子数持续上升。原因:发射器没有设置最大粒子数或生命周期,粒子不断生成但不销毁。解决:给发射器设maxParticles,并启用粒子的生命周期回收;同时检查解算器的粒子上限,超出后要么停止发射,要么回收最旧粒子。4.2 液体在容器底部抖动不止现象:静止后液面仍在高频抖动。原因:密度约束刚度过高或迭代次数过多,粒子在平衡位置附近过冲。解决:降低迭代次数 1 到 2,或稍微增大粒子半径,让邻居搜索更平滑;也可以开启解算器的阻尼选项。4.3 高速水流穿透薄壁现象:水柱直接穿过容器壁,没有碰撞。原因:子步进不足,粒子在单个时间步内移动距离超过碰撞体厚度。解决:提高子步进到 4 以上,或加厚碰撞体;如果性能不允许,降低发射速度。4.4 渲染表面出现空洞或撕裂现象:液体表面有随机孔洞,粒子明明很密。原因:渲染平滑半径小于粒子间距,元球场没有融合。解决:增大渲染平滑半径到粒子半径的 1.5 到 2 倍,或提高发射率让粒子更密。4.5 粘度调高后液体变成“橡皮泥”现象:液体不流动,像固体一样保持形状。原因:粘度约束过强,迭代次数不足以让系统松弛。解决:降低粘度或提高迭代次数;如果必须高粘度,把子步进也提高,并适当降低重力影响。注意:排查顺序建议从发射器到解算器再到渲染,因为渲染问题最容易掩盖物理问题。先把渲染平滑关掉,看粒子本身行为是否正常。5. 进阶:用脚本批量测试参数并导出对比到这一步,你应该能跑通一个基本流体场景了。但真正做项目时,靠手动拖滑条找参数太慢。我一般会写一个批量测试脚本,在运行时遍历几组子步进、迭代次数和粘度组合,每组的稳定时间、粒子数、帧率记录下来,导出成表格对比。这样能在半小时内锁定适合当前场景的参数区间,而不是靠玄学调一整天。// 批量测试参数组合,记录稳定时间和帧率 using UnityEngine; using Obi; using System.Collections; using System.Collections.Generic; public class FluidParamSweep : MonoBehaviour { public ObiSolver solver; public ObiEmitter emitter; public float testDuration = 5f; IEnumerator RunSweep() { int[] substepsList = { 2, 3, 4 }; int[] iterationsList = { 3, 5, 7 }; float[] viscosityList = { 0.02f, 0.2f, 0.6f }; foreach (int ss in substepsList) { foreach (int it in iterationsList) { foreach (float vis in viscosityList) { solver.substeps = ss; solver.iterations = it; emitter.viscosity = vis; // 重置场景,清空粒子 solver.ClearParticles(); yield return new WaitForSeconds(0.5f); float startTime = Time.time; float minFps = 999f; while (Time.time - startTime < testDuration) { float fps = 1f / Time.deltaTime; if (fps < minFps) minFps = fps; yield return null; } Debug.Log($"substeps={ss}, iterations={it}, vis={vis}, minFps={minFps:F1}, particles={solver.particleCount}"); } } } } void Start() { StartCoroutine(RunSweep()); } }逻辑说明:这段脚本按三层循环遍历参数组合,每组跑固定时长,记录最低帧率和粒子数。ClearParticles用于每组开始前重置状态,避免上一组残留影响。参数说明:testDuration根据场景复杂度调整,简单场景 3 秒够,复杂场景 10 秒;minFps比平均帧率更能反映卡顿,因为流体爆炸往往发生在某一瞬间。导出对比时,我习惯把日志复制到表格里,按最低帧率排序,再人工看粒子数是否在合理范围。如果某组帧率达标但粒子数异常少,说明发射或回收有问题,不能只看帧率。最终选定的参数还要在实际渲染下再看一遍,因为渲染开销可能让原本达标的组合掉帧。最后一个习惯:每次改完参数,先保存一份配置到 ScriptableObject 或 JSON,不要只留在场景里。流体调参是反复迭代的过程,没有后悔药,但有存档就能随时回退。希望帮到你。本文还有配套的精品资源,点击获取
简介:这份资源是面向Unity3D开发者、尤其是流体特效初学者的Obi Fluid插件配套教程与可运行源码包,帮助读者掌握基于粒子技术的流体模拟方案,解决水、烟雾、火焰等特效在项目中的落地问题。压缩包共6个文件,约11KB,以js脚本、json配置、html页面、md说明及inscode工程文件为主,分别承担核心模拟逻辑、依赖与参数配置、可视化预览、学习笔记和项目结构说明等用途,体积轻量便于快速导入与二次修改。目前已有88人学习下载。内容围绕粒子产生与运动、重力粘度表面张力等物理属性、编辑器参数调节、脚本API自定义控制,以及项目规划、开发调试与性能优化展开,并附拓展学习资源与社区交流指引,可帮助读者从入门逐步过渡到独立开发流体特效。1. 流体模拟为什么总在“最后一帧”翻车做过流体特效的人大概都有过这种体验:预览时水流顺滑得像广告片,一按渲染,粒子突然炸开、液体穿模、帧率断崖式下跌。问题往往不在渲染器,而在解算器参数和发射体设置之间的耦合关系。Obi Fluid 是一套基于粒子(PBD/XPBD 思路)的实时流体解算方案,常见于 Unity 环境下的液体、粘稠物、气体近似模拟。它和传统 SPH 网格法不同,核心是把流体离散成一堆带约束的粒子,靠迭代求解密度约束和碰撞约束来表现不可压缩性。这套方案适合谁?做游戏内交互液体、短视频特效、工业演示动画的开发者,尤其是需要“可运行源码”直接改参数、看效果的人。这篇笔记就围绕 Obi Fluid 插件教程和可运行源码,把发射器、解算器、碰撞、渲染这条链路拆开讲,让你拿到工程后知道每个参数动了会发生什么。2. Obi Fluid 的解算链路与最小可运行工程2.1 粒子、约束与子步进:先搞懂它在算什么Obi Fluid 的每一帧大致走这么几步:发射器按速率生成粒子,解算器收集所有粒子并构建邻居列表,然后对密度约束、碰撞约束、摩擦约束做多次迭代,最后把粒子位置同步给渲染网格。这里的关键词是“子步进”(substeps)和“迭代次数”(iterations)。子步进是把一帧切成若干小时间段分别解算,迭代次数是在每个子步里反复修正约束。两者都调高,稳定性上升,但计算量近似线性增长。很多人第一次用会把迭代次数拉到很高,结果发现液体变得“发僵”,像果冻而不是水。原因是密度约束被过度满足,粒子间排斥过强,宏观上就失去了流动性。我一般会先把子步进设为 2 到 4,迭代次数 3 到 6,再根据粘度和流速微调。另一个容易忽略的是粒子半径(particle radius),它直接决定邻居搜索范围和密度约束的刚度。半径设大,液体显得“胖”且稳定;设小,细节多但容易穿透。提示:粒子半径和解算器里的“平滑半径”不是一回事,前者影响碰撞体积,后者影响密度核函数的作用范围,改一个要回头看另一个。2.2 从空场景到可运行:最小工程搭建步骤下面这套步骤是我在多个 Demo 里复现过的,不依赖特定版本号,只讲操作顺序和参数含义。假设你已经有一个 Unity 工程,并且拿到了 Obi Fluid 的可运行源码包。第一步,在场景里新建一个空物体,挂上 Obi Solver 组件。这个组件是解算中枢,所有发射器和碰撞体都要注册到它下面。第二步,创建 Obi Fluid Emitter,把它拖到 Solver 的发射器列表里。发射器决定粒子从哪来、以什么速度来。第三步,给发射器指定一个 Obi Fluid Blueprint,Blueprint 里存的是粒子分布和初始属性,相当于“流体模板”。第四步,加一个 Obi Collider 到地面或容器上,让粒子有东西可撞。第五步,加 Obi Fluid Renderer,把粒子渲染成连续表面。// 最小化配置 Obi Solver 的示例,挂在 Solver 物体上 using UnityEngine; using Obi; public class MinimalFluidSetup : MonoBehaviour { public ObiSolver solver; public ObiEmitter emitter; void Start() { // 子步进设为 3,迭代 4 次,兼顾稳定与性能 solver.substeps = 3; solver.iterations = 4; // 开启碰撞,否则粒子会穿过所有 Collider solver.collisionMaterial = new ObiCollisionMaterial(); solver.collisionMaterial.dynamicFriction = 0.4f; solver.collisionMaterial.staticFriction = 0.5f; // 发射器速度不要一上来就给大,先 1~2 单位/秒观察 emitter.speed = 1.5f; emitter.emissionRate = 200f; // 每秒粒子数,按需调 } }这段代码的逻辑说明:substeps和iterations是解算精度和开销的旋钮,先给中间值。collisionMaterial里的摩擦系数影响液体在斜面上的滞留行为,动态摩擦低则流得快,静态摩擦高则不容易启动。emissionRate和speed共同决定单位时间进入系统的粒子量和初速度,速率过高而解算迭代不足时,粒子会挤在一起产生爆炸式排斥。参数说明:substeps常见范围 1 到 6,移动端建议 1 到 2;iterations常见 2 到 8;dynamicFriction0 到 1,水一般 0.1 到 0.3,粘液 0.5 以上;emissionRate取决于粒子半径和容器尺寸,没有万能值,先小后大。2.3 渲染网格与粒子不同步:表面张力参数怎么设粒子解算完,渲染器要把离散点变成连续表面。Obi Fluid Renderer 通常提供两种模式:基于粒子元球(metaball)的平滑,和基于网格重建的平滑。元球模式性能好,但边缘容易“糊”;网格重建细节多,但开销大。表面张力(surface tension)参数在这里起作用:它让粒子在表面处互相吸引,形成液滴和液面收缩效果。我一般这样调:先关掉表面张力,看基础流动是否正常;然后从 0.1 开始加,每次加 0.05,观察液滴是否成形、液面是否过度收缩。表面张力过高,液体会缩成球状,失去铺展性;过低,液面散开像沙堆。渲染平滑半径(smoothing radius)要和粒子半径匹配,一般设为粒子半径的 1.5 到 2 倍,否则会出现“空心”或“过厚”的视觉 bug。注意:渲染参数不影响物理,只影响观感。调物理时先把渲染平滑关掉或调低,避免视觉误导你对解算稳定性的判断。3. 发射器、碰撞与粘度:三个最常改的参数区3.1 发射器形状与方向:别让粒子一出生就打架发射器不是只有“点”和“圆”两种。常见形状包括球形、盒形、环形、网格形,每种都有对应的尺寸和方向参数。新手最容易犯的错是把发射器体积设得很大,同时发射速率很高,结果粒子在出生位置就重叠,密度约束瞬间产生巨大排斥力,表现为“喷发”或“炸开”。正确做法是让发射器体积和粒子半径匹配:如果粒子半径是 0.1,发射器最小维度不要小于 0.2,否则粒子挤在一条线上。方向参数决定初速度矢量。做水龙头效果时,发射器朝下,速度 1 到 3;做喷泉时,速度 5 到 10,但要把子步进调高,否则高速粒子会穿透薄壁碰撞体。我习惯在发射器上挂一个脚本,运行时用滑条调速度和速率,方便找临界点。// 运行时动态调整发射器,方便找参数临界点 using UnityEngine; using Obi; public class EmitterTuner : MonoBehaviour { public ObiEmitter emitter; [Range(0f, 10f)] public float speed = 2f; [Range(0f, 1000f)] public float rate = 300f; void Update() { emitter.speed = speed; emitter.emissionRate = rate; // 当速率超过 600 且速度超过 6 时,打印提醒检查子步进 if (rate > 600f && speed > 6f) { Debug.LogWarning("高发射率+高速度,建议 substeps >= 4"); } } }逻辑说明:把参数暴露到 Inspector,运行时拖动滑条观察粒子行为。警告条件是我在多个场景里总结的经验阈值,超过后穿透和爆炸概率明显上升。参数说明:speed单位是 Unity 单位/秒,rate是粒子/秒,两者乘积近似单位时间进入系统的粒子总数,要和解算器承载能力匹配。3.2 碰撞体厚度与摩擦:液体穿模的根源粒子穿透碰撞体,九成原因是碰撞体太薄或解算子步进不够。Obi Collider 有一个“厚度”概念,实际是碰撞距离阈值。如果容器壁厚度小于粒子半径,粒子在高速下会直接跳过碰撞检测。解决办法不是无限加厚,而是把容器壁厚度设为粒子半径的 1.5 到 2 倍,同时把子步进提到 3 以上。摩擦参数分动态和静态。动态摩擦影响滑动,静态摩擦影响启动。做“液体沿斜面流下”时,动态摩擦 0.1 到 0.2 比较像水;做“蜂蜜缓慢下滑”时,动态摩擦 0.6 以上,静态摩擦 0.8 以上。注意摩擦是成对出现的:碰撞体材质和粒子材质都有摩擦系数,最终效果是两者某种组合,不是简单相加。提示:如果液体在平底容器里“抖”个不停,先检查碰撞体是不是有微小缝隙或重叠面,再检查静态摩擦是否过低。3.3 粘度模型:让水像水,让蜂蜜像蜂蜜Obi Fluid 的粘度通常通过“粘度系数”或“粘性约束”来实现。数值低时,粒子间切向相对运动阻力小,表现像水;数值高时,粒子被“粘”在一起,表现像油或蜂蜜。但粘度不是越高越好:过高会让解算器需要更多迭代才能收敛,否则液体变得僵硬且抖动。我一般把粘度分三档来试:0.01 到 0.05 接近水,0.1 到 0.3 接近食用油,0.5 以上接近糖浆。每调高一档,迭代次数加 1 到 2,子步进加 1。如果发现液体在容器里“爬壁”,那是粘度和表面张力共同作用的结果,适当降低表面张力或提高重力补偿。// 按档位设置粘度与配套解算参数 using UnityEngine; using Obi; public class ViscosityPreset : MonoBehaviour { public ObiSolver solver; public ObiFluidEmitter emitter; public enum FluidType { Water, Oil, Syrup } public FluidType fluidType = FluidType.Water; void ApplyPreset() { switch (fluidType) { case FluidType.Water: emitter.viscosity = 0.02f; solver.iterations = 4; solver.substeps = 3; break; case FluidType.Oil: emitter.viscosity = 0.2f; solver.iterations = 5; solver.substeps = 4; break; case FluidType.Syrup: emitter.viscosity = 0.6f; solver.iterations = 7; solver.substeps = 5; break; } } }逻辑说明:粘度改变后,约束系统的刚度分布变了,需要更多迭代来收敛。这段代码把粘度和解算参数绑定成预设,避免只改粘度导致抖动。参数说明:viscosity是相对值,不同 Obi 版本可能范围不同,以实际插件面板为准;iterations和substeps的配套关系是经验值,移动端可整体减 1 到 2。4. 避坑与排查:五个真实翻车现场4.1 粒子数量暴涨导致帧率骤降现象:运行几秒后帧率从 60 掉到 10,粒子数持续上升。原因:发射器没有设置最大粒子数或生命周期,粒子不断生成但不销毁。解决:给发射器设maxParticles,并启用粒子的生命周期回收;同时检查解算器的粒子上限,超出后要么停止发射,要么回收最旧粒子。4.2 液体在容器底部抖动不止现象:静止后液面仍在高频抖动。原因:密度约束刚度过高或迭代次数过多,粒子在平衡位置附近过冲。解决:降低迭代次数 1 到 2,或稍微增大粒子半径,让邻居搜索更平滑;也可以开启解算器的阻尼选项。4.3 高速水流穿透薄壁现象:水柱直接穿过容器壁,没有碰撞。原因:子步进不足,粒子在单个时间步内移动距离超过碰撞体厚度。解决:提高子步进到 4 以上,或加厚碰撞体;如果性能不允许,降低发射速度。4.4 渲染表面出现空洞或撕裂现象:液体表面有随机孔洞,粒子明明很密。原因:渲染平滑半径小于粒子间距,元球场没有融合。解决:增大渲染平滑半径到粒子半径的 1.5 到 2 倍,或提高发射率让粒子更密。4.5 粘度调高后液体变成“橡皮泥”现象:液体不流动,像固体一样保持形状。原因:粘度约束过强,迭代次数不足以让系统松弛。解决:降低粘度或提高迭代次数;如果必须高粘度,把子步进也提高,并适当降低重力影响。注意:排查顺序建议从发射器到解算器再到渲染,因为渲染问题最容易掩盖物理问题。先把渲染平滑关掉,看粒子本身行为是否正常。5. 进阶:用脚本批量测试参数并导出对比到这一步,你应该能跑通一个基本流体场景了。但真正做项目时,靠手动拖滑条找参数太慢。我一般会写一个批量测试脚本,在运行时遍历几组子步进、迭代次数和粘度组合,每组的稳定时间、粒子数、帧率记录下来,导出成表格对比。这样能在半小时内锁定适合当前场景的参数区间,而不是靠玄学调一整天。// 批量测试参数组合,记录稳定时间和帧率 using UnityEngine; using Obi; using System.Collections; using System.Collections.Generic; public class FluidParamSweep : MonoBehaviour { public ObiSolver solver; public ObiEmitter emitter; public float testDuration = 5f; IEnumerator RunSweep() { int[] substepsList = { 2, 3, 4 }; int[] iterationsList = { 3, 5, 7 }; float[] viscosityList = { 0.02f, 0.2f, 0.6f }; foreach (int ss in substepsList) { foreach (int it in iterationsList) { foreach (float vis in viscosityList) { solver.substeps = ss; solver.iterations = it; emitter.viscosity = vis; // 重置场景,清空粒子 solver.ClearParticles(); yield return new WaitForSeconds(0.5f); float startTime = Time.time; float minFps = 999f; while (Time.time - startTime < testDuration) { float fps = 1f / Time.deltaTime; if (fps < minFps) minFps = fps; yield return null; } Debug.Log($"substeps={ss}, iterations={it}, vis={vis}, minFps={minFps:F1}, particles={solver.particleCount}"); } } } } void Start() { StartCoroutine(RunSweep()); } }逻辑说明:这段脚本按三层循环遍历参数组合,每组跑固定时长,记录最低帧率和粒子数。ClearParticles用于每组开始前重置状态,避免上一组残留影响。参数说明:testDuration根据场景复杂度调整,简单场景 3 秒够,复杂场景 10 秒;minFps比平均帧率更能反映卡顿,因为流体爆炸往往发生在某一瞬间。导出对比时,我习惯把日志复制到表格里,按最低帧率排序,再人工看粒子数是否在合理范围。如果某组帧率达标但粒子数异常少,说明发射或回收有问题,不能只看帧率。最终选定的参数还要在实际渲染下再看一遍,因为渲染开销可能让原本达标的组合掉帧。最后一个习惯:每次改完参数,先保存一份配置到 ScriptableObject 或 JSON,不要只留在场景里。流体调参是反复迭代的过程,没有后悔药,但有存档就能随时回退。希望帮到你。本文还有配套的精品资源,点击获取