很多人第一次尝试复刻《塞尔达传说:风之杖》这类水面时,最容易犯的错误是打开一个标准 PBR 材质,把 Metallic 调到 0,Smoothness 拉高,然后铺一层蓝色贴图就结束。结果出来的不是清新明快的卡通海面,而是一片油腻腻的塑料水。问题出在思路:这类低多边形卡通水面根本不该用“物理真实”来理解,它更像是把流体模拟的结果“翻译”成动画语言。
这篇文章要做的,就是拆解这种“动画语言”在 Unity Shader Graph 里怎么落地。我会从风之杖海面视觉特征入手,拆出它真正起作用的三层结构,再带你在 URP 项目里从零搭一个 Shader Graph 水面 Shader,覆盖颜色分层、程序化波纹、风格化高光、顶点波浪和 C# 动态控制,最后给出一份实际项目中踩过的坑和性能优化建议。
如果你正在学图形学、想做卡通渲染(NPR)、或者刚接触 Shader Graph 但不想一上来就啃 HLSL,这篇文章可以直接作为你的“第一个风格化水面”练手项目。整个实现不需要手写完整 Shader,主要靠节点完成,只有顶点波浪部分用到一个很小的 HLSL Custom Function。
1. 为什么要复刻这种水面:它解决的是“风格化”的认知问题
很多开发者已经会调 PBR 材质,知道 Metallic、Smoothness、法线贴图这些词,但一做“赛尔达这种卡通水”就翻车。翻车的原因不是 Shader 语法看不懂,而是底层认知没切换过来。
PBR 的思路是模拟光线与表面的物理交互:粗糙度控制高光扩散,法线贴图控制微表面,反射用环境贴图,所有参数的目标是“让物体看起来像真实世界中的某样东西”。而风之杖水面完全是另一套逻辑,它的目标是“让观众一眼认出这是水,同时保持和角色、天空、岛屿一致的动画感”。它不追求真实,它追求“可读性”。
这种风格化的水面,真正依赖的是三样东西:
- 清晰的颜色分层:近处浅水、远处深水、高光带、泡沫边缘,每一层有明确边界。
- 有节奏感的波浪运动:它不是物理海浪,而是类似节拍器驱动的正弦波叠加,重复但有变化。
- 形状化的高光:高光不是来自物理光照计算,而是用程序化图案“画”上去的,像卡通动画里扫过画面的光斑。
只要理解这三层,哪怕你不手写 Shader,而是用 Shader Graph 拼节点,也能做出一眼就有“风之杖味”的水面。
这篇文章适合三类人:刚学 Shader Graph 想拿真实案例练手的 Unity 开发者;做风格化游戏美术,需要和 TA 沟通“我要什么效果”的美术同学;以及研究图形学、想看 NPR 卡通渲染中“水的形状感”如何实现的同学。
2. 风之杖水面的视觉拆解:三要素模型
先做一个“去掉贴图滤镜”的拆解。你在游戏里看到的水面,不是靠一张高分率法线贴图或复杂的反射探头撑起来的。把它分解成三个可复现的技术要素。
2.1 要素一:低频率、强节奏感的波纹
风之杖的水面波动频率很低,波浪密度也不高。它不像写实水面那样有无数的细小涟漪,而是把波纹简化成几个大波浪,再用一点点噪声打破重复感。
这本质上是多个正弦波叠加的结果:
高度 = A1 * sin(X * freq1 + time * speed1) + A2 * sin(Y * freq2 + time * speed2) + A3 * sin((X + Y) * freq3 + time * speed3)每个波的振幅、频率、方向都不同。实际制作时,2 到 3 层足够,叠多了反而会让水面看起来“脏”,破坏卡通感。
2.2 要素二:大块面的颜色分层
颜色是风格化水面最重要的信息载体。远处是深海蓝,靠近岸边的浅滩区域会变浅,和沙滩交界处可能有一圈白色泡沫线。
在 Shader 里,实现颜色分层常用两种方式:
- 高度渐变:用波浪高度或 UV 坐标的某个分量做 Gradient,映射到渐变色。
- 区域 Mask:用噪声或距离场生成色块,配合 Smoothstep 做柔和过渡。
风之杖的颜色分层更多是“平面色带”,不是细腻的物理渐变。做的时候要注意,Lerp 中间的过渡区不能太大,否则又回到写实感。
2.3 要素三:形状化高光
这是最容易被忽略但决定“风味”的部分。PBR 的高光是随光照实时计算的,光斑形状由粗糙度和法线决定;而风之杖的高光更像是一块一块的“亮斑”铺在波浪上。
实现时它不是依赖光照方向,而是依赖 UV 和波纹本身。常用方法是:
- 用 Voronoi 噪声生成不规则的小格子。
- 用波浪高度值的局部峰值做 Step 或 Smoothstep,只保留“波峰”上的高光。
- 高光颜色直接叠加,而不是乘一个光源颜色。
这样出来的高光会跟随波纹流动,产生一种“水被阳光打亮”的动态感。
2.4 写实水面与卡通水面的核心区别
| 维度 | 写实水面(PBR) | 风格化卡通水面(NPR) |
|---|---|---|
| 法线来源 | 法线贴图,细节丰富 | 程序化波浪,低频大块 |
| 高光形态 | 物理高光,受粗糙度控制 | 形状化亮斑,图案化 |
| 反射 | 依赖 Reflection Probe / Planar Reflection | 只用颜色模拟或简单渐变 |
| 颜色 | 近岸复杂,受环境光照影响大 | 色带分层,边界清晰 |
| 透明 | 常带半透明和焦散 | 大多是 Opaque 或不透明混合 |
| 性能 | 高,需要贴图和反射 | 低,几乎纯数学运算 |
这一段的结论是:做风之杖水面,你不需要“更真实”,你需要“更形状化”。所有后续节点的组织,都围绕这个结论展开。
3. Unity Shader Graph 环境准备与 URP 配置
用 Shader Graph 必须搭配可编程渲染管线,推荐使用 URP(Universal Render Pipeline)。HDRP 也能用,但功能过重,移动端目标基本不会选它。
3.1 版本选择
建议使用 Unity 2021.3 LTS 或更高版本。2021.3 开始 Shader Graph 和 URP 已经非常稳定,网上能找到的大多数教程也都基于这个版本线,遇到问题更容易查资料。本文不绑定具体 Patch 版本,你只要确保编辑器里能看到 Shader Graph 和 Universal RP 两个包即可。
3.2 新建 URP 项目
最简单的方式是直接创建带 URP 的模板项目:
Unity Hub -> New Project -> Universal 2D / 3D 模板模板里已经包含 URP 包、Shader Graph 包、Renderer Pipeline Asset,可以直接开始。
3.3 已有项目升级到 URP
如果是老项目,可以直接在 Package Manager 里安装依赖。更可控的方式是修改Packages/manifest.json,加入下面两个包:
{ "dependencies": { "com.unity.render-pipelines.universal": "14.0.11", "com.unity.shadergraph": "14.0.11" } }具体版本号以你打开 Package Manager 时能看到的最新稳定版本为准。安装完成后,Unity 会提示你创建 Render Pipeline Asset,也可以手动创建:
Create -> Rendering -> URP Asset(Universal Render Pipeline)接着把管线指定给项目:
Project Settings -> Graphics -> Scriptable Render Pipeline Settings -> 指定 URP Asset如果用的是 URP 2D Renderer,那就选择 2D 模板,不要在 3D 项目里强行使用 2D Renderer,否则 Shader 的渲染路径会对不上。
3.4 认识 Shader Graph 编辑器
打开方式是:
Create -> Shader Graph -> URP -> Unlit Shader Graph创建后双击进入编辑器。左边是 Node 搜索面板,中间是主预览区,右上角是材质预览球,右下角是 Graph Inspector,用来设置属性、更新模式和 Master Stack 的渲染设置。
第一次打开时就注意:Shader Graph 的 Master Stack 在底部,最常用的入口是Fragment和Vertex两个 Block。Fragment 控制像素输出,Vertex 控制顶点位移。这个结构后面会反复提到。
4. 水面 Shader 的整体结构与核心节点设计
4.1 选择 Unlit 还是 Lit
风之杖水面完全可以用 Unlit Shader Graph 来做,因为它的效果主要靠程序化颜色和高光 Mask,不依赖实时物理光照。用 Lit 反而需要关掉一堆 PBR 特性,属性面板会非常乱。
但很多想保留一点光影联动的场景也可以用URP/Lit,然后把 Metallic 固定为 0,Smoothness 设为 0.5 左右,Base Color 靠程序化生成。两种方案没有绝对好坏,我的建议是:
- 玩家角色入水交互多,周围有大量动态光照:用 Lit,保留最基本的响应。
- 只是做环境背景水面、岛屿周围海域:直接用 Unlit,性能最好,效果最可控。
4.2 核心属性设计
水面 Shader 的属性不需要多,但要保证美术能调出不同海域感觉。推荐暴露以下属性:
| 属性名 | 类型 | 作用 |
|---|---|---|
| DeepColor | Color | 深海区域颜色 |
| ShallowColor | Color | 浅水区颜色 |
| FoamColor | Color | 浪花/高光区域颜色 |
| WaveSpeed | Float | 波浪整体流动速度 |
| WaveDensity | Float | 波浪密度,控制频率 |
| WaveAmplitude | Float | 波浪高度振幅 |
| DistortionStrength | Float | 噪声扰动强度 |
| HighlightContrast | Float | 高光 Mask 的锐利程度 |
属性在Graph Inspector -> Graph Inspector -> Node Settings中创建,类型选择 Float / Color / Vector1。
4.3 数学核心:用正弦波叠加替代法线贴图
前面已经提过,核心公式是多层正弦波叠加。在 Shader Graph 里可以这样理解节点链路:
UV -> Add 时间偏移 -> Multiply 密度 -> Sine -> 得到 -1 到 1 的波形把这个波形乘振幅后输出到Vertex Position或者Fragment Base Color,水面就“活”了。为了避免多个波叠起来太规则,可以使用 Gradient Noise 节点来调制相位:
UV -> Gradient Noise -> Multiply 扰动强度 -> Add 到 UV -> 再进 Sine这笔“噪声调制”就是卡通水面不生硬的关键。只要相位被噪声打乱,波浪的重复感就会降低很多。
4.4 为什么不用法线贴图
写实水面用一张海浪法线贴图能获得非常丰富的细节,但卡通风格的问题是:法线贴图带来的高光非常“物理”,它不会形成形状化的亮斑,反而让水面像是铺了一层波光粼粼的塑料膜。
如果你确实需要一点法线扰动,可以在 Shader Graph 里用法线贴图 + Unpack Normal,然后把扰动叠加给 UV,但强度建议控制在 0.05 到 0.15 之间,只是做辅助,不能是主力。
这一段的判断是:程序化波纹 + 噪声相位调制,是风格化水面的地基;法线贴图可以存在,但永远只是配角。
5. Shader Graph 实操:从零搭建低多边形卡通水面
现在开始正式搭建。我们以 Unlit Shader Graph 为例,一步一步把水做出来。
5.1 创建 Shader Graph 和材质
在 Project 窗口里:
Create -> Shader Graph -> URP -> Unlit Shader Graph 命名:Water_Toon然后创建材质:
Create -> Material 命名:Water_Toon_Mat 把 Water_Toon 拖到材质的 Shader 槽位此时 Scene 里建一个 Plane 或 Cube,把材质拖上去,就能实时预览效果。先不要管预览黑不黑,后面 Shader 还没写,黑色是正常的。
5.2 暴露基础颜色属性
在 Graph Inspector 里创建三个 Color 属性:
Graph Inspector -> 添加属性 -> Color 默认值建议: DeepColor: (0.05, 0.15, 0.35, 1) ShallowColor:(0.2, 0.5, 0.7, 1) FoamColor:(0.85, 0.95, 1.0, 1)在 Fragment 的Base ColorBlock 上临时连接DeepColor,确保材质先显示出一个颜色,方便后续预览。
5.3 构建 UV 基础
从UV节点开始,Split 拆出 UV.x 和 UV.y,方便对不同方向波做不同处理。如果不拆,单一 UV 做二维波形也可以,但控制力会弱一些。
5.4 创建第一层波浪:纵向波纹
这层波用于控制水面的基本起伏颜色。
节点链路:
UV.y -> Add(Time 节点 * WaveSpeed) -> Multiply(WaveDensity) -> Sine -> Multiply(WaveAmplitude)把Time节点和WaveSpeed做乘法时,注意 Time 输出的是向量(t, sin(t), cos(t)),只取Time.x即可。不能用整个 Vector 去乘一个数。
这一层输出的值在 -1 到 1 之间,后续需要重新映射到 0-1 范围。最简单的做法是用Remap节点,把 -1 to 1 映射到 0 to 1,或者用Divide后加 0.5:
Sine 输出 -> Add 1 -> Multiply 0.55.5 创建第二层波浪:横向波纹
复制一份刚才的链路,但是改用 UV.x,相位上加上第一层波浪的输出作为偏移。这样两层波浪会产生明显的交叉流动感:
UV.x -> Add(第一层波浪输出 * 0.3 + Time * WaveSpeed)-> Sine这里的关键是“把第一层波浪作为相位偏移”,它让第二层波浪不是平移,而是真正被第一层波“推着走”,视觉上会自然很多。
5.6 加入噪声扰动
创建Gradient Noise节点,输入 UV * WaveDensity,输出范围为 0-1。把它乘以 DistortionStrength,叠加到两层波浪的输入 UV 上:
Gradient Noise (UV * WaveDensity) -> Multiply DistortionStrength -> Add 给 UV 相位调节 DistortionStrength 从 0 到 0.3 左右,你会明显看到波浪从“塑料规律”变成“自然流动”。
5.7 颜色分层
现在把两层波浪合并成一个水量因子。简单方式是 Add 两层波后的值,再经过Smoothstep约束范围。
节点链路:
波浪总高度 = Layer1 + Layer2 Smoothstep(0.3,0.7,波浪总高度)用这个值在DeepColor和ShallowColor之间 Lerp:
Lerp(DeepColor,ShallowColor,Smoothstep 输出)边缘处再用一个小范围的 Smoothstep 混合 FoamColor。比如:
Smoothstep(0.65,0.95,波浪总高度)这就是“高光色”,混合进去后,水面的层次立刻丰富。
5.8 风格化高光 Mask
传统做法是把高光叠加上去,风格化做法是“画”上去。用Voronoi节点制造碎斑:
UV * VoronoiScale -> Voronoi(Cell Density 控制格子大小)Voronoi 节点输出Distance和Cells,这里用Distance,配合 Smoothstep 取出边界的亮线:
Smoothstep(0.7,1.0,Voronoi Distance)把结果乘 FoamColor,叠加到颜色输出上。这就是典型的风之杖海面高光区域:不规则、块状、随 UV 展开。
如果你希望高光只出现在波峰上,还可以把 Voronoi 输出和波浪总高度做乘法,让高光跟着波浪一起动。
5.9 连接 Master Stack
最终在 Fragment Block 中连接:
Base Color = 分层颜色 + 高光色 Alpha = 1(暂不透明)如果要做半透明水面,把 Surface Type 改成 Transparent,并给 Alpha 流出透明度。但移动端性能考虑,建议优先实验 Opaque,半透明放到后面加强。
5.10 顶点波浪:连接到 Vertex Block
上述操作只改变颜色,水是平的,看起来像贴图在动。要让水面真正起伏,需要在VertexBlock 上输出额外高度:
Vertex Position = 原顶点位置 + 波浪总高度 * 法线方向Shader Graph 里操作方式是:
Position(Object Space) -> Add(波浪总高度 * 法线方向) -> 输出到 Vertex Position这也是风之杖水面上浪花起伏的基础。做完这一步,你的水面才真正“立体”。
6. 进阶:用 HLSL Custom Function 做多层波浪叠加
当波浪层数变多,Shader Graph 节点会非常冗长。更好的做法是把波浪计算写成一个 HLSL 文件,然后用 Custom Function 节点引入。这样既保留 Shader Graph 的灵活性,又能写更高效的数学逻辑。
6.1 创建 HLSL 文件
在 Assets 下新建WaterWave.hlsl,内容如下:
// 文件路径:Assets/Shaders/WaterWave.hlsl #ifndef WATER_WAVE_INCLUDED #define WATER_WAVE_INCLUDED float3 GetWaveHeight(float3 positionOS, float time, float waveDensity, float waveAmplitude, float speed) { float x = positionOS.x; float z = positionOS.z; float wave1 = sin(x * waveDensity * 1.0 + time * speed); float wave2 = sin(z * waveDensity * 1.4 + time * speed * 0.8); float wave3 = sin((x + z) * waveDensity * 0.6 + time * speed * 1.2); float height = (wave1 + wave2 + wave3) * waveAmplitude; // 保持平整的大范围,同时给一个可调的整体偏移 return float3(0, height, 0); } #endif这个文件只输出 Y 轴高度,后续可以在 Shader Graph 里加到顶点位置上。
6.2 导入 Custom Function 节点
在 Shader Graph 中右键新建节点Custom Function,设置:
Type: File Source: WaterWave.hlsl Name: GetWaveHeight连接输入:
positionOS:Object Space Position time:Time 节点输出 waveDensity:WaterShiness 相关 waveAmplitude:WaveAmplitude speed:WaveSpeed输出Out接到 Vertex Block 的 Position 增量上。
6.3 为什么要把顶点波浪单独写 HLSL
纯 Shader Graph 也能做顶点波浪,但节点多、性能差,而且调试困难。写成 HLSL 之后,后续想加 FBM(分形布朗运动)叠加也容易:
float noisePhase = sin(x * 3.0 + time) + cos(z * 5.0 + time * 0.5); float height = wave1 * noisePhase * 0.3 + wave2 + wave3;FBM 本质就是多层不同频率的噪声叠加,在 HLSL 里只是多几行循环的事,但在 Shader Graph 里会膨胀出一大片节点。
7. 用 C# 脚本控制水面动态参数
Shader Graph 暴露的参数可以直接通过 C# 控制。这一招在实际项目里非常常用,比如角色站在水里时加剧波浪、触发机关时水面颜色变深、昼夜切换时换水的颜色。
下面是一个简单的 C# 脚本:
// 文件路径:Assets/Scripts/WaterController.cs using UnityEngine; public class WaterController : MonoBehaviour { public Material waterMaterial; public Color deepColor = new Color(0.05f, 0.15f, 0.35f, 1f); public Color shallowColor = new Color(0.2f, 0.5f, 0.7f, 1f); public float waveScale = 1.2f; public float disturbStrength = 0.15f; void Update() { if (waterMaterial == null) return; // 修改颜色 waterMaterial.SetColor("_DeepColor", deepColor); waterMaterial.SetColor("_ShallowColor", shallowColor); // 修改数值参数 waterMaterial.SetFloat("_WaveDensity", waveScale); waterMaterial.SetFloat("_DistortionStrength", disturbStrength); } public void SwitchStormMode() { waterMaterial.SetColor("_DeepColor", new Color(0.02f, 0.06f, 0.12f, 1f)); waterMaterial.SetColor("_ShallowColor", new Color(0.1f, 0.2f, 0.3f, 1f)); waterMaterial.SetFloat("_WaveAmplitude", 2.5f); waterMaterial.SetFloat("_WaveSpeed", 3.5f); } }几点提醒:
- 属性名必须和 Shader Graph 里暴露的 Reference 一致,建议在 Shader Graph 属性面板里手动把 Reference 改成
_DeepColor这种风格,不要用默认的_Color_1234ABCD。 - 如果使用
renderer.material,每次访问都会生成一个材质实例,频繁修改会造成实例泄漏,建议直接缓存Material或使用renderer.sharedMaterial(明确知道不会影响其他使用同一材质的物体时才安全)。 - 不要在 Update 里每帧 SetFloat 大量参数,默认情况下 MaterialPropertyBlock 更适合高频更新,或者只对需要动态变化的水面区域用独立材质实例。
8. 常见问题与排查清单
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 水面材质变成紫红色 | Shader 编译失败或 GPU 不支持当前特性 | 打开 Console 看错误日志,检查 Shader 是否引用缺失属性 | 修复节点连接,删除不兼容节点;升级驱动 |
| 预览窗口全黑 | Master Stack 的 Base Color 没有连接;或 Surface Type 不正确 | 检查 Fragment Block 是否有值输出 | 连接一个临时颜色,比如 DeepColor |
| 波浪不动 | Time 节点没有正确传入;或属性被覆盖 | 检查 WaveSpeed 是否大于 0;看 Time 是否接了 x 分量 | 用 Time.x 乘 WaveSpeed,确认波形链路有信号 |
| 波浪太规律、像塑料 | 噪声扰动不足 | 检查 Gradient Noise 节点强度和输入 UV | 增加 DistortionStrength;叠加第二层噪声 |
| 高光花屏 / 闪烁 | Smoothstep 阈值过窄,边缘采样不足 | 调大 Smoothstep 的宽度;降低 Voronoi Cell Density | 把阈值改为 0.3-0.7 而非 0.9-1.0 |
| 移动端帧率暴跌 | 使用了过多噪声 / Voronoi 节点 | 通过 Frame Debugger 看 Overdraw;Shader 变体数量 | 减少 Voronoi 使用,用 Step/Smoothstep 代替 |
| Shader Graph 编辑器打不开 | 项目没有安装 Shader Graph 包 | Package Manager 检查包列表 | 安装 com.unity.shadergraph |
| 报错“a d3d11-compatible gpu is required” | 当前 GPU 或虚拟机不支持 DX11 Feature Level 11.0 | 检查显卡支持情况,确认编辑器运行环境 | 换一台支持 DX11 的设备;检查显卡驱动 |
| 半透明水面排序错误 | Transparent 渲染队列,物体绘制顺序不对 | 调整 Render Queue,或改用 Opaque | 非必要场景使用 Opaque 最稳妥 |
这里必须强调:凡是出现紫红色材质,第一件事不是怀疑 Shader 代码逻辑,而是看 Console 窗口的编译错误。紫红色是 Unity 在 Shader 编译失败时给材质的“占位色”,错误日志会直接告诉你哪个节点或属性出问题。
9. 最佳实践与工程建议
9.1 把波浪计算封装成 Sub Graph
实际项目里,水面高光、颜色分层、顶点波浪都会用到同一个“波浪高度”值。建议把它做成 Sub Graph:
右键 -> Create Node -> Sub Graph 输入:UV、Time、Speed、Density、Amplitude 输出:WaveHeight这样颜色链路和顶点链路共享同一个波形,避免两边计算不一致导致颜色和起伏对不上。
9.2 控制 Shader 变体数量
很多人不知道,URP 的 Shader Graph 每勾选一个特性,就多一批变体。水面这种低频 Shader,要特别注意:
- 不用阴影时,关闭
Cast Shadows/Receive Shadows。 - 不用反射时,关闭
Reflection Probes。 - 不在多个 Renderer 间频繁切材质,尽量用 MaterialPropertyBlock。
9.3 移动端性能预算
如果目标是手机端,建议把水面限制在两层正弦波,不需要按桌面端的 5 层叠加。全屏水面尽量少用 Voronoi,Voronoi 在移动端的 OpenGL ES 3.0 设备上依然有较大 ALU 开销,可以改用棋盘格或简单的 Smoothstep 组合。
水面如果可以做成 Opaque,就不要做 Transparent。不要小看这一点,Transparent 的 Overdraw 在大量水体区域会直接造成帧率悬崖。
9.4 美术和程序之间的协作
Shader Graph 项目一定要注意属性 Reference 命名。暴露属性时把 Reference 改成有含义的名字,比如_DeepColor而不是_Color_ABC123,否则 C# 脚本很难维护。
Sub Graph 也要按模块分目录存放:
Assets/Shaders/Water/SubGraphs/ Assets/Shaders/Water/Nodes/ Assets/Shaders/Water/HLSL/团队协作时,Sub Graph 和 HLSL 文件也要纳入版本管理,并约定好命名前缀。
9.5 不要迷信“完全不用代码”
Shader Graph 能解决 80% 的拼装工作,但遇到顶点波浪、复杂的相位噪声、多平台性能优化时,HLSL Custom Function 才是真正能“兜底”的地方。建议在学会 Shader Graph 的同时,花时间看懂它生成的 Shader 文件:
打开材质 -> Shader 旁边的小齿轮 -> View Generated Shader这件“反编译自己”的习惯,能让你从拼节点的人进阶到真正懂图形学的开发者。
10. 总结
风之杖风格水面的核心,不是某个高深节点,而是从“写实物理”切换成“形状化表达”的思维方式。把它拆成三层就足够了:低频强节奏的波纹、清晰的颜色分层、白色块状高光。Shader Graph 能把这些层变成可以调的参数,HLSL Custom Function 则用于处理更灵活的顶点波浪。
建议你先在工程里跑通第 5 节的基础流程,把水面做成 Opaque,用两个方向的正弦波加一层噪声,已经能获得不错的效果。然后逐步添加 Voronoi 高光、顶点波浪和 C# 动态控制。每一步都先看效果再继续,不要一口气把所有节点全堆上去,那样既难排查,也学不到东西。
下一步可以继续深入的方向包括:法线扰动下的高光偏移、屏幕空间焦散、海水与沙滩交界处的泡沫边缘、以及 FBM 分形噪声在顶点波浪中的运用。每一个方向单独展开都可以是一篇长教程,如果你正好也在研究这些,可以顺着这套水面框架继续往下做。