mcpField
W
water-fluid-shaders

water-fluid-shaders

github.com/adevra开发1648 次安装31 周前更新
代码仓库

关于 water-fluid-shaders

为 Unity 开发者提供水体与流体着色器的创建、修改和优化指导,重点考虑移动端与 WebGL 限制,并涵盖模板、功能预算和节点工作流。

主要功能

  1. 01移动端着色器性能约束
  2. 02URP HLSL 起步模板
  3. 03Gerstner 波浪指导
  4. 04Shader Graph 节点设置
  5. 05水体功能成本分级

适用场景

  1. 01构建移动端水体着色器
  2. 02添加岸边泡沫和深度颜色
  3. 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 的水体着色器仓库时,也应触发。


核心规则——移动端水体限制

  1. 移动端不使用细分。 移动 GPU 无法高效支持硬件细分。应改为在现有网格上进行顶点位移。

  2. 水体着色器的纹理采样数限制在 ≤ 4 次。 典型的移动端水体着色器预算:1 张法线贴图(平铺、滚动)、1 张深度纹理(用于岸线/泡沫)、1 张场景颜色纹理(用于折射)、1 张泡沫/细节纹理。若超出预算,应优先移除场景颜色折射。

  3. **必须在 URP Asset 设置中启用 Depth Texture 和 Opaque Texture,**才能进行基于深度的岸线检测和折射。这会增加一个复制通道——移动端预算约为 1ms。如果开销过高,可改用基于顶点颜色的深度。

  4. 使用单层法线。 桌面端水体通常会在不同尺度混合 2–3 次法线贴图采样。移动端应使用 1 次法线贴图采样,并通过 UV 滚动实现动画。

  5. 只在顶点着色器中计算 Gerstner 波。 切勿逐片元计算波浪位移。移动端限制为 2–3 个波浪倍频(桌面端为 4 个以上)。

  6. 除世界坐标和 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 用户,以下是适用于移动端且节点开销较低的水体实现方式:

  1. 深度渐隐:Scene Depth 节点 → 减去 Fragment Eye Depth → 除以最大距离 → 饱和 → 在浅水/深水颜色之间进行 lerp

  2. 法线动画:UV 节点 → Add(Time × 速度向量)→ Sample Texture 2D(法线贴图,设为 Normal)→ Normal Strength 节点

  3. 岸线泡沫:使用相同的深度计算,但采用更小的距离 → Step 节点 → 乘以泡沫颜色 → 加到水体颜色上

  4. Fresnel:Fresnel Effect 节点(幂约为 3)→ 在基础 Alpha 和 1.0 之间进行 lerp

  5. 顶点位移(波浪):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 波、焦散—

资料来源

评论

支持 Markdown