首页
/ 从零打造Unity 2D水波着色器:复刻《Kingdom》经典水面效果

从零打造Unity 2D水波着色器:复刻《Kingdom》经典水面效果

2026-01-17 08:33:05作者:邬祺芯Juliet

你是否曾惊叹于《Kingdom》游戏中那波光粼粼的水面效果?是否想在自己的2D项目中实现类似的沉浸式水体渲染?本文将带你深入剖析基于HLSL(High-Level Shading Language,高级着色器语言)的2D水波着色器实现原理,通过10个核心步骤完成从环境搭建到效果优化的全流程开发。读完本文,你将掌握位移贴图(Displacement Mapping)技术、顶点动画原理、泡沫边缘检测算法,以及如何将这些技术整合为高性能的Unity着色器解决方案。

项目架构解析

2D-Water-Shader项目采用典型的Unity资源组织方式,核心文件结构如下:

2D-Water-Shader/
├── Assets/
│   ├── Materials/         # 材质资源(Water.mat)
│   ├── Scenes/            # 场景文件(2DWaterScene.unity)
│   ├── Shaders/           # 核心着色器(WaterShader.shader)
│   ├── Sprites/           # 背景精灵资源
│   └── Textures/          # 纹理资源(含位移图、噪声图)
├── Packages/              # Unity包管理配置
└── ProjectSettings/       # 项目设置文件

关键技术文件说明:

  • WaterShader.shader:使用HLSL编写的自定义着色器,实现水波动画、位移扰动和泡沫效果
  • DisplacementMap.png:主位移纹理,控制水面整体波动形态
  • Perlin.jpg:Perlin噪声纹理,用于顶点位移实现水面高度变化
  • WaterRT.renderTexture:渲染纹理(Render Texture),用于水面下方场景的正确显示

环境准备与基础设置

1. 渲染纹理配置

渲染纹理(Render Texture)是实现水面反射/折射效果的关键组件,它能捕获相机视图并作为纹理应用到水面材质。正确配置步骤如下:

  1. 在Project窗口右键创建Render Texture,命名为WaterRT
  2. 设置合适分辨率:建议使用屏幕分辨率的1/3.75(如1920×1080屏幕对应512×288)
  3. 格式选择RGBA32,启用sRGB选项以确保色彩正确性
flowchart LR
    A[创建Render Texture] --> B[设置分辨率=屏幕尺寸/3.75]
    B --> C[配置格式=RGBA32+sRGB]
    C --> D[分配给专用相机]

2. 双相机系统搭建

为实现水面上下内容的分层渲染,需要配置两个相机:

  • 主相机:渲染场景主体内容(背景、角色等)
  • 水面相机:仅渲染水面下方内容到WaterRT纹理

配置水面相机步骤:

  1. 创建新相机并命名为WaterCamera
  2. 设置Clear FlagsSolid Color,背景色设为透明
  3. Target Texture字段指定WaterRT
  4. 调整相机深度(Depth)使其低于主相机,确保渲染顺序正确
// 相机配置关键参数示例(可通过Inspector手动设置)
waterCamera.targetTexture = waterRT;  // 分配渲染纹理
waterCamera.clearFlags = CameraClearFlags.SolidColor;
waterCamera.backgroundColor = new Color(0, 0, 0, 0);  // 透明背景
waterCamera.depth = -1;  // 确保在主相机之后渲染

3. 水面平面设置

创建承载水波纹效果的3D平面:

  1. 新建3D Plane对象,缩放至覆盖水面区域
  2. 创建材质Water.mat并指定Custom/WaterShader
  3. WaterRT渲染纹理赋值给材质的Texture属性

核心着色器原理与实现

着色器结构解析

WaterShader.shader采用Unity标准的着色器结构,包含属性定义、SubShader和Pass三个主要部分:

