You can write shader snippets that share common code, but have different functionality when a given keyword is enabled or disabled. When Unity compiles these shader snippets, it creates separate shader programs for the different combinations of enabled and disabled keywords. These individual shader programs are called shader variants.
Shader variants can be useful for project workflow reasons; you can assign the same shader to different Materials, but configure the keywords differently for each. This means that you write and maintain your shader code in a single place, and have fewer shader assets in your project. You can also use shader variants to change shader behaviour at runtime, by enabling or disabling keywords.
Shaders with a large number of variants are called “mega shaders” or “uber shaders”. Unity’s Standard Shader is an example of such a shader.
To create shader variants, you add one of the following directives to your shader snippet:
#pragma multi_compile
#pragma multi_compile_local
#pragma shader_feature
#pragma shader_feature_local
You can use these directives in regular shaders (including surface shaders) and compute shaders.
Unity then compiles the same shader code multiple times with different preprocessor directives.
To enable and disable shader keywords, use the following APIs:
CommandBuffer
to enable a global keywordCommandBuffer
to disable a global keywordWhen you enable or disable a keyword, Unity uses the appropriate variant.
You can prevent shader variants from being included in your build, if you know that they are not required. This can reduce build times and file size.
To do this, use the following APIs:
For more information on this subject, see the Unity blog post Stripping scriptable shader variants .
Example directive:
#pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON
This example directive produces two shader variants: one with FANCY_STUFF_OFF
defined, and another with FANCY_STUFF_ON
. At run time, Unity activates one of them based on the Material or global shader keywords. If neither of these two keywords are enabled, then Unity uses the first one (in this example, FANCY_STUFF_OFF
).
You can add more than two keywords on a multi_compile line. For example:
#pragma multi_compile SIMPLE_SHADING BETTER_SHADING GOOD_SHADING BEST_SHADING
This example directive produces four shader variants: SIMPLE_SHADING
, BETTER_SHADING
, GOOD_SHADING
, and BEST_SHADING
.
To produce a shader variant with no preprocessor macro defined, add a name that is just underscores (__
). This is a common technique to avoid using up two keywords, because there is a limit on how many you can use in a project (see later section on Keyword limits). For example:
#pragma multi_compile __ FOO_ON
This directive produces two shader variants: one with nothing defined (__
), and one with FOO_ON
defined.
shader_feature
is very similar to multi_compile
. The only difference is that Unity does not include unused variants of shader_feature
shaders in the final build. For this reason, you should use shader_feature
for keywords that are set from the Materials, while multi_compile
is better for keywords that are set from code globally.
Additionally, there is a shorthand notation with just one keyword:
#pragma shader_feature FANCY_STUFF
Which is just a shortcut for #pragma shader_feature _ FANCY_STUFF
. It expands into two shader variants (first one without the define; second one with it).
If you provide multi_compile
lines, Unity compiles the resulting shader for all possible combinations of the lines. For example:
#pragma multi_compile A B C
#pragma multi_compile D E
This produces three variants for the first line, and two for the second line. In total, it produces total six shader variants (A+D, B+D, C+D, A+E, B+E, C+E).
Think of each multi_compile
line as controlling a single shader “feature”. Keep in mind that the total number of shader variants grows really fast this way. For example, ten multi_compile
features, each with two options, produces 1024 shader variants in total.
When using Shader variants, there is a limit of 384 keywords in Unity, and Unity uses around 60 of them internally (therefore lowering the available limit). The keywords are enabled globally across a Unity project, so be careful not to exceed the limit when you define multiple keywords in several different Shaders.
The main disadvantage of shader_feature and multi_compile is that all keywords defined in them contribute towards Unity’s global keyword count limit (384 global keywords, plus 64 local keywords). To avoid this issue, you can use different shader variant directives: shader_feature_local and multi_compile_local.
Local directives keep defined keywords under them specific to that shader, rather than applying them to the whole Project. For this reason, you should use local keywords instead of global keywords, unless you are planning to enable those particular keywords through the global API.
You might see a change in performance when you start using local keywords, but the difference depends on how your Project is set up. The total number of local and global keywords per shader affects performance: in an ideal set-up, use more local keywords and fewer global keywords, to reduce the total keyword count per shader.
If there are global and local keywords with the same name, Unity prioritises the local keyword.
You cannot use local keywords with APIs that make global keyword changes (such as Shader.EnableKeyword or CommandBuffer.EnableShaderKeyword).
There is a maximum of 64 unique local keywords per shader.
If a Material has a local keyword enabled, and its shader changes to one that is no longer declared, Unity creates a new global keyword.
#pragma multi_compile_local __ FOO_ON
This directive produces two shader variants: one with nothing defined (__
), and one with FOO_ON
defined as a local keyword.
The process for enabling local keywords is the same as enabling global keywords:
public Material mat;
Private void Start()
{
mat.EnableKeyword("FOO_ON");
}
There are several “shortcut” notations for compiling multiple shader variants. These are mostly to deal with different light, shadow and lightmap types in Unity. See documentation on the rendering pipeline for details.
multi_compile_fwdbase
compiles all variants needed by PassType.ForwardBase. The variants deal with different lightmap types, and the main Directional Light’s shadows being enabled or disabled.multi_compile_fwdadd
compiles variants for PassType.ForwardAdd. This compiles variants to handle Directional, Spot or Point Light types and their variants with cookie Textures.multi_compile_fwdadd_fullshadows
- same as multi_compile_fwdadd
, but also includes ability for the lights to have real-time shadows.multi_compile_fog
expands to several variants to handle different fog types (off/linear/exp/exp2).Most of the built-in shortcuts produce many shader variants. if you know the project doesn’t need them, you can use #pragma skip_variants
to skip compiling some of them. For example:
#pragma multi_compile_fwdadd
#pragma skip_variants POINT POINT_COOKIE
This directive skips all variants containing POINT
or POINT_COOKIE
.
At runtime, Unity examines the capabilities of the GPU and determines which graphics tier it corresponds to. In the Built-in Render Pipeline, you can automatically create a set of shader variants for each graphics tier; to do this, usethe #pragma hardware_tier_variants
directive.
This feature is compatible with the Built-in Render Pipeline only. It is not compatible with the Universal Render Pipeline (URP), the High Definition Render Pipeline (HDRP), or custom Scriptable Render Pipelines.
To enable this feature, add #pragma hardware_tier_variants renderer
, where renderer
is a valid rendering platform, like this:
#pragma hardware_tier_variants gles3
Unity generates three shader variants for each shader, in addition to any other keywords. Each generated variant has one of the following defines, which correspond to the same numbered values of the GraphicsTier enum:
UNITY_HARDWARE_TIER1
UNITY_HARDWARE_TIER2
UNITY_HARDWARE_TIER3
You can use these to write conditional fallbacks or extra features for lower or higher-end hardware.
When Unity first loads your application, it detects the GraphicsTier
and stores the result in Graphics.activeTier. To override the value of Graphics.activeTier
, set it directly. Note that you must do this before Unity loads any shaders that you want to vary. A good place to set this value is in a pre-load Scene before you load your main Scene.
To help keep the impact of these variants as small as possible, Unity only loads one set of shaders in the player. Shaders that are identical (for example, if you only write a specialised version for TIER1
, but all others are the same) do not take up any extra space on disk.
To test the tiers in the Unity Editor, navigate to to Edit > Graphics tier and choose the tier that you want the Unity Editor to use.
Note that graphics tiers are not related to Quality settings. They are in addition to this setting.
In the Built-in Render Pipeline, you can use the EditorGraphicsSettings.SetShaderSettingsForPlatform API to override Unity’s internal #defines for a given BuildTarget and GraphicsTier.
This feature is compatible with the Built-in Render Pipeline only. It is not compatible with the Universal Render Pipeline (URP), the High Definition Render Pipeline (HDRP), or custom Scriptable Render Pipelines.
Note that if you provide different TierSettings
values for the different GraphicsTier
of a given BuildTarget
, Unity generates tier variants for the shader even if you do not add #pragma hardware_tier_variants
to your shader code.