Shader Permutations in Unreal Engine
- Abby Karnstein
- Nov 6, 2024
- 4 min read
Updated: Nov 15, 2024
Shader permutations are talked about quite a bit in AAA development, but where do they come from? What performance and development challenges can they present? And... what actually are they?
Shader permutations occur when a material has more than one use case, and thus we need to compile new bytecode to accomodate those changes. If I make a basic material with 3 independent features, I would have 8 permutations to compile:
Y Y Y
Y Y N
Y N N
N Y Y Each new feature added doubles the permutation count,
N Y N which adds up quickly. The total permutations in a shader
N N Y are 2^N, with N being the number of features.
Y N Y
N N N
When shaders are compiled in Unreal the source HLSL is 'flattened' down into bytecode in order to run on graphics hardware. If we only have materials made to be used in one way on one type of asset to run on one specific platform, then we only have one bytecode file to worry about- but, realistically, that's almost never going to be the case. As bytecode is different for each permutation, the GPU processes variants of the same material as it would an entirely new material, which can slow the time taken to render frames.
One upside for the players if not the developers is that Unreal compiles the majority of its shaders into an intermediate format (bytecode, in most cases) on cook rather than at runtime- if your permutations are getting out of hand, the biggest and most apparent issue will be a cook time to rival a prison sentence. Besides features and usages, materials in an engine as widely and diversely used as Unreal need to support many different target hardwares, all of which require different shader code to be compiled.
Permutations in Materials
The permutations created by the most developers on a project are in the material graph, so we'll start here. StaticSwitch, StaticSwitchParameters, and StaticComponentMaskParameters added by users all create permutations, with each combination of True and False creating, in essence, a new shader. This is because the final compile will only contain code used by the set branch, rather than for both outcomes. Interestingly, Ifs and Lerps will not generate permutations, but instead both inputs are calculated, leading to higher runtime costs but removing it from cooktime.
Unreal is very good at culling things that exist in the material graph, but aren't used, as shown below in red:

As you might be able to guess, property overrides will also generate permutations. Enabled subsurface, two-sided and changing the blend mode all requiring different shader code to compile.
In addition to this, the assets used with the material will generate permutations if different enough- the same material used on a static mesh, skeletal mesh, or Niagara system will have different compiled shader code for each. If you've ever had a warning of a 'missing Vertex Factory', the likely culprit is that shader code is not set to compile for the type of asset you're allocating the material to. “Automatically Set Usage In Editor” can be disabled in the material's detail panel, if usage on a particular asset type is prohibited. This is usually why technical artists set up material architecture in a sort of Lego brick fashion, wrapped in material functions: to make sure materials are creating fewer permutations by only having the functionality they need.
If you're an artist, and want to see where permutations might be caused in editor, a material briefly swapping to the default Unreal material is a good indicator a permutation was created:
Landscape Materials, as always, have different things going on. I won't pretend I can say anything new or profound about those eldritch creations, so you can find their specific permutations here.
Project-Wide Settings
AKA the easiest way to reduce your total permutations and reduce cook times. Turning off certain flags globally for a project can be an invaluable optimisation. All shader usage settings and supported platforms will be on by default, to allow users to go in and create whatever they want without restriction. It's likely that you aren't using all of them in your game, so finding which ones to disable could save you a lot of time if builds are taking too long. Maybe you want to play your game on Windows and Linux, but aren't interested in supporting Mac, Android or IOS. More of these flags exist in the Engine > Rendering tab within Project Settings, some specifically under 'Shader Permutation Reduction', which Tom Looman outlines on his website:
Stationary Skylight Stationary skylight requires permutations of the basepass shaders. You can disable this when never using a Stationary Skylight as the name implies.
Low-Quality Lightmap shader permutations The mobile renderer requires low-quality lightmaps, disabling this setting is not recommended for mobile titles using static lighting.”
PointLight WholeSceneShadows requires many vertex and geometry shader permutations for cubemap rendering.
Atmospheric Fog requires permutations of the basepass shaders. You can disable this if you don’t use the AtmosphericFog Actor which simulates atmospheric light scattering.
Sky Atmosphere requires extra samplers/textures to be bound to apply aerial perspective on transparent surfaces (and all surfaces on mobile via per-vertex evaluation).
Sky Atmosphere Affecting Height Fog The sky atmosphere component can light up the height fog but it requires extra samplers/textures to be bound to apply aerial perspective on transparent surfaces (and all surfaces on mobile via per-vertex evaluation). It requires SupportSkyAtmosphere to be true.”
In summary, permutations are basically unavoidable, but there are things we can do to lower their amount and thus lessen their impact. Besides project wide settings, architecture is important for bigger projects that will have potentially hundreds of materials and instances of them. While everything might be smooth running the game after initial caching, iterations on builds can suffer massively if permutations aren't managed well, so it's always a good thing to think about.
Comments