Branching, variants, and keywords
Conditionals in shaders
Sometimes, you want the same shaderA program that runs on the GPU. More info
See in Glossary to do different things under different circumstances. For example, you might want to configure different settings for different materials, define functionality for different hardware, or dynamically change the behavior of shaders at runtime. You might also want to avoid executing computationally expensive code when it’s not needed, such as texture reads, vertex inputs, interpolators, or loops.
You can use conditionals to define behavior that the GPU only executes under certain conditions.
Different types of conditionals
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 variantsA verion of a shader program that Unity generates according to a specific combination of shader keywords and their status. A Shader object can contain multiple shader variants. More info
See in Glossary: 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.
When to use which type of conditional
There is no “one size fits all” approach to conditionals in shaders, and you should consider the advantages and disadvantages of each approach for a given shader, in a given project.
Bear in mind the following information:
- If you know the conditions at compile time (for example, if you know that you are building for given hardware), then static branching is likely the best choice. Static branching code is simple to write and maintain, and it does not negatively impact build times, file sizes, or runtime performance. However, you cannot use it to execute code for different conditions at runtime.
- If you need to execute code for different conditions at runtime, you should consider the following options:
- Shader variants do not incur any GPU performance penalty. However, a large number of shader variants in a project can lead to significant problems: increased build times, file sizes, runtime memory usage, and loading times. Shader variants also introduce additional code complexity when manually preloading (“prewarming”) shaders.
- Dynamic branching incurs a GPU performance penalty, which might be small or large depending on the shader code and the hardware. However, it allows you to use conditionals at runtime without increasing the number of shader variants in your project.
- It’s possible to author your shader code so that it avoids branching or variants, and instead uses mathematical operations that return 0 or 1 to execute conditional code. This can lead to complex code that is difficult to maintain. Depending on the circumstances, it might result in only a very small performance improvement over dynamic branching, or no advantage at all.
In general, the best approach is to profile the performance of your application and carefully consider your decisions case-by-case. For example, if you can afford the slightly increased GPU cost, it might be best to use dynamic branching to achieve “good enough” GPU performance and reduce the risk of introducing more variants. However, if GPU performance is the primary concern for this shader and you have already accounted for the cost of additional variants, you might choose to use variants.
Branching, variants, and keywords