Shader "Custom/WaterShader" {
    Properties {
        // 纹理属性
        _MainTex("Texture", 2D) = "white" {}           // 主纹理(渲染纹理)
        _DisplacementTex("Displacement Texture", 2D) = "white" {}  // 主位移纹理
        
        // 动画属性
        _DisplacementSpeedDivider("Displacement Speed", Float) = 30  // 位移速度
        
        // 效果控制属性
        _DisplacementAmountDivider("Displacement Amount Divider", Float) = 40  // 位移强度
        _FoamThreshold("Foam Threshold", Float) = 0.022  // 泡沫阈值
        // ...其他属性
    }
    
    SubShader {
        Tags { "RenderType" = "Opaque" }
        LOD 100
        
        Pass {
            CGPROGRAM
            #pragma vertex vert  // 顶点着色器函数
            #pragma fragment frag  // 片元着色器函数
            // ...Shader Features和包含文件
            ENDCG
        }
    }
}

顶点着色器:实现水面高度动画

顶点着色器负责处理网格顶点的位置变换,通过采样噪声纹理实现水面起伏效果:

v2f vert(appdata v) {
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);  // 基础顶点变换
    o.uv = TRANSFORM_TEX(v.uv, _MainTex);       // UV坐标变换
    
    // 顶点位移(仅在VERTEX_DISPLACEMENT特性启用时)
    #ifdef VERTEX_DISPLACEMENT
        // 采样随时间移动的噪声纹理
        float2 vertexDispUV = o.uv.xy + _Time[1] / _VertexDisplacementSpeedDivider;
        float4 noise = tex2Dlod(_VertexDisplacementTex, float4(vertexDispUV, 0, 0));
        // 将噪声值转换为顶点偏移量(范围[-1,1]映射到实际位移)
        o.vertex.xy += (2 * noise.rg - 1) / _VertexDisplacementAmountDivider;
    #endif
    
    UNITY_TRANSFER_FOG(o,o.vertex);  // 雾效果传递
    return o;
}

顶点位移动画原理:

  1. 使用_Time[1]获取随时间增长的数值,实现纹理滚动
  2. 采样Perlin噪声纹理_VertexDisplacementTex获取随机偏移值
  3. 通过(2 * noise.rg - 1)将采样值从[0,1]范围转换为[-1,1]
  4. 除以_VertexDisplacementAmountDivider控制位移强度

片元着色器:实现水面扰动与泡沫效果

片元着色器是实现水面视觉效果的核心,包含位移贴图采样、纹理坐标调整和泡沫检测三个关键步骤:

1. 位移贴图采样与UV扰动

// 根据视角修正选择不同的采样方式
#ifdef PERSPECTIVE_CORRECTION
    // 带透视修正的位移采样
    offset = tex2D(_DisplacementTex, float2(i.uv.x + _Time[1]/_DisplacementSpeedDivider + 
                  _WorldSpaceCameraPos.x/_ParallaxDivider, i.uv.y) + perspectiveCorrection).rg +
             tex2D(_DisplacementDetailTex, float2(i.uv.x + _Time[1]/_DisplacementDetailSpeedDivider + 
                  _WorldSpaceCameraPos.x/_ParallaxDivider, i.uv.y) + perspectiveCorrection).rg;
#else
    // 标准位移采样
    offset = tex2D(_DisplacementTex, float2(i.uv.x + _Time[1]/_DisplacementSpeedDivider + 
                  _WorldSpaceCameraPos.x/_ParallaxDivider, i.uv.y)).rg +
             tex2D(_DisplacementDetailTex, float2(i.uv.x + _Time[1]/_DisplacementDetailSpeedDivider + 
                  _WorldSpaceCameraPos.x/_ParallaxDivider, i.uv.y)).rg;
#endif

位移实现机制:

  • 使用主位移纹理(_DisplacementTex)和细节位移纹理(_DisplacementDetailTex)叠加扰动
  • 通过_Time[1]/_DisplacementSpeedDivider控制纹理滚动速度,产生流动效果
  • 添加_WorldSpaceCameraPos.x/_ParallaxDivider实现视差效果,增强立体感

