Often it is convenient to keep most of a piece of shader 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.
In Unity this can be achieved by adding a #pragma multi_compile
or #pragma shader_feature
directive to a shader snippet. This works in surface shaders too.
At runtime, the appropriate shader variant is picked up from the Material keywords (Material.EnableKeyword and DisableKeyword) or global shader keywords (Shader.EnableKeyword and DisableKeyword).
A directive like:
#pragma multi_compile FANCY_STUFF_OFF FANCY_STUFF_ON
Will produce two shader variants, one with FANCY_STUFF_OFF
defined, and another with FANCY_STUFF_ON
. At runtime, one of them will be activated based on the Material or global shader keywords. If neither of these two keywords are enabled then the first one (“off”) will be used.
There can be more than two keywords on a multi_compile line, for example this will produce four shader variants:
#pragma multi_compile SIMPLE_SHADING BETTER_SHADING GOOD_SHADING BEST_SHADING
When any of the names are all underscores, then a shader variant will be produced, with no preprocessor macro defined. This is commonly used for shaders features, to avoid using up two keywords (see notes on keywork limit below). For example, the directive below will produce two shader variants; first one with nothing defined, and second one with FOO_ON
defined:
#pragma multi_compile __ FOO_ON
#pragma shader_feature
is very similar to #pragma multi_compile
, the only difference is that unused variants of shader_feature shaders will not be included into game build. So shader_feature makes most sense for keywords that will be set on the materials, while multi_compile for keywords that will be set from code globally.
Additionally, it has a shorthand notation with just one keyword:
#pragma shader_feature FANCY_STUFF
Which is just a shortcut for #pragma shader_feature _ FANCY_STUFF
, i.e. it expands into two shader variants (first one without the define; second one with it).
Several multi_compile lines can be provided, and the resulting shader will be compiled for all possible combinations of the lines:
#pragma multi_compile A B C
#pragma multi_compile D E
This would produce three variants for first line, and two for the second line, or in total six shader variants (A+D, B+D, C+D, A+E, B+E, C+E).
It’s easiest to 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” with two options each produces 1024 shader variants in total!
When using shader variants, you should bear in mind that there is a limit of 128 keywords in Unity and a few of these are used internally and therefore subtract from the limit. Also, the keywords are enabled globally throughout a particular Unity project so you should be careful not to exceed the limit when multiple keywords are defined in several different shaders.
There are several “shortcut” notations for compiling multiple shader variants; they are mostly to deal with different light, shadow and lightmap types in Unity. See rendering pipeline for details.
multi_compile_fwdbase
compiles all variants needed by ForwardBase
(forward rendering base) pass type. The variants deal with different lightmap types and main directional light having shadows on or off.multi_compile_fwdadd
compiles variants for ForwardAdd
(forward rendering additive) pass type. This compiles variants to handle directional, spot or point light types, and their variants with cookie textures.multi_compile_fwdadd_fullshadows
- same as above, but also includes ability for the lights to have realtime shadows.multi_compile_fog
expands to several variants to handle different fog types (off/linear/exp/exp2).Most of the built-in shortcuts result in quite many shader variants. It is possible to skip compiling some of them if you know they are not neeeded, by using #pragma skip_variants
. For example:
#pragma multi_compile_fwdadd
// will make all variants containing
// "POINT" or "POINT_COOKIE" be skipped
#pragma skip_variants POINT POINT_COOKIE