type
status
date
slug
summary
tags
category
icon
password
大体流程
从
FHLSLMaterialTranslator::Translate()
出发,我们看一下流程概况,开头即讲述了由于normal可能以节点PixelNormalWS
的形式被其他property的计算用到,因此先得到normal的计算代码。在此之前,本身材质编译是在Pixel层面去做,有一些操作希望放到Vertex层面做,那这一部分也需要先计算出来,同理还有一些自定义的表达式(
CustomExpression
)。
接下来,可以看到每个材质属性(
Material Property
)均是通过一个Material->CompilePropertyAndSetMaterialProperty(MP_XX,this)
的入口生成各自的代码,其中细节略后再提。但此处并不是真正生成代码的地方,这里仅仅是将该材质属性相关联的代码段(
FShaderCodeChunk
)生成,并没有做拼接。代码段的拼接核心在
GetFixedParameterCode
中,
再往后看,则没有更多稀奇的代码了,不在本文的关注范畴内。打开
GetFixedParameterCode
一看,更加不稀奇,主要使用了GetDefinitions()
来获得所有代码段拼接的结果。。。一看,好家伙,就全从头到尾直接连起来是吧
当然,这么做是对的,正确的原因来自于它的核心翻译规则:递归翻译。
在这里,其实已经有很多的疑问了:
- 频繁出现的
FShaderCodeChunk
说是代码段,但具体倒底是啥,怎么起作用的
- 这些
ShaderdXXX
的变量是干啥用的,就说SharedPropertyCodeChunks
好了
- 递归翻译是怎么具体体现的?为什么直接线性拼接是对的?
FShaderCodeChunk的构成
感恩UE在此处仍然留有一定的注释。

Hash
不必多说,其计算来自于const uint64 Hash = CityHash64((char*)FormattedCode, Result * sizeof(TCHAR));
FormattedCode
其实就是Definition
。
SymbolName
是一个变量名字,以“Local_N
”的形式由FHLSLMaterialTranslator::CreateSymbolName
创建
Definition
可以被总结为两种情况:UniformExpression
或者bInline
:- 常数 比如
0.1,0.5,Material3(0,0,0)
- UniformExpression, 通常是由于我们在材质上设置的非布尔参数构成,翻译后会变成
MaterialScalar(Vector)Expressions[i].x/y/z/w
- bInline, 比如
Local_0.rgb,false,true
这类并不需要赋值和计算的 - 非Constant以及非UniformExpression:
Type Local_N = Func(Local_0,Local_1,...,Local_{N-1})
Type
是这个CodeChunk的结果的数据类型,一般通过FHLSLMaterialTranslator::GetArithmeticResultType
进行运算得到
总结,
FShaderCodeChunk
可以是一段简单的变量\常量的读取截断,也可以是一个完整的变量声明+赋值,根据代码内容有对应的Hash。从CompilePropertyAndSetMaterialProperty
开始的翻译细节
映入眼帘的是
进去
FHLSLMaterialTranslator::SetMaterialProperty
一看,主要是配置了翻译时当前是在哪个ShaderFrequency
(SF_Vertex?SF_Pixel?SF_Compute?):这里把将符合
InShaderFrequency
的CodeChunks
作为当前编译时用到CodeChunks
,可见这个Shared
其实是在ShaderFrequency
上Share翻译出来的CodeChunk。返回来继续看,主要是调用了
MaterialInterface->CompileProperty
,后续会跳转到有FHLSLMaterialTranslator::CallExpression(FMaterialExpressionKey ExpressionKey,FMaterialCompiler* Compiler)
作为编译某个材质节点的切入口。
此处微微一品~(虽然我确实品了老长一会)可以知道通过
FunctionStacks[ShaderFrequency]->
ExpressionCodeMap
存储已经翻译过的Expression结果(无论失败与否),避免重复翻译同一Expression, Expresssion即UMaterialExpression
是材质蓝图里面的一个个节点。核心的切入点在这一段:
递归翻译
此时您切入Compile()可以发现来到了
MaterialExpressions.cpp
,总而言之,基本都是Compile
内部会调用输入节点的Compile
,这也就是所谓的递归翻译。可以脑补出一个流程,水流从材质属性往深深的材质蓝图向下流去,由深入浅的返回时一条条翻译出来,由此,语句之间的依赖关系自动解决,因此最后直接线性拼接即可得到正确的翻译结果。此时,还想补充一下一些Compile中的细节,
FShaderCodeChunk
和FShaderCodeChunk::Definition
是如何生成的。FShaderCodeChunk
的生成
我们可以简单从一个
UMaterialExpressionAdd::Compile
::Compile进行解读

注意两个函数
AddCodeChunkWithHash
和GetParameterCode
GetParameterCode
的作用是返回此处用到的CodeChunk的符号,我愿统称为变量名,与前文的Definition基本是一个含义。AddCodeChunkWithHash
内部调用了AddCodeChunkInner
生成codechunk并塞入到当前工作的CurrentScopeChunks
中:返回新生成的CodeChunk在数组中的位置,也是这个原因,大多数时候看到的Chunk[]数组里面存的都是int32,而不是FShaderCodeChunk这样的数据。
- 作者:Dongfangliu
- 链接:https://www.morningheart.com/article/8ccf931b-b287-45e0-ad0a-288e37536571
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。