Built-in shader variables
GLSL Shader programs

Making multiple shader program variants

Often it is convenient to keep most of a piece of shaderA small script that contains the mathematical calculations and algorithms for calculating the Color of each pixel rendered, based on the lighting input and the Material configuration. More info
See in Glossary
code fixed but also allow slightly different shader “variants” to be produced. This is commonly called “mega shaders” or “uber shaders”, and is achieved by compiling the shader code multiple times with different preprocessor directives for each case.

To achieve this in Unity, you can add a #pragma multi_compile or #pragma shader_feature directive to a shader snippet. This also works in surface shadersUnity’s code generation approach that makes it much easier to write lit shaders than using low level vertex/pixel shader programs. More info
See in Glossary
.

At run time, Unity picks up the appropriate shader variant from the Material keywords (Material.EnableKeyword and Shader.DisableKeyword) or global shader keywords (Shader.EnableKeyword and Shader.DisableKeyword).

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.

Shader hardware variants

Shader hardward variants allow you to provide a specially optimised set of variants for different levels of hardware capability. You can create simplified shaders that can run efficiently on both high-end and low-end hardware within a single target platform (such as OpenGL ES).

To enable the generation of shader hardware variants, add #pragma hardware_tier_variants renderer, where renderer is one of the available renderering platforms for shader program pragmas. With this #pragma, Unity generates three shader variants for each shader, regardless of any other keywords. Each variant has one of the following defined:

UNITY_HARDWARE_TIER1
UNITY_HARDWARE_TIER2
UNITY_HARDWARE_TIER3

Use these to write conditional fallbacks or extra features for lower or higher-end hardware. In the Unity Editor, you can test any of the tiers in the Graphics Emulation menu, which allows you to change between each of the tiers.

To help keep the impact of these variants as small as possible, Unity only ever loads one set of shaders in the player. Shaders that are identical (for example, if you only write a specialised version for TIER1 and all others are the same) do not take up any extra space on disk.

At load time, Unity examines the GPU that it is using and auto-detects a tier value. It defaults to the highest tier if it can’t auto-detect the GPU. You can set Shader.globalShaderHardwareTier to override this tier value, but you must do this before Unity loads any shaders you want to vary. A good place to set this is in a pre-load Scene before you load your main Scene.

These shader hardware tiers are not related to the Quality settings of the player. They are detected from the relative capability of the GPU that the player runs on.

Platform shader settings

Apart from tweaking your shader code for different hardware tiers, you might want to tweak Unity’s internal #defines (for example, you might want to force cascaded shadowmaps on mobiles). For details on how to do this, see documentation on UnityEditor.Rendering.PlatformShaderSettings, which provides a list of currently supported features for overriding per-tier.

Use UnityEditor.Rendering.EditorGraphicsSettings.SetShaderSettingsForPlatform to tweak Platform Shader Settings per-platform per-tier.

If PlatformShaderSettings set to different tiers are not identical, then Unity generates tier variants for the shader, even if #pragma hardware_tier_variants is missing.

See also

Did you find this page useful? Please give it a rating:

Built-in shader variables
GLSL Shader programs