Compile-time switches
A GPU runs the same shader code for many pixels at once, and branches in that code aren't free. Unity's answer is shader variants: the shader is compiled several times, once for each combination of features, and each material uses the variant with only the code it needs.
In Shader Script, you get variants by writing ordinary if statements on values that are known at compile time. The branches that don't apply to a variant are removed from it, so checking such a value costs nothing when the shader runs.
if (IsAlphaTestEnabled)
OUT.alphaClipThreshold = Cutoff;
What's known at compile time
| Value | What it tells |
|---|---|
A [KeywordSwitch] property |
The value the material selected (see below). |
A [TextureIsSet] property |
Whether a texture is assigned to the material. |
IsUrp, IsHdrp |
Which render pipeline the shader is compiled for. |
CurrentPass |
Which pass the code is compiled for. See Passes. |
IsTransparent |
Whether the material is transparent. |
IsAlphaTestEnabled |
Whether alpha clipping is on. |
IsAlphaModulate |
Whether the material uses multiply blending. |
IsPremultiplyAlpha |
Whether the shader multiplies the color by alpha itself (a transparent URP material with Preserve Specular Lighting). |
IsEmissionEnabled |
Lit shaders: whether emission is on. |
IsSpecularWorkflow, IsMetallicWorkflow |
Lit shaders, URP: the workflow the material uses. |
IsReceiveShadowsDisabled |
Lit shaders, URP: whether the material doesn't receive shadows. |
| Constants and enum values | Always: const fields and enum values are replaced by their values. |
The settings flags, like IsTransparent, follow the material's surface settings, whether the shader fixes them with an attribute or the material chooses them in the Inspector. See Surface settings.
KeywordSwitch
[KeywordSwitch] turns a material property into a compile-time switch. On an enum property, each enum value compiles into its own variant; on a float or int property, shown as a checkbox, on and off do.
public enum NoiseQuality { Low, Medium, High }
[KeywordSwitch] public NoiseQuality Quality { get; } = NoiseQuality.Medium;
[KeywordSwitch] public float UseDetail { get; } = 0;
public override void Frag(FragmentInput IN, ref UnlitFragmentOutput OUT)
{
float2 uv = IN.uv0;
float noise = ShaderLib.SimpleNoise(uv, 10);
// Only the branch for the material's Quality is compiled into its variant.
if (Quality == NoiseQuality.High)
noise = ShaderLib.GradientNoise(uv, 40) * 0.5f + noise * 0.5f;
if (UseDetail > 0.5f)
noise *= ShaderLib.Voronoi(uv, 0, 20).x;
OUT.albedo = (half3)new float3(noise, noise, noise);
}
In shader code, a [KeywordSwitch] property is a constant: an enum value, or 0 and 1 for a checkbox. Besides comparing it, you can use it in expressions like any other number.
Compare it with [Toggle], which also shows a checkbox but leaves the value an ordinary runtime value: both branches of the if are then compiled and evaluated. Use [KeywordSwitch] for features that are expensive or that most materials leave off, and [Toggle] for cheap choices, where more variants would cost more than the branch.
Keywords and builds
Each switch value corresponds to a material keyword:
- an enum value:
_<PROPERTY>_<VALUE>, for example_QUALITY_HIGH; - a checkbox:
_<PROPERTY>_ON, for example_USEDETAIL_ON.
The Material Inspector keeps the keywords in sync with the property. If you change the property from a script, also enable the matching keyword and disable the others with Material.EnableKeyword and Material.DisableKeyword.
When you build a player, Unity includes only the variants that the project's materials use. If a script switches a material at runtime to a value that no material in the build uses, that variant is missing. Make sure some material uses every value you need, or include the variants with a shader variant collection.
Pipeline-specific code
IsUrp and IsHdrp let you write code that differs between the render pipelines. The branch for the other pipeline is removed from the compiled shader, but it's still compiled for both pipelines, so it can only use what exists on both: pipeline-specific built-in variables, such as the URP-only matrices, can't be used even inside such a branch.
// HDRP uses physical light units, so emission needs a much higher intensity there.
OUT.emission = IsHdrp ? EmissionColor.rgb * 1000 : EmissionColor.rgb;