材质图到 HLSL 的翻译
Material Graph to HLSL Translation
材质编辑器点 Apply 之后,引擎先把节点图翻译成 HLSL,再把 HLSL 编译成 GPU 字节码。翻译以 MaterialTemplate.ush 为模板、由 FHLSLMaterialTranslator 做文本替换与常量折叠;内置节点能被折叠,Custom 材质表达式 则不能。每个材质还会按特性/平台/用途生成多份着色器排列。
两步:翻译 vs 编译
点 Apply 时引擎收集混合模式、着色模型、用途标记,以及描述表面的全部节点,填进模板,得到「着色器配方」(HLSL)。之后再把 HLSL 编成 GPU 字节码。Epic KB 强调本轨只需弄清图→HLSL;字节码与排列爆炸的细节可另见 Matt Pettineo《The Shader Permutations Problem》等(文中外链,本库未收录)。(How the Unreal Engine Translates a Material Graph to HLSL)
在编辑器中:Window → Shader Code → HLSL Code 可只读查看生成代码,再复制到带 HLSL 高亮的编辑器里拆读。(How the Unreal Engine Translates a Material Graph to HLSL、Essential Material Concepts)
Essential Concepts 说得更直白:节点图在幕后被静默翻译成 HLSL;用户能看、不能在编辑器里直接改那段 HLSL。(Essential Material Concepts)
模板与调用链
生成的 HLSL 基于 Engine/Shaders/Private/MaterialTemplate.ush:结构体与函数体里是 %s 占位符,运行时做文本替换。填好的函数由像素着色器 BasePassPixelShader.usf 调用,真正画每个像素。(How the Unreal Engine Translates a Material Graph to HLSL)
FMaterialCompiler 是接口;标准路径实现是 FHLSLMaterialTranslator(另有 Lightmass、Proxy)。调用路径:
flowchart TB A["FMaterial::CacheShaders"] --> B["BeginCompileShaderMap"] B --> T["FHLSLMaterialTranslator::Translate"] B --> E["GetMaterialEnvironment"] B --> S["GetMaterialShaderCode"] B --> C["FPartialShaderMap::Compile"]
| 步骤 | 职责 |
|---|---|
| Translate | 为每个材质输入针脚准备子图;补齐顶点插值、UV、Material Parameter Collection、虚拟纹理采样等;对非法特性组合报错(例如逐像素着色模型配 SingleLayerWater) |
| GetMaterialEnvironment | 写入最终 HLSL 里用的 #define(着色模型与特性常用 #ifdef 切换) |
| GetMaterialShaderCode | 把编译好的数据打印进模板字符串 |
| Compile | 翻译成功后进入字节码编译 |
(How the Unreal Engine Translates a Material Graph to HLSL)
一个材质通常对应多份着色器(渲染特性 × 平台 × Usage),不是「一材质一着色器」。(How the Unreal Engine Translates a Material Graph to HLSL)这与 着色频率与材质系统 里「材质模板 → 着色器变体」以及静态参数导致的排列爆炸(见 材质系统)是同一工程问题的不同侧面。
节点如何变成表达式:UMaterialExpression::Compile
每个彩色节点继承 UMaterialExpression,关键虚函数是 Compile(另有 GetCaption、GetKeywords)。声明在各自头文件,实现集中在 MaterialExpressions.cpp;节点可自带常量与 FExpressionInput。(How the Unreal Engine Translates a Material Graph to HLSL)
Compile 返回 int32:已编译结果在翻译器内部数组中的下标(逻辑上的「指针」)。若输入针脚已连线,先递归 Compile 子树;否则用节点自带常量(如 Compiler->Constant(ConstA))。以 Add 为例,最终落到 FHLSLMaterialTranslator::Add。(How the Unreal Engine Translates a Material Graph to HLSL)
常量折叠:内置节点的核心优化
Add 并不只是拼 "%s + %s",而是按操作数「是否整次 draw call 恒定」分支:
| A / B | 翻译结果 |
|---|---|
| 双方都是常量 | CPU 预先算好,着色器里写字面量 |
| 一方常量 + 一方参数 | 生成隐藏参数,在 CPU 侧把常量加进参数,GPU 只看到一个固定数 |
| 双方都非常量 | 发出 A + B |
这就是常量折叠 (Constant Folding):避免 GPU 反复算「整次绘制都不变」的值。(How the Unreal Engine Translates a Material Graph to HLSL)同文在 Custom 节点文档中强调:Custom 阻止常量折叠,指令数往往明显高于等价内置节点图——见 Custom 材质表达式。(Custom Material Expressions)
示例图在 CalcPixelMaterialInputs 中的典型落点(便于对照 HLSL Code 窗口):
| 材质输入 | 生成形态 |
|---|---|
| Roughness(两常量相加) | PixelMaterialInputs.Roughness = (1.0 + 2.0); |
| Metallic(常量+参数已折叠) | Material.ScalarExpressions[0].z(均匀表达式打成 float4 打包) |
| Base Color(TexCoord + 参数) | MaterialFloat Local1 = (...); PixelMaterialInputs.BaseColor = Local1; |
非常量中间结果写成 LocalN;子图会做哈希,相同计算复用同一 LocalN,避免重复算。(How the Unreal Engine Translates a Material Graph to HLSL)
编辑器版 Emissive 的特殊 lerp
即使 Emissive 针脚空着,编辑器编译出的材质仍可能出现「用户自发光」与某个 VectorExpression 的 lerp,系数由引擎控制——用于选中高亮;Cook 后的着色器会优化掉。(How the Unreal Engine Translates a Material Graph to HLSL)读 HLSL Code 时不要把这段当成运行时必付成本。
与图形学轨的对应
| UE 机制 | 本库理论 / 练习页 |
|---|---|
| 节点 = HLSL 片段的可视化 | 可编程着色器与 GPU 管线、像素着色器 |
| 图→HLSL→字节码 | 通识「着色器编译」;常量折叠另见 构建配置与编译优化(CPU 编译器侧同类思想) |
| 手写片元语法(GLSL) | 片元着色器与 GLSL 基础(BoS / LearnOpenGL);UE 侧手写入口是 Custom 节点(HLSL) |
| 材质变体 / 排列 | 着色频率与材质系统、材质系统 静态参数 |
出处
- How the Unreal Engine Translates a Material Graph to HLSL Epic KB Matt Oztalay / Joe Radak(2022;更新至 2026-04)
- Essential Material Concepts UE 5.8 文档
- Custom Material Expressions UE 5.8 文档
- Materials (landing) UE 5.8 文档