
1. 动手之前先把路线选对Max 里做液体的三条技术路径做液体这件事在 3ds Max 里从来不是“装个插件按个按钮”这么简单。同样是水瀑布和杯子里溢出的饮料底层用到的模拟思路、参数区间、甚至渲染策略几乎完全相反。我带过几个项目最常返工的情况是前期随手选了个方案做到一半发现分辨率拉不上去或者干脆物理尺度就是错的只能推倒重来。所以在打开模拟面板之前先把三条主流路线摆清楚比什么参数预设都重要。所谓“3ds Max 流体模拟”本质上是你借助一套基于物理的求解器让一堆粒子或者一个体素网格在重力、压力、黏性、表面张力的共同作用下自然演化最后把结果转成可渲染的几何体。它能做的事情从几十米高的瀑布冲刷岩壁到玻璃杯沿着一圈薄薄的水膜缓缓溢出中间跨度极大。适合看这篇内容的是已经会做基础建模和动画、但对液体模拟没有系统认知的中级使用者如果你完全没接触过动力学也能看懂思路照着步骤能把第一个像样的水做出来。1.1 体素网格流体、粒子网格化、程序化假水各自解决什么问题第一条路线是基于体素的网格流体业内通常叫它“网格法”或者“体积法”。它的工作方式是先把一个长方体区域切成无数个小立方体每个格子记录压力、速度、密度这些标量然后按纳维-斯托克斯方程逐帧推进。优点是液体内部的连续性天然成立不会出现粒子疏密不均的“噪点感”缺点是对内存和 CPU 极其贪婪分辨率每翻一倍计算量大约翻八倍。第二条是粒子法包括 SPH 和 FLIP 这两大流派。粒子法把液体离散成一堆带质量的点靠粒子间的核函数计算相互作用。FLIP 的巧妙之处是“粒子负责搬运网格负责解压力”所以它既有粒子的飞溅细节又有网格的稳定体积。做瀑布这种需要大量飞沫和破碎的场景FLIP 类方案通常是首选。第三条是程序化假水。用变形器、位移动画、贴图滚动、Blob 网格或者简单的刚体加约束去“演”液体。听起来很土但在远景、背景、时间极紧的镜头里它的性价比高到离谱。我做过一个三十秒的产品短片背景那条小溪全程是滚动贴图加水花粒子客户看完没提出任何问题。这里的关键认知是三条路线没有优劣只有匹配度。你拿网格法去算一杯水的飞溅会得到一坨糊状的团块你拿粒子法去算一整条河机器能跑到天亮。选错路线后面所有参数调整都只是无效努力。1.2 用场景的“物理尺度”反推方案而不是用喜好决定方案我习惯在开工前先用一张表把项目里所有液体镜头过一遍把“画面里最小的可见细节”作为分水岭。这个判断标准比任何参数教程都实用因为决定精度的永远不是软件而是观众的眼睛能看清多细的东西。场景类型典型物理尺度最小可见细节推荐路线备注远景瀑布、大坝泄流20 至 60 米5 至 10 厘米体素网格 程序化白水主体用低分辨率飞沫单独做粒子层中景溪流、水渠3 至 10 米1 至 3 厘米FLIP 粒子 网格化兼顾体积稳定和破碎细节杯中溢出、倒酒5 至 15 厘米1 至 3 毫米高分辨率体素或小规模 FLIP表面张力权重必须给足水滴特写、镜头级1 至 5 厘米0.2 至 1 毫米粒子法 手工补帧纯模拟往往需要后期修饰背景装饰水面不限不参与近景程序化假水周期位移 法线贴图这张表最右边那列是我的私货远景和背景永远不要用和近景一样的方案。很多人图省事一个场景一套参数跑到底结果是近景不够细、远景浪费算力。分层做远景用便宜的近景用贵的这才是能按时交片的做法。还有一点必须提前确认场景单位。3ds Max 默认可能是英寸也可能是通用单位。如果单位没设成米重力加速度 9.8 这个数字就完全没意义你的水看起来会像在月球上慢动作飘。我一般开场第一件事就是进单位设置把系统单位和显示单位统一改成米然后才去建模。-- 开场检查把系统单位统一成米避免重力尺度错乱 units.DisplayType #Metric units.MetricType #Meters units.SystemType #Meters units.SystemScale 1.0 -- 顺手检查当前帧率和时间配置液体对帧率相当敏感 local cfg timeConfiguration format 当前帧率: % fps, 起始帧: %, 结束帧: %\n cfg.framerate cfg.animRange.start cfg.animRange.end这段脚本没什么技术含量但它救过我两次。有一次接手别人的场景继续做水怎么调都像糖浆往下淌最后发现单位是厘米但重力按米算实际重力被放大了 100 倍粘度相对就完全错位了。2. 参数背后的物理把数字调对水才像水液体模拟面板上那一堆滑块看着像玄学其实每一个都对应现实世界里一个可以查到的物理量。理解了这个对应关系你就不用再抄别人的参数表了因为你知道了该往哪个方向调。这一节我把最影响观感的几个参数拆开讲包括它们物理上是什么、在软件里通常被怎么归一化、以及实际调到什么区间比较合理。先说明一个前提绝大多数流体求解器对物理量做了无量纲化处理。也就是说你看到的黏度 0.05 并不等于 0.05 帕秒它只是相对于网格尺寸和时间步长的一个系数。所以下面给出的区间都是“常见实践中的相对值”不同软件之间不能直接平移必须结合自己的场景尺度和体素大小重新校准。这一点特别重要我见过太多人把网上抄来的参数直接怼进去结果完全不对然后归咎于软件不行。2.1 分辨率与体素尺寸精度和时间的汇率流体模拟里最贵的一个参数就是分辨率。它的含义是把模拟区域沿最长边切成多少格或者直接指定每个体素的边长。两者是一回事换算关系很简单体素尺寸 模拟域最长边长度 ÷ 分辨率举例做一条 30 米长的瀑布模拟域长度 30 米如果分辨率设成 600那么每个体素边长就是 30 ÷ 600 0.05 米也就是 5 厘米。这意味着任何比 5 厘米还细的结构求解器根本“看不见”会在计算中被抹平。这个数字直接决定了两件事。第一是细节下限水柱撞击岩石产生的薄水膜、沿着杯壁滑落的细流如果比体素还薄就会被吞掉。第二是计算成本体素数 分辨率三次方三维方向600 分辨率意味着约 2.16 亿个格子即使只算液体占据的部分内存占用也相当可观。我的经验法则是先按最小可见细节的 1/2 来定体素尺寸。也就是说如果你希望画面里能看清 2 厘米的水花体素就定 1 厘米。宁可多花一倍时间也不要做到后期发现细节全糊了再重算重算的代价远大于第一次就算对。这里有个很常见的误区有人以为把分辨率调高就万事大吉。实际上分辨率高了以后表面张力、黏度这些相对参数的“手感”会变原本合适的数值突然让水变得像胶水。正确做法是提高分辨率时同步微调黏度一般往小的方向走一点因为高分辨率下数值耗散更小液体本身就显得更“稠”。2.2 黏度、表面张力、密度三件套这三个参数决定了液体“是什么”而不是“怎么动”。密度相对简单水是 1000 kg/m³大多数求解器直接用这个值或者归一化成 1。它主要影响液体和刚体的相互作用比如木块会不会浮起来、浮多高。做漂浮物的时候密度必须给准否则木头会像秤砣一样沉底。黏度描述的是液体内部的“摩擦阻力”。水的运动黏度在 20 摄氏度大约是 1×10⁻⁶ m²/s属于非常低的水平。在软件里如果黏度给到 0.1 以上水就会开始像稀糖浆给到 1 以上基本就是蜂蜜了。做瀑布和倒水黏度要在非常接近 0 的区间里找常见范围是 0.001 到 0.01 这个量级具体看求解器怎么归一化。表面张力是最容易被忽略、又最能决定“像不像水”的参数。它让液面倾向于收缩成最小的表面积所以小水滴是球形的大液面上会形成细微的弧度杯口的水会鼓起来一点才溢出去。水的表面张力系数是 0.0728 N/m20 摄氏度但软件里通常用一个无量纲系数表示常见区间是 0.01 到 0.1。关键洞察在这里表面张力的相对影响力随尺度变小而急剧增强。一个 10 米高的瀑布表面张力对整体形态几乎无影响一个 1 厘米的水滴表面张力直接决定它是不是球。所以同一套参数做大场景和做小场景必须分开调。我一般的做法是大场景表面张力给 0 到 0.02几乎不用杯中溢出给到 0.05 到 0.15要能明显看到液面鼓起和挂壁。参数物理量水的真实值软件常见相对区间调整方向提示密度ρ1000 kg/m³通常直接用影响浮力不影响流动形态运动黏度ν约 1×10⁻⁶ m²/s0.001 至 0.01越大越稠高分辨率时下调表面张力σ0.0728 N/m0.01 至 0.15小尺度加大大尺度趋零重力g9.8 m/s²直接用单位错了这里最先出问题2.3 碰撞体、厚度与边界条件白模阶段的“隐形地基”液体模拟翻车有一半原因不在液体参数而在碰撞体。求解器判断“液体碰到固体”的方式是检查体素格子里有没有被碰撞体占据所以碰撞体必须是封闭的实体而且厚度要大于体素尺寸。薄薄一片单面模型在求解器眼里就是个筛子水会直接穿过去。我总结的硬性要求碰撞体厚度至少是体素尺寸的 2 倍。如果体素是 1 厘米那杯子壁最薄也得 2 厘米。现实中的玻璃杯壁厚可能只有 3 毫米但你在建模阶段必须把它做厚等模拟算完、网格化完成后再换成薄的渲染模型。这是行业里非常标准的“模拟用厚模、渲染用薄模”双模型做法。另外碰撞体本身不能有动画抖动。如果你的杯子在第 10 帧到第 11 帧之间位置偏移了 0.1 毫米比如用约束或者手动打帧没对齐求解器会认为杯子以极高速撞向液体瞬间产生巨大的压力尖峰画面直接炸开。我修改别人的工程时第一步永远是检查碰撞体曲线的插值方式把所有不必要的关键帧改成线性或者直接删掉多余帧。提示模拟域模拟范围的那个方框本身也是一个边界条件。域设得太紧液体会被边界“弹”回来形成不自然的堆积设得太松等于白白浪费一半算力。经验是域的边缘距离液体最远可能到达的位置留出 2 到 3 个体素即可不需要更多。3. 大尺度瀑布模拟从白模到白水的完整流程瀑布是流体模拟里最能体现“工业化分工”的题材。它的问题不在于单点技术难度而在于体量几十米的水体、大量的飞溅、持续的湍流任何一个环节设计不当都可能让缓存膨胀到硬盘装不下。我把这类项目拆成“主体水流”和“白水层”两个独立系统去做从来没变过。3.1 尺度对齐与真实重力下的时间感大尺度场景的第一个坑是时间感。一个 30 米高的瀑布水从顶部落到水面按自由落体算大约需要 2.47 秒t √(2h/g) √(60/9.8)。这个数字可以直接当验证标准如果你的模拟里水流落地用了 6 秒那说明尺度或者时间缩放有问题。我见过不少工程为了让水看起来“优雅”故意把模拟速度放慢到 0.5 倍。这在特写镜头里可以接受但大瀑布一旦放慢失重感立刻出来观众会觉得水像在油里落。正确的提速方式是加大发射初速度而不是调慢时间。实操上我会这样设置先按真实尺寸建好岩壁和落差不做任何艺术夸张单位是米。在瀑布顶部区域创建发射器尺寸略小于水流实际宽度因为水会自然扩散。给发射器一个向下的初速度通常取 2 到 5 m/s。这个值模拟的是水从岩缝里被挤压出来的初速不是下落速度。让重力接手剩下的部分不要再额外加力场。帧率锁定 24 或 25 fps用自适应时间步每帧最少 2 个子步、最多 8 个。关于子步数逻辑是时间步长必须满足 CFL 条件也就是流体在一个子步内移动的距离不能超过一个体素。瀑布下落速度可以到 20 m/s 以上1/24 秒的帧间隔内水会走 0.83 米。如果体素是 5 厘米那至少需要 17 个子步才稳定但软件允许的上限通常只有 8 到 10所以实际做法是接受一定误差或者降低初速、把落差分段。这也是为什么大瀑布我更喜欢分成上下两段来做上段自由落体下段撞击区中间用速度继承衔接。每段的动态范围小子步数就够用。3.2 发射器分层与缓存策略一个 30 米瀑布、600 分辨率的模拟单帧缓存可能到几百兆跑 300 帧就是几十上百 G。我的一般做法是分三次跑第一遍低分辨率预览。分辨率 150 到 200算出整体形态和落地位置确认时间节奏对不对。这一遍可能只需要几十分钟但能省掉后面绝大部分返工。第二遍中分辨率运行。分辨率 400 左右把主体水流算出来同时输出速度场。这一遍是真正消耗时间的通常会挂着跑一整晚。第三遍局部高分辨率补算。只在镜头最关注的区域比如撞击点、水帘边缘用一个更小的模拟域、更高的体素重算一遍然后把结果拼进去。这种“分块高精”的思路是把有限算力用在刀刃上的核心手法。缓存路径我坚持用相对路径加固定命名因为一旦把工程挪到另一台机器或者换个盘符绝对路径全部失效缓存就白算了。-- 缓存目录规范化按场景名 分辨率建子目录方便区分不同批次 fn makeCacheDir baseDir sceneName res ( local sub baseDir \\ sceneName _res (res as string) makeDir sub all:true sub ) local dir makeCacheDir D:\LiquidCache Waterfall_A 400 format 本批次缓存目录: %\n dir我给自己定的规矩是缓存目录里必须有一个文本文件记录这一批的分辨率、体素尺寸、黏度、表面张力、重力值、总帧数。听起来像强迫症但当你同时开着三个镜头、五天后再回来改其中一版没有这份记录就是灾难。3.3 白水泡沫、飞溅、水雾的分层思路真实瀑布最有说服力的部分不是那根水柱而是白水撞击处的泡沫、飞散的细雾、水面上漂浮的白色絮状物。这些如果交给主流体一起去算成本高到不现实。标准做法是在主模拟之后用主模拟的速度场驱动一层独立的粒子系统。具体逻辑是三条第一泡沫。在主水流的液面以上一定范围内按速度阈值和曲率阈值生成粒子。速度快、曲率大的地方就是撞击和水花破碎的位置泡沫密集。这些粒子静止在液面上随时间缓慢消散。第二飞溅。在速度超过某个阈值的表面点上发射粒子给一个随机方向的初速然后让它们自由飞行、受重力下落落地后并入液体或者直接消亡。这里的阈值要用绝对速度比如 8 m/s 以上才发射太低会满屏都是无意义的碎点。第三水雾。尺度更大、更稀薄通常在撞击点附近用体积方式表现而不是粒子否则数量会爆炸。三层的密度参数要拉得很开我一般设成 1 : 0.3 : 0.05也就是说泡沫最多、飞溅中等、水雾极少。出现过一次事故我把三层密度设成差不多渲染出来像一锅沸腾的牛奶返工重做了两天。注意白水粒子必须和主液体的网格共享同一套运动模糊和景深设置否则在画面里会明显“浮”在液体表面之上穿帮感极强。3.4 网格化与重算时机模拟出来的原始数据体素场或粒子云不能直接渲染必须转成多边形网格。这一步的参数看起来不起眼但对最终画面影响很大。核心是三个值表面偏移控制网格相对液面的膨胀或收缩。正值让体积变大负值缩小。默认 0我一般在 -0.05 到 0.05 之间微调目的是消除液面收缩造成的“瘦一圈”现象。平滑迭代次数一般 2 到 5 次。太高会把水花的锐利感磨平太低则网格有明显棱角。大瀑布用 2 到 3 次即可因为远端看不出来杯中水要用 4 到 5 次近景经不起锯齿。细节保留半径比这个半径小的独立液滴会被丢弃或者单独处理。这个值设大了飞溅的水珠会消失设小了网格面数会暴涨。重算时机是我特别想强调的一点永远不要在没锁死碰撞体动画之前跑最终高分辨率。我吃过一次教训客户在最终模拟跑到第 200 帧时提出杯子位置要往右挪 2 厘米整个缓存作废前面两天白费。后来的流程改成动画打完 → 渲染低模预览逐帧检查 → 签字确认 → 才开高分辨率。多花半天看预览省下的是整晚的机时。4. 小尺度杯中溢出的饮料细节决定成败如果说瀑布考验的是算力和工程管理那杯中溢出的饮料考验的是对物理细节的耐心。这个题材的物理尺度只有十几厘米表面张力开始唱主角任何一点参数偏差都会被镜头放大。同时它又是商业项目里出现频率最高的液体镜头之一值得单独拆开讲。4.1 玻璃杯建模厚模模拟、薄模渲染前面提过碰撞体要厚这里的细节更具体。杯子的模拟模型我一般这样建杯壁厚度设成体素尺寸的 2.5 到 3 倍。如果体素是 2 毫米杯壁就做 5 到 6 毫米。杯口做一个明显的倒角或者圆角半径 1 到 2 毫米。直角杯口会让液体在溢出瞬间形成不自然的锐利边缘因为求解器在尖角处的法线计算不稳定。杯底内侧做成一整块实体不要留空腔。空腔会让求解器把杯子内部也当成液体可以进入的区域导致水从底下“渗”出来。杯子内壁的法线必须全部朝内外壁法线朝外。如果用了单面建模加壳修改器务必检查壳的方向。渲染用的杯子单独做一份高模壁厚可以是真实的 2 到 3 毫米带真实的折射和厚度。两个模型的位置必须严格对齐我会把渲染模型链接到模拟模型上模拟模型隐藏这样动画改的时候不会错位。还有一个容易忽略的点杯子的水面初始状态。如果你直接从空杯开始倒前面的倒入过程会占掉大量帧数。更高效的做法是先给液体一个静态的初始体积一个填满杯子下半部分的初始块让它先静置 20 到 30 帧达到平衡再从第 30 帧开始倒。静置阶段能看到液面从方块慢慢松弛成平面这个过程本身也很真实。4.2 小尺度下黏度与表面张力的取值区间杯中的水观感上要满足三个特征液面在杯口略微鼓起、溢出时沿着杯壁形成连续的水膜而不是断裂的水滴、落下时能拉出细长的水线。这三个特征分别对应表面张力、湿润性和黏度的正确设置。表面张力我给到 0.08 到 0.15 这个相对区间比大场景高一个数量级。判断标准很直观静置状态下液面应该有一个肉眼可见的弯月面靠近杯壁的地方微微上翘 1 到 2 毫米。如果没有这个弧度说明表面张力不够如果液面像果冻一样整个鼓起来说明过头了。黏度在这个尺度要非常小心。水的黏度几乎为零但完全为零会导致求解不稳定出现细碎的抖动。我一般给一个极小的值让数值稳定但不影响流动看具体求解器通常比大场景低一到两个数量级。还有一个参数叫湿润性或者接触角有些求解器会暴露出来。它控制液体是“喜欢”贴着固体表面接触角小水膜铺开还是“排斥”表面接触角大形成水珠。玻璃和水的接触角大约在 20 到 30 度属于很亲水的组合所以水会沿着杯壁铺成薄膜往下淌。如果你的求解器有这个参数一定要往“亲水”方向调否则水会缩成一颗颗珠子往下滚完全不像饮料。参数杯中饮料建议区间判断标准表面张力0.08 至 0.15液面有明显弯月面但不成果冻黏度极低接近求解稳定下限流动自然无细碎抖动接触角/湿润性20 至 30 度亲水沿杯壁铺成水膜而非水珠体素尺寸1 至 2 毫米能看清杯壁上的水膜厚度每秒子步4 至 8溢出瞬间不炸4.3 溢出时序力场、关键帧与“手动加戏”自然的溢出是很难“等”出来的。你给杯子持续注水它会在某个不确定的时刻溢出来这个时刻往往和你的镜头节奏对不上。所以实际项目里溢出这件事需要人为设计时序。我的做法分三步。第一步让液体静置到液面接近杯口但还差 3 到 5 毫米这个状态可以稳定几十帧。第二步用一个小力场在杯口附近制造局部扰动比如一个很弱的、朝向杯外的推力或者干脆给杯体一个极轻微的水平加速度。第三步在第 N 帧精确地让液面越过临界点形成连续的溢出。这里的核心技巧是用加速度代替移动。很多人的第一反应是把杯子倾斜但倾斜会让整个液面重算很难控制。更可控的方式是让杯子做一次极短的、幅度只有 1 到 2 毫米的水平位移速度曲线用缓入缓出持续 3 到 4 帧。液体的惯性会让它继续往前从杯口溢出。这个手法我用过很多次效果比倾斜自然得多而且容易反复微调。还有个小花招如果模拟出来的溢出太“理性”沿着杯口均匀地淌可以在后期或者模拟时人为加一点不均匀。真实的水溢出通常只从一个点开始因为杯口总有一处最低点。可以让杯口模型有一点点不平整或者给初始液面一点点倾斜这样溢出点会自然集中看起来真实得多。4.4 气泡、挂杯与液面细节的补足模拟到这一步主体形态基本对了但近景一看还是“缺东西”。缺的就是那些让画面可信的微小细节。气泡严格来说需要在液体内部做多相模拟成本极高。实际做法是后期用粒子系统在玻璃杯内侧的液体区域随机撒一些小粒子受浮力上浮到达液面后消失。注意气泡分布要贴着杯壁因为真实的杯壁上最容易附着气泡。这个细节在特写镜头里价值极高成本却很低。挂杯也就是水膜残留在杯壁上往下淌的痕迹。这个可以交给模拟但更省事的做法是在模拟完成之后用一层薄薄的、跟随液体边缘的几何体单独做或者直接在材质里用程序化纹理模拟。如果要模拟记得把杯壁的粗糙度调低光滑表面对水膜的吸附更明显。液面张力感体现在液面的细微起伏和反光上。纯平的液面看起来像塑料片。我的处理方法是在材质里叠加一层极低强度的噪波凹凸尺度小、幅度小配合正确的折射率在动起来的时候会形成非常自然的高光流动。提示所有补足的细节都要在渲染测试里检查一遍。有些粒子在视口里看不见渲染时才冒出来挡住关键画面那种返工最冤。5. 让液体真正好看材质、渲染与画面问题模拟只是前半程材质和渲染才是把“一坨几何体”变成“水”的最后一步。这一块翻车的频率甚至比模拟还高因为它的错误往往很隐蔽画面看着不太对但说不上哪里不对。这一节我把水的材质拆成几个必调的数值配合常见画面问题一起讲。5.1 折射率、吸收与散射的基础数值水的折射率是 1.333玻璃是 1.5。这两个数字必须给准因为观众对液体的认知很大程度上来自折射吸管在水里会“折断”杯底的图案会偏移这些都在潜意识里验证“这是水”。很多新手把折射率设为 1.0理由是“水是透明的”结果水看起来像一层塑料膜完全没有体积感。正确的做法是水的折射率固定 1.333然后靠吸收和散射控制透明度。吸收决定光穿过液体时哪个颜色先被吃掉。纯水对红光的吸收比对蓝光强所以在足够长的光程下水会呈现淡青色。但在杯子里这种几厘米的光程吸收几乎看不出来水应该接近无色。很多人抄了水族馆或者深海的材质参数做出来的杯子是蓝的这就是光程没有匹配。经验规则光程短于 10 厘米吸收颜色接近纯白光程超过 1 米才开始明显偏青。散射决定液体内部有没有“雾感”。水非常清澈散射极小。如果散射给多了水会像牛奶。我一般在纯水里把散射系数设到几乎为零只有做果汁、牛奶、浑水的时候才加大。真实感还有一个隐藏来源液体内部的体积阴影。厚水体会挡住一部分光形成内部明暗变化。如果你的渲染器支持体积材质一定要打开这是让水看起来“有厚度”的关键。5.2 网格平滑、置换与运动模糊模拟输出的网格永远是“块状”的因为它是从体素转换来的。渲染前必须经过处理第一加细分平滑修改器迭代 1 到 2 次。注意不要在低面数网格上直接加高迭代会丢失体积。第二用网格平滑或者OpenSubdiv做最终平滑。这一步能让液面看起来有张力边缘圆润。第三如果需要叠加极低强度的置换制造液面细微的不平整。强度控制在 1 毫米以内否则会变成一锅粥。运动模糊是液体镜头的灵魂也是最容易出错的地方。液体运动速度快、形状变化大用默认的中心采样会得到断断续续的残影。正确做法是用几何体速度作为模糊依据而不是位移采样。同时如果液体网格每帧顶点数在变某些渲染器会算不准速度这时需要先缓存顶点速度或者在渲染器里开启“变形物体运动模糊”的专门选项。我遇到过一个非常典型的问题水面在运动模糊下出现了明显的“台阶”像被打了一格一格的马赛克。查了半天发现是模拟的缓存帧率和渲染帧率不匹配中间少了几帧。解决办法是把模拟的保存间隔设成每帧而不是每两帧。5.3 渲染阶段的坑黑边、噪点与内部液面穿帮黑边指的是水和玻璃交界处出现的一条深色线条。成因通常有两个一是两个表面的法线方向有冲突二是折射光线在交点处无限次反弹被截断。处理方法把玻璃设成薄壁或者给一个极小的厚度偏移同时提高折射反弹次数。噪点主要来自玻璃的高光、折射和液体内部的散射。单纯提高采样数效率很低正确做法是分层渲染先只渲染液体和杯子的直接光照把折射噪点用降噪器处理再单独渲染高光通道。液体镜头的采样需求比普通场景高两到三倍这是没办法的事但可以通过关闭不必要的间接照明来省时间。内部液面穿帮指的是通过玻璃看到杯子里面有一条奇怪的水面线或者液面看起来是双层的。原因是液体网格和玻璃内壁之间有一层薄薄的空隙或者重叠。检查办法是把玻璃设成全透明、只显示液体看看液体和杯壁是不是严丝合缝。如果不贴合要么把液体网格稍微膨胀一点表面偏移调正要么把玻璃内壁往里挪 0.5 毫米。画面问题最可能的原因处理方向液体发蓝吸收颜色/光程不匹配近距离场景吸收趋白水像塑料膜折射率设为 1.0固定 1.333交界黑线法线冲突或折射反弹不足调法线、提高反弹次数运动模糊断层模拟与渲染帧率不一致每帧保存缓存内部双液面液体与杯壁不贴合微调表面偏移6. 排查实录炸开、穿模、丢失缓存怎么救模拟出问题的时候最怕的是不知道从哪儿下手。我踩过的坑足够多逐渐整理出一套固定的排查顺序基本上能在十分钟内定位到八成的问题。这一节按症状分类讲每一条都是真实遇到过的。6.1 液体炸开四类成因和对应的验证方法液体突然从某帧开始向外爆散画面像爆炸一样这是最典型的故障。成因有四类可以按顺序排除。第一类时间步长不足。速度太快一个子步内流体移动超过一个体素求解器直接失控。验证方法把子步数翻倍看是否还炸。如果好了就是这个问题。解决方向是减小速度或者增大体素单纯加子步只能缓解。第二类碰撞体穿透。碰撞体太薄或者有破面液体从缝里钻进去后被“挤压”出来。验证方法隐藏液体只看碰撞体从前视图和后视图各扫一遍看有没有破洞。这一步我每次必做快速且有效。第三类参数极端。黏度或者表面张力给了过大的值在某些求解器里会导致隐式求解不收敛。验证方法把这两个参数归零重跑十帧看是否正常。如果正常就是参数问题一点点加回去找临界值。第四类初始状态不合法。液体一开始就和碰撞体重叠求解器要花几个子步把它们推开推开的过程会产生巨大的初速。验证方法看第一帧的液体和碰撞体是否相交。所以初始体积一定要和杯子错开宁可留一点空隙。6.2 穿模从“漏水”到“渗水”的区分穿模和炸开是两回事。穿模通常表现为液体缓慢地从杯子底部或者侧面“渗”出去或者细流从一条看不见的缝里钻出来。最常见的原因是碰撞体法线翻转。用壳修改器加厚的时候如果原模型法线朝外壳会向外扩实际的内腔比你想的小液体就被挤出去了。解决办法是先把碰撞体转成可编辑多边形统一法线方向再加厚。第二种是模型不封闭。杯子的底部和侧壁如果是分开建的两块接缝处可能有极小的缝隙。体素求解器对缝隙非常敏感只要缝宽超过半个体素水就能钻过去。解决办法是用布尔或者桥接把它们合并成一个整体转成可编辑网格后检查有没有孤立顶点。第三种是模拟域边界设在了碰撞体内部。这种情况会让边界条件覆盖碰撞条件液体被推出去。听起来很蠢但这个错误我犯过两次。6.3 缓存管理与版本兼容缓存丢失是另一种形式的灾难而且往往发生在你已经跑完两天之后。成因通常有三个路径变化、文件被覆盖、软件版本不兼容。路径问题的根本解法是从一开始就用相对路径并且把缓存放在工程目录下面而不是系统盘根目录。这样整个工程打包拷走缓存跟着走。文件被覆盖通常发生在多人协作。我的做法是缓存目录带版本号比如Waterfall_A_v03_res400每次大改必升版本旧版本保留至少一轮。版本兼容这块要说清楚不同软件版本的缓存格式经常不通用尤其是一些求解器的二进制缓存。如果你要跨版本协作最好在模拟完成之后立刻导出成通用格式比如 Alembic 或者 VDB 序列而不是指望原工程的缓存能直接读。VDB 的兼容性最好用于渲染非常合适如果是后续还要修改模拟就只能保留原生缓存并且所有协作方统一版本。-- 工程打包前自检统计缓存目录里的文件数与总大小 fn scanCache dir ( local files getFiles (dir \\*.vdb) local total 0L for f in files do total getFileSize f format 缓存文件数: %, 总大小: % MB\n files.count (total / 1048576.0) if files.count 0 do format 警告该目录没有找到缓存文件\n ) scanCache D:\LiquidCache\Waterfall_A_res400这个脚本很土但导出前跑一下能避免那种“以为在算其实没存”的尴尬。我遇到过一次模拟跑了一整晚第二天发现缓存路径写错一个文件都没落地那一晚的时间彻底浪费。7. 一些让我少走弯路的工程习惯技术细节讲得差不多了最后聊几个不属于技术、但决定了你能否稳定交付的习惯。这些东西没有教程会写都是被项目逼出来的。7.1 目录结构与命名的强制规范我现在所有液体项目都遵循同一套目录结构即使是个人练习也不例外01_Scene场景文件按版本号命名。02_Cache模拟缓存按“场景名_分辨率_版本”分目录。03_Mesh网格化输出按帧序列保存。04_Render渲染输出按通道分目录。05_Ref参考素材包括实拍参考和测量数据。命名的强制规则是文件名里必须能看出尺度。比如Glass_10cm_surf0.12_res2mm.max这样的名字一眼就知道这是一个 10 厘米的场景、表面张力 0.12、体素 2 毫米。半年后回来改不用打开文件就知道参数。听起来啰嗦但对比“final_v2_final_真的最终.max”这种命名效率差距是数量级的。7.2 分层预演把返工成本压到最低这是我觉得最有价值的一个习惯任何液体镜头都必须先出低分辨率预览再看一遍再开高分辨率。低分辨率预览的成本可能只有最终的十分之一但它能暴露 90% 的问题节奏对不对、落地位置准不准、液面高度合不合适、有没有穿模。我现在的流程是低分辨率预览必看三遍第一遍看整体第二遍逐帧看异常帧第三遍把速度调慢看细节。这个习惯的直接收益是高分辨率模拟的重跑次数从平均三次降到几乎一次。而高分辨率重跑一次的成本可能是低分辨率预览的二十倍。这道算术题很值得算。7.3 时间预算与单帧耗时的心理预期最后聊一个现实问题你得知道一个镜头大概要跑多久才能和需求方谈时间。我的粗略估算方式是单帧耗时大约等于体素数 ÷ 1000 万×子步数 ÷ 4× 基础耗时。基础耗时取决于你的机器和求解器可以先跑 5 帧测出来。比如 400 分辨率、体素约 40 万到 60 万格实际液体、每帧 4 个子步单帧可能在一分钟上下300 帧就是五小时。这个数字要在开工前就知道而不是跑到一半发现来不及。还有一个经验渲染耗时通常是模拟耗时的两到五倍液体材质非常吃采样。所以要给渲染留足时间不要在模拟上耗尽预算。我个人在实际项目里的体会是液体模拟真正的门槛不在软件操作而在于你是否愿意在开工前花半天时间把尺度、路线、碰撞体、缓存策略全部想清楚。想清楚之后再动手整个过程会顺畅到让你怀疑是不是漏了什么。另外分享一个挺实用的小技巧如果你要做的液体镜头有好几个尽量用同一个基础场景去做只改发射器位置和关键参数。求解器的稳定性很大程度上依赖场景结构同样的碰撞体形状和布局参数调整起来的手感是一致的换成全新场景又得重新摸一遍。这个习惯让我在一系列产品短片里保持了很高的效率。