2. 纹理坐标调整

// 计算调整后的UV坐标(原始UV加上位移偏移)
float2 adjusted = i.uv.xy + (offset - 0.5) / _DisplacementAmountDivider;
// 采样主纹理(渲染纹理)
fixed4 col = tex2D(_MainTex, adjusted);

UV调整原理:

  • offset是位移纹理采样结果,范围[0,1]
  • (offset - 0.5)将范围转换为[-0.5,0.5],使位移能在正负方向同时发生
  • 除以_DisplacementAmountDivider控制整体位移强度(值越小位移越强烈)

3. 泡沫边缘检测

// 泡沫阈值检测
if ((abs((offset.x - 0.5)/_DisplacementAmountDivider) > _FoamThreshold && 
     abs((offset.y - 0.5)/_DisplacementAmountDivider) > _FoamThreshold) ||
    i.uv.y < _EdgeFoamThreshold * (offset.x - 0.5)/_DisplacementAmountDivider) {
    // 泡沫区域:返回白色带透明度的颜色
    return float4(1, 1, 1, _FoamAlpha) + (1 - _FoamAlpha) * colAdj;
} else {
    // 非泡沫区域:返回调整后的颜色
    return colAdj;
}

泡沫生成规则:

  • 当X和Y方向的位移量都超过_FoamThreshold时,判定为水波峰值区域
  • 当UV坐标接近水面底部(i.uv.y < ...)时,生成边缘泡沫
  • 通过_FoamAlpha控制泡沫透明度,实现半透明效果

关键参数调优指南

为帮助开发者快速获得理想效果,以下是经过实践验证的参数配置方案:

基础水面效果(默认配置)

参数名称 推荐值 作用
Displacement Speed 30 主位移纹理滚动速度,值越小速度越快
Displacement Detail Speed 60 细节位移纹理滚动速度,建议为主速度的2倍
Displacement Amount Divider 40 位移强度控制,值越小效果越明显(建议30-50)
Foam Threshold 0.022 泡沫生成阈值,值越小泡沫越多
Edge Foam Threshold 0.005 边缘泡沫阈值,值越小边缘泡沫范围越大

性能优化配置

对于移动平台或低配置设备,建议采用以下优化参数:

pie
    title 性能优化参数调整方向
    "禁用Vertex Displacement" : 40
    "降低Render Texture分辨率" : 30
    "使用256x256位移纹理" : 20
    "关闭Perspective Correction" : 10
  1. 禁用顶点位移:在材质面板取消勾选"Vertex Displacement"
  2. 降低渲染纹理分辨率:从512×288降至256×144
  3. 简化位移纹理:使用单一位移纹理而非主+细节两层纹理

特殊效果配置

  • 平静水面:Displacement Amount Divider = 80,Displacement Speed = 60
  • 湍急水流:Displacement Amount Divider = 20,Displacement Speed = 15
  • 浓雾水面:Foam Alpha = 0.8,Foam Threshold = 0.03

完整实现步骤

步骤1:克隆项目代码

git clone https://gitcode.com/gh_mirrors/2d/2D-Water-Shader
cd 2D-Water-Shader

步骤2:创建渲染纹理

  1. 在Unity编辑器中右键选择Create > Render Texture
  2. 命名为WaterRT,设置尺寸为512×288,格式为RGBA32
  3. 确保勾选sRGB选项

步骤3:配置水面相机

  1. 创建新相机(GameObject > Camera),命名为WaterCamera
  2. Target Texture字段中选择WaterRT
  3. 调整相机位置与主相机一致,设置Depth为-1
  4. 清除标志设置为Solid Color,背景色设为透明(0,0,0,0)

