Shader Graph:可视化编程在 URP/HDRP 中的应用
深入解析 Shader Graph 的设计理念、节点系统、与手写 HLSL Shader 的协同方式,以及如何在实际项目中选择最优渲染方案。
01 · 概述
什么是 Shader Graph?
Shader Graph 是 Unity 2019.1 引入的可视化着色器编辑器,它让开发者通过拖拽节点、连接端口来构建复杂的渲染效果,而无需手写大段 HLSL 代码。
传统手写 Shader 需要开发者对 GPU 编程有深入理解——你需要熟悉顶点着色器与片元着色器的编写规范、语义绑定、寄存器分配等底层细节。Shader Graph 将这些复杂性抽象为直观的节点网络,让艺术家和程序员的协作门槛大幅降低。
Shader Graph 生成的并非"解释型"代码——它会在构建时编译为标准的 HLSL 源码。这意味着你可以随时导出生成的 .hlsl 文件进行审查和进一步的手写优化。
02 · 管线对比
URP 与 HDRP:如何选择?
Unity 的 Scriptable Render Pipeline(SRP)允许你用 C# 脚本来定义渲染流程。URP 和 HDRP 是两套官方预制管线,它们对 Shader Graph 的支持程度和侧重点有所不同。
| 特性 | Universal RP (URP) | High Definition RP (HDRP) |
|---|---|---|
| 目标平台 | 移动端 · 主机 · PC(低至中配) | 高端 PC · 主机 · VR(高品质) |
| Shader Graph | 完整支持(Fragment 模式) | 完整支持(Vertex + Fragment 分离) |
| 光照模型 | 简化的 PBR(无延迟支持) | 完整 PBS · SSS · 体积光 · 光线追踪 |
| Master Node | 单个 PBR Master(全部输出) | 分立:Vertex / Fragment / Forward/Deferred |
| Decal 支持 | ✓ | ✓ |
| 自定义光照 | 受限(需手写完整 Pass) | ✓(Layered Lit 支持) |
| 渲染路径 | 前向渲染(Forward Only) | 前向 + 延迟(Forward + Deferred) |
| 推荐场景 | 移动游戏 · 多人在线 · WebGL | 主机 AAA · 影视级 · 建筑可视化 |
Shader Graph 兼容性说明
URP 11+ 和 HDRP 12+ 之后,两套管线都引入了Shader Graph 7.x版本,节点结构趋于统一。但部分高级节点(如 HDRP 的Bitangent Material、URP 的Screen Space Occlusion)仅限于各自管线。跨管线迁移时请务必检查节点兼容性。
URP Shader Graph 的 Master Node
URP 使用单一的 PBR Master Node 作为输出,它聚合了所有标准属性:
03 · 节点系统
核心节点类型一览
Shader Graph 的强大之处在于丰富的节点库。以下按功能分类介绍最常用的节点,以及它们在 URP/HDRP 中的差异。
PBR 材质节点
数学运算节点
纹理采样节点
UV/坐标节点
光照相关节点
自定义节点
纹理与采样节点
数学节点
数学节点是 Shader Graph 的核心计算引擎。它们完全基于向量运算实现,不存在"性能陷阱"——生成的 HLSL 与手写代码无异。
数学
Multiply / Add / Subtract
最基础的算术运算。Shader Graph 中所有数值默认是 Vector4,Multiply 逐分量相乘,Add 逐分量相加。
数学
One Minus / Negate
1 - x的向量版本,常用于反相遮罩(如 Roughness 反转为 Smoothness)。
数学
Power / Square Root
幂运算和开方。Gamma 校正中pow(color, 2.2)是典型应用。
数学
Clamp / Smoothstep
值域限制和软边缘过渡。Smoothstep 是抗锯齿边缘和渐变遮罩的利器。
数学
Lerp
线性插值:lerp(a, b, t)=a + (b - a) * t。两状态之间的平滑过渡。
数学
Remap
将一个范围的值线性映射到另一个范围。例如将 [0,1] 的噪声映射到 [0.2, 0.8] 以避免极端值。
UV 节点
UV 坐标是纹理采样的基础。Shader Graph 提供多种 UV 来源:
时间与动画节点
Shader Graph 内置Time节点,它暴露了 Unity 的时间数据,无需任何额外 C# 脚本即可驱动动画:
// Shader Graph 的 Time 节点等价于以下 HLSL 全局变量 float Time ; // 自游戏启动后的时间(秒) float SinTime ; // sin(Time) 的预计算值 [-1, 1] float CosTime ; // cos(Time) 的预计算值 [-1, 1] float DeltaTime ; // 上一帧的时长(秒),用于帧率无关动画 float SmoothDelta ; // 平滑处理后的 DeltaTime float TimeSinceLevelLoad; // 自场景加载后的时间 float4 _Time ; // float4(Time, SinTime, CosTime, TanTime) float4 _SinTime ; // 周期时间,用于 uvw 动画04 · 工作流设计
Shader Graph 的典型工作流
将 Shader Graph 集成到项目管线中,需要考虑架构设计。以下是经过验证的三种主流工作流模式。
1
基础材质层(Shader Graph 主导)
使用 Shader Graph 构建 80% 的标准材质:PBR 表面、地形材质、特效纹理混合、程序化噪声等。这是最快速的产出层,艺术家可以直接操作,无需程序员介入。
推荐节点:
Sample Texture 2D Sample Texture 2D LOD Sample Normal Map Triplanar Blend Smoothstep
2
Sub Graph 模块化封装
将高频复用的节点组合封装为 Sub Graph(子图)。例如"带细节法线的 PBR"、"程序化砖墙"、"水波 UV 扰动"等。Sub Graph 在项目内跨材质共享,保证一致性。
Sub Graph 的输入端口成为参数接口,输出端口可以定义新的数据类型(Struct)。
3
Custom Function 嵌入手写 HLSL
当 Shader Graph 的节点无法满足需求时(例如自定义 BRDF、光线步进、体积渲染),在 Custom Function 节点中直接编写 HLSL 代码。这保留了手写 Shader 的全部能力,同时将入口点无缝集成到图中。
4
完整手写 Shader + Include 混合
对于高度复杂的渲染效果(如地形系统、自定义光照通道),完全手写一个 .hlsl 文件,通过#include指令被 Shader Graph 生成的 Pass 所引用。此时 Shader Graph 成为"参数面板",手写代码是真正的渲染引擎。
05 · 手写协同
Shader Graph 与手写 Shader 的协同
这是 Shader Graph 最强大的能力之一:通过多个入口点,你可以在可视化编辑器中调用手写的 HLSL 函数,实现"图形化控制 + 底层实现"的混合架构。
方法一:Custom Function 节点
Custom Function 节点允许你在 Shader Graph 中直接编写一小段 HLSL 代码。它是临时嵌入代码的首选方式,适合"一小段手写逻辑 + 大量节点"的使用场景。
// 输入端口:In (float), Sharpness (float) // 输出端口:Out (float) void CustomFunction ( In.float In, In.float Sharpness, Out.float Out ) { // Smoothstep 的边缘增强版本 float t = saturate(In); Out = lerp(0.0, 1.0, pow(t, Sharpness)); }方法二:引用外部 .hlsl 文件
当手写代码较长或需要在多个 Shader Graph 中复用时,将代码写入单独的.hlsl文件,通过 Custom Function 的File模式引用。
// ──────────────────────────────────────────── // 文件:MyCustomFunction.hlsl // 说明:自定义 UV 扰动与菲涅尔叠加函数 // ──────────────────────────────────────────── #ifndef MY_CUSTOM_FUNCTION_INCLUDED #define MY_CUSTOM_FUNCTION_INCLUDED // 菲涅尔方程(Fresnel-Schlick 近似) float FresnelSchlick ( float cosTheta, float F0 ) { return F0 + (1.0 - F0) * pow(1.0 - saturate(cosTheta), 5.0); } // 带法线扰动的 UV 扰动函数 void UVDistortion ( In.float2 UV, In.float Strength, In.float Speed, Out.float2 OutUV ) { float time = _Time.x * Speed; float2 offset = float2( sin(UV.y * 6.28 + time) * 0.03, cos(UV.x * 6.28 + time * 0.7) * 0.03 ); OutUV = UV + offset * Strength; } #endif // MY_CUSTOM_FUNCTION_INCLUDED方法三:Sub Graph(子图)作为中间层
Sub Graph 是一种可嵌套的 Shader Graph 资源。它有自己的输入/输出端口定义,类似于一个自定义节点库。你可以在 Sub Graph 中组合多个节点,并嵌入 Custom Function,然后将这个 Sub Graph 作为黑盒在其他 Shader Graph 中使用。
06 · 实战示例
从零构建一个程序化水面 Shader
以下示例展示如何用 Shader Graph 构建一个支持法线扰动、菲涅尔反射、深度着色的程序化水面,并混合手写 HLSL 实现波浪运动方程。
示例环境
URP 14+ · Shader Graph 14+ · Unity 2022.3 LTS。本示例同时适用于 HDRP(仅需替换 Master Node)。
Step 1:基础 Setup
创建新 Shader Graph(URP → PBR Graph),设置Surface Type = Transparent,启用Two Sided,为水面开启透明度排序。
// ── 图表设置 ────────────────────────────── Surface Type : Transparent // 水面需要透明度 Blend Mode : Alpha // Alpha 混合模式 Render Face : Both // 双面渲染(水底也要可见) Pipeline : Universal // URP // ── 新增 Property(Shader 属性面板)───────── _WaterColor : Color (HDR) = #0D47A1 // 水体基础色 _WaveSpeed : Float = 1.0 // 波浪速度 _WaveStrength : Float = 0.05 // 扰动强度 _FresnelPower : Float = 3.0 // 菲涅尔指数 _NormalMap : Texture2D // 法线贴图(可选)Step 2:节点网络结构
整体节点网络分为四层:水色层、法线扰动层、菲涅尔层、最终输出层。
Step 3:手写波浪方程(Custom Function)
节点化方法适合简单波纹,但真实水面需要 Gerstner Wave 等物理模型。将以下代码嵌入 Custom Function:
/ ────────────────────────────────────────────────── // Gerstner Wave — 真实感水面模拟 // 输入:uv, amplitude, wavelength, speed, direction, time // 输出:displacement (float3), normal (float3) // ────────────────────────────────────────────────── struct GerstnerOutput { float3 displacement; float3 normal; }; GerstnerOutput GerstnerWave ( In.float2 uv, In.float amplitude, In.float wavelength, In.float speed, In.float2 direction, In.float time ) { GerstnerOutput output; float k = 2.0 * 3.14159265 / wavelength; // 波数(角频率) float c = sqrt(9.8 / k); // 相速度(深水波近似) float2 d = normalize(direction); float f = k * (dot(d, uv) - c * time * speed); // Gerstner 位移公式 output.displacement = float3( d.x * (amplitude * cos(f)), // X 位移 amplitude * sin(f), // Y 高度(法线方向) d.y * (amplitude * cos(f)) // Z 位移 ); // 法线计算(从波面梯度推导) float3 n = float3( -d.x * k * amplitude * sin(f), 1.0 - k * amplitude * cos(f), -d.y * k * amplitude * sin(f) output.normal = normalize(n); return output; }Step 4:最终组装到 Shader Graph
将 GerstnerWave 的输出连接到 PBR Master Node 的对应端口。注意 Vertex 端口需要在 Fragment 之前处理——Shader Graph 会自动处理依赖顺序。
效果预览
完成后在场景中创建一个 Plane 或 Mesh,将材质应用到其上。Gerstner Wave 会驱动几何体的顶点位移,菲涅尔项会在掠射角处产生高光反射,配合环境立方体贴图实现真实水面效果。通过调整 Shader 属性面板中的参数,艺术家可以实时控制波浪强度、速度与菲涅尔指数,无需修改代码。
07 · 最佳实践
Shader Graph 与手写代码的协作原则
经过多个项目的验证,以下原则可以帮你建立健康的 Shader 工作流:
URP vs HDRP 能力对照
渲染能力 URP HDRP Custom Function 调用手写光照 需覆盖整个 Forward Pass 支持 Layered Lit + 覆盖 Pass Decal System 集成 Shader Graph 支持 Decal 贴图 完整 Decal Graph 支持 顶点位移(Vertex Displacement) URP 14+ Vertex Stage HDRP 12+ 分立 Vertex Master Ray Tracing 节点 不支持 Raytracing Shader Graph 节点 Lit Shader(光照着色器) PBR Master Node 分立 Lit/Unlit/Graph Master 导出完整 HLSL 右键 → Copy Generated Code 同上
