前言
本文是一次问题和解决的记录,其中相关的具体细节有部分我并不完全理解,无法作出有效总结……
问题
新增ShaderingModel之后,使用了原本的CustomData0和CustomData1材质属性,结果发现在编译后的metal文件中,存在相同的局部变量,进行了重复采样,导致在ios上相关的材质GPU消耗直接翻倍
拿一段生成的metal来做例子,如下:
1 2 3 4 5 float4 _197 = Material_Texture2D_1.sample (Material_Texture2D_1Sampler, float2 (in_var_TEXCOORD0[0 ].x, in_var_TEXCOORD0[0 ].y)); half4 _198 = half4 (_197); float4 _214 = Material_Texture2D_2.sample (Material_Texture2D_2Sampler, float2 (half2 (min (max (_193 - (half (1.0 ) - half (Material.Material_ScalarExpressions[0 ].x * float (_198.x))), half (0.0 )), half (1.0 )), half (0.5 )))); float4 _280 = Material_Texture2D_1.sample (Material_Texture2D_1Sampler, float2 (in_var_TEXCOORD0[0 ].x, in_var_TEXCOORD0[0 ].y)); float4 _293 = Material_Texture2D_2.sample (Material_Texture2D_2Sampler, float2 (half2 (min (max (_193 - (half (1.0 ) - half (Material.Material_ScalarExpressions[0 ].x * float (half4 (_280).x))), half (0.0 )), half (1.0 )), half (0.5 ))));
仔细看一下就会发现
把_198替换成half4(_197)
1 2 float4 _214 = Material_Texture2D_2.sample (Material_Texture2D_2Sampler, float2 (half2 (min (max (_193 - (half (1.0 ) - half (Material.Material_ScalarExpressions[0 ].x * float (half4 (_197).x))), half (0.0 )), half (1.0 )), half (0.5 )))); float4 _293 = Material_Texture2D_2.sample (Material_Texture2D_2Sampler, float2 (half2 (min (max (_193 - (half (1.0 ) - half (Material.Material_ScalarExpressions[0 ].x * float (half4 (_280).x))), half (0.0 )), half (1.0 )), half (0.5 ))));
此时214和293只有197和280这俩不一样
再看一下197和280
1 2 float4 _197 = Material_Texture2D_1.sample (Material_Texture2D_1Sampler, float2 (in_var_TEXCOORD0[0 ].x, in_var_TEXCOORD0[0 ].y)); float4 _280 = Material_Texture2D_1.sample (Material_Texture2D_1Sampler, float2 (in_var_TEXCOORD0[0 ].x, in_var_TEXCOORD0[0 ].y));
一模一样啊……
_280与_197一样……
也就是说_293与_214一模一样……
sample直接重复了一次,没有合并优化掉…
据leader和大佬们分析,是因为我们在自定义ShadingModel的时候使用了原本EMaterialProperty的MP_CustomData0和MP_CustomData1,所以最后在metal文件中这两个相关的值没有成功的被优化合并。
尝试策略
leader让我添加新的自定义的材质属性,但是要注意不要和customData0和customData1一致,参考一下基础的几个材质属性,看一下这样能不能成功在metal里优化掉这个问题
打开引擎的r.DumpShaderDebugInfo选项后在材质编译生成的.rewritten.hlsl里看到
1 half3 GetMaterialCustomData0 (FMaterialPixelParameters Parameters)
CustomData0是在FMaterialPixelParameters 结构体里面的
这个结构体在MaterialTemplate.ush
1 2 3 4 5 6 7 8 struct FMaterialPixelParameters { };
但是有些属性比如透明度是这样的:
1 half GetMaterialOpacity (FPixelMaterialInputs PixelMaterialInputs)
是在结构体FPixelMaterialInputs 里的
这个结构体也在aterialTemplate.ush
1 2 3 4 5 6 7 struct FPixelMaterialInputs { %s };
那么如果我们自定义一个材质属性用来给自定义的ShadingModel输入值的话,将这个材质属性和透明度保持近似,也放在FPixelMaterialInputs结构体里面,应该可以成功优化掉,而不是像customData一样。
FDeferredShadingSceneRenderer::Render()中会调用 RenderBasePass() ,之后在BasePassPixelShader.usf中会计算GBuffer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 FGBufferData GBuffer = (FGBufferData)0 ; GBuffer.GBufferAO = MaterialAO; GBuffer.PerObjectGBufferData = GetPrimitiveData (MaterialParameters.PrimitiveId).PerObjectGBufferData; GBuffer.Depth = MaterialParameters.ScreenPosition.w; GBuffer.PrecomputedShadowFactors = GetPrecomputedShadowMasks (LightmapVTPageTableResult, Interpolants, MaterialParameters.PrimitiveId, MaterialParameters.AbsoluteWorldPosition, VolumetricLightmapBrickTextureUVs);const float GBufferDither = InterleavedGradientNoise (MaterialParameters.SvPosition.xy, View.StateFrameIndexMod8);SetGBufferForShadingModel ( GBuffer, MaterialParameters, Opacity, BaseColor, Metallic, Specular, Roughness, Anisotropy, SubsurfaceColor, SubsurfaceProfile, GBufferDither, ShadingModel );
SetGBufferForShadingModel函数位于ShadingModelsMaterial.ush中
在SetGBufferForShadingModel的函数体中可以看到部分参数是直接赋值的,但是GBuffer.CustomData要根据不同的ShaderModel进行赋值。
在UE4本身原有的ShaderModel中,SHADINGMODELID_CLEAR_COAT有使用CustomData0和CustomData1,代码是这样的:
1 2 3 4 5 6 7 8 9 else if (ShadingModel == SHADINGMODELID_CLEAR_COAT) { float ClearCoat = saturate ( GetMaterialCustomData0 (MaterialParameters) ); float ClearCoatRoughness = saturate ( GetMaterialCustomData1 (MaterialParameters) ); GBuffer.CustomData.x = ClearCoat; GBuffer.CustomData.y = ClearCoatRoughness; }
而里面调用到的GetMaterialCustomData0和GetMaterialCustomData1来自MaterialTemplate.ush
1 2 3 4 5 6 7 8 9 half GetMaterialCustomData0 (FMaterialPixelParameters Parameters) { %s; }half GetMaterialCustomData1 (FMaterialPixelParameters Parameters) { %s; }
可以看出这里参数是从FMaterialPixelParameters 获取的
而像透明度之类的参数是这样的:
1 2 3 4 half GetMaterialOpacityRaw (FPixelMaterialInputs PixelMaterialInputs) { return PixelMaterialInputs.Opacity; }
这里参数是FPixelMaterialInputs
那么把自定义参数也加到这个里面去,并提供类似的Get函数
1 2 3 4 5 6 7 8 9 half3 GetMaterialCustomData3 (FPixelMaterialInputs PixelMaterialInputs) { return PixelMaterialInputs.CustomData3; }half GetMaterialCustomData4 (FPixelMaterialInputs PixelMaterialInputs) { return PixelMaterialInputs.CustomData4; }
在BasePassPixelShader.usf中,调用SetGBufferForShadingModel的时候将新增加的变量也传过去
在ShadingModelsMaterial.ush中
void SetGBufferForShadingModel函数
根据不同的ShadingModel,GBuffer需要有不同的赋值操作,对于我们新增的着色模型,之前是通过CustomData0和customData1获得的,在这里改成直接传进来的两个新增的自定义参数
完成以上步骤之后,再次检查了编译后的debug信息以及对metal平台进行测试,原本没有被优化掉的sample成功优化掉了
总结
在自定义ShadingModel的时候,如果使用CustomData0和CustomData1两个材质属性,可能,有可能会在部分平台(比如metal)存在shader编译的优化问题,导致性能表现较差,可以考虑使用自定义材质属性节点来使材质编译能够正确优化。
参考
关于如果自定义ShadingModel和新增材质属性,这篇大佬写的很仔细,手把手教学型:
UE4 shadingmodel终极版之如何新增材质接口(4.27)