做卡通渲染的时候,很多人会遇到一个比较尴尬的情况:想控制色阶过渡、想单独调节高光形状、想给不同部位叠不同的边缘光,但传统的光照模型往往只能给到“全局统一的参数”,要么牺牲真实感,要么在材质编辑器里打一大堆补丁,最后材质连线乱得连自己都不愿意再打开。
Substrate 的出现,正好把这个问题从引擎架构层面拆开了。它不再把材质看作一个固定的“Shading Model”,而是拆成可组合的材质层(Layer)与表面模型(Slab),让着色器开发者可以像搭积木一样组织漫反射、高光、边缘光、描边这些效果。本文围绕 UE 5.8 的 Substrate 架构,梳理卡通渲染着色器的实现思路,覆盖概念、环境准备、节点网络、HLSL 示例、常见问题与工程建议,适合已经熟悉 UE 材质编辑器、想往引擎底层深入的程序向 TA,也适合想了解 Substrate 能给非真实感渲染带来什么的设计师。
1. 背景与核心概念
1.1 Substrate 是什么
先说通俗理解:传统 UE 材质系统的渲染方式,是给每个材质指定一个“着色模型(Shading Model)”,比如 DefaultLit、Clear Coat、Hair、Eye 等等。一旦选好模型,后续能调的参数基本被限制在这个模型里,想在同一个材质里混合两种模型的表现,通常需要大量 workaround。
Substrate 是一套新的材质渲染框架,它在引擎内部把“材质”重新定义为若干个更细粒度的“表面描述”组合。这些表面描述携带各自的 BRDF 参数,再通过层混合、叠色、局部遮罩等方式组合成最终材质。官方早期内部代号是 Strata,在 UE 5.4 左右开始面向开发者开放,后续版本持续迭代,到 UE 5.8 已经可以作为新项目的重要渲染方案来评估。
换句话讲:Substrate 的目标是让材质系统“可编程、可组合”,而不是把一大套光照逻辑写死在某个枚举类型里。
1.2 为什么用 Substrate 做卡通渲染
卡通渲染(NPR,Non-Photorealistic Rendering)和 PBR 渲染的思路差异很大。PBR 追求物理一致性,重点观察能量守恒、菲涅尔反射、微表面分布;卡通渲染则追求“风格化表达”,需要的是可控的色阶、清晰的明暗分界、高强度边缘光、夸张的描边,甚至局部区域单独改变光照反馈。
在传统材质模型里实现这些不是不可能,但有一个核心问题:很多 NPR 效果不是简单改几个参数,而是需要替换或扩展光照计算过程。比如三色明暗需要修改 NdotL 映射,卡通高光需要重新定义高光分布,描边可能需要额外模型或后处理。这些需求如果引擎层不支持,就只能靠材质编辑器“硬接”,可维护性和性能都不理想。
Substrate 解决的是“材质表达能力”的问题。它允许开发者把不同着色需求拆成独立层:
- 漫反射层负责色阶与明暗;
- 高光层负责卡通高光形状;
- 边缘光层负责背光轮廓;
- 描边可以由材质层或单独模型实现。
每一层都可以单独调整参数、单独混合,最终通过 Substrate 的层组合机制串联起来。这种思路天然适合风格化渲染,也让卡通材质在不同光照环境下更稳定。
1.3 与传统 Shading Model 对比
| 对比维度 | 传统 Shading Model | Substrate 材质层 |
|---|---|---|
| 材质组织方式 | 固定模型 + 固定参数 | 可组合的层 + 混合规则 |
| 混合 PBR/NPR | 困难,需大量 hack | 分层组合,思路清晰 |
| 光照计算可定制性 | 需要改引擎或大量材质节点 | 可在层级别自由控制 |
| 学习成本 | 较低 | 较高,需要理解层与模型概念 |
| 性能开销 | 相对可控 | 需要结合层数量分析 |
| 适合场景 | 常规 PBR 项目 | 需要材质表现力的复杂项目、NPR |
2. 环境准备与版本说明
2.1 引擎与硬件环境
本文的示例基于虚幻引擎 5.8。Substrate 自 UE 5.4 起逐步开放,5.8 中已经具备较完整的材质编辑器支持与运行时管线,但不同小版本之间的菜单命名、节点细节仍可能有差异,因此具体操作以你本机编辑器为准。
建议使用通过 Epic Games Launcher 安装的正式版本,或从 GitHub 拉取对应版本的源码版引擎。卡通材质开发经常需要观察材质编译结果、调试 Shader,源码版引擎能提供更多排查手段,但对磁盘空间和编译时间要求较高。
硬件方面,Substrate 材质需要现代图形 API 支持。Windows 平台优先使用 DX12,也可以根据项目需要测试 Vulkan;移动端和低端显卡需要先验证兼容性,不建议在早期阶段直接投入生产。
2.2 新建测试工程
打开 UE 5.8,新建一个第三人称模板项目,模板自带角色、灯光和基础场景,适合快速验证卡通材质效果。
创建项目后,先确认渲染相关设置:
- 打开“项目设置(Project Settings)”。
- 在“引擎(Engine)-> 渲染(Rendering)”分类下,检查是否能看到 Substrate 相关选项。
- 根据不同版本,Substrate 可能默认开启,也可能需要通过项目设置或插件启用。
- 如果编辑器界面没有该选项,可以在命令行控制台输入
r.Substrate 1或r.Substrate=1尝试开启,再重启编辑器。
注意:Substrate 在旧版本中属于实验性功能,UE 5.8 中可能仍然会标记为“实验性”或“Beta”。生产项目切换前必须经过完整的渲染效果与性能验证。
2.3 建议的项目目录结构
为了方便管理,建议在工程 Content 下建立独立的素材目录:
Content/ └── ToonDemo/ ├── Materials/ # 主要卡通材质 ├── MaterialFunctions/ # 可复用的材质函数 ├── Textures/ # Ramp、噪点、遮罩纹理 └── Maps/ # 测试关卡这样后续调试、打包、交接都不会把第三人称模板自带的素材和自定义材质混在一起。
3. Substrate 架构与材质模型拆解
3.1 从 Shading Model 到 Slab / Layer
Substrate 中最核心的概念可以拆成三层理解:
- Closure:描述最终材质的光照响应,是材质到渲染器之间的接口。
- Slab:可以理解为“材质切片”,包含一组光照模型和参数,比如漫反射颜色、粗糙度、高光颜色、各向异性等。
- Layer:一个 Slab 经过遮罩、混合、叠加后形成的中间结果,可以被继续参与组合。
写材质时不需要逐字记住这些术语,但需要建立“材质是由层组合出来的”这个观念:最终着色的结果,不是某个固定模型拍板,而是多个层按权重混合出来的结果。
3.2 Substrate 材质如何组织
在材质编辑器中,传统材质主要看“Shading Model”下拉框,Substrate 材质则更关注“层如何连接”。一个典型的组织结构如下:
最终输出 Root │ ▼ 混合多个 Layer │ ├── Layer A(漫反射 + 色阶控制) ├── Layer B(卡通高光) ├── Layer C(边缘光 / 轮廓效果) └── 各层 Mask / Blend Weight这种结构的好处是可以把一个角色的皮肤、衣服、头发分别做成独立层,再通过 Mask 混合。对于卡通渲染,三个 Layer 通常就够用:一个主光照层、一个高光层、一个边缘控制层。
3.3 与卡通渲染相关的着色参数
Substrate 层的参数并不一定叫“BaseColor”和“Roughness”,而是以 BRDF 相关的语义出现。在做卡通材质之前,需要先想清楚把哪些传统参数映射到哪些效果上:
| 卡通效果 | 传统 PBR 对应参数 | 在卡通渲染中的含义 |
|---|---|---|
| 明暗色阶 | BaseColor + 光照强度 | 控制固有色和阴影阈值 |
| 高光形状 | Roughness / Specular | 控制高光范围和边缘硬度 |
| 边缘光 | Fresnel / Emissive | 控制背光轮廓颜色与宽度 |
| 阴影过渡 | Shadow 相关参数 | 控制明暗交界是否平滑 |
理解这些映射关系后,Substrate 材质层才能写出“外表卡通、内部结构清晰”的效果。
4. 卡通渲染着色器实现解析
4.1 整体思路
在 Substrate 架构下实现卡通渲染,推荐分层进行:
- 先做基础明暗层:把 NdotL 映射成两级或三级色阶。
- 再加高光层:控制高光的形状和范围。
- 再叠边缘光层:用菲涅尔实现轮廓光。
- 最后处理描边:单独模型描边或后处理描边。
下面逐个拆解。
4.2 三色明暗与色阶过渡
卡通渲染最明显的特点是阴影不是渐变,而是像动画赛璐璐一样分成硬边界。实现思路本质上是对光照点积做离散化:把连续的 NdotL 值映射到几个离散档位。
在材质编辑器中,你可以用最基础的方式连出来:
- 计算“法线方向(WorldNormal)”和“光照方向(Light Direction)”的点积。
- 用
Saturate限制到 0~1。 - 用
Step或SmoothStep处理成硬边界。 - 将结果作为颜色混合的权重。
对应的 HLSL 片段可以直接写在材质编辑器 Custom 节点里。以下是一个“三级色阶”的示例:
// Custom 节点输入: // Normal : float3,世界空间法线 // L : float3,世界空间光照方向 // Steps : float,色阶数量,比如 3 // Smooth : float,过渡硬度,0 表示完全硬边,0.1 表示柔和过渡 float NoL = dot(Normal, L); float NoL01 = NoL * 0.5 + 0.5; // 映射到 0~1 // 离散化到指定档位 float bands = floor(NoL01 * Steps + 0.5) / Steps; // 用平滑系数控制过渡位置 float alpha = smoothstep(0.0, Smooth, bands - NoL01 + 0.5); return lerp(bands, NoL01, alpha);这段代码的核心思路是“先离散,再局部柔化”。如果你需要更精细的色阶控制,推荐把离散后的值作为 UV 去采样一张 Ramp 纹理,这样美术可以自由绘制不同光影颜色,而不是只调某个亮度和颜色。
一个更常用的 Ramp 采样方法:
// RampTexture : Texture2D,横向是 NdotL 0~1,纵向可以是不同部位/不同环境光 // UV :从点积结果生成 float NoL01 = saturate(dot(Normal, L) * 0.5 + 0.5); float2 RampUV = float2(NoL01, RampRow); float3 rampColor = RampTexture.Sample(RampTextureSampler, RampUV).rgb; return rampColor;实际项目中,我认为 Ramp 纹理方案比纯节点离散更实用。原因很简单:前者让美术直接控制“亮部什么颜色、暗部什么颜色”,而不是去理解 Step 节点的数学含义。
4.3 卡通高光控制
卡通高光和 PBR 高光的思路不同,PBR 高光追求真实,卡通高光追求“清晰形状”和“位置可控”。常见做法是两点:
- 用 Blinn-Phong 或 GGX 计算高光强度。
- 再用 Step 或 SmoothStep 把高光收敛成一块很干净的光斑。
下面给出一个简化示例:
// Custom 节点输入: // Normal : float3 世界法线 // L : float3 光照方向 // V : float3 视线方向 // SpecSize : float 高光大小,越小高光越窄 // SpecHard : float 高光硬度 float3 HalfDir = normalize(L + V); float NoH = saturate(dot(Normal, HalfDir)); float spec = pow(NoH, SpecSize); // 硬边高光 float toonSpec = smoothstep(0.5, 0.5 + SpecHard, spec); return toonSpec;在 Substrate 层中,你可以把高光结果作为 Slab 的高光权重或高光颜色输入。这样既能保留高光的形状控制,又不影响漫反射层的明暗逻辑。
需要注意:如果角色身上有金属质感或宝石等特殊材质,高光层需要单独处理,不能一套参数通吃全身。
4.4 边缘光(Rim Light)
边缘光在卡通渲染里非常常见,它利用菲涅尔效应,让模型边缘微微发光。UE 材质编辑器中有专门的Fresnel节点,理想情况下可以直接把Fresnel的结果作为边缘强度。
边缘光的思路:
// Custom 节点输入: // Normal : float3 世界法线 // V : float3 视线方向 // RimPower: float 边缘光衰减 // RimColor: float3 边缘光颜色 float fresnel = 1.0 - saturate(dot(Normal, V)); float rimMask = pow(fresnel, RimPower); return rimMask * RimColor;把这段代码接到 Substrate 层的 Emissive 或额外光照层上,就能在镜头方向变化时看到轮廓发亮。
为了更好看,还可以给边缘光叠加一个细节扰动,比如用噪声纹理作为 Mask,避免边缘光太“塑料”。
4.5 描边实现
描边方案有很多种,常用的有两种:
Inverted Hull(反向外壳): 复制模型,在顶点方向或法线方向膨胀,然后把它的材质设为纯黑或纯描边色,并只渲染背面。这是日式卡通渲染最常用的方案之一,成本较低,但容易在袖口、裙摆等开口处穿帮。
后处理轮廓检测: 通过深度或法线差异检测边缘,适合大面积、动态角色的统一描边,但控制精细度不如材质方案。
在 Substrate 材质阶段,Inverted Hull 使用起来和传统材质几乎一样:新建一个材质,输出为描边颜色,模型开启“Render CustomDepth/背面剔除”等设置。如果要让描边宽度随距离变化,可以把摄像机距离换算成缩放系数。
描边材质的核心逻辑并不复杂,关键点在于“法线方向”的处理:
- 静态网格可以用对象空间法线直接偏移。
- 骨骼动画角色建议使用世界空间法线,并要注意法线随骨骼变化是否正确。
4.6 完整材质网络示例
这里给出一套简化但可落地的 Substrate 卡通材质连接思路。真正的编辑器节点名称会因版本不同略有差异,但核心逻辑一致。
假设目标是一个“角色主材质”,分成三个 Layer:
Layer 1:漫反射 + 三色明暗 - 漫反射贴图采样得到 BaseColor - NdotL -> Ramp 采样得到光照颜色 - 将颜色合并输出到 Layer1 颜色 Layer 2:卡通高光 - Blinn 高光 -> Step 硬边 -> Layer2 权重 - 控制高光颜色为白色或淡冷色 Layer 3:边缘光 - Fresnel -> Power 调节 -> 与边缘光颜色相乘 - 通过 Mask 控制只有指定部位生效 混合: - 先用 Mask 控制 Layer2 与 Layer3 的权重 - 再把最终颜色接入 Substrate 输出(Root)实际编辑器中,你可以用下面这种思路组织材质图表:
1. TextureSample: Body_Diffuse -> Mul(颜色) -> Layer1 BaseColor 2. WorldNormal + LightDirection -> DotProduct -> Saturate 3. DotProductResult -> TextureSample: Toon_Ramp(Vector) -> Layer1 Lighting 4. BlinnPhong(高光参数) -> Step(阈值) -> Layer2 Specular 5. Fresnel(RimPower) -> Mul(RimColor) -> Layer3 Emission 6. LayerMask(纹理/顶点颜色) -> Lerp/LayerBlend 7. 输出到 Substrate Root Material这种连接方式的好处是每个效果独立成块,修改某一块不会影响其他层。
5. 运行与验证
5.1 查看效果
材质搭建完成后,把它赋给第三人称模板里的角色模型。为了让效果更明显,建议场景中同时放置:
- 一个主方向光(模拟太阳)。
- 一个低强度的补光(天光)。
- 一个大面积的背景色板,方便观察轮廓。
这时你可以拖动材质实例(Material Instance)里的参数,比如色阶数量、高光大小、边缘光强度,实时观察变化。
5.2 调试与性能分析
Substrate 材质因为存在多层组合,理论上会比普通材质更复杂。建议使用 UE 内置的视图模式检查:
- Shader Complexity 视图:观察材质像素着色密度,颜色越红说明越复杂。
- Lighting Only / Unlit 视图:排查光照是否按预期参与计算。
- Buffer Visualization 相关模式:确认法线、粗糙度等输出是否正确。
控制台命令中,常用的是:
r.MaterialQualityLevel 1 r.ShaderComplexity 1 r.SceneColorFringeQuality 0 r.Tonemapper.Quality 5这些命令主要用于调试显示,不代表最终打包效果。真正的性能验证要把项目打包到目标平台,在真机上观察帧耗时和 GPU 分区。
6. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 材质编辑器找不到 Substrate 相关选项 | 引擎版本未启用 Substrate,或项目渲染设置不支持 | 检查项目设置中的渲染选项,确认 RHI 为 DX12 或 Vulkan,再尝试开启相关开关 |
| 卡通材质光照效果不明显 | 场景光照过强,多灯叠加后离散化不明显 | 降低补光强度,尽量用单一主光验证色阶 |
| 色阶边缘闪烁或噪点明显 | Ramp 采样精度不足,或过渡宽度太小 | 提高贴图分辨率,采样时增加滤波,适当加宽 SmoothStep 过渡 |
| 描边在动画中穿帮 | Inverted Hull 方案对网格拓扑敏感 | 增加模型开口处顶点偏移处理,或改用后处理描边 |
| 高光过亮或过暗 | 高光层权重和颜色不匹配 | 将高光层独立输出到可视化通道,单独调整 |
| Substrate 材质移动端表现异常 | 移动端对复杂材质层支持有限 | 先在桌面端验证,再针对移动端做降级方案 |
| 打包后材质颜色与原样不一致 | 色彩空间、Tonemapper 或光源参数差异 | 检查项目色彩设置与目标平台颜色管理 |
7. 最佳实践与工程建议
7.1 材质参数进行分层管理
卡通渲染参数很多,不建议把所有参数堆在材质实例里。更好的做法是分层管理:
- 全局参数:光照方向、阴影颜色、整体色阶数量等,放到 Material Parameter Collection 中。
- 材质参数:单个材质独有的颜色、贴图、Mask 开关。
- 动态参数:运行时驱动的参数,比如受击闪烁、技能发光,尽量用单独的动态材质实例。
这样不同环节各司其职,后续优化和换肤也方便。
7.2 Ramp 纹理比纯数学离散更可控
在卡通渲染中,Ramp 纹理几乎是必备工具。相比在节点里算 Step,Ramp 的好处是:
- 美术可以直接绘制光影过渡的颜色范围。
- 可以方便模拟不同环境光、不同时段的光影风格。
- 调整成本低,不需要动材质逻辑。
建议把 Ramp 纹理的命名、尺寸、压缩格式纳入团队规范,避免出现十几张风格不一致的 Ramp。
7.3 描边方案要根据项目形态选择
Inverted Hull 适合角色渲染,后处理描边适合全局统一轮廓。如果项目有大量动态物件、复杂角色皮肤,建议先做小规模原型测试,确认穿帮误差在可接受范围内再推广。
另外,描边宽度与分辨率相关,需要加一个“基于屏幕尺寸的修正系数”,否则 4K 和 1080P 下描边观感差异会很大。
7.4 先验证性能上限再量产
Substrate 给了很大的材质自由度,但性能成本必须前置评估。建议在项目早期就建立一个包含典型角色、典型灯光的中型场景,跑 GPU Profiler,重点观察:
- 材质节点的复杂度。
- 多层混合带来的 Overdraw。
- 阴影采样、描边渲染带来的额外 Draw Call。
如果性能不达标,可以提前决定哪些效果放在低端设备上关闭。
8. 总结与学习路线
本文从 Substrate 的架构概念出发,拆解了卡通渲染着色器的实现思路:明暗色阶、卡通高光、边缘光、描边效果都可以在层组合结构中被独立控制和混合。相比传统 Shading Model,Substrate 的优势在于表达力更灵活,同一个材质里可以任意组合 PBR 与 NPR 效果,而不用反复修改引擎源码。
下一步建议,如果你还有精力继续深入,可以按照下面路径学习:
- 打开 UE 官方文档中 Substrate 与材质系统相关章节,对照 5.8 版本的编辑器逐个菜单和节点过一遍。
- 在材质编辑器中新建一个空材质,尝试把漫反射、高光、边缘光分别做成独立层再组合。
- 阅读引擎源码中 Substrate 相关模块(源码版引擎),观察 Closure 与 Slab 在渲染管线里如何被转换。
- 结合经典 NPR 论文或优秀游戏角色的美术拆解,尝试复刻一套属于自己的三色卡通材质模板。
在实际项目中,最值得注意的还是那句话:不要一开始就想着做出复杂的材质系统,先从“一个材质、一盏主光、三层结构”开始,把基础链路打通后再逐步加效果。遇到问题先回到最小复现场景,比盲目堆节点和查文档高效得多。