行路杂谈 · 2025年7月31日

UnityShader学习笔记——flowmap与肥皂泡

在游戏开发中,肥皂泡作为一种常见的自然现象,其独特的光学特性对Shader实现提出了有趣的挑战。肥皂泡表面的彩虹色(iridescence)反射让肥皂泡展现五彩斑斓的效果。本文将记录在Unity中复现肥皂泡效果的完整过程,包括实现气泡的材质,表面流动效果。使用的unity版本2022.3.61f1c1,最终得到的效果还需要优化,仅供参考。

效果预览

气泡材质

气泡反射原理参考:https://developer.unity.cn/projects/62b97859edbc2a77ce95afca

气泡反射材质实现参考:https://developer.unity.cn/projects/62b97c11edbc2a7848d41dda

创建urp的shader graph,图设置如上图所示。

下图展示了实现基础肥皂泡反射的连线,这里的噪声纹理可以先用任意噪声代替,我们之后再继续实现。

整个实现的核心在于彩虹色函数,函数的设置和代码如下:

float3 bump3y(float3 x, float3 yoffset)
{
    float3 y = 1 - x*x;
    y = saturate(y - yoffset);
    return y;
}

float3 spectral_zuccioni6_float(float w)
{
    float x = saturate((w - 400.0) / 300.0);
    const float3 c1 = float3(3.54585104, 2.93225262, 2.41593945);
    const float3 x1 = float3(0.69549072, 0.49228336, 0.27699880);
    const float3 y1 = float3(0.02312639, 0.15225084, 0.52607955);
    const float3 c2 = float3(3.90307140, 3.21182957, 3.96587128);
    const float3 x2 = float3(0.11748627, 0.86755042, 0.66077860);
    const float3 y2 = float3(0.84897130, 0.88445281, 0.73949448);
    return
        bump3y(c1 * (x - x1), y1) +
        bump3y(c2 * (x - x2), y2);
}

void iridescence_float(float cosThetaR, float thickness, float power, float phase_shift, out float3 color)
{
    color = 0;
    float value = 2.66 * cosThetaR * thickness; // 2.66 = 2 * 1.33, ior of water
    for (int n = -4; n <= 4; n++)
    {
        float wavelength = value / (n + 0.5);
        wavelength = pow(wavelength, power);
        color.rgb += spectral_zuccioni6_float(wavelength + phase_shift);
    }
    color = saturate(color);
}

spectral_zuccioni6_float

这个函数将波长值 w 转换为对应的 RGB 颜色

使用 Zucconi 方法模拟光谱颜色(基于物理的光谱近似)

参数 w 代表波长,通过 saturate((w - 400.0) / 300.0) 将波长范围规范化到 [0,1]

使用两组参数化的凸函数( bump3y)合成光谱颜色

六个常量向量(c1, x1, y1, c2, x2, y2)定义了光谱的参数

iridescence_float

这是主函数,计算薄膜干涉产生的虹彩颜色:

  • 参数说明:
    • cosThetaR: 观察角度的余弦值(影响反射光路径长度)
    • thickness: 薄膜厚度
    • power: 用于调整波长分布的幂次
    • phase_shift: 相位偏移,可用于调整颜色偏移
    • color: 输出的 RGB 颜色
  • 工作原理:
    • 计算 value = 2.66 * cosThetaR * thickness(2.66 是水的折射率 1.33 的 2 倍)
    • 循环计算 9 个干涉级数(n = -4 到 4),模拟多级干涉
    • 对每个级数,计算波长 wavelength = value / (n + 0.5)
    • 应用 power 参数调整波长分布
    • 累加每个波长叠加phase_shift过后对应的光谱颜色贡献
    • 最后 saturate 确保颜色值在有效值范围内

肥皂泡波动效果

这一部分用顶点偏移方法模拟肥皂泡的随形效果,如果要实现肥皂泡的碰撞变形,就需要对这个部分再做改动。

Flowmap实现表面流动效果

肥皂的表面具有流动的反射,之前我们用噪声纹理暂时制作了反射,现在我们要细化并制作出肥皂泡表面流动的彩虹色纹理。

FlowMap 是 Unity 中一种纹理技术,用于模拟表面材质的流动和扭曲效果。这种技术将流动方向信息存储在纹理中,通常利用 RGB 图像的红色和绿色通道分别表示 X 和 Y 方向的流动向量,蓝色通道则可用于存储流动强度或其他辅助信息。

在实际应用中,FlowMap 通过在着色器中扭曲 UV 坐标来实现纹理的动态流动。开发者首先从 FlowMap 纹理中读取方向向量,然后将这些向量应用于原始 UV 坐标,使纹理沿着指定方向偏移。为了创造连续流动的错觉,避免循环时的明显跳变,通常会采用双周期混合技术,即使用两个时间相位错开的采样并在它们之间平滑过渡。

Flowmap制作参考:https://developer.unity.cn/projects/63059542edbc2a001c44ed42

Flowmap制作工具:https://teckartist.com/?page_id=107

下图为制作中使用的Flowmap纹理:

利用两个通道实现双周期之间的时序混合,达成持续的流动效果。

材质部署

由于透明物体的渲染问题,我们需要在物体内外分别挂载材质

设置如下:

注意修改材质Surface Options中的Render Face属性,对应材质所需渲染的面。

为了实现反射周边环境物体的效果,为物体挂载反射探针(Reflection Probe)。在这里使用Custom类型,反射天空盒的图像,在使用场景中可以使用Baked或者Realtime模式反射周边物体。