
从零复刻《塞尔达传说风之杖》那种清亮、半透明又带一圈白色浪边的卡通水面不少人在 Unity 里尝试过但很容易陷入两个极端要么用写实水体的思路调物理参数最后得到一滩“油腻海洋”要么只是给平面贴一张蓝蓝的纹理动起来完全没有游戏里的流动感。问题在于这类风格化水面并不是“把真实水面做简化”而是把水面视觉拆解为颜色、波纹、高光、边缘、透明度多个独立层次再按卡通风格重新组合。本文将围绕这一目标完整讲解基于 Unity Shader Graph 的塞尔达风格化水面复刻流程内容包括NPR 卡通渲染基础、URP 管线环境搭建、Shader Graph 节点连接、材质参数调优、常见报错排查以及移动端适配建议。无论你是刚接触图形学、想从写实渲染迈入风格化渲染的初学者还是正在做卡通项目、需要对水体表现做落地的开发者都可以按本文步骤搭建出一版可运行、可继续扩展的风格化水面效果。1. 从写实到风格化为什么要复刻卡通水面1.1 风格化渲染NPR到底是什么在进入节点操作前先弄清楚一个基础概念NPR即 Non-Photorealistic Rendering非真实感渲染。它和 PBR基于物理的渲染正好是两条思路——PBR 追求“如何让材质看起来像真实世界”NPR 关心的是“如何让材质看起来符合画面表现”。卡通渲染是 NPR 里最常见的一类主要表现特征是颜色明快、过渡锐利、边缘带有描边或色带、对光照做简化处理。游戏社区里经常提到的 lilToon 这类第三方卡通渲染插件核心做的事情都是同一件事把原本连续的光照变化改造成“亮部、暗部、高光”几个色阶再配合描边与半透明控制让模型呈现日式动画质感。本文要做水面正好可以把这套思路单独抽出来看水面在卡通画风里并不是透明玻璃而是一块有颜色分层、有波光闪烁、有边缘白浪的半透明色块。1.2 《风之杖》水面为什么看起来“特别”如果把《风之杖》水面截图放大来看你会发现它的水面视觉其实可以拆成几个独立图层底层是干净的蓝色从深蓝到浅蓝有渐变但色阶并不连续存在明显的“色带化”感觉中层是随 UV 流动的波纹这种波纹不是细腻法线贴图更像大块的色块噪声上层是零散分布的高光亮度集中不会像真实水面那样出现连续反射边缘有一圈亮色有时是白浪有时是更浅的蓝色描边整体是半透明的能隐约看到水下的地面或景物。这几个图层单独拿出来都不复杂难点在于把它们组合到一个 Shader 里并保证动态效果自然。这很符合以画面管理为核心的游戏美术思路每一个材质都不是“模拟现实”而是“为画面服务”。1.3 为什么选择 Shader Graph 而不是手写 HLSL手写 Shader 有更强的底层控制力但视觉验证速度慢且要同时考虑平台、渲染管线、变体编译等问题。Shader Graph 的优势在于节点化编辑不需要先熟悉 HLSL 语法修改参数后立即预览效果适合美术反复调节在 URP 和 HDRP 中能自动生成对应的 Shader 代码降低维护成本官方全称是 Unity 的可视化 Shader 工具属于项目常用能力。对复刻塞尔达风格化水面这种“需要大量手调参数”的场景用 Shader Graph 非常适合。手写 HLSL 依然是进阶方向但不适合在第一篇快速验证效果。本文所有操作均基于 Shader Graph 完成你不需要打开代码编辑器。2. 环境准备与版本说明2.1 Unity 与渲染管线选择要做 Shader Graph项目必须使用可编程渲染管线也就是 URPUniversal Render Pipeline或 HDRPHigh Definition Render Pipeline。内置渲染管线也能安装 Shader Graph但很多功能与节点使用方式不同本文不推荐在内置管线上照搬。建议选择 URP原因有几点URP 对移动端优化更好适合卡通风格项目的中小型团队Shader Graph 的 URP 模板内置了透明混合、深度纹理、场景颜色采样等节点URP 在 Unity 2021.3 LTS、2022.3 LTS 以及 Unity 6 中都能顺畅使用。版本方面本文示例使用的是一种常见配置Unity 2022.3 LTS URP 14/15 对应版本。你完全可以使用 Unity 6 或更高版本节点名称和位置可能稍有变化但核心思路一致。如果你使用的是较新的大版本界面中个别节点路径发生变化是正常现象请以编辑器面板中的实际名字为准。2.2 创建 URP 项目与确认 Shader Graph 包打开 Unity Hub新建项目时选择 Universal 3D或 URP模板这样项目会自带 URP 资源和 Shader Graph 包。如果你的项目已经存在且是内置渲染管线可以在 Package Manager 中先安装 URP 包然后把项目的 Graphics Settings 切换到 URP Asset但这样在已有项目里改动范围较大新手建议直接新建 URP 项目。创建完成后打开 Window Package Manager确认已安装以下包包名作用Universal RPURP 渲染管线资源与 Shader 支持Shader Graph可视化 Shader 编辑工具Core RP LibraryURP 与 Shader Graph 依赖的公共库如果 Package Manager 里找不到 Shader Graph点击左上角加号选择 “Add package by name”输入com.unity.shadergraph再点击安装。不要手动删除 URP 包否则项目可能回到内置管线导致 Shader 报错。2.3 显卡兼容性与常见启动报错用 Shader Graph 前先确认电脑满足一个基本条件支持 Direct3D 11feature level 11.0及以上也就是能运行 Shader Model 5.0 的 GPU。如果电脑显卡太旧或者是在虚拟机、远程桌面里运行 Unity经常会出现类似下面这行报错A D3D11-compatible GPU (feature level 11.0, shader model 5.0) is required to run the Unity Editor.这个问题常见的解决办法有几种更新显卡驱动到厂商最新版本如果使用笔记本确认 Unity 使用的是独立显卡而不是核显虚拟机场景下需要开启 GPU 直通或改用物理机如果设备实在无法支持 SM5.0只能放弃 URP Shader Graph 方案改用内置管线的代码 Shader 做简化版。这属于硬件前置条件不是 Shader 代码问题很多人在装完 Unity 后第一步就卡在这里所以放在环境准备一节特别提醒。3. 风格化水面渲染原理解析3.1 颜色分层从深蓝到浅蓝的卡通渐变做过写实水面的朋友都知道PBR 水里要用到水的反射颜色、吸收系数、散射颜色等概念最终颜色是物理计算出来的。但风格化水面不追求准确只要“看起来是水”即可。我们先定义一个两段式颜色深层颜色WaterDeepColor和浅层颜色WaterShallowColor然后用某个因子在这两个颜色之间插值。常见做法是用基础 UV 坐标或者扰动后的噪声值作为 Lerp 的系数让颜色在平面空间上自然渐变。更进一步可以加 Smoothstep 把连续渐变变成两三个色阶这种截断正是卡通感的来源。3.2 波纹与流动感噪声贴图 UV 偏移水面要动必须让采样坐标随时间变化。具体方法是把 Time 节点接入噪声节点的 Offset 输入让噪声图案不断产生位移。这个过程不需要复杂的顶点动画在片元阶段处理 UV 即可。为了让波纹更丰富通常用两组噪声大尺度慢波Scale 较小移动速度慢形成宽阔的水面起伏色块小尺度快波Scale 较大移动速度快形成细碎的近处波光。把两组噪声结果做加法或乘法后再重新采样基础色水面就有了两层流动叠加感。跑起来时人的视觉会自然把它解读为“水面在流动”。3.3 波光高光做减法更出效果真实水面高光来自光线反射风格化水面高光通常是“手动点亮”。常见做法是在噪声纹理中筛选亮度较高的区域用 Power 节点把亮度集中到高数值区域然后叠加到原有颜色上。举例来说如果噪声输出范围是 0 到 1用 Power 8 次方后只有接近 1 的区域能保留较高亮度其余区域几乎变黑。这样得到的高光区域会呈现“星星点点”的感觉和《风之杖》那种闪烁的波光很像。参数调得越小高光点越松散参数越大高光点越锐利。3.4 菲涅尔与透明混合边缘白边的关键水面边缘变亮在 PBR 里是菲涅尔效应Fresnel Effect。在 Shader Graph 中可以直接用 Fresnel Effect 节点获取边缘程度再和白色或浅蓝色混合。不过只有把连续菲涅尔值改成阶梯状才更符合卡通水面的边缘白浪。另一半是透明。URP 的 Shader Graph 可以通过 Surface Type 选项切换 Opaque 或 Transparent。Transparent 模式下可以通过 Alpha 控制半透明度同时需要开启高度写入或调整混合模式否则两个半透明物体叠加时可能出现遮挡错乱。4. 完整实战创建 URP 项目与测试场景4.1 项目创建步骤打开 Unity Hub点击 New Project选择 Universal 3D 模板项目名称建议命名为StyleWaterURP。创建后等待编译完成。下面是一个简单的环境参考Unity Hub 版本3.x 及以上 Unity 版本2022.3 LTS或 Unity 6 项目模板Universal 3D 项目名称StyleWaterURP进入编辑器后首先检查 Project Settings Graphics。默认情况下URP 模板已经指定了 Universal Render Pipeline Asset如果你的 Graphics 设置里是空的要把 URP Asset 拖到 Scriptable Render Pipeline Settings 里否则 Shader 无法正确编译。4.2 场景搭建与测试用水面创建一个空场景按以下步骤搭出测试环境在 Hierarchy 中右键创建 3D Object Plane作为水面再创建一个 Cube放在 Plane 下方模拟水下物体添加一个平行光或者点光源方便查看明暗关系把 Camera 的清除颜色改成亮一点的天蓝或浅灰方便观察半透明叠加效果水面 Plane 建议使用默认的 10x10 单位大小Camera 从 45 度角斜向下看。这里特别提醒本文只讲渲染技术不要从游戏里提取原始模型资源或贴图用 Unity 自带几何体和参数颜色即可。自制演示场景完全可以验证 Shader 效果使用原始游戏资源反而会带来版权问题。4.3 固定渲染管线配置深度纹理与颜色采样为了使后续的水面能与场景产生更真实的半透明关系建议提前开启 URP Asset 中的深度纹理选项。在 Project 窗口中找到UniversalRP-Asset检查 Depth Texture 是否为 On如果为 Off点击开启。这样 Shader Graph 中的 Scene Depth 节点才能正常获取场景深度信息。资源UniversalRP-Asset Depth TextureOn Opaque TextureOn可选用于更复杂的场景颜色折射如果暂时不想开启 Depth Texture也可以跳过深度相关节点直接用固定 Alpha 实现半透明只是水面与水底物体的融合效果会弱一些。5. 核心实战Shader Graph 水面节点搭建5.1 创建 Unlit Shader Graph 并配置 Surface在 Project 窗口右键选择 Create Shader Graph URP Unlit Shader Graph命名为Water_WindWaker。打开后先选中 Graph Inspector 面板的 Graph Settings做如下设置Surface Type 改为 TransparentRender Face 设为 BothAlpha Clip 不开启混合模式保持 Alpha。为什么用 Unlit 而不是 Lit因为风格化水面更依赖手动控制颜色如果用 Lit场景灯光和 PBR 计算会改变我们调好的海水色相。你可以理解成我们直接在 Shader 里“画”水面颜色而不是让光照去“算”水面颜色。创建好后在 Project 窗口新建一个 Material把 Shader 设为Shader Graphs/Water_WindWaker并拖到 Plane 上。此时 Plane 应该是灰色半透明状态接下来开始朝里面连接节点。5.2 第一组节点基础渐变颜色打开 Shader Graph 编辑界面在左侧 Blackboard 面板创建属性WaterDeepColor和WaterShallowColor类型都选 Color默认值分别为深蓝#0B3B64和天蓝#3E9FC7。创建一个 UV 节点把它连接到 Lerp 节点的 T 输入上。Lerp 表示根据第三个值在 A 和 B 之间插值这里 A 输入 WaterDeepColorB 输入 WaterShallowColorT 是 UV 的某个分量。为了得到更自然的渐变可以先把 UV 的 Y 分量用 Multiply 拉伸但水面 Plane 的 UV 范围本身有限可以先用基础方式观察效果。把 Lerp 结果连接到 Base Color 上Main Preview 中应该能看到一块从深蓝到浅蓝的渐变平面。此时还没有波纹只是基础底色。5.3 第二组节点UV 扰动与流动波纹为了让水面“动起来”需要创建两组噪声采样。在 Blackboard 新建属性WaveScale默认 0.5WaveSpeed默认 0.15WaveStrength默认 0.08。接下来在编辑区创建Gradient Noise 节点Tiling And Offset 节点Time 节点。连接思路如下UV 输入到 Tiling And Offset 的 UVWaveScale 输入到 Tiling And Offset 的 TilingTime 分成两个通道乘上 WaveSpeed 后输入 Offset 的 X 和 Y。Tiling And Offset 的输出接入 Gradient Noise 的 UV得到一张随时间移动的噪声图。只用一组噪声会显得太规则建议再做一组反向或不同速度的噪声把两张噪声图 Add 到一起再与原始 UV 相加生成一个“扰动后的 UV”。这个扰动后的 UV 可以作为后续高光采样的坐标。简单示意如下UV - Tiling And Offset (WaveScale, WaveSpeed * Time) - Gradient Noise A - (再加上另一组不同参数的噪声) - Add - OffsetUV从这张扰动 UV 里我们能获得水面的波纹细节。把它应用到基础渐变或后边的波光高光上水面的流动感就出来了。注意 WaveStrength 不要给太大否则 UV 扭曲会非常严重看起来像故障效果。5.4 第三组节点波光高光新建属性SpecularPower默认给 16。这个值控制高光点的锐利程度。创建第二个 Gradient Noise使用上一步得到的 OffsetUV 作为 UV 输入。噪波输出经过 Saturate 限制到 0~1然后用 Power 节点做乘方运算把输出接到 Power 的 Base参数的 Base 由 SpecularPower 控制。Power 的输出可以直接接到 Base Color 上叠加也可以先与一个浅色属性SpecularColor相乘再叠加。为了保持“塞尔达”那种偏冷的高光建议 SpecularColor 使用接近白的浅蓝色#E8F4FF。高光叠加公式可以理解为baseColor specularColor * pow(saturate(noiseValue), SpecularPower)这样噪声里原本中等亮度的区域会被乘方压暗只有少数高点被点亮视觉上就是海面上闪烁的波光。5.5 第四组节点菲涅尔边缘与色带创建 Fresnel Effect 节点它有两个输入法线方向和视线方向。因为水面是一个平面默认法线是向上的所以不需要额外处理法线贴图直接输出即可。Fresnel Effect 默认输出一个连续 0 到 1 的边缘值。为了让边缘更卡通用 Smoothstep 节点把连续值截取成两段。Smoothstep 有三个主要输入In、Edge1、Edge2。把 Fresnel 输出接入 InEdge1 给 0.2Edge2 给 0.6。Edge1 和 Edge2 差值越小色带越锐利。新建属性FresnelColor默认#EAF9FF。用 Lerp 把基础颜色和 FresnelColor 混合T 输入为 Smoothstep 的结果得到一个白色/浅蓝色的边缘带。这时候你的 Main Preview 应该已经能看到中间是深蓝带波光边缘一圈浅蓝白色已经有了塞尔达风格水面的雏形。5.6 第五组节点透明度与深度衰减最后处理透明。新建属性Opacity默认 0.85。把它接到 Alpha 上让水面整体半透明。此时在游戏视图里你应能看到 Plane 下方的 Cube 透过水面显示出来。如果要让透明度根据深度变化在浅水区更透明、深水区更不透明可以使用 Scene Depth 节点。连接方式是Screen Position 节点获取屏幕坐标Scene Depth 节点获取场景深度用相机深度和场景深度相减得到水面下方物体的深度差用 Smoothstep 做衰减输出到 Alpha 或 Lerp。这部分比较进阶如果你的项目没有开启 Depth Texture可以先使用固定 Opacity。开启后可以看到水面在 Cube 上方时更透明在空旷深水区域更不透明层次感会明显提升。5.7 最终参数整理整体连接完成后Shader Graph 的 Blackboard 大概会有这些属性属性默认参考值作用WaterDeepColor#0B3B64深水颜色WaterShallowColor#3E9FC7浅水颜色WaveScale0.5波纹大小WaveSpeed0.15波纹流动速度WaveStrength0.08UV 扰动强度SpecularPower16高光集中度SpecularColor#E8F4FF波光颜色FresnelColor#EAF9FF边缘亮色Opacity0.85整体不透明度这些参数不是标准答案而是“一个能出效果的起点”。在美术调参阶段你通常会根据场景灯光、摄像机角度、目标画风微调多个属性。6. 材质调试与效果验收6.1 调节颜色让水面融入场景第一次跑出来的水面往往会偏暗或者偏灰。原因很常见未开启场景光照或平行光强度太低。由于我们用 Unlit Shader材质本身不受光照影响但 Base Color 的数值仍要手动提亮。《风之杖》的水面整体偏亮、偏蓝如果场景整体偏暖可以考虑把 WaterShallowColor 压暗一些避免颜色溢出。在 Game 视图下调整 Camera 角度观察水面在俯视和侧视两个角度的表现。侧视时菲涅尔边缘应该明显俯视时波光应该仍然存在但不能大面积闪烁。6.2 在 Shader Graph 中预览动态效果Main Preview 默认情况下 Time 节点是不走动画的除非你在预览窗口点击播放按钮。打开 Main Preview点击预览视图上的 Animate 开关才能看到 WaveSpeed 驱动的流动效果。很多新手把节点连完之后发现水面“完全不动”就是因为这个预览开关没有打开但在实际运行或 Game 视图中会正常。6.3 验收清单可以用下面这四条快速判断效果是否达标水面是否半透明能隐约看到水下 Cube波纹是否持续流动并且方向不完全一致高光是否呈点状分布而不是一大片亮斑从斜下方仰视时边缘是否有一圈浅色白浪如果四个方面都满足说明这套风格化水面已经具备了基础表现力。7. 常见问题与排查思路7.1 问题总览这里把复刻过程中最高频的问题整理成一张表方便直接对照。问题现象常见原因解决思路模型显示为紫红色Shader 编译失败或材质丢 Shader检查 Shader Graph 是否编译通过确认管线设置报错 D3D11 GPU 不支持显卡过旧或虚拟化环境更新驱动切换到独显换物理机水面完全透明看不见Surface Type 不是 Transparent 或 Alpha 太小检查 Surface Type调高 Opacity水面波纹不动Main Preview 未开启动画或 Time 未连接打开预览 Animate运行时检查 WaveSpeed高光过曝SpecularPower 过小或叠加过于粗暴提高 SpecularPower 到 16~64半透明遮挡错误未配置混合模式或深度写入调整 Surface Options 中的 Mixed Lighting/Blend边缘白浪没有Fresnel 节点输入不对或 Smoothstep 参数过宽检查法线输入缩小 Edge1/Edge2 区间7.2 紫红色材质紫红色是 Unity 的一种报错色表示当前渲染的材质没有可用的 Shader。出现这种情况先打开 Shader Graph 窗口查看右下角是否存在编译错误。常见原因有三个一是 Shader Graph 没有正确导入包缺失二是项目的渲染管线没有指向 URP Asset三是 Surface Options 里启用了节点尚未支持的混合模式。排查顺序建议打开 Window Package Manager 确认 URP 和 Shader Graph 安装完成然后检查 Project Settings Graphics 的 Scriptable Render Pipeline Settings 是否已赋值 URP Asset最后再看 Shader Graph 底部 Error 信息。7.3 水面不透明白花花如果水面效果像一张纯色贴图没有透明度多半是 Surface Type 没有设置成 Transparent。Unlit Shader Graph 默认是 OpaqueOpaque 模式下 Alpha 输出会被忽略。把 Graph Settings 的 Surface Type 改成 Transparent同时确认 Alpha 已经连到 Fragment 的 Alpha 通道材质球上也要把 Rendering Mode 设为 Transparent。7.4 水面完全没有动画第一检查是否在 Game 视图下运行编辑器 Scene 视图不会自动播放时间第二检查 Time 节点是否被接入而不是用静态常量第三检查 WaveSpeed 是否为 0。如果三者都正常那么把 Main Preview 的 Animate 按钮打开看是不是编辑器预览状态误判。7.5 性能开销突然很高Gradient Noise 属于程序化噪声在 Shader 中每次采样都要进行多层运算。一张水面用两三个噪声节点还好如果盲目添加很多噪声采样移动端性能会明显下降。建议从两组噪声开始把 UV 扰动和波光高光合并复用同一组噪声而不是每组功能都重新采样一遍。8. 工程建议与移动端优化8.1 属性命名与材质规范Shader Graph 的属性名会出现在材质面板上命名要尽量语义化不要用 a、b、c 这种无意义名字。比较好的方法是用WaterDeepColor、WaterShallowColor、WaveScale、WaveSpeed这类驼峰命名。团队协作时把同一批风格化水面的属性命名统一可以方便地复制调参结果。另外建议为每种画风建立单独的 URP Asset 或材质目录不要在一个大项目里把所有水面 Shader 都放在 Default 目录。项目后期材质数量一多命名混乱会导致“改一个参数所有物体一起变”的问题。8.2 Shader 变体控制与打包体积Shader Graph 会自动生成大量变体尤其当你同时开启多套功能时变体数量可能膨胀。在属性面板中可以使用 Shader Graph 的 Keyword 或 Static Switch 功能控制某些功能是否参与变体编译。比如把“深度渐隐”做成开关关闭时直接使用固定 Opacity不生成对应变体对移动端会更友好。发布时在 Player Settings 的 Graphics 里点击 Optimize Mesh Data能剔除不用的着色器编译数据。8.3 半透明物体的性能注意事项半透明物体本身不适合大量叠加水面如果覆盖整个关卡建议保持单层如果需要多层水面比如水面上再叠一层瀑布飞沫要注意混合顺序。半透明 Shader 一般不开启 Receive Shadows因为透明物体的阴影接受逻辑比较复杂反而可能让水体看起来有明显暗块。在场景中可以给水面单独设置 Layer摄像机剔除时针对水层做优化或者在 Frame Debugger 中观察 Draw Call 是否偏高。8.4 走向更复杂的水面扩展方向本文完成的是一块基础风格化水面已经足够用于中远景场景。想进一步提升可以往这些方向扩展添加 Flowmap用纹理控制水流方向在顶点阶段做 Gerstner 波浪位移让几何体本身起伏使用深度采样在水底边缘生成湿润带添加 Flash 高光动画在水面中心周期性亮起用顶点色控制深浅区域让不同水域颜色自然过渡。把这些功能和当前 Unlit Shader Graph 组合起来就可以从单面水体变成可配置的通用风格化水系统。9. 总结与下一步学习方向到这一步你已经完成了一次完整的水面风格化复刻从掌握 NPR 与 PBR 的区别到理解水面视觉可以被拆成颜色、波纹、高光、菲涅尔、透明这五个层次从创建 URP 项目到在 Shader Graph 中使用 Time、Tiling And Offset、Gradient Noise、Fresnel Effect、Smoothstep 等节点组合出动态水面同时你也知道了紫红色材质、D3D11 报错、动画面板不播放等常见问题的排查路径。接下来可以做的事有很多如果对 Shader Graph 还不太熟练把本文的噪声复用、Smoothstep 截断、Lerp 混合练熟再动手做浪花泡沫如果你更想深入底层可以把 Shader Graph 生成的 HLSL 编译产物拉出来看研究节点到代码的转换关系如果你在做完整游戏场景下一步应该重点研究 Flowmap 顶点扰动和深度波沫让水面和海岸线真正形成互动。图形学里的真实感渲染有大量公式和模型但风格化渲染更像“用图形手段做画”。当你理解了水面不是被渲染成水而是被渲染成“看起来像水的一堆颜色关系”你就已经跨过了复刻风格化效果最关键的一道门槛。后面的进阶就是不断往这套关系里增加新的视觉层让它更耐看、更符合你的项目画风。如果这篇水体教程对你有帮助建议先收藏起来实际操作时对照调参。有任何节点连接不上的问题也可以先回到 5.7 节检查属性配置是否完整。“能运行”比“一次性做完美”更重要先跑通基础版本再逐步优化是更合适的节奏。下一篇可以继续聊塞尔达风格的浪花泡沫、Gerstner 波形或角色脚下水面交互欢迎保持关注。