Untoon Engine

Toon-Rendering-Pipeline

本页目录

Untoon 渲染管线总览

设计目标

Untoon 渲染管线把卡通渲染做成引擎原生路径,不依赖项目级后处理拼装。

当前 untoon 分支的核心目标:

  • 原生 MSM_Untoon Shading 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.usf
  • Engine/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.usf
  • Engine/Source/Runtime/Renderer/Private/LightRendering.cpp
  • Engine/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 和材质绑定
  • 输入包含可读取的 UntoonShadow snapshot
  • 更新 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.usf
  • Engine/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 允许读取已经生成的 UntoonShadowTexture
  • UNTOON_NANITE_TOON_SHADOW_MASK_EXPORT:当前是 Nanite Toon Shadow Mask compute export
  • UNTOON_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.DebugLogNaniteToonShadowMaskExport
  • r.Untoon.DebugForceNaniteToonShadowMaskG
  • r.Untoon.DebugForceNaniteToonLightSceneColor
  • r.Untoon.DebugClearToonShadowMaskG

排查顺序:

  1. 先确认 RenderLights 是否写入 UntoonShadow.R
  2. 再确认 RenderToonShadowMask 是否写入 UntoonShadow.G/A
  3. 再确认 Shadow blur 是否更新 UntoonShadow.B
  4. 最后确认 RenderToonLightPass 是否合成回 SceneColor
  5. 如果颜色正确但边缘异常,再检查 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/End marker 内;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 / PostLight SceneColor 拷贝、RenderToonOutlinePass。
  • Nanite 分发的门控不再以 RasterResults.ShadingMask 非空为依据——任何 compute-shaded Nanite 场景都有 ShadingMask,与是否 toon 无关。
  • 延迟光源的 UntoonShadow RT1(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。