https://medium.com/@lordned/unreal-engine-4-rendering-part-5-shader-permutations-2b975e503dd4
翻译:yarswang 转载请保留
如前所述,Unreal将编译给定着色器/材质的多个排列以应对不同的用例。 修改材质时,Unreal将查找该着色器使用的 .ush/.usf 文件并重新加载它们。 然后,Unreal将你的材质图转换为HLSL代码,然后开始构建着色器的每个排列。
我们都熟悉每次在Unreal中使用材质编辑器的过程。不幸的是,这有一个问题:它只重新加载与材料本身相关的着色器。因为Unreal使用了延迟渲染,一些着色器是全局的(例如,对GBuffer进行采样并计算最终颜色的通道)。这意味着这些着色器实际上不是材质的一部分,因此修改该材质不会导致它们重新加载。
另外,一旦你更改了其中一个全局着色器并重新启动,Unreal就会重新编译每个使用该文件的着色器,所以如果你编辑一个共用着色器,你会看到一个空项目有约125个着色器/约10,000个的排列完全重新编译。对于修改渲染模块C++代码也是如此。这会对迭代时间产生可怕的影响,因为你需要重新编译所有内容并等待10分钟以查看你的更改是否有效。更糟糕的是,如果你在着色器内部修改#if X_Y区域内的某些内容,则会在重新编译的过程中遇到无法编译的排列。你可以去修复它,但修复它会使我们所已编译过的需要重新编译。
爽!

