【Unreal从0到1】【第三章:基于物理的渲染】3.3,浅谈 ...
Strata是伴随UE5.1推出的新的材质设计流程,目前属于测试版。使用Strata要在Project Setting中启用:项目设置里启用Strata
然后新建一个测试材质,可以对比下传统材质设计模式与Strata的区别:
Strata Workflow与传统材质开发对比
启用Strata后左边ShadingModel选项变为禁用状态,Strata Blend Mode被激活,提供了:
[*]Opaque
[*]Masked
[*]Translucent-Grey Transmittance
[*]Translucent-Colored Transmittance
[*]Colored Transmittance Only
[*]Alpha Holdout
后面会对几种混合模式的作用机制展开详细分析,因为还有另一个变动需要我们关注,也就是输出节点。Strata的Master节点除了”Opacity Mask,WPO,PDO, Refraction“被保留外,其余PBS关键数据都被”Front Material“的PIN接口取缔,同时默认提供一个”Strata Slab BSDF“的数据节点。
Strata Slab bsdf接口说明
注意!该节点带有一个模式选择器,包含“Use Metaliness, Use Subsurface Diffusion”两个控制项,这也就暗示了Strata同样包含“金属,电介质”两条WorkFlow,以及用户可以选择使用“扩散剖面”技术计算光散射。
然后可能需要进一步关注两个问题:这个节点做了什么;数据流的传递过程;
一,Strata BSDF做了什么事情
针对第一个问题,可以在“MaterialExpressionStrata.h”与“MaterialExpressions.cpp”文件中找到答案:
Strata Slab BSDF
每个函数起到的作用如下:
[*]Compile:表述该节点被如何编译,函数内部实现告诉我们实际Strata流程下还是隐含了两条WorkFlow,对于金属类材质会激活“BaseColor, EdgeColor, Specular, Metallic”,对于电介质类材料会使用默认的“DiffuseAlbedo(反照率),F0 (视图垂直表面的高光占比), F90(视图平行于表面的高光占比)”。
[*]GetCaption:输入关键词从库里检索出节点
[*]GetOutputType:输出MCT_Strata数据类型
[*]GetInputType:设置每个输入接口的Index并返回对应Index的数据类型,注意因为隐含了两条工作流,所以从Roughness接口开始Index采用“偏移计数”
[*]GetInputName:根据PIN的Index返回对应接口的字符串名称
[*]GetInputs:取到节点每个输入接口的地址,并存入一个FExpressionInput类型的数组容器
[*]GetConnectorToolTip:获取每个PIN接口的链接器提示,鼠标悬浮以显示对应的提示
[*]IsResultStrataMaterial:写死的返回true, 该状态下Master节点会按照Strata流程编译
[*]GatherStrataMaterialInfo:每个PIN接口被激活后,所连接的旧的PBS属性,比如DiffuseAlbedo与MP_DiffuseColor相连,F0与MP_SpecularColor相连,SSSMFP与MP_SubsurfaceColor相连。但并不是Strata Slab BSDF节点上所有的PIN都在该函数内存在一个对应的变量映射。只有那些与旧的材质系统兼容的数据才会这样映射,也就是说Strata并不是针对原有材质流程的简单上层封装。
[*]StrataGenerateMaterialTopologyTree:该函数负责生成Strata材质拓扑树,源码上看整合了各个Bool函数的状态,根据返回值的真假状态重组Strata BSDF的操作状态
[*]PostEditChangeProperty:这个函数主要解决Mode切换避免节点错误
然后几个Bool函数决定了流程控制:
[*]HasEdgeColor:启用Metalness流程后返回True, 意味着EdgeColor接口被激活,否则为F0
[*]HasFuzz : 当FuzzAmount接口被激活(也就是有数据连入时),返回True。同时状态会在StrataGenerateMaterialTopologyTree中被传给bBSDFHasFuzz
[*]HasSecondRoughness:当SecondRoughnessWeight接口被连接时函数返回True, 同时状态会在StrataGenerateMaterialTopologyTree中被传递给bBSDFHasSecondRoughnessOrSimpleClearCoat;在该节点的Compile方法中被传递给bHasSecondRoughness。意味着次高光波瓣参与计算,并会最终参与到主高光波瓣的混合做为最终的高光状态,这种照明模型主要针对车漆类材质进行模拟。
[*]HasSSS:扩散剖面不为空,或SSSMFP接口被连接时都会返回True。区别在于前者指认了光照计算考虑次表面散射,后者选择人为提供一张纹理或更复杂上层计算的结果做为Profile传给SSSMFP。该状态会在StrataGenerateMaterialTopologyTree中被传给bBSDFHasSSS。同时也会影响GatherStrataMaterialInfo方法与Compile方法,前者将StrataMaterialInfo切换到SubsurfaceProfile ShadingModel,否则使用DefaultLit ShadingModel;后者会Compile出SSSProfileCodeChunk。
[*]HasSSSProfile() : 返回SubsurfaceProfile不为空
[*]HasMFPPluggedIn():返回SSSMFP接口是否被激活,同时状态会在StrataGenerateMaterialTopologyTree中被传递给bBSDFHasMFPPluggedIn
[*]HasAnisotropy():返回Anisotropy接口是否被激活,同时状态会在StrataGenerateMaterialTopologyTree中被传递给bBSDFHasMFPPluggedIn,同时在Compile方法中被传递给bHasMFPPluggedIn;
这里有个StrataMaterialInfo的类需要额外关注下,声明在EngineTypes.h中:
Strata Material Info
可以看到Strata流程下依旧保留了“ShadingModel”的概念,不过这里的ShadingModel已经与之前的ShadingModel不一样了,对比如下:
新旧两套ShadingModel的对比
新的Strata SM支持了Unlit,DefaultLit,SubsurfaceLit,VolumetricFogCloud,Hair,Eye,SingleLayerWater,LightFunction,PostProcess,Decal。但是当按照习惯返回材质编辑器面板切换ShadingModel时却发现状态不可更改:
这是因为在Strata流程下“PBS数据节点与底层着色模型进行了一一绑定”,即切换不同的Strata BSDF意味着使用不同的SSM,这种方式与切换ShadinModel改变Master节点的激活接口是两种完全不同的封装模式。所以除了StrataSlabBsdf,还可以获取其他的BSDF Model Graph:
Strata BSDF Node Display
这样可以通过Strata Operators Nodes实现不同Strata ShadingModel的混合【关于混合节点后续详细展开】。其实类似ShadingModel混合的功能在UE4的时候就有,只不过它以一种特殊的SM存在,被称为“From Material Expression”
From Material Expression
除了Operator节点,在5.1中还提供了用于计算模型参数的“Strata Haziness-To-Secondaty-Roughness, Strata Metalness-To-DiffuseAlbdedo-F0,Strata Transmittance-To-MeanFreePath,StrataFlipFlop”用于“艺术友好的参数向更接近材料性质的参数的映射”。如果一开始不知道怎么使用Strata的BSDF Node,官方也提供了几个Strata Building Blocks Nodes,其实就是一些封装好的函数,函数内部官方给了一个基本的实现可用来参考。当然,如果更习惯旧的开发流程,也有Strata Legacy Conversion节点用于转换:
Strata Legacy Conversion
所有材质节点总结如下:
Strata Graph Node Display
这就是目前5.1版本中所提供的全部了。
二,数据是如何流入管线的
对整个框架有整体认识后,我们来解决第二个问题:数据是如何流入渲染管线的。
入口显然在FrontMaterial接口中,找到BasePassPixelShader.usf,可以看到下面的代码:
Base Pass Shader中的数据入口
可知这阶段从材质图获取BSDF数据包,可能包括特殊BSDF的层混合数据,入口点等;初始化寄存器中的Strata标头,拓扑树,BSDF计数,共享基数据,AO,接触式阴影,动态间接阴影等。我们来进一步关注下这几个数据是从哪传进来的。
2.1,StrataData
首先直接来自材质图输入的FrontMaterial被传给了StrataData,找到其所属数据类型定义:
FStrataData
其中FStrataBSDF存储了BSDF混合累积评估的一些数据
FStrataBSDF
这个数据类型描述了每一个BSDF包含的信息,Stata是一个很特殊的值,它是一个32bit的uint,将材质状态分量按位存储,这样每次通过位偏移就能找到对应的状态:
材质状态分量按位存储与偏移
这是一种非常紧凑与高效的数据分装方法,后面在进行层材料的数据处理时会依据树形拓扑结构找到对应的BSDF叶子,并依据其Stata值做为case切换到相应的分支进行计算,这些分支代表了不同的Strata ShadingModel,它们分别有自己独特的着色特性。
2.2,Strata像素着色器标头数据
然后是FStrataPixelHeader数据类型:
FStrataHeader
在STRATA_INLINE_SHADING宏包裹中有三个成员,其中FSharedLocalBases应该描述了一个多BSDF共享基数据的最大注册值:
FSharedLocalBase
2.3,拓扑树与操作算子
Header中的FStrataTree描述的是分层材料的BSDF拓扑树:
FStrataTree
里面包含多个FStrataBSDF类型的BSDFs,最多15个,代表多个BSDF。还有一个包含多个FStrataOperator类型的Operators, 同样最多15个。FStrataOperator内部如下:
FStrataOperator
这里用一小节重点解释下StrataOperator的概念,直译过来是“层算子”,但这样听起来很奇怪,似乎很难从名字意会出其作用。后来我在一个名为“FStrataLobeStatistic” 【层波瓣估计】的成员中找到了源头。Operator一词应该衍生自Laurent Belcour在2018年的一篇名为《Efficient Rendering of Layered Materials using an Atomic Decomposition with Statistical Operators》[1]的论文。他从统计学的视角给出了一种多层介质多级散射照明模型的实现方案,即计算分层BSDF的能量,均值,方差,而不是计算完整的物理光照传输。根据每一步的统计结果研究光在交互过程中的“原子操作”【单步光行为:例如折射,反射】对方向统计的影响,然后将这些”“原子操作符”与统计数据通过一种“Adding-doubling算法”相结合形成一种评估模型。这样物料表面复杂的光照传输【物理BSDF】的模拟计算就等价于多个层的高光波瓣在评估模型下所求得的能量,方向分布。整个过程如下:
Belcour 2018
在这一视角下,Belcour提出了“光的原子操作符”或“行为算子”的概念——复杂的光照传输过程可以拆分为“反射,折射,吸收,散射”的等基本行为:
光线行为的原子操作符
这个时候再回过头来看FStrataOperator就清晰很多了,很多的概念都衍生自Belcour对分层材料渲染的研究,Strata就是“层的概念”,Operator就是“操作符或行为算子”,每一个成员的具体释意可参见上图。
进一步的,如果有关注Belcour早期的工作,会发现他使用了另外一种更朴素的视角去解释与描述光在介质层中的传输过程。2017的一篇名为《A Practical Extension to Microfacet Theory for the Modeling of Varying Iridescence》[2]的论文中,Belcour给出了一种模拟两层介质,多级散射路径的照明模型,使用Airy多项式重建了BRDF模型,并分别给出了用于离线渲染器设计的数学模型与可方便插入任意BRDF以快速用于实时渲染的数学模型。这种BRDF能够很好的还原材料表面的薄膜干涉效应,而这一特点是很多模型所刻意忽略或无法很好实现的。
Belcour 2017
但由于还是基于光照传输模拟的视角,多级光路散射所带来的复杂数学运算是无法避免的,同时更深层的介质间散射还原也会让计算量迅速增加,所以只考虑了两层。于是在2018年,Belcour从统计学的视角给出了一份适应面更广泛的分层材料模拟方案。而这套思路也成为了Unreal在设计自家分层材质解决方案——Strata时的重要参考。
2.4,间接辐照度与AO
我们回到对FStrataPixelHeader的分析,第三个成员是FStrataIrradianceAndOcclusion:
FStrataIrradianceAndOcclusion
这个没有特别需要说明的,存储了AO, 间接辐照度,间接漫射采样遮挡等数据。紧接着是名为STRATA_DEFERRED_SHADING的宏包裹起来的四项,包括2D纹理数组类型的SatrataBuffer等。
2.5,Strata 阴影掩码设置
阴影类型与启用状态会改变Strata寄存器标头内的掩码状态,具体如下:
[*]接触式阴影:
void SetCastContactShadow (inout FStrataPixelHeader Out, bool bIn){ Out.State |= (bIn ? HEADER_MASK_CASTCONTACTSHADOW : 0u); }
SetCastContactShadow(StrataPixelHeader, GetPrimitiveData(MaterialParameters).Flags & PRIMITIVE_SCENE_DATA_FLAG_HAS_CAST_CONTACT_SHADOW)
这里的Flags是指是否使用16 bits ,与启用接触式阴影宏同时启用时为真,如果为真则将Header的State设置为HEADER_MASK_CASTCONTACTSHADOW,这个宏代表了Stata掩码的位移操作。
[*]胶囊体间接阴影:
void SetDynamicIndirectShadowCasterRepresentation(inout FStrataPixelHeader Out, bool bIn){ Out.State |= (bIn ? HEADER_MASK_HASDYNINDIRECTSHADOWCASTER : 0u); }
SetDynamicIndirectShadowCasterRepresentation(StrataPixelHeader, GetPrimitiveData(MaterialParameters).Flags & PRIMITIVE_SCENE_DATA_FLAG_HAS_CAPSULE_REPRESENTATION);
类似接触式阴影,如果16bit与PRIMITIVE_SCENE_DATA_FLAG_HAS_CAPSULE_REPRESENTATION宏同时启用,则使用HEADER_MASK_HASDYNINDIRECTSHADOWCASTER宏进行位操作。
三,对渲染管线的影响
总结下新的材质流程对底层渲染管线的影响与改动。Shader端需要阅读源码的文件有:
[*]BasePassPixelShader.usf : BasePass入口
[*]StrataStatisticalOperators.ush : 各类BSDF波瓣分布的封装
[*]Strata.ush : Strata Workflow的函数库,各种Shading功能部署在此
[*]StartaLightingCommon.ush : Strata Workflow的通用灯光功能
[*]StrataForwardLighting.ush :用于计算前向照明的函数实现,例如StrataForwardLighting
[*]StrataEvaluation.ush : 照明评估功能函数,用于BSDF计算的中间向量等数据的封装
[*]StrataExport.ush : 分层材料评估模型的定义,数据预处理与最终导出
新的材质流程被嵌入进原有的Base Pass,光照与阴影环节,分别针对Deferred与Forward+进行了区分。需要注意的是上述底层支持仅限PC端,移动端目前是无法享有的。除了在一些特别的数据处理上,比如DBuffer,GBuffer,缓冲区的重新路由,间接漫射光屏蔽,静态阴影的掩码处理等,Strata有自己“独立”的Code Block,比较好区分开来。对于后者,整体可以分为四部分:STRATA_MATERIAL_EXPORT_EXECUTED;STRATA_OPAQUE_DEFERRED,也就是延迟管线的支持;STRATA_TRANSLUCENT_FORWARD或STRATA_FORWARD_SHADING,也就是在半透明与Forward+中的支持;还有STRATA_OPTIMIZED_UNLIT,也就是对Unit类材质的处理。
下面分别介绍:
3.1,Strata Material Export
第一部分用于导出Strata材质属性,包括Coverage,TransmittancePreCoverage,BaseColorPostCoverage, WordNormal, EmissiveLumiance。StrataMaterialExportOut函数会接收StrataPixelHeader与StrataData数据,依据特定模式更新层的拓扑树信息,然后由下至上遍历每一个BSDF叶子,依据“沿视线方向的亮度”和“沿法线方向的透明度”的乘积做为权重对每层的BaseColor, 法线,自发光亮度进行累加求和,然后分别输出最终的结果。
Strata Material Export
3.2,Strata Deferred
第二部分是对延迟管线的支持,主要内容为材料层数据处理,接收来自材质图与顶点插值的数据并做打包与输出,写入MRT等操作
Strata Deferred PackStrataOut
这里的PackStartaOut是一个很重要的函数,前面我们提到在Belcour的“材料介质的分层渲染框架”中通过计算各层BSDF的统计分布,然后借由一个评估模型去根据光线算符累积各层BSDF的贡献并输出结果从而间接获得复杂物理传输过程的计算值。在Unreal的解决方案中,评估模型的实现就被封装在PackStrataOut函数中。
在StrataExport.ush中可以找到实现,这600行代码大概做了以下事情:
[*]数据预处理
Step1 数据预处理
[*]对树进行遍历,检索出每一层的BSDF并判断其可见性,对于可见的BSDF依旧目标材料类别选择特定的评估模型进行属性累加,并输出:
Step2 Strata BSDF 评估
[*]对各层BSDF高光屏蔽,间接辐照度的评估计算,并将最终的值累加到EmissiveColor,同时将AO值写入StrataPixelHeader中的AO成员项:
Step3 各层BSDF高光屏蔽与间接辐照度评估
[*]将辐照度和 AO 存储到标头的状态位中,以便稍后可以将它们作为公共标头状态位的一部分写入。
Step4 AO 写入标头
[*]分4类编码模式【简单,复杂,单一,自定义】分目标材料类型导出Starta Data:
Step5 分材料类别打包Strata Data
PackStrataOut函数执行完毕后,会将数据写入MRT:
写入MRT
并将EmissiveLuminance叠加到输出Color上。至此完成了Strata 在Deferred BasePass阶段的着色计算。
3.3,Strata Forward+
Forward+下的Strata分支如下:
Strata Forward+
核心是StrataForwardLighting函数,该函数的封装思路类似传统Forward+,只不过按照Starta Data的要求对数据进行了重新封装,所以能看到很多在Strata Deferred中眼熟的代码块。主要功能是判断BSDF可见性 ,循环遍历树结构,针对每一层评估环境漫射,IBL,直接光,自发光与本地光的贡献进行GI累积。不再做展开分析,可去StartaForwardLighting.ush中查阅具体实现。
3.4,Strata Unlit
前面提到的都是非Unlit材料,对于Unlit材料被单独拆了出来进行处理,这一块没有什么需要展开的:
Strata Unlit
四,总结
前面一直使用Strata Workflow的原因在于,Strata已经与Unreal在14年所提出的基于ShadingModel概念的材质开发流程完全不一样了:从底层实现来看有着大量框架性的重构【当然,也保留了原有的流程】;从用户层来看,需要TA去改变以往的开发思路与认知,进而带动新的规范与设计流程。所以这不是简单的重封装,而是彻底的与传统决裂,我们需要基于一种新的,更接近材料物理属性本质的认识与思路去规划与编写Shader。
从底层支持情况来看,这套框架目前仍处于相对初步的阶段,重点针对常规不透明带粗糙度的分层材料进行了照明评估模型的详细设计,但对于其他类型的材料比如头发,眼睛,水则相对简单。不管如何,单从技术应用角度而言,官方推出Strata去与Nanite管线配套已经一定程度声明了”旧的时代已经远去“,剩下的就看市场需要与用户能否尽快跟上技术迭代的步伐了。
最后,由于中文互联网上目前关于Strata的讨论非常少,因此这里基于对UE5.1源码的粗略阅读给出一些非官方的个人分析。我目前对这套流程只是看了一个大概,很多细节尚未深入研究。可留言交流与讨论,佛系登知乎~
五, Reference:
Belcour 2018 《Efficient Rendering of Layered Materials using an Atomic Decomposition with Statistical Operators》ACM
Belcour 2017《A Practical Extension to Microfacet Theory for the Modeling of Varying Iridescence》
页:
[1]