在 Web 图形学领域,利用 Three.js 实现逼真的液体扩散效果始终是极具挑战性且极富创造性的课题。本文将系统梳理一个真实商业项目中液体粉底扩散效果的完整技术实现路径,从流体模拟的速度图生成、高度图的初始化与平流更新、透明度图的历史痕迹保留、到基于高度图的法线计算与 Matcap 材质渲染,逐层剖析每个着色器环节的数学原理与工程实践。文章同时探讨为何选择牺牲物理体积守恒换取视觉自然度、8 位与 32 位浮点纹理精度对法线平滑度的决定性影响、以及多液体混合实验中简化 Kubelka-Munk 模型的应用思路,旨在为前端图形开发者提供一套可落地、可扩展的液体视觉效果实现范式。 项目起源于 2021 年某日本彩妆品牌液体粉底推广页面的技术需求。当时面临的核心难题是如何在浏览器端用极低性能开销模拟手指拖拽液体时的隆起、拉伸、残留痕迹等复杂流体行为。开发者最初尝试用简单位移贴图或几何变形方案,但无法捕捉流体"反向隆起"与"周围物质被带动"的耦合动态。
经过一个月的反复观察真实粉底流动特性,最终确立以 Jos Stam 经典 Stable Fluids 算法为基础的 GPU 并行流体模拟管线,利用速度图驱动高度图演化,再通过片元着色器重建表面法线实现光照高光,形成一套完整的"模拟-渲染"分离架构。 流体模拟阶段采用双缓冲 FBO 交替存储速度场与压力场,每帧执行加速度施加、压力投射、速度扩散三步核心运算。速度图的 RG 通道分别编码屏幕空间 X、Y 方向流速大小,该图随后作为高度图更新的唯一动力学驱动源。高度图初始化通过片元着色器生成中心隆起的半球体,数学表达为对归一化 UV 坐标计算到中心距离,经 smoothstep 平滑阶跃函数控制边缘衰减,再以 sqrt((1-d²)/12) 近似半球体高度分布,最终乘以 0.66 系数归一化至 [0,1] 区间存入 R 通道。这种纯 GPU 初始化方式避免了 CPU 侧几何生成与纹理上传开销,且天然适配后续平流计算的纹理采样模式。 高度图的每帧更新是整个效果的动态核心。
着色器首先在当前像素采样前一帧高度值 previousHeight 与速度向量 flow,利用速度模长经 smoothstep 映射为 previousStrength,计算当前像素流出的厚度 removed = min(previousHeight, previousStrength * 0.3 * previousHeight)。随后执行关键的反向平流回溯:backtraceUv 函数依据当前 UV、速度向量、FBO 分辨率比率与时间步长 dt 计算源采样坐标 sourceUv = uv - flow * velocityRatio * dt * ratio。在 sourceUv 处采样源高度 sourceHeight 与源速度强度 sourceStrength,得到流入厚度 added = min(sourceHeight, sourceStrength * 0.3 * sourceHeight)。最终 nextHeight = max(0, previousHeight - removed + added) 写入下一帧高度图。这种欧拉视角的反向采样完美契合 GPU 并行特性,但也导致多像素可能回溯至同一源区域,造成厚度重复累加、总体积不守恒。实测直线拖拽时体积相对稳定,圆周运动会因采样偏差累积导致液体量持续增多。
开发者明确接受这一非物理特性,通过经验系数 0.3 与速度阈值平滑控制,使视觉表现更符合用户对"涂抹越多越厚"的直觉预期。 仅靠高度图无法还原液体薄涂后留下的色彩痕迹。为此引入独立的透明度图 Alpha Map,其 R 通道记录历史最大透明度 maximum,G 通道存储当前帧透明度 current 与 maximum 的等权混合。current 由当前高度图经 smoothstep(0, 0.01/0.7, height) 映射,maximum = max(current, previousMaximum) 实现单调非递减的痕迹累积。渲染时采样 G 通道作为最终不透明度,配合 step(0.1, alphaValue) 截断过低值,使液体离开后仍保留淡薄色彩,模拟粉底附着肌肤的视觉残留。该机制非物理干燥吸收模型,纯粹作为视觉历史缓冲,极低成本实现了极强的材质真实感。
渲染阶段完全在片元着色器中完成,无几何位移。法线计算采用有限差分法:以当前像素高度 height 为中心,分别在 UV X、Y 方向偏移一个纹素步长 stepSize = 1/resolution 采样邻域高度,构建三维位置向量 position、positionX、positionY,其中 Y 分量为高度乘以 heightScale (0.2/0.66),X/Z 分量经 resolution 纵横比修正。两切向量叉积归一化后取反得到表面法线 normal。该法线在平坦区接近 (0,1,0),隆起边缘呈现倾斜,直接映射为 Matcap 纹理坐标 matcapUV = normal.zx * 0.5 + 0.5 采样颜色,叠加 0.05 基础亮度补偿。最终片元颜色为 vec4(color, step(0.1, alphaValue) * alphaValue),实现高光随表面起伏自然流动、边缘透明度平滑过渡。 纹理精度对法线质量影响显著。
8 位 RGBA 纹理每通道仅 256 离散层级,高度量化导致法线阶跃突变,渲染出明显马赫条纹;32 位浮点纹理提供连续高度表示,法线平滑过渡,高光细腻自然。对比演示中切换两种精度即时可见差异,揭示了精度瓶颈往往不在色彩渐变而在几何导数计算上。工程上若性能允许,高度图、速度图、透明度图均应采用 RGBA32F 格式,必要时通过 EXT_color_buffer_float 扩展启用。 多液体混合实验进一步验证了架构扩展性。三组独立高度图与透明度图共享单一速度图,各自按单液体逻辑更新。混色阶段采用简化 Kubelka-Munk 模型:按厚度加权融合三基色,厚度趋零处退化为透明度历史最大值比例混合。
法线则基于三液体高度加权和重新计算。该实验展示了速度场复用与状态解耦的设计优势,为多材质流体交互提供了可行路径。 回顾整个实现,核心思想是"以视觉说服力为最高准则,在物理正确性与计算可行性之间寻找平衡点"。流体模拟提供宏观流动趋势,高度图记录微观厚度演化,透明度图锁定历史痕迹,法线重建赋予光照响应,Matcap 纹理封装复杂材质外观。每个模块单一职责、数据流向清晰、着色器逻辑可独立调试替换。这种模块化 GPU 管线思维,不仅适用于液体粉底,更可迁移至熔岩流动、颜料混合、凝胶变形等广泛流体视觉场景。
随着 WebGPU 普及与 AI 辅助着色器生成工具成熟,此类效果的开发门槛将进一步降低,但理解底层数学原理与工程权衡的能力,依然是创造差异化视觉体验的关键护城河。 。