降低总排列数
我们的第一个目标是查看可以在引擎中或涉及材质的哪些设置可以更改,来减少需要编译的排列总数。我不一定建议你禁用这些设置,但暂时减少排列数会使你在开发着色器时加快迭代时间。
在基于材质的设定上,有些事项我们可以注意一下。第一个是Usage设置。
我们第一个目标是看改变什么设置可以降低排列的数量。这些设置看起来代表引擎中特殊的顶点工厂,如果 Automatically Set Usage in Editor 被勾选的话,它将生成该排列。禁用此功能可以让你获得更多控制权,并且可以让美术同学在分配材料方面三思而行,但可能不会为你节省太多的排列。
下一件基于材质的设定中,需要注意的是Quality and Static开关。因为开启静态开关,在编译时每种可能组合都需要一个新的排列,开启太多静态开关可能会导致相当多的排列。同样,这个可能不会节省你太多的排列。这是要记住,但是如果你正在尝试迭代并且你正处于一个近乎空的项目中,你可能不会有任何这些设置,并且运行时开启静态开关的额外性能增益值得根据需要使用它们。
现在我们来看看项目设置中哪些可以减少排列 —— 其中一些设置需要长期启用,但如果可能的话,我们可以暂时禁用它们。这需要Unreal 4.19或更高版本,编译本篇文章撰写日期之后的最新引擎源码就能够起作用。打开项目设置>渲染,有几个设置可以调整。根据你的特定项目/测试需求,你可能需要启用其中一些。如果你在此处禁用场景需要的着色器排列,你将在视窗中收到警告,比如:当尝试使用雾效却禁用Support Atmospheric Fog时,会有“PROJECT DOES NOT SUPPORT ATMOSPHERIC FOG”(工程不支持雾效)的警告出现。
光照
- Allow Static Lighting
允许静态照明(注意:禁用此功能表示标记为静态的灯光不会影响你的材料。如果禁用此选项,请将它们设置为Movable进行测试)
减少着色器排列
- Support Stationary Skylight
(注意:禁用此功能意味着标记为静态的天光对不会影响你的材质。如果禁用此功能,请将它们设置为Movable进行测试) - Support Low Quality Lightmap Shader Permutations
- Support PointLight WholeSceneShadows
- Support Atmospheric Fog
减少移动着色器排列
- Support Combined Static and CSM Shadowing
- Support Distance Field Shadows
- Support Movable Directional Lights
- Max Movable Point Lights = 0
禁用所有这些后,打开ConsoleVariables.ini并添加r.ForceDebugViewModes = 2, 这会进一步减少排列的数量。这将在编辑器中禁用Buffer Visualization(用来显示GBuffer通道)但这是值得的。
所有这些操作使着色器排列数量从8809减少到3708,高达58%的降幅。
加快编译速度
你可以将CFLAG_StandardOptimization添加到编译环境中,以确保D3D编译器不会花时间优化着色器。 将设置应用到所有着色器最简单的方法是打开ShaderCompiler.cpp并向下滚动到GlobalBeginCompileShader函数。 然后只需添加一行代码:Input.Environment.CompilerFlags.Add(CFLAG_StandardOptimization);! 如果你尝试分析性能的话,那么请不要这样做,因为优化着色器很重要的。 注意:这与r.Shaders.Optimize = 0不同,后者是关于调试信息而不是代码优化。
仅重新编译已更改的着色器
最后,我们将研究重新编译特定着色器的技巧。 Unreal有一个不公开的控制台命令recompileshaders可以使用几个不同的参数。 请注意,这仅适用于.usf文件,因为.ush文件是仅包含在.usf文件中的标头。
recompileshaders changed查询FShaderType和FShaderPipelineType以返回所有过时的着色器、工厂和着色器管线。 如果发现任何过时(由于源文件更改),则重新编译。 注释表明可以使用此命令可重新编译全局着色器。 在实践中我发现因为它最终会重新编译所有受影响的全局着色器,修改像ShadingModels.ush这样的文件最终会导致可观的数量,3-5分钟的编译。
recompileshaders global获取全局着色器表,清除它并强制重新编译所有全局着色器。修改像ShadingModels.ush这样的文件需要1-2分钟编译。如果你知道你正在修改的着色器是全局的而不是材质着色器,那么也许有用。
recompileshaders material<MaterialName>将重新编译找到该名称的第一个材质。不应包括路径信息。这是通过在材质上调用PreEditChange/PostEditChange来实现的,因此它与在材质编辑器中点击”Apply”按钮没有什么不同。
recompileshaders all都将重新编译所有内容。这就像修改所有文件然后重新启动编辑器一样。在更改结束时可能有用,以确保没有排列被破坏。可以与Project Settings > Rendering Overrides (Local) > Force All Shader Permutation Support,以确保在每个#if变体下确实编译了代码。
recompileshaders <path>将尝试通过shadertype或Shader Platform类型重新编译着色器。这允许我们指定特定的着色器文件路径,例如recompileshaders /Engine/Private/BasePassPixelShader.usf。
重要说明:该命令大小写敏感,无论您使用哪个命令,都会运行一段时间;错误的使用情况(不正确的文件路径或文件名)会输出错误信息到控制台而不会重新编译着色器。
使用此命令后,打开新的地图和材质(“材质编辑器”预览窗口)可能会不稳定,因此如果要打开其他材质或场景,请确保您的测试环境已设置好,或者不介意崩溃。
它也只适用于场景中被显式使用的代码段。如果您修改仅适用于亮度半透明的部分但在场景中没有半透明对象,则热重载将不会检查该代码且不会有编译错误。
抛开这些警告,这些命令非常有用,因为它运行只需要3-5秒,而不是3-5分钟!我最常运行的两个命令是
recompileshaders /Engine/Private/BasePassPixelShaders.usf和
recompileshaders /Engine/Private/DeferredLightPixelShaders.usf,这两个命令涵盖了你可以在基本通道中做的大多数更改。
常见的#if定义
下面是在延迟着色管道中找到的一些常见的预处理器定义。 我根据C++ 代码触发它们的方式列出了它们的预期含义。 这将有助于你确定延迟管线的哪些部分适用于你的代码,因为你可以根据此列表检查预处理器定义,以查看它被转换成UE4的哪些设置。
- #if NON_DIRECTIONAL_DIRECT_LIGHTING
这可以在DeferredLightingCommon.ush中找到,但只在ForwardLightingCommon.ush中被定义为#define NON_DIRECTIONAL_DIRECT_LIGHTING(TRANSLUCENCY_LIGHTING_VOLUMETRIC_NONDIRECTIONAL || TRANSLUCENCY_LIGHTING_VOLUMETRIC_PERVERTEX_NONDIRECTIONAL) - #if SUPPORT_CONTACT_SHADOWS
支持Unreal的Contact Shadows功能。 - #if REFERENCE_QUALITY
在DeferredLightingCommon.ush的顶部定义为0 - 可能是用于电影渲染? - #if ALLOW_STATIC_LIGHTING
如果r.AllowStaticLighting控制台变量设置为1,则值为true。这与静态照明支持的 Project Settings > Rendering 匹配。 - #if USE_DEVELOPMENT_SHADERS
如果COMPILE_SHADERS_FOR_DEVELOPMENT为真(且平台支持),则为真。如果设置了r.CompileShadersForDevelopment,则COMPILE_SHADERS_FOR_DEVELOPMENT为true。 - #if TRANSLUCENT_SELF_SHADOWING
是为使用FSelfShadowedTranslucencyPolicy渲染的对象定义的。我相信这是针对Lit Translucency的支持。 - #if SIMPLE_FORWARD_DIRECTIONAL_LIGHT和
#if SIMPLE_FORWARD_SHADING
似乎是在光照贴图渲染期间为静止定向光设置的。 - #if FORWARD_SHADING
当r.ForwardShading设置为1时,被设置。
综述
我们了解到,我们可以通过更改一些项目设置来减少着色器排列的总数(即使只是暂时的),这样可以更快地完全重新编译。 使用未列出的recompileshader控制台命令可以避免所有着色器重新编译,但下一个编辑器启动仍将需要完全重新编译。 我们还了解了一些常见预处理器定义的条件,使我们能够更好地了解哪些代码与我们的兴趣相关。
下章
终于我们要进入教程章节了! 下一篇文章将介绍修改现有着色器管道以添加新的着色模型,该模型在遵循PBR规则的同时提供类似卡通的效果。 这篇文章的技术内容主要来自FelixK的开发博客,但更新至4.18+版本,并对我们正在改变的内容进行了更多解释 - 特别是现在我们知道延时通道的工作原理了!