type
status
date
slug
summary
tags
category
icon
password

大体流程

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

FShaderCodeChunk的构成

感恩UE在此处仍然留有一定的注释。
notion image
  • Hash不必多说,其计算来自于const uint64 Hash = CityHash64((char*)FormattedCode, Result * sizeof(TCHAR)); FormattedCode其实就是Definition
  • SymbolName是一个变量名字,以“Local_N”的形式由FHLSLMaterialTranslator::CreateSymbolName 创建
  • Definition 可以被总结为两种情况:
      1. UniformExpression或者bInline
        1. 常数 比如 0.1,0.5,Material3(0,0,0)
        2. UniformExpression, 通常是由于我们在材质上设置的非布尔参数构成,翻译后会变成MaterialScalar(Vector)Expressions[i].x/y/z/w
        3. bInline, 比如Local_0.rgb,false,true这类并不需要赋值和计算的
      1. 非Constant以及非UniformExpression:
        1. 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?):
这里把将符合InShaderFrequencyCodeChunks作为当前编译时用到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中的细节,FShaderCodeChunkFShaderCodeChunk::Definition是如何生成的。
 

FShaderCodeChunk 的生成

 
我们可以简单从一个UMaterialExpressionAdd::Compile::Compile进行解读
notion image
notion image
注意两个函数AddCodeChunkWithHashGetParameterCode
GetParameterCode 的作用是返回此处用到的CodeChunk的符号,我愿统称为变量名,与前文的Definition基本是一个含义。
AddCodeChunkWithHash 内部调用了AddCodeChunkInner 生成codechunk并塞入到当前工作的CurrentScopeChunks中:
返回新生成的CodeChunk在数组中的位置,也是这个原因,大多数时候看到的Chunk[]数组里面存的都是int32,而不是FShaderCodeChunk这样的数据。
Texture Streaming中的LOD计算路径UE4中通过代码创建贴图
Dongfangliu
Dongfangliu
一个想做游戏制作人的引擎民工🍚
公告
type
status
date
slug
summary
tags
category
icon
password
🎉NotionNext 4.0即将到来🎉
-- 感谢您的支持 ---
👏欢迎更新体验👏