Unity Shader FLOWMAP岩浆流动制作案例

使用 Flowmap 技术在 Unity URP 中实现真实感岩浆流动效果, 深入解析 UV 扰动、相位混合与颜色映射原理。

什么是 Flowmap

Flowmap(流动贴图) 是一张 RGB 纹理,其中 R 通道 与 G 通道 分别存储了 UV 空间中水平和垂直方向上的流动速度向量。通过在每帧对 UV 坐标施加累积的偏移,再叠加循环相位抖动,可以在视觉上模拟出连续、自然的液体流动------而无需任何物理模拟。

对于岩浆效果,这一技术尤为有效:岩浆粘稠缓慢,不同区域流速各异,正好契合 Flowmap 能精细控制局部方向的优势。

🔑 核心思路

将 Flowmap 中采样到的向量乘以时间偏移 UV,得到两个相位差 0.5 的扭曲 UV,分别采样岩浆纹理后做 线性插值(Lerp),消除因 UV 拉伸产生的周期性接缝,最终叠加自发光和颜色梯度。

两个相位在 [0, 1] 之间循环,彼此相差 0.5 ,当其中一个相位接近 0 或 1(UV 拉伸最严重处)时,另一个恰好在 0.5(最平滑处),权重此消彼长,从而实现无缝过渡

纹理资源准备

岩浆噪声纹理(Lava Noise Texture)

使用 Substance DesignerPhotoshop 制作一张包含高频岩浆纹路的噪声贴图,建议分辨率 512×512 或 1024×1024,格式 R8 或 RGB8,开启 Repeat 平铺。颜色范围集中在橙红灰区间,不需要颜色信息------颜色将由 Shader 内部的渐变色阶(Gradient)动态注入。

📐 Substance Designer 节点推荐

在 Substance Designer 中,推荐节点路径:Perlin NoiseWarp (用 FBM 扭曲)→ Levels (拉高对比度)→ Blur HQ (轻微模糊消除锯齿)→ 导出为 Grayscale PNG。 最终目标是高亮区域(值接近 1)形成连续的细窄裂纹,暗区(值接近 0)为大块岩石体。

Flowmap 纹理绘制

Flowmap 本质是一张方向场。可用 Flowmap Painter (免费工具)在模型 UV 上手绘流向。编码规则:

R = (dirX + 1) / 2 G = (dirY + 1) / 2 B = 遮罩 / 强度

静止区域填充 (0.5, 0.5)(即向量 (0, 0)),导出前关闭 sRGB 勾选(Linear 空间)。

💡Unity 导入 Flowmap 时,务必在 Texture Import Settings 中将 sRGB (Color Texture) 取消勾选,否则颜色空间转换会使方向向量发生偏差,导致流向错误。同时将 Compression 设为 None 或至少避免使用 BC1/DXT1,防止低精度压缩破坏方向信息。

颜色渐变贴图(Gradient Ramp)

制作一条从 深黑/暗棕深红亮橙亮黄 的 256×1 渐变条,存为 PNG,关闭压缩,Filter Mode 设为 Bilinear,Wrap Mode 设为 Clamp。

完整 Shader 实现

以下为基于 Unity URP 的 HLSL Shader,使用 ShaderGraph 可以照相同逻辑搭建节点图。核心分为三部分:属性声明、顶点程序、片元程序。

cs 复制代码
// Unity URP Flowmap Lava Shader
// 适配 URP 14.x | Unity 2022 LTS+

Shader "Custom/LavaFlowmap"
{
    Properties
    {
        // 岩浆噪声纹理
        _LavaTex     ("Lava Noise Texture", 2D) = "white" {}
        // Flowmap(Linear 空间)
        _FlowMap     ("Flow Map", 2D) = "bump" {}
        // 颜色渐变贴图
        _RampTex     ("Color Ramp", 2D) = "white" {}
        _FlowSpeed   ("Flow Speed", Range(0, 2)) = 0.3
        _FlowStrength("Flow Strength", Range(0, 1)) = 0.25
        _EmissivePow ("Emissive Power", Range(1, 5)) = 2.0
        _Tiling      ("Lava Tiling", Vector) = (2, 2, 0, 0)
    }

    SubShader
    {
        Tags { "RenderPipeline"="UniversalPipeline"
               "RenderType"="Opaque" }

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode" = "UniversalForward" }

            HLSLPROGRAM
            #pragma vertex   vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            // ── 纹理 & 采样器声明 ──────────────────────
            TEXTURE2D(_LavaTex);  SAMPLER(sampler_LavaTex);
            TEXTURE2D(_FlowMap);  SAMPLER(sampler_FlowMap);
            TEXTURE2D(_RampTex);  SAMPLER(sampler_RampTex);

            CBUFFER_START(UnityPerMaterial)
                float4 _LavaTex_ST;
                float  _FlowSpeed;
                float  _FlowStrength;
                float  _EmissivePow;
                float4 _Tiling;
            CBUFFER_END

            struct Attributes {
                float4 positionOS : POSITION;
                float2 uv         : TEXCOORD0;
            };

            struct Varyings {
                float4 positionCS : SV_POSITION;
                float2 uv         : TEXCOORD0;
            };

            // ── 顶点着色器 ────────────────────────────
            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionCS = TransformObjectToHClip(IN.positionOS.xyz);
                OUT.uv = IN.uv;
                return OUT;
            }

            // ── 片元着色器 ────────────────────────────
            half4 frag(Varyings IN) : SV_Target
            {
                float2 uv = IN.uv;

                // 1. 采样 Flowmap,解码方向向量 [-1, 1]
                float2 flowVec = SAMPLE_TEXTURE2D(_FlowMap,
                                   sampler_FlowMap, uv).rg;
                flowVec = flowVec * 2.0 - 1.0;  // unpack

                // 2. 计算两个相位(相差 0.5)
                float t = frac(_Time.y * _FlowSpeed);
                float phaseA = t;
                float phaseB = frac(t + 0.5);

                // 3. 偏移 UV(叠加平铺)
                float2 tiledUV = uv * _Tiling.xy;
                float2 uvA = tiledUV - flowVec * phaseA * _FlowStrength;
                float2 uvB = tiledUV - flowVec * phaseB * _FlowStrength;

                // 4. 分别采样岩浆纹理
                half sampleA = SAMPLE_TEXTURE2D(_LavaTex,
                                  sampler_LavaTex, uvA).r;
                half sampleB = SAMPLE_TEXTURE2D(_LavaTex,
                                  sampler_LavaTex, uvB).r;

                // 5. 基于相位计算混合权重(消除接缝)
                half weight = abs(sin(phaseA * PI));
                half noise   = lerp(sampleA, sampleB, weight);

                // 6. 通过渐变贴图映射颜色
                half4 color = SAMPLE_TEXTURE2D(_RampTex,
                                 sampler_RampTex,
                                 float2(noise, 0.5));

                // 7. 叠加自发光(亮区幂次增强)
                half3 emissive = color.rgb * pow(noise, _EmissivePow);
                color.rgb += emissive;

                return color;
            }
            ENDHLSL
        }
    }
}

关键参数详解

参数名 类型 说明 推荐值
_FlowSpeed float 控制岩浆整体流动速度,值越大流动越快 0.2 ~ 0.5
_FlowStrength float Flowmap 向量对 UV 的扰动幅度,过大会导致明显拉伸 0.1 ~ 0.35
_EmissivePow float 自发光的幂次曲线,控制亮纹裂隙的对比度与发光强度 1.5 ~ 3.0
_Tiling Vector2 岩浆噪声纹理的平铺次数,独立于 Flowmap 平铺 2 ~ 4(视模型大小)
_FlowMap Texture2D 方向流动贴图,必须以 Linear 导入(关闭 sRGB) ---
_RampTex Texture2D 颜色渐变条,Wrap Mode = Clamp,Filter = Bilinear 黑→红→橙→黄

ShaderGraph 节点对照

如果你更习惯可视化连线,以下是 ShaderGraph 中对应的节点组合,逻辑与 HLSL 完全等价:

进阶优化技巧

A

噪声扰动叠加(Detail Noise)

在主流动 UV 基础上,再叠加一层高频噪声偏移,为岩浆表面增加小尺度涌动细节。可用一张独立的 Voronoi 或 Perlin 噪声纹理,以较快速度平移,再以 0.05 ~ 0.1 的强度叠加到主 UV 上。

B

边缘暗化 / 冷却效果

利用 Flowmap B 通道(或单独遮罩贴图)标记冷却区域,在颜色渐变采样前将 noise 值乘以遮罩,使边缘区域采样到渐变的暗端(暗红/黑色),模拟岩浆冷却固化的岩石边缘。

C

顶点动画(Vertex Displacement)

在顶点着色器中,使用相同的 noise 值(需传入 VS 或通过 Vertex Texture Fetch)驱动法线方向的顶点位移,让岩浆表面产生轻微隆起的涌动感,增强三维真实感。位移量控制在 0.01 ~ 0.05 世界单位以内。

D

屏幕空间光照 & 热浪扰动

结合 URP 的 Distortion Pass 或 Grab Pass(兼容性较差),对岩浆上方的空气进行热浪折射扭曲。将岩浆亮度值映射为折射强度,亮缝隙产生更强的折射,暗区几乎不扰动。

cs 复制代码
// 在 frag() 中,计算 uvA/uvB 之前追加:

// 高频细节噪声平移
float2 detailOffset = float2(
    frac(_Time.y * 0.07),
    frac(_Time.y * 0.05)
);
half detail = SAMPLE_TEXTURE2D(_DetailNoise,
               sampler_DetailNoise,
               uv * 6.0 + detailOffset).r;

// 将细节偏移叠加到 flowVec
flowVec += (detail - 0.5) * 0.06;

⚡性能提示:Flowmap 本身只需要一次额外的纹理采样,主要开销来自岩浆纹理的双重采样。在移动端,可将 _FlowStrength 降低并减少纹理分辨率,或用单次采样 + 噪声扰动代替完整双相位方案。

实时效果预览

相关推荐
小贺儿开发2 小时前
【Arduino与Unity交互探究】01 摇杆模块
科技·unity·游戏引擎·arduino·串口通信·摇杆·硬件交互
Yasin Chen4 小时前
Unity TMP_SDF 分析(三)顶点着色器1
unity·游戏引擎·着色器
mxwin4 小时前
Unity Shader 使用 Noise 图 制作Shader 溶解效果
unity·游戏引擎
mxwin6 小时前
Unity Shader 用 Ramp 贴图实现薄膜干涉效果
unity·游戏引擎·贴图·shader·uv
魔士于安7 小时前
Unity星球资源,八大星球,带fps显示
游戏·unity·游戏引擎·贴图·模型
张老师带你学8 小时前
unity资源,深空陨石,适合太空背景的游戏开发
游戏·unity·模型
鹿野素材屋10 小时前
Unity动画幅度太大怎么办
unity·游戏引擎
垂葛酒肝汤11 小时前
Unity Sprite Rect 越界问题笔记
笔记·unity·游戏引擎
平行云12 小时前
数字孪生信创云渲染系列(一):混合信创与全国产化架构
unity·ue5·3dsmax·webgl·gpu算力·实时云渲染·像素流送