步骤4:创建水面平面

  1. 创建3D Plane(GameObject > 3D Object > Plane)
  2. 缩放平面至覆盖水面区域(建议X=10, Y=1, Z=5)
  3. 创建新材质(右键 > Create > Material),命名为WaterMaterial
  4. 材质Shader选择Custom/WaterShader
  5. WaterRT拖入Texture属性,DisplacementMap拖入Displacement Texture属性

步骤5:参数调整与效果优化

  1. 在场景视图中实时调整材质参数,观察水面效果变化
  2. 启用"Vertex Displacement"并将Vertex Displacement Amount Divider设为60
  3. 调整Foam Threshold至0.025,获得适量泡沫效果
  4. 测试不同视角下的水面表现,必要时启用"Perspective Correction"

常见问题解决方案

问题1:水面显示为纯色而非场景内容

可能原因

  • 渲染纹理未正确分配给水面相机
  • 水面相机被禁用或层级设置错误
  • WaterMaterialTexture属性未设置为WaterRT

解决方案

  1. 检查WaterCameraTarget Texture是否为WaterRT
  2. 确保WaterCameraEnabled选项被勾选
  3. 验证WaterMaterialTexture字段已正确赋值

问题2:水面没有动画效果

可能原因

  • 位移纹理未正确赋值
  • Displacement Speed设置过大
  • 着色器编译错误

解决方案

  1. 检查Displacement Texture是否已设置为DisplacementMap.png
  2. Displacement Speed调整为30左右
  3. 在Console窗口查看是否有编译错误,修复着色器代码

问题3:性能低下或帧率下降

解决方案

  1. 降低WaterRT分辨率至256×144
  2. 禁用"Vertex Displacement"和"Perspective Correction"
  3. 简化场景复杂度,减少水面相机渲染的物体数量

项目扩展与高级应用

扩展方向1:添加水面交互效果

通过在着色器中添加对鼠标位置或碰撞体的检测,可以实现点击水面产生波纹的交互效果:

// 在片元着色器中添加鼠标交互代码
float2 mousePos = _MousePosition.xy / _ScreenParams.xy;  // 获取归一化鼠标位置
float distance = distance(i.uv, mousePos);  // 计算像素到鼠标的距离
if (distance < 0.1 && _MouseDown > 0) {
    // 添加鼠标位置的额外位移
    offset += float2(sin(_Time[1] * 5) * 0.1, cos(_Time[1] * 5) * 0.1) * exp(-distance * 10);
}

扩展方向2:实现水面折射效果

当前版本使用的是简单的位移映射技术,要实现更真实的折射效果,可以修改采样方式:

// 折射效果实现代码
float3 normal = float3(offset.x, offset.y, 1.0);  // 从位移计算法向量
normal = normalize(normal);  // 归一化法向量
float2 refractUV = i.uv + normal.xy * _RefractAmount;  // 计算折射UV
fixed4 col = tex2D(_MainTex, refractUV);  // 使用折射UV采样

总结与展望

本文详细介绍了基于Unity和HLSL的2D水波着色器实现方案,从环境搭建、着色器原理到完整实现步骤,全面覆盖了创建高质量水面效果的核心技术。通过位移贴图实现的水面扰动、顶点动画产生的高度变化,以及基于阈值检测的泡沫生成算法,共同构建了接近《Kingdom》游戏的视觉效果。

该项目仍有进一步优化和扩展的空间,未来可以探索:

  • 基于物理的水面模拟(如使用Navier-Stokes方程)
  • 水面与物体交互的浪花效果
  • 移动端GPU优化与性能适配

希望本文能为你的2D游戏项目提供实用的水面渲染解决方案,让你的游戏世界更加生动逼真。如有任何技术问题或优化建议,欢迎在项目仓库提交Issue或Pull Request。

资源与参考资料

  • Unity官方文档:ShaderLab语法
  • HLSL编程指南:Microsoft HLSL文档
  • 《Kingdom》游戏美术分析:水面效果设计思路
  • Craftpix免费2D背景资源:用于项目演示场景构建
登录后查看全文
热门项目推荐
相关项目推荐