Untoon Engine
Toon-Rendering-Pipeline
本页目录
Untoon 渲染管线总览
设计目标
Untoon 渲染管线把卡通渲染做成引擎原生路径,不依赖项目级后处理拼装。
当前 untoon 分支的核心目标:
- 原生
MSM_UntoonShading Model - 可由材质图控制的
Toon Light和ToonShadowMask - 光照后可重新评估材质的 Untoon Shadow Mask 阶段
- 与 Nanite 可见性 / compute material shading 路径兼容
- 与 Deferred Rendering、Lumen、Ray Tracing 等主渲染框架共存
当前有效标准流程
当前有效流程以 UE Deferred Renderer 为主路径,Untoon 在 RenderLights 之后插入 Untoon 专用的后置阶段。
RenderToonBasePass 目前是保留入口:DeferredShadingRenderer.cpp 会调用它,但 ToonBasePassRendering.cpp 中函数体被 if (false) 禁用,因此它不是当前有效渲染链路的一部分。当前 Untoon 材质基础数据主要来自常规 Base Pass / GBuffer / GBufferToon,而不是独立 ToonBasePass。
flowchart LR
A["常规 Base Pass<br/>写入 SceneColor/GBuffer/GBufferToon"] --> B["RenderLights<br/>收集 SurfaceShadow"]
B --> C["UntoonShadow.R<br/>主光表面阴影"]
C --> D["RenderToonShadowMask<br/>重新评估 ToonShadowMaskOutput"]
D --> E["UntoonShadow.G<br/>材质自定义 Shadow Mask"]
E --> F["Untoon Shadow Blur<br/>模糊反向光照遮罩"]
F --> G["UntoonShadow.B<br/>Blur 后光照遮罩"]
G --> H["RenderToonLightPass<br/>重新评估 ToonLightOutput"]
H --> I["SceneTextures.Color<br/>基于 PostLight / AreaLightLighting 合成"]
I --> J["RenderToonOutlinePass<br/>描边合成"]
可选相机可见性阶段
Toon Priority 在常规 Base Pass 之前执行。原生深度阶段先临时剔除已登记组里让位的头发,补全普通网格深度,再接纳 Nanite 深度;然后按视图、按组做局部遮挡测试,把其他组的头发当作遮挡物,把有效的 Target 深度记入图集;最后补回没有被本组有效目标覆盖的头发,清掉被替换像素上的 Nanite shading mask/stencil 和速度,再生成后续 HZB。
flowchart LR
A[不含让位头发的完整深度] --> B[Nanite 遮挡深度]
B --> C[逐组补入其他组头发]
C --> D[Target 有效覆盖图集]
D --> E[恢复仍应可见的头发]
E --> F[Nanite 标记和速度修正]
F --> G[最终 HZB 和常规 Base Pass]
Base Pass、独立 Velocity、Toon ShadowMask 与 Toon Light 消费同一套图集规则,遮挡判定使用材质 WPO、Masked 裁剪、PDO 和当前抖动投影。启用视图暂时绕过历史 primitive/instance occlusion 和预计算遮挡,保留视锥、距离、显式隐藏与 LOD,避免遗漏上一帧被头发遮住的眼睛或墙体。Nanite 不使用旧头发覆盖下的 HZB。
没有有效优先组时不分配图集或提交优先 Pass。阴影地图、Lumen 场景捕获和光追反射使用原始几何;屏幕追踪可能间接消费发生变化的相机画面。
描边合成输出 RGB 和覆盖率,通过硬件 alpha 混合写入 SceneColor;源像素及自身剔除均检查 Untoon Shading Model。Shadow Blur 在 ShadowMask 完成后读取每个 ViewRect 的独立快照,保持降采样、模糊与合成坐标范围,最终仅写 B 通道,避免同一资源同时读写。
UntoonShadow 通道语义
UntoonShadow 是当前 Untoon 阴影与光照后置阶段的核心中间纹理。
| 通道 | 写入阶段 | 当前语义 | 主要消费者 |
|---|---|---|---|
R |
RenderLights |
主光照阶段收集的表面阴影值,材质侧可作为 UntoonSurfaceShadow 一类输入读取 |
RenderToonShadowMask、后续 Untoon 材质逻辑 |
G |
RenderToonShadowMask |
ToonShadowMaskOutput 输出的自定义风格化 Shadow Mask,1 表示阴影 |
Untoon Shadow Blur |
B |
AddToonMainLightShadowBlurPass |
晕染后的光照遮罩,1 表示光照区域 |
RenderToonLightPass / ToonLightOutput |
A |
RenderToonShadowMask / Nanite ShadowMask export |
Untoon 覆盖标记,1 表示当前像素属于 Untoon shadow mask export 覆盖范围 |
Untoon Shadow Blur |
重要原则:
RenderLights只通过红色写掩码更新UntoonShadow.R。RenderToonShadowMask会保留已有UntoonShadow通道,同时更新UntoonShadow.G和覆盖标记UntoonShadow.A;写入前会复制UntoonShadow作为可读取 snapshot,避免读写同一纹理冲突。- Untoon Shadow Blur 只通过蓝色写掩码更新
UntoonShadow.B,并使用UntoonShadow.A屏蔽非 Untoon 像素。 ToonShadowMaskOutput不应直接依赖 deferred light shader 内部临时变量,而应通过共享 SceneTexture / UntoonShadow 读取接口获取上一阶段结果。- 普通 mesh 和 Nanite mesh 都应保持同一套
UntoonShadow.R -> ToonShadowMaskOutput -> UntoonShadow.G/A -> blur -> UntoonShadow.B语义。
阶段明细
0. 常规 Base Pass 与 GBufferToon
Untoon 材质使用原生 MSM_Untoon。常规 Base Pass 负责写入 UE 标准 GBuffer,同时通过 GBufferToon 写入当前管线需要的 Untoon 专用材质载荷和 Shading Model 相关标识。
当前独立 RenderToonBasePass 不是有效阶段;不要把后续 ShadowMask / LightPass 的输入归因到它。
相关入口:
Engine/Shaders/Private/BasePassPixelShader.usfEngine/Source/Runtime/Renderer/Private/BasePassRendering.*Engine/Source/Runtime/Renderer/Private/Untoon/ToonBasePassRendering.cpp
1. RenderLights 写入 UntoonShadow.R
Deferred light 阶段仍然负责场景光照。Untoon 在 deferred light pixel shader 中收集主光的 SurfaceShadow,并写入 UntoonShadow.R。
实现要点:
LightRendering.cpp将SceneTextures.UntoonShadow绑定为额外 render target。- 对该 render target 使用
CW_RED写掩码。 DeferredLightPixelShaders.usf在IS_UNTOON_MAINLIGHT路径中计算 shadow terms,并输出到OutUntoonShadow。
相关入口:
Engine/Shaders/Private/DeferredLightPixelShaders.usfEngine/Source/Runtime/Renderer/Private/LightRendering.cppEngine/Shaders/Private/SceneTexturesCommon.ush
2. RenderToonShadowMask 写入 UntoonShadow.G
RenderToonShadowMask 在 RenderLights 之后执行。它会重新评估材质图中的 ToonShadowMaskOutput,材质可以读取上一阶段已经写好的 UntoonShadow.R,并输出最终风格化 Shadow Mask 到 UntoonShadow.G。
普通 mesh 路径:
- 使用
EMeshPass::UntoonShadowMaskPass - 使用
FToonShadowMaskPS - 输出目标为
UntoonShadow - 保留已有
UntoonShadow通道,并更新G与覆盖标记A
Nanite 路径:
- Nanite mesh 不进入普通 mesh pass
- 使用 Nanite-only 后置 compute export
- 复用 Nanite base pass 的可见性、shading bin 和材质绑定
- 输入包含可读取的
UntoonShadowsnapshot - 更新
UntoonShadow.G和覆盖标记UntoonShadow.A,不重写R/B
相关入口:
Engine/Source/Runtime/Renderer/Private/Untoon/ToonShadowMaskRendering.*Engine/Source/Runtime/Renderer/Private/Nanite/NaniteShading.*Engine/Shaders/Private/BasePassPixelShader.usf
3. Untoon Shadow Blur 写入 UntoonShadow.B
Shadow Mask 写入完成后,AddToonMainLightShadowBlurPass 读取 UntoonShadow.G/A。由于 G=1 表示阴影,Shadow Blur 会先反转为光照遮罩并只在 A=1 的 Untoon 覆盖区域内模糊,再通过 CW_BLUE 写掩码写入 UntoonShadow.B。
这一步用于让受光区域的模糊结果向阴影侧扩散,生成可由 Untoon Light 阶段消费的风格化光照过渡。
实现上,RenderToonLightPass 先逐视图调用 AddToonMainLightShadowBlurPass,再准备 light 参数与提交 ToonLight 绘制/compute 合成。
相关入口:
Engine/Source/Runtime/Renderer/Private/Untoon/ToonShadowBlur.*Engine/Shaders/Private/Untoon/ToonShadowBlurShader.usf
4. RenderToonLightPass 合成最终 Untoon 光照
RenderToonLightPass 在 Shadow Mask 与 Blur 之后执行。它会提供 LightPassLight、UntoonShadow、PreLightSceneColorCopy、PostLightSceneColorCopy 等上下文,重新评估材质图中的 ToonLightOutput,并把结果写回 SceneTextures.Color 的对应位置。
PreLightSceneColorCopy 是 RenderLights 之前保存的 SceneColor 快照,主要表示灯光前底色;PostLightSceneColorCopy 是 RenderLights / RenderMegaLights 之后、RenderToonLightPass 开始时保存的快照。两者差分用于得到 Toon Light Pass 看到的区域光照结果:
AreaLightLighting = max(PostLightSceneColor - PreLightSceneColor, 0.0f);
AreaLightLighting *= AreaLightLightingModulate;
SimpleMainDirectionalDiffuse = (BaseColor / PI) * MainLightColor * saturate(dot(N, L)) * (1.0 - UntoonShadow.R);
SceneColor.rgb = lerp(PostLightSceneColor + SimpleMainDirectionalDiffuse, ToonColor.rgb + AreaLightLighting, ToonColor.a);
这里的 AreaLightLightingModulate 来自 ToonLightOutput 的第二个输出,未连接时默认为白色乘子。SimpleMainDirectionalDiffuse 是用于 ToonColor.a = 0 端点的主方向光简化漫反射补偿。UntoonShadow.R 存储的是 1 - Shadow.SurfaceShadow 的阴影量,因此补偿会使用 1 - UntoonShadow.R 还原默认光照需要的受光可见度,并会随着 ToonColor.a 增大逐渐减弱。
普通 mesh 路径:
- 使用
EMeshPass::UntoonLightPass - 使用
FToonLightPassPS - 绑定
SceneTextures.Color.Target为 render target - 通过
ToonLightOutput控制最终颜色、混合权重和区域光照调制
Nanite 路径:
- Nanite mesh 不进入普通
UntoonLightPass - 使用 Nanite-only ToonLight compute export
- 复用 Nanite 可见像素和材质绑定
- 读取同一套
PreLightSceneColorCopy/PostLightSceneColorCopy - 重新评估
ToonLightOutput - 通过 compute UAV 合成回
SceneColor
合成语义与 ToonMainLightShader.usf 保持一致:
SceneColor.rgb = lerp(PostLightSceneColor + SimpleMainDirectionalDiffuse, ToonColor.rgb + AreaLightLighting, ToonColor.a);
相关入口:
Engine/Source/Runtime/Renderer/Private/Untoon/ToonLightPassRendering.*Engine/Source/Runtime/Renderer/Private/Nanite/NaniteShading.*Engine/Shaders/Private/Untoon/ToonMainLightShader.usfEngine/Shaders/Private/BasePassPixelShader.usf
5. RenderToonOutlinePass 描边
RenderToonOutlinePass 在 Untoon Light 合成之后执行,用于基于 GBufferToon、深度、法线等信息进行边缘提取和描边合成。
相关入口:
Engine/Source/Runtime/Renderer/Private/Untoon/ToonOutlinePassRendering.*Engine/Shaders/Private/Untoon/*Outline*
普通 Mesh 与 Nanite 分流
flowchart TB
A["RenderLights<br/>UntoonShadow.R"] --> B{"几何路径"}
B --> C["普通 mesh<br/>UntoonShadowMaskPass"]
B --> D["Nanite mesh<br/>Nanite ToonShadowMask compute export"]
C --> E["UntoonShadow.G/A"]
D --> E
E --> F["Shadow Blur<br/>UntoonShadow.B"]
F --> G{"几何路径"}
G --> H["普通 mesh<br/>UntoonLightPass"]
G --> I["Nanite mesh<br/>Nanite ToonLight compute export"]
H --> J["SceneColor"]
I --> J
J --> K["Outline"]
Nanite 不会进入传统 EMeshPass::UntoonShadowMaskPass 和 EMeshPass::UntoonLightPass。因此 Untoon 为 Nanite 增加两条后置 compute export:
- Nanite ToonShadowMask export:补齐
UntoonShadow.G/A - Nanite ToonLight export:补齐最终
SceneColor的 Untoon Light 合成
这两条路径仍保留 Nanite 的核心优势:
- Nanite cluster 可见性仍由 Nanite 主路径负责
- 只处理最终可见像素
- 不退回普通 StaticMesh raster 路径
- 复用 Nanite material shader / compute shading 基础设施
材质编译环境语义
Untoon 材质依赖编译环境定义来判断当前 pass。
重要定义包括:
TOONMAINLIGHT:当前 pass 允许使用LightPassLight中的主 Untoon 光照上下文TOONLIGHTPASS:当前材质图应按 Untoon Light / post-light 语义执行UNTOON_POST_LIGHT_TOON_SHADOW_MASK_EXPORT:当前是 post-light Toon Shadow Mask 导出阶段UNTOON_ALLOW_UNTOONSHADOW_TEXTURE_READ:当前 pass 允许读取已经生成的UntoonShadowTextureUNTOON_NANITE_TOON_SHADOW_MASK_EXPORT:当前是 Nanite Toon Shadow Mask compute exportUNTOON_NANITE_TOON_LIGHT_EXPORT:当前是 Nanite Toon Light compute export
普通 mesh 与 Nanite 的关键要求是:材质图在相同语义阶段必须看到相同上下文。
例如,普通 FToonShadowMaskPS 与 Nanite TUntoonNaniteToonShadowMaskCS 都应表达 post-light ShadowMask export 语义,因此都需要让材质图能够:
- 通过
TOONMAINLIGHT使用LightPassLight - 通过
TOONLIGHTPASS进入 Untoon Light / post-light 分支 - 通过共享 helper 读取
UntoonShadow.R
Lumen 与默认 UE 路径关系
Untoon 不替换 UE 的 Lumen 或 Deferred Lighting。
当前语义是:
- Lumen、Nanite、Deferred Lighting 仍按 UE 主路径工作
- Untoon 在主路径之后增加 Untoon 专用数据导出与合成
- Untoon 后置合成影响最终
SceneColor - Untoon Shadow Mask / Untoon Light 的结果通常不会反向写回 Lumen GI 解算输入
因此,Nanite 几何和 Lumen 场景光照能力可以继续使用,但 Untoon 最终风格化结果属于 Untoon 的后续屏幕空间 / 材质导出阶段。
调试开关
常用调试开关包括:
r.Untoon.DebugLogNaniteToonShadowMaskExportr.Untoon.DebugForceNaniteToonShadowMaskGr.Untoon.DebugForceNaniteToonLightSceneColorr.Untoon.DebugClearToonShadowMaskG
排查顺序:
- 先确认
RenderLights是否写入UntoonShadow.R - 再确认
RenderToonShadowMask是否写入UntoonShadow.G/A - 再确认 Shadow blur 是否更新
UntoonShadow.B - 最后确认
RenderToonLightPass是否合成回SceneColor - 如果颜色正确但边缘异常,再检查
RenderToonOutlinePass
维护关注点
后续维护这条管线时,应重点关注:
RenderToonBasePass当前是禁用入口,重新启用前必须明确与现有 R/G/B 语义的关系- GBuffer / SceneTexture 结构变动
FToonLightPassLightParameters字段变动- Nanite compute material shading API 变动
BasePassPixelShader.usf中 compute shaded 分支变动ToonPassSwitch、custom output、MIR / HLSL translator 变动- Lumen、Ray Tracing、Virtual Shadow Map 与 SceneColor copy 路径
- 上游文件中的 Untoon 改动必须保留在单层
Untoon Engine Start/Endmarker 内;Engine/Shaders/Private/Untoon和Engine/Source/Runtime/Renderer/Private/Untoon例外
执行门控(2026-09 优化)
整条 Untoon 管线按「本帧是否有 Untoon 工作」门控,非 toon 场景零成本:
- 判定来源有两个:
HasUntoonRasterWork(Views)(任一视图的UntoonShadowMaskPass/UntoonLightPass有 draw)与Nanite::HasUntoonShadingPipelines(Scene)(Nanite BasePass pipeline 集合中存在MSM_Untoon材质)。二者任一为真即视为有 Untoon 工作。 - 门控对象:
UntoonShadow的清除(未清除即未生产,RDG 会整体裁剪该纹理)、PreLight/PostLightSceneColor 拷贝、RenderToonOutlinePass。 - Nanite 分发的门控不再以
RasterResults.ShadingMask非空为依据——任何 compute-shaded Nanite 场景都有 ShadingMask,与是否 toon 无关。 - 延迟光源的
UntoonShadowRT1(MRT1)绑定与IS_UNTOON_MAINLIGHT排列仅在「该光源是 Untoon 主光且UntoonShadow已生产且平台未启用 OpaqueRoughRefraction / SeparatedSubsurfaceDiffuse」时生效;这两个上游特性在 SV_Target1 上保留原布局,shader 侧通过UNTOON_SHADOW_MRT宏声明同一条件。新增占用 MRT1 的上游特性时,两处条件必须同步更新。 UntoonBasePass(禁用入口)与UntoonOutlinePass(全屏 pass,无 mesh processor)不再参与 SceneVisibility 的 mesh command 收集;重新启用RenderToonBasePass时需在 SceneVisibility 恢复对应AddCommandsForMesh/PassMask。