如何生成自定义渲染Pass?LiliumToonGraph 的 Lilium Toon Target 源码完整剖析
【免费下载链接】LiliumToonGraphUniversalRP Toon Shader with ShaderGraph项目地址: https://gitcode.com/gh_mirrors/li/LiliumToonGraph
LiliumToonGraph 是一款基于 UniversalRP 的卡通(Toon)着色器项目,它让 ShaderGraph 用户也能在 URP 中玩转卡通渲染。本文从 Lilium Toon Target(自定义渲染 Target)入手,拆解 ToonTarget.cs 如何一步步生成 Forward、Outline、ShadowCaster、DepthOnly 四个自定义渲染 Pass,并教你扩展自己的 Pass。
上图为项目示例场景中的 Lilium Toon Target ShaderGraph:左侧 MasterStack 上可见 Position/Normal/Tangent 与 Base Color 等输出插槽,右侧 Inspector 展示了该 Target 的属性面板。
一、为什么自定义渲染Pass需要自定义 Target
在 Unity 的 ShaderGraph 中,MasterStack 上的 Target 决定了最终生成的 SubShader 包含哪些渲染 Pass。URP 自带的UniversalTarget 只会生成 URP 标准的 Pass 组合,无法满足"卡通描边"这类需求——描边本质上是一个额外的渲染通道。
LiliumToonGraph 的思路是:改造 UniversalTarget,克隆出自己的 Lilium Toon Target,在生成阶段注入自定义 Pass。由于需要访问 ShaderGraph 包的非公开(Internal)类,作者还用一个巧妙手段——把程序集命名为 HDRP 的编辑器程序集名,从而"伪装"出访问权限(见 LiliumToonGraph.Editor.asmdef):
"name": "Unity.RenderPipelines.HighDefinition.Editor"这也是 README.md 中"HDRP 伪装"一节解释的原理。💡
创建入口则由菜单命令提供,见 CreateToonShaderGraph.cs:在 Project 窗口右键 →Create > Shader > Universal Render Pipeline > Lilium Toon Shader Graph,即可生成带 Toon Target 的 ShaderGraph。
二、PassDescriptor:自定义渲染Pass的"配方"
每个 Pass 由一个PassDescriptor描述,它规定了 Pass 的完整生成配方,核心字段如下:
| 字段 | 作用 |
|---|---|
displayName/referenceName/lightMode | Pass 名称、预处理器宏名、渲染状态机匹配模式 |
passTemplatePath | 代码模板(如PassMesh.template),生成顶/片元入口函数骨架 |
validVertexBlocks/validPixelBlocks | 端口掩码:决定该 Pass 允许哪些 MasterStack 插槽参与 |
requiredFields/structs | 强制携带的 Varyings/Attributes 字段 |
renderStates | Cull、ZTest、Blend 等渲染状态 |
pragmas/keywords/includes | 编译指令、关键字、附加的 hlsl 代码 |
以核心 Pass(DepthOnly / ShadowCaster)为例,见 ToonTarget.cs#L302-L355:ShadowCaster 只开放 Alpha 相关片元插槽(FragmentAlphaOnly掩码),并强制要求normalOS字段用于阴影偏移——这正是端口掩码的典型用法:同一个 MasterStack,不同 Pass 看到不同的插槽。
三、ToonTarget 默认生成的 4 个渲染 Pass
在SubShaders区域(ToonTarget.cs#L409-L452),Target 声明了两组 SubShader:Unlit与UnlitDOTS(DOTS 版本只是替换为 DOTS 实例化 pragmas),每组按顺序包含 4 个 Pass:
| Pass | lightMode | 职责 | 定义位置 |
|---|---|---|---|
| Lilium Toon | UniversalForward | 主渲染 Pass,输出卡通着色结果 | UnlitPasses.Unlit |
| Lilium Outline | 空(无状态机匹配) | 描边 Pass,输出轮廓颜色 | UnlitPasses.Outline |
| ShadowCaster | ShadowCaster | 投射阴影 | CorePasses.ShadowCaster |
| DepthOnly | DepthOnly | 深度预渲染 | CorePasses.DepthOnly |
注意主 Pass 的displayName是 "Lilium Toon" 但lightMode是 URP 标准的UniversalForward——这样既保持与 URP 渲染管线兼容,又能注入自己的 hlsl:ToonIncludes.Forward 在片元图执行完之后追加 ToonForwardPass.hlsl,构建InputData供卡通光照子图使用。
四、描边Pass剖析:Cull Front 反转网格
描边 Pass 是 Lilium Toon Target 的招牌功能,其实现非常简洁,靠两点配合:
1. 反转渲染状态。见 ToonRenderStates.Outline:
ZTest Less + ZWrite On // 让背面被正面深度挡掉,只在轮廓外可见 Cull Front // 丢弃正面、只渲染背面网格2. 顶点阶段外扩 + 片元阶段输出描边色。在 ToonOutlinePass.hlsl 中,顶点函数把 MasterStack 的OutlinePosition插槽结果赋给input.positionOS(外扩后的位置);片元函数最终返回half4(surfaceDescription.OutlineColor, alpha)(ToonOutlinePass.hlsl#L133-L156)——描边颜色完全由 ShaderGraph 节点驱动。
描边 Pass 的端口掩码(ToonBlockMasks)额外开放了OutlineWidth与OutlinePosition两个顶点插槽,以及OutlineColor片元插槽。
五、自定义插槽如何接入 Pass
除了内置的 URP 插槽,Target 还声明了自己的插槽字段 ToonBlockFields.cs:
OutlinePosition(顶点阶段,Position 控制)OutlineColor(片元阶段,颜色控制,默认灰色)
这些插槽通过GetActiveBlocks(ToonTarget.cs#L207-L219)注册到 MasterStack,之后在 PassDescriptor 的端口掩码中按需开放——"声明插槽 → 注册到 Target → Pass 端口掩码放行",三者缺一不可,这就是 ShaderGraph 自定义渲染 Pass 的完整接线方式。
Target 的属性面板(Surface 类型、Alpha 混合模式、Alpha Clipping、法线空间等)则在GetPropertiesGUI中定义,并驱动renderQueue/renderType(ToonTarget.cs#L108-L130),最终在Setup里写入各 SubShader 的渲染状态(ToonTarget.cs#L153-L175)。
六、动手:如何生成自己的自定义渲染Pass
给 Lilium Toon Target 追加一个新 Pass(例如一个"发光预渲染"通道)的标准步骤:
- 新建 PassDescriptor:参照
UnlitPasses结构,填写displayName、唯一的referenceName和lightMode(需要状态机匹配则写明,否则留空); - 配置端口掩码:用
validVertexBlocks/validPixelBlocks决定新 Pass 能读取哪些插槽(可复用CoreBlockMasks中现成的组合); - 声明渲染状态与编译指令:设置
renderStates(ZTest/Cull/Blend)、pragmas(建议至少包含 target 2.0 与 instancing); - 附加 hlsl:在
includes中用IncludeLocation.Postgraph追加你自己的 Pass 实现文件,像ToonIncludes.Outline那样(ToonTarget.cs#L930-L939); - 挂载到 SubShader:把新 Pass 加入
SubShaders.Unlit.passes的 PassCollection(注意放在 ShadowCaster/DepthOnly 之前,保证按预期顺序执行); - 修改 kSourceCodeGuid:源码依赖的 GUID 变化后需要所有 ShaderGraph 重新追加 Target——这也是 CHANGELOG.md 中 0.4.0 版本提示"所有 ShaderGraph 需要重新做 Target 再追加作业"的原因。⚠️
需要提醒的是:由于 LiliumToonGraph 依赖非公开 API,对 URP 版本不具前后兼容性,升级 URP 后可能需要重新调整 Target 代码。
七、相关文件清单
- Target 主体与 Pass 定义:ToonTarget.cs
- 自定义插槽字段:ToonBlockFields.cs
- 主 Pass 片元实现:ToonForwardPass.hlsl
- 描边 Pass 实现:ToonOutlinePass.hlsl
- 创建菜单入口:CreateToonShaderGraph.cs
- 示例 ShaderGraph 与场景:Toon.shadergraph、ToonSample.unity
小结:Lilium Toon Target 展示了 ShaderGraph 自定义 Target 生成渲染 Pass 的完整链路——用PassDescriptor写配方、用端口掩码切插槽、用 Postgraph include 注入 hlsl、用SubShaderDescriptor组装 Pass 序列。理解了这套机制,你就能为 URP 的 ShaderGraph 加描边、加发光、加任何你需要的自定义渲染通道。
【免费下载链接】LiliumToonGraphUniversalRP Toon Shader with ShaderGraph项目地址: https://gitcode.com/gh_mirrors/li/LiliumToonGraph
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考