Version: 2020.2
Built-in shader variables
GLSL Shader programs

Shader variants and keywords

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.

Using shader variants and keywords

Creating shader variants

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.

Enabling and disabling shader keywords

To enable and disable shader keywords, use the following APIs:

When you enable or disable a keyword, Unity uses the appropriate variant.

How multi_compile works

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.

Difference between shader_feature and multi_compile

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).

Combining several multi_compile lines

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.

Keyword limits

When using Shader variants, there is a limit of 256 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.

Local keywords

The main disadvantage of shader_feature and multi_compile is that all keywords defined in them contribute towards Unity’s global keyword count limit (256 global keywords, plus 64 local keywords). To avoid this issue, you can use different shader variant directives: shader_feature_local and multi_compile_local.

  • shader_feature_local: similar to shader_feature, but enumerated keywords are local.
  • multi_compile_local: similar to multi_compile, but enumerated keywords are 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.

Limitations

  • 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.

Example

#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");
}

Built-in multi_compile shortcuts

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.

Graphics tiers and shader variants

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.

Per-platform shader define settings and graphics tier variants

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.

See also


  • [Multiple variants for compute shaders] added in 2020.1 NewIn20201
Built-in shader variables
GLSL Shader programs