Only before an if, not an #if.
UNITY_BRANCH is for flow control, that is to say “make this conditional work like you expect in a programming language” where only the code that’s needed is run for each pixel. The counter to UNITY_BRANCH
is UNITY_FLATTEN
which makes the shader run both sides of the conditional all of the time and choose a result after doing the work. Those two macros are really just [branch] and [flatten] for HLSL, and do nothing on other platforms.
Examples:
UNITY_BRANCH
if (screenPos.x < 0.5) {
// run expensive code A
} else {
// run expensive code B
}
- On one side of the screen only the code for A will run, and on the other only code B will run.
UNITY_FLATTEN
if (screenPos.x < 0.5) {
// run expensive code A
} else {
// run expensive code B
}
- Both sides of the screen will run both A and B all of the time, effectively making the shader twice as expensive.
Reasons for why you would do one over the other come down to some fairly low level GPU stuff. Most of the time you’ll want to branch, but for especially cheap calculations the branch itself might be more expensive than just doing both. On modern AMD “GCN” GPUs (any from the last 6 years), a dynamic branch costs 6 instructions in itself, so choosing between two values with a dynamic branch could potentially be more expensive than lerping between the two! Modern shader compilers are pretty good about knowing which option to use, and OpenGL doesn’t even have a way to force one option over the other and it’s assumed it’ll make the correct choice for you (basically it’ll always branch) and if you want to flatten it out then write it out flattened yourself. Generally speaking you don’t need to use UNITY_BRANCH at all.
Now #if
does something completely different. This isn’t a shader compiler thing, but a shader preprocessor conditional directive. In lay terms this means before compiling the shader, include or remove this code. Many programming languages have this, including c# where you can do stuff like #if UNITY_EDITOR
to only include and compile a block of code if it’s running in the editor. UNITY_BRANCH does nothing for #if, infact it may just end up confusing the compiler. I would expect warnings or even errors from the shader compiler.
Example:
fixed4 frag(v2f i) : SV_Target {
#if defined(SHADER_API_MOBILE)
// run cheap mobile code
#else
// run expensive desktop code
#endif
}
If you build the project for mobile, only the code for mobile will be included in the shaders, the desktop code won’t even exist. If you build the project for PC or in the editor only the expensive code will be included in the shader. Effectively you’ll have two completely different shaders that look like:
fixed4 frag(v2f i) : SV_Target {
// run cheap mobile code
}
and
fixed4 frag(v2f i) : SV_Target {
// run expensive desktop code
}
If you use an #if with a matching #pragma multi_compile then it’s effectively making multiple shaders that can be swapped between by setting keywords on the material. Unlike an if, this is something that will be enabled or disabled for the entire draw call and cannot be changed per pixel.
Example:
#pragma multi_compile _ MYDEFINE
fixed4 frag(v2f i) : SV_Target {
#if defined(MYDEFINE)
// do code A
#else
// do code B
#endif
}
That will create two completely separate shaders with either code A or code B and you can enabled code A by using myMaterial.EnableKeyword(“MYDEFINE”).
In the case of stuff like UNITY_PASS_SHADOWCASTER
, that’s a #define that’s placed in the generated shader code for the shadowcaster pass of surface shaders, and all other passes will completely omit that code as they won’t have that #define.
edit - some other random notes:
On older mobile devices which only support OpenGL ES 2.0, they always “flatten” as they do not support dynamic branching at all. This was true for early desktop hardware as well, which is why there’s a stigma against using if conditions in shaders.
--------------------------------------------
There is one case where you might want to use UNITY_BRANCH
before an #if, and that’s when there’s an if after that #if, like:
UNITY_BRANCH
#if defined(TEST_GREATER)
if (x > 0.5) {
#else
if (x < 0.5) {
#endif
// do code
}
which will generate a shader with either:
UNITY_BRANCH
if (x > 0.5) {
// do code
}
or:
UNITY_BRANCH
if (x < 0.5) {
// do code
}
--------------------------------------------
A case where UNITY_FLATTEN is useful is because derivative values (how much a value changes from one pixel to the one next to it) can’t be within a dynamic branch. A simple example would be:
fixed4 color = fixed4(0,0,0,0);
UNITY_BRANCH
if (screenPos.x > 0.5) {
float2 uv = screenPos * 2.0;
color = tex2D(_MyTex, uv);
}
That’ll cause an error as tex2D uses the derivatives of the uvs to determine the mip map to display, but since the uv is only calculated in the if statement it can’t guarantee it can calculate the derivative. Using UNITY_FLATTEN would fix this, as would simply writing the above code like this:
fixed4 color = fixed4(0,0,0,0);
float2 uv = screenPos * 2.0;
if (screenPos.x > 0.5) {
color = tex2D(_MyTex, uv);
}
The bottom code has the benefit of not calling tex2D at all if its not needed, where using UNITY_FLATTEN still would, though this is kind of terrible example since likely UNITY_FLATTEN would end up faster on most modern hardware.