Hey fellow Shader Artists!
Not so long ago, I posted about Shader Graphs Properties , and in a similar fashion, I wanted to share some information about Keywords and Branching to save everyone a long read through the documentation.
As with that other post, I might be wrong and will correct and/or add more as I learn new things.
Please reply to this thread if you have any questions or information you want to share on the topic.
Purpose
Let’s begin with the purpose of Keywords: branching.
Keywords allow branching between different implementations (Shader Variants) to enable or disable a given behavior in a Shader. It can be used for technical optimization, such as implementing a simpler version of an effect in a given situation (hardware, distance from Camera, etc.) or artistic control such as toggling between different rendering modes (night vision and the likes).
From the docs:
To use conditionals in your shader, you can use the following approaches:
Static branching: the shader compiler evaluates conditional code at compile time.
Dynamic branching: the GPU evaluates conditional code at runtime.
Shader variants: Unity uses static branching to compile the shader source code into multiple shader programs. Unity then uses the shader program that matches the conditions at runtime.
Static Branching
Although not available as a built-in feature, it is possible to use Static Branching using a Custom Function Node. More on that further down this post.
You can read more about static branching in the docs.
Dynamic Branching
In hand coded Shaders, Dynamic Branching can be tied to either Uniforms or Keywords.
In Shader Graph, it can only be done using a Branch Node with either a Boolean Node or Integer and Comparison Nodes.
Although not available as a Keyword declaration type, it’s possible to use Dynamic Branching with Keywords using a Custom Function Node. More on that further down this post.
You can read more about dynamic branching in the docs.
Shader Variants
From the docs:
Shader variants, also sometimes called shader permutations, are one way of introducing conditional behavior into shader code.
Unity compiles shader source files into shader programs. Each compiled shader program has one or more variants: different versions of the shader program for different conditions. At runtime, Unity uses the variant that matches the current requirements. You configure variants using shader keywords.
For a general overview of conditionals in shader code and when to use which technique, see Conditionals in shader code. For more information on how Unity loads shader variants, see Shader loading.
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.
Declaration Types
From the docs:
Shader Feature
“shader feature” declares a set of keywords for use with shader variants, and also instructs the compiler to compile variants where none of these keywords are enabled.
Unity examines the state of your project at build time, and only compiles variants for keywords that are in use. A keyword is in use if a material that is included in the build has that keyword enabled.
Multi-Compile
“multi compile” declares a set of keywords for use with shader variants.
Unity compiles shader variants for all keywords in the set.
Predefined
Unity uses predefined sets of shader keywords to generate shader variants that enable common functionality.
Unity adds the following sets of shader variant keywords at compile time:
By default, Unity adds this set of keywords to all graphics shader programs: STEREO_INSTANCING_ON, STEREO_MULTIVIEW_ON, STEREO_CUBEMAP_RENDER_ON, UNITY_SINGLE_PASS_STEREO. You can strip these keywords using an Editor script. For more information, see Shader variant stripping.
By default, Unity adds this set of keywords to the Standard Shader: LIGHTMAP_ON, DIRLIGHTMAP_COMBINED, DYNAMICLIGHTMAP_ON, LIGHTMAP_SHADOW_MIXING, SHADOWS_SHADOWMASK. You can strip these keywords using the Graphics settings window.
In the Built-in Render Pipeline, if your project uses tier settings that differ from each other, Unity adds this set of keywords to all graphics shaders: UNITY_HARDWARE_TIER1, UNITY_HARDWARE_TIER2, UNITY_HARDWARE_TIER3. For more information, see Graphics tiers: Graphics tiers and shader variants.
Scope
When you declare a keyword or keywords set, you choose whether it is local or global. This sets whether you can override the state of this keyword at runtime using a global shader keyword.
Local
When a Keyword is set to Local, it can only be set locally with Material.SetKeyword(), Material.EnableKeyword() and Material.DisableKeyword().
Global (Overridable)
When a Keyword is set to Global, it can be set locally but also overridden globally with Shader.SetKeyword(), Shader.EnableKeyword() and Shader.DisableKeyword().
Type
Boolean
Boolean Keywords are fairly straightforward as they have only two states: enabled and disabled.
It’s important to note that a locally disabled Boolean Keyword can be overridden globally to be enabled, but a locally enabled one cannot be globally disabled.
It can be represented with the logical expression:
IS_ON = IS_LOCALLY_ON OR IS_GLOBALLY_ON
Enum
Enum Keywords present a few caveats to be aware of.
Not mutually exclusive
Although they’re meant to represent enums, Enum Keywords are not enums but Boolean Sets.
Which means they are not forced into mutually exclusive behavior. Enabling one does not disable the others and more than one can be enabled at a time.
When more than a Keyword in a Set is enabled, the first enabled one is considered for branching.
It is good practice to keep track of their state when setting it from C# so that we can disable the current keyword as we enable another.
For example, say we have a set of three keywords (_COLOR_RED _COLOR_GREEN _COLOR_BLUE), red being the default, we need to disable the first keyword to enable another, or else it’ll have no visible effect.
Shader.DisableKeyword(_COLOR_RED);
Shader.EnableKeyword(_COLOR_BLUE);
All off state
In hand coded Shaders, Keyword Sets can be declared to feature an optional “all off” state with an extra _
(underscore) preceding the keywords.
#pragma multi_compile _ _COLOR_RED _COLOR_GREEN _COLOR_BLUE
In Shader Graph, to prevent extra Variants to be generated, this option is not available.
If all Keywords in a Set are disabled, the first Variant is picked.
Global Overriding
The two aforementioned limitations can make it somewhat confusing when overriding an Enum globally.
Like with Boolean Keywords, a locally enabled Keyword cannot be overridden as disabled globally.
As an example, let’s consider a Keyword Set _A, _B and _C.
When locally set to _B, the underlying boolean states are:
_A = false
_B = true
_C = false
Trying to overriding it with a Global value of _A will result in:
_A = true
_B = true
_C = false
In which case, it’ll appear to work since _A will be the first enabled option.
Trying to overriding it with a Global value of _C will result in:
_A = false
_B = true
_C = true
In which case, it’ll appear not to work since _B will remain the first enabled option.
Material Property
A Keyword can be associated with a Material Property.
This allows its value to be set from the Material Inspector.
Note: if a Keyword isn’t given a Material Property, its default value won’t be serialized.
More tech details in the docs.
Advanced Branching with a Custom Function Node
Static Branching
Static Branching is useful when you want to branch between different implementations based on certain conditions at compile time not already available as Predefined Keywords, such as platform or custom settings.
Custom Function Nodes allow for static branching based on preprocessor directives or built-in macros.
Simply add a Custom Function Node with a Boolean output, used in a Branch Predicate input.
The Branch node’s varying connector types make it easy to use it with either floats or vectors.
The Custom Function Node can also be nested in a Subgraph to reuse it across several Graphs.
Preprocessor Directives
Custom preprocessor directives can be defined in the Custom Function HLSL file.
#define _DEBUG
Preprocessor directives can also be gathered in a Shader Include File (HLSL), and referenced in the Custom Function HLSL file.
#include "Assets/local path to include/Includes.hlsl"
The custom function can then return 1 or 0 based on whether the given name is defined.
void IsDebug_float(out float result)
{
#if defined (_DEBUG)
result = 1;
#else
result = 0;
#endif
}
Built-in Macros
Built-in Macros allow branching based on compile time built-in symbols, such as target platforms…
void IsMobile_float(out float result)
{
#if defined (SHADER_API_MOBILE)
result = 1;
#else
result = 0;
#endif
}
Dynamic Branching tied to Keywords
While Dynamic Branching can be based on Uniforms, it may at times be more convenient to use existing Keywords.
For example, some shaders may already use a Keyword to toggle between Variants at runtime, but others may just get away with Dynamic Branching to limit the number of Variants.
To simplify the C# code required to implement the toggle, and only use Shader.SetKeyword(), you may implement Dynamic Branching based on Keywords.
Simply add a Custom Function Node with one or several Boolean outputs, used with Branch Nodes.
For a Boolean Keyword, the declaration looks like this:
#pragma dynamic_branch _ _SHINY
Followed by the function that’ll return the state.
void IsShiny_float(out float result)
{
if (_SHINY)
result = 1;
else
result = 0;
}
For an Enum Keyword, the declaration looks like this. (Note the extra _ preceding keywords).
#pragma dynamic_branch _ _ENUM_RED _ENUM_GREEN _ENUM_BLUE
Followed by the function that’ll return the state of every Keyword in the set.
void Color_float(out float isRed, out float isGreen, out float isBlue)
{
if (_ENUM_RED)
isRed = 1;
else
isRed = 0;
if (_ENUM_GREEN)
isGreen = 1;
else
isGreen = 0;
if (_ENUM_BLUE)
isBlue = 1;
else
isBlue = 0;
}
More in the docs on declaring Keywords.
UX Improvements (2023.3)
Shader Graph Keywords
We have reworked the UX to make the declaration and property settings easier to work with.
-
The Scope dropdown (Local/Global) was replaced with a Is Overridable checkbox to better reflect the effect.
-
Exposed was renamed to Generate Material Property.
-
When set to Predefined, options with no effect are grayed out.
I hope this helps. As always, your feedback is welcome.