关于 water-fluid-shaders
为 Unity 开发者提供水体与流体着色器的创建、修改和优化指导,重点考虑移动端与 WebGL 限制,并涵盖模板、功能预算和节点工作流。
主要功能
- 01移动端着色器性能约束
- 02URP HLSL 起步模板
- 03Gerstner 波浪指导
- 04Shader Graph 节点设置
- 05水体功能成本分级
适用场景
- 01构建移动端水体着色器
- 02添加岸边泡沫和深度颜色
- 03创建顶点波浪动画
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ adevra/ unity-shader-agent-skills --skill water-fluid-shaders安装后在 Claude Code 中输入/water-fluid-shaders确认已加载。
详细介绍
适用于移动端 Unity 的水体与流体着色器
触发条件
当用户希望在 Unity 中创建、修改或优化水体、海洋、河流、湖泊、池塘、流体、熔岩或液体着色器时,尤其是面向移动端或 WebGL 时,使用此技能。触发词包括:“水体着色器”“海洋”“波浪”“泡沫”“岸线”“折射”“焦散”“Gerstner”“流体”“深度着色”“风格化水体”“卡通水体”,或提及任何与水体相关的视觉效果。用户提及 NVJOB、bmjoy 或 Alexander Ameye 的水体着色器仓库时,也应触发。
核心规则——移动端水体限制
-
移动端不使用细分。 移动 GPU 无法高效支持硬件细分。应改为在现有网格上进行顶点位移。
-
水体着色器的纹理采样数限制在 ≤ 4 次。 典型的移动端水体着色器预算:1 张法线贴图(平铺、滚动)、1 张深度纹理(用于岸线/泡沫)、1 张场景颜色纹理(用于折射)、1 张泡沫/细节纹理。若超出预算,应优先移除场景颜色折射。
-
**必须在 URP Asset 设置中启用 Depth Texture 和 Opaque Texture,**才能进行基于深度的岸线检测和折射。这会增加一个复制通道——移动端预算约为 1ms。如果开销过高,可改用基于顶点颜色的深度。
-
使用单层法线。 桌面端水体通常会在不同尺度混合 2–3 次法线贴图采样。移动端应使用 1 次法线贴图采样,并通过 UV 滚动实现动画。
-
只在顶点着色器中计算 Gerstner 波。 切勿逐片元计算波浪位移。移动端限制为 2–3 个波浪倍频(桌面端为 4 个以上)。
-
除世界坐标和 UV 坐标外,所有水体计算都使用半精度。
移动端水体着色器——精简模板(URP HLSL)
此模板为移动端水体提供可用于生产环境的起点:
Shader "Mobile/Water_Simple"
{
Properties
{
_ShallowColor ("Shallow Color", Color) = (0.3, 0.8, 0.9, 0.6)
_DeepColor ("Deep Color", Color) = (0.1, 0.2, 0.5, 0.9)
_DepthMaxDistance ("Depth Max Distance", Float) = 3.0
_NormalMap ("Normal Map", 2D) = "bump" {}
_NormalScale ("Normal Scale", Float) = 0.5
_WaveSpeed ("Wave Speed", Float) = 0.05
_FoamColor ("Foam Color", Color) = (1, 1, 1, 1)
_FoamDistance ("Foam Distance", Float) = 0.4
_FoamCutoff ("Foam Cutoff", Float) = 0.5
}
SubShader
{
Tags {
"RenderType" = "Transparent"
"Queue" = "Transparent"
"RenderPipeline" = "UniversalPipeline"
}
Pass
{
Blend SrcAlpha OneMinusSrcAlpha
ZWrite Off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
CBUFFER_START(UnityPerMaterial)
half4 _ShallowColor;
half4 _DeepColor;
half _DepthMaxDistance;
float4 _NormalMap_ST;
half _NormalScale;
half _WaveSpeed;
half4 _FoamColor;
half _FoamDistance;
half _FoamCutoff;
CBUFFER_END
TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap);
struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};
struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float4 screenPos : TEXCOORD1;
float3 positionWS : TEXCOORD2;
};
Varyings vert(Attributes v)
{
Varyings o;
VertexPositionInputs posInputs = GetVertexPositionInputs(v.positionOS.xyz);
o.positionCS = posInputs.positionCS;
o.positionWS = posInputs.positionWS;
o.screenPos = ComputeScreenPos(o.positionCS);
o.uv = TRANSFORM_TEX(v.uv, _NormalMap);
return o;
}
half4 frag(Varyings i) : SV_Target
{
// --- Depth-based coloring ---
float2 screenUV = i.screenPos.xy / i.screenPos.w;
float rawDepth = SampleSceneDepth(screenUV);
float sceneEyeDepth = LinearEyeDepth(rawDepth, _ZBufferParams);
float surfaceEyeDepth = i.screenPos.w;
half depthDiff = saturate((sceneEyeDepth - surfaceEyeDepth) / _DepthMaxDistance);
half4 waterColor = lerp(_ShallowColor, _DeepColor, depthDiff);
// --- Animated normal map (single sample) ---
float2 scrollUV = i.uv + _Time.y * _WaveSpeed;
half3 normal = UnpackNormalScale(
SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, scrollUV),
_NormalScale
);
// --- Shoreline foam ---
half foamDepth = saturate((sceneEyeDepth - surfaceEyeDepth) / _FoamDistance);
half foam = step(foamDepth, _FoamCutoff);
waterColor = lerp(waterColor, _FoamColor, foam * _FoamColor.a);
// --- Simple fresnel ---
half3 viewDir = normalize(_WorldSpaceCameraPos - i.positionWS);
half fresnel = pow(1.0h - saturate(dot(half3(0, 1, 0), viewDir)), 3.0h);
waterColor.a = lerp(waterColor.a, 1.0h, fresnel);
return waterColor;
}
ENDHLSL
}
}
Fallback "Universal Render Pipeline/Unlit"
}
GERSTNER 波实现
Gerstner 波可产生逼真的摆线波运动。只在顶点着色器中计算:
// Add to vertex shader — call for each wave octave
float3 GerstnerWave(float3 worldPos, float2 direction, float steepness,
float wavelength, float speed)
{
float k = 2.0 * PI / wavelength;
float c = sqrt(9.8 / k); // Phase speed from dispersion relation
float2 d = normalize(direction);
float f = k * (dot(d, worldPos.xz) - c * speed * _Time.y);
float a = steepness / k; // Amplitude
return float3(
d.x * a * cos(f),
a * sin(f),
d.y * a * cos(f)
);
}
// In vertex shader:
Varyings vert(Attributes v)
{
float3 worldPos = TransformObjectToWorld(v.positionOS.xyz);
// 2-3 wave octaves for mobile (vary direction, wavelength, steepness)
float3 wave1 = GerstnerWave(worldPos, float2(1, 0.5), 0.25, 8.0, 1.0);
float3 wave2 = GerstnerWave(worldPos, float2(0.3, 1), 0.15, 4.0, 1.2);
worldPos += wave1 + wave2;
// Recalculate normals from wave tangent/binormal
// (simplified — use cross product of partial derivatives)
Varyings o;
o.positionCS = TransformWorldToHClip(worldPos);
// ... rest of vertex setup
return o;
}
水体功能开销等级
使用此表判断哪些功能适合你的移动端预算:
等级 1:超低开销(低端移动设备,约 0.5ms)
- 基于顶点颜色或 UV 的深度着色(不使用深度纹理)
- 单张滚动法线贴图
- 不使用泡沫或折射
- 不透明渲染(不进行 Alpha 混合)
等级 2:标准移动端(约 1–2ms)
- 基于深度纹理的岸线着色
- 通过深度阈值生成岸线泡沫
- 单张滚动法线贴图
- 基于 Fresnel 的 Alpha
- 1–2 个 Gerstner 波倍频
等级 3:高品质移动端(约 2–4ms)
- 使用深度纹理和不透明纹理进行折射
- 混合两张法线贴图
- Gerstner 波(3 个倍频)
- 动态泡沫纹理
- 主光源产生的高光
- 焦散(投射纹理或着色器生成)
等级 4:仅限桌面端
- 平面反射或 SSR
- 细分
- 基于 FFT 的海洋模拟
- 次表面散射
- 4 个以上 Gerstner 波倍频
Shader Graph 水体——节点设置指南
对于 Shader Graph 用户,以下是适用于移动端且节点开销较低的水体实现方式:
-
深度渐隐:Scene Depth 节点 → 减去 Fragment Eye Depth → 除以最大距离 → 饱和 → 在浅水/深水颜色之间进行 lerp
-
法线动画:UV 节点 → Add(Time × 速度向量)→ Sample Texture 2D(法线贴图,设为 Normal)→ Normal Strength 节点
-
岸线泡沫:使用相同的深度计算,但采用更小的距离 → Step 节点 → 乘以泡沫颜色 → 加到水体颜色上
-
Fresnel:Fresnel Effect 节点(幂约为 3)→ 在基础 Alpha 和 1.0 之间进行 lerp
-
顶点位移(波浪):Position 节点(Object Space)→ 使用正弦/余弦的自定义子图 → 加到 Position 输出
节点总数预算:移动端水体着色器的节点数应控制在 60 个以内。使用 Sub Graphs 进行整理——它们不会增加开销,还能让图表更易读。
参考仓库 — 复制并学习
以下是值得参考的优秀开源水面着色器:
| 仓库 | 支持移动设备? | 特性 | 许可证 |
|---|---|---|---|
| nvjob/nvjob-water-shader-simple-and-fast | 是 | 法线贴图、视差效果、不使用曲面细分 | MIT |
| nvjob/nvjob-water-shaders-v2 | 是 | V1 + 反射 + 岸边泡沫 | MIT |
| bmjoy/Stylized-Water-Shader-Unity-URP | 是 | URP、深度着色、泡沫、折射 | — |
| N7RX/Simple-Water-Shader | 是 | 轻量,优先面向移动设备 | — |
| danielshervheim/unity-stylized-water | 部分支持 | 风格化,带材质预设(约 953★) | — |
| MatrixRex/Uber-Stylized-Water | 部分支持 | Unity 6,高度可定制 | — |
| tuxalin/water-shader | 否 | 多平台、Gerstner 波、焦散 | — |
资料来源
- Alexander Ameye:风格化水面着色器 — https://ameye.dev/notes/stylized-water-shader/
- Roystan:卡通水面 — https://roystan.net/articles/toon-water/
- Cyanilux:2D 水面着色器解析 — https://www.cyanilux.com/tutorials/2d-water-shader-breakdown/
- Harry Alisavakis:水面着色器(CC0)— https://halisavakis.com/
- Catlike Coding:Flow 系列 — https://catlikecoding.com/unity/tutorials/
- 80.lv:自然着色器(水面 + 沙地)— https://80.lv/articles/tutorial-how-to-make-nature-shaders-in-unity
- 80.lv:熔岩灯(流体模拟)— https://80.lv/articles/tutorial-creating-a-lava-lamp-shader-with-unity
- Brabl:移动端水面着色器 — https://brabl.com/unity-5-mobile-water-shader/
- Linden Reid:简单水面着色器 — https://lindenreidblog.com/2017/12/15/simple-water-shader-in-unity/
- Gabriel Aguiar:2D 水面 / 瀑布 VFX — https://www.gabrielaguiarprod.com