从零打造Unity 2D水波着色器:复刻《Kingdom》经典水面效果
你是否曾惊叹于《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)是实现水面反射/折射效果的关键组件,它能捕获相机视图并作为纹理应用到水面材质。正确配置步骤如下:
- 在Project窗口右键创建
Render Texture,命名为WaterRT - 设置合适分辨率:建议使用屏幕分辨率的1/3.75(如1920×1080屏幕对应512×288)
- 格式选择
RGBA32,启用sRGB选项以确保色彩正确性
flowchart LR
A[创建Render Texture] --> B[设置分辨率=屏幕尺寸/3.75]
B --> C[配置格式=RGBA32+sRGB]
C --> D[分配给专用相机]
2. 双相机系统搭建
为实现水面上下内容的分层渲染,需要配置两个相机:
- 主相机:渲染场景主体内容(背景、角色等)
- 水面相机:仅渲染水面下方内容到
WaterRT纹理
配置水面相机步骤:
- 创建新相机并命名为
WaterCamera - 设置
Clear Flags为Solid Color,背景色设为透明 - 在
Target Texture字段指定WaterRT - 调整相机深度(Depth)使其低于主相机,确保渲染顺序正确
// 相机配置关键参数示例(可通过Inspector手动设置)
waterCamera.targetTexture = waterRT; // 分配渲染纹理
waterCamera.clearFlags = CameraClearFlags.SolidColor;
waterCamera.backgroundColor = new Color(0, 0, 0, 0); // 透明背景
waterCamera.depth = -1; // 确保在主相机之后渲染
3. 水面平面设置
创建承载水波纹效果的3D平面:
- 新建3D Plane对象,缩放至覆盖水面区域
- 创建材质
Water.mat并指定Custom/WaterShader - 将
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;
}
顶点位移动画原理:
- 使用
_Time[1]获取随时间增长的数值,实现纹理滚动 - 采样Perlin噪声纹理
_VertexDisplacementTex获取随机偏移值 - 通过
(2 * noise.rg - 1)将采样值从[0,1]范围转换为[-1,1] - 除以
_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
- 禁用顶点位移:在材质面板取消勾选"Vertex Displacement"
- 降低渲染纹理分辨率:从512×288降至256×144
- 简化位移纹理:使用单一位移纹理而非主+细节两层纹理
特殊效果配置
- 平静水面: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:创建渲染纹理
- 在Unity编辑器中右键选择
Create > Render Texture - 命名为
WaterRT,设置尺寸为512×288,格式为RGBA32 - 确保勾选
sRGB选项
步骤3:配置水面相机
- 创建新相机(GameObject > Camera),命名为
WaterCamera - 在
Target Texture字段中选择WaterRT - 调整相机位置与主相机一致,设置
Depth为-1 - 清除标志设置为
Solid Color,背景色设为透明(0,0,0,0)
步骤4:创建水面平面
- 创建3D Plane(GameObject > 3D Object > Plane)
- 缩放平面至覆盖水面区域(建议X=10, Y=1, Z=5)
- 创建新材质(右键 > Create > Material),命名为
WaterMaterial - 材质Shader选择
Custom/WaterShader - 将
WaterRT拖入Texture属性,DisplacementMap拖入Displacement Texture属性
步骤5:参数调整与效果优化
- 在场景视图中实时调整材质参数,观察水面效果变化
- 启用"Vertex Displacement"并将
Vertex Displacement Amount Divider设为60 - 调整
Foam Threshold至0.025,获得适量泡沫效果 - 测试不同视角下的水面表现,必要时启用"Perspective Correction"
常见问题解决方案
问题1:水面显示为纯色而非场景内容
可能原因:
- 渲染纹理未正确分配给水面相机
- 水面相机被禁用或层级设置错误
WaterMaterial的Texture属性未设置为WaterRT
解决方案:
- 检查
WaterCamera的Target Texture是否为WaterRT - 确保
WaterCamera的Enabled选项被勾选 - 验证
WaterMaterial的Texture字段已正确赋值
问题2:水面没有动画效果
可能原因:
- 位移纹理未正确赋值
- Displacement Speed设置过大
- 着色器编译错误
解决方案:
- 检查
Displacement Texture是否已设置为DisplacementMap.png - 将
Displacement Speed调整为30左右 - 在Console窗口查看是否有编译错误,修复着色器代码
问题3:性能低下或帧率下降
解决方案:
- 降低
WaterRT分辨率至256×144 - 禁用"Vertex Displacement"和"Perspective Correction"
- 简化场景复杂度,减少水面相机渲染的物体数量
项目扩展与高级应用
扩展方向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背景资源:用于项目演示场景构建
kernelopenEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。C0105
baihu-dataset异构数据集“白虎”正式开源——首批开放10w+条真实机器人动作数据,构建具身智能标准化训练基座。00
mindquantumMindQuantum is a general software library supporting the development of applications for quantum computation.Python059
PaddleOCR-VLPaddleOCR-VL 是一款顶尖且资源高效的文档解析专用模型。其核心组件为 PaddleOCR-VL-0.9B,这是一款精简却功能强大的视觉语言模型(VLM)。该模型融合了 NaViT 风格的动态分辨率视觉编码器与 ERNIE-4.5-0.3B 语言模型,可实现精准的元素识别。Python00
GLM-4.7GLM-4.7上线并开源。新版本面向Coding场景强化了编码能力、长程任务规划与工具协同,并在多项主流公开基准测试中取得开源模型中的领先表现。 目前,GLM-4.7已通过BigModel.cn提供API,并在z.ai全栈开发模式中上线Skills模块,支持多模态任务的统一规划与协作。Jinja00
AgentCPM-Explore没有万亿参数的算力堆砌,没有百万级数据的暴力灌入,清华大学自然语言处理实验室、中国人民大学、面壁智能与 OpenBMB 开源社区联合研发的 AgentCPM-Explore 智能体模型基于仅 4B 参数的模型,在深度探索类任务上取得同尺寸模型 SOTA、越级赶上甚至超越 8B 级 SOTA 模型、比肩部分 30B 级以上和闭源大模型的效果,真正让大模型的长程任务处理能力有望部署于端侧。Jinja00