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

📐 Substance Designer 节点推荐
在 Substance Designer 中,推荐节点路径:Perlin Noise → Warp (用 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 降低并减少纹理分辨率,或用单次采样 + 噪声扰动代替完整双相位方案。
实时效果预览
