Version: 2020.3
Built-in shader variables
Shader compilation: targeting shader models and GPU features
How do you use documentation throughout your workflow? Share your experience with us by taking this survey.

Shader variants and keywords

You can write shaderA program that runs on the GPU. More info
See in Glossary
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 use one of the following pragma directives:

  • #pragma multi_compile
  • #pragma multi_compile_local
  • #pragma shader_feature
  • #pragma shader_feature_local

You can use these directives in all shader source files (including surface shadersA streamlined way of writing shaders for the Built-in Render Pipeline. More info
See in Glossary
) and compute shaders.

If a keyword affects only a single shader stage, you can add a suffix to these directives to reduce redundant shader compilation work. For more information, see Stage-specific keyword directives.

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.

Stripping shader variants from your build

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 .

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

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

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

Stage-specific keyword directives

When you create shader variants, the Unity Editor’s default behavior is to generate every stage of the shader program in every variant. For example, if your shader program contains a vertex stage and a fragment stage, Unity generates a vertex stage and a fragment stage for every keyword combination.

If a keyword does not affect all stages, this default behavior results in redundant work. For example, if a keyword affects only the fragment stage, the Editor generates an identical vertex stage for each variant. Unity identifies and removes duplicates afterwards, so this redundant work does not affect build sizes or runtime performance; however, if you have a lot of stages and/or variants, the time wasted during shader compilation can be significant.

To avoid this problem, you can use stage-specific keyword directives. These are suffixes that you apply to regular keyword directives. They tell the Editor which shader stage a given keyword affects, so it can skip the redundant work when building shaders for supported graphics APIs.

Supported graphics APIs

Unity does not fully support the use of stage-specific keyword directives with all graphics APIs.

  • When compiling shaders for OpenGL and Vulkan, the Editor automatically reverts any stage-specific keyword directives to regular keyword directives.
  • When compiling shaders for Metal, any keyword targeting vertex stages will also affect tessellation stages, and vice versa.

Using stage-specific keyword directives

The available suffixes are _vertex, _fragment, _hull, _domain, _geometry, and _raytracing. You apply the suffix at the end of a keyword directive, for example: multi_compile_fragment, or shader_feature_local_vertex. To target multiple shader stages, you use multiple stage-specific keyword directives declaring the same keywords.

Note: you are responsible for ensuring that the keywords are only used in the specified shader stages.

Built-in multi_compile shortcuts

In the Built-in Render Pipeline, ther are several “shortcut” notations for compiling multiple shader variants. These are mostly to deal with different light, shadow and lightmapA pre-rendered texture that contains the effects of light sources on static objects in the scene. Lightmaps are overlaid on top of scene geometry to create the effect of lighting. More info
See in Glossary
types in Unity. See documentation on rendering paths and shaders 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 graphics API, 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 SceneA Scene contains the environments and menus of your game. Think of each unique Scene file as a unique level. In each Scene, you place your environments, obstacles, and decorations, essentially designing and building your game in pieces. More info
See in Glossary
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.

Built-in shader variables
Shader compilation: targeting shader models and GPU features