Search Unity

  1. Looking for a job or to hire someone for a project? Check out the re-opened job forums.
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Resolved Boolean in Shader Graph

Discussion in 'Shader Graph' started by AssetArchive, Mar 29, 2020.

  1. AssetArchive

    AssetArchive

    Joined:
    Jan 11, 2014
    Posts:
    353
    Could anyone possibly show me how to use the boolean function in a shader graph, I've got no idea how to set it up correctly, a super simple example would do, like how to switch between texture 1 and texture 2 would be great ?

    Thanks

    EDIT : Managed to figure it out ! ;)

     
    Last edited: Mar 29, 2020
    Erveon and alexanderameye like this.
  2. alexanderameye

    alexanderameye

    Joined:
    Nov 27, 2013
    Posts:
    1,300
    Boolean goes into Predicate slot of the branch node

    Stick Tex1 into True, Tex2 into False, and you got your Output

    The boolean can be a boolean keyword, the output of a comparison node or any other node that outputs a boolean.
     
    AssetArchive likes this.
  3. AssetArchive

    AssetArchive

    Joined:
    Jan 11, 2014
    Posts:
    353
    Thanks, man, I couldn't figure it out from the documentation. I'm using it to switch features on and off in my shader, so I just experimented within the Shader Graph itself and was able to put it together fairly quickly.

    Regards
     
    alexanderameye likes this.
  4. Noogy

    Noogy

    Joined:
    May 18, 2014
    Posts:
    89
    To piggyback off this question, are there any optimizations made in the shader with the branch node? For example, if a series of computations were made in the true and false paths, will both be processed before boolean is checked? I'm using a couple branch nodes now and worried that each logic branch might be adding to the overhead. Thanks.
     
    Sinister-Design, _geo__ and _watcher_ like this.
  5. _watcher_

    _watcher_

    Joined:
    Nov 7, 2014
    Posts:
    231
    I'd like to know this too. Think i heard somewhere that Unity internally compiles into different shader variations (so if you got 2 branches you'd get 4 variations) from which based on props settings the appropriate is chosen during runtime (but hwat happens if you dynamically change the value during runtime, does it swap the shader variant?)? A lot of guessing from my side, so id be happy to know the actual answer too.
     
    Noogy likes this.
  6. Noogy

    Noogy

    Joined:
    May 18, 2014
    Posts:
    89
    Yeah, this was my assumption as well, but it's always nice to be able to toggle the boolean at runtime if possible. I do worry that I'm just adding a ton of overhead.
     
  7. _watcher_

    _watcher_

    Joined:
    Nov 7, 2014
    Posts:
    231
    I suppose it cant be that hard to test in Profiler. Just create graph and a subgraph with some complexity, and duplicate the subgraph few times to have something to measure perf of (A test). Then duplicate the A test (B test) (complexity(A)=complexity(B)). Measure the graph perf of the final graph (A+B). If its the same perf as A (or B), then you got no overhead. if its half the perf (or just a decrease, of significant decrease in perf [depends if you're GPU throttling], then both execute at once).
     
  8. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    151
    Stumbled upon this question and found the documentation saying that no matter what, both branches are always evaluated.

    QUOTE: "... This is determined per vertex or per pixel depending on shader stage. Both sides of the branch will be calculated in the shader, even if one is never output."
    Source: https://docs.unity3d.com/Packages/com.unity.shadergraph@6.9/manual/Branch-Node.html

    The part with "... Unity internally compiles into different shader variations (so if you got 2 branches you'd get 4 variations) ..." which @_watcher_ mentioned sounds interesting. Has anyone more intel on this?
     
    Last edited: Jul 31, 2020
    kai303 and MaximumTre like this.
  9. MagdielM

    MagdielM

    Joined:
    May 27, 2020
    Posts:
    31
    In case you're still wondering, this refers to shader variants, which are compiled from the defined shader keywords (preprocessor defines) in your shader. You can also define keywords in Shader Graph and use them to create variant branches via the Keyword node.
     
    _geo__ and _watcher_ like this.
  10. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    151
    Thanks @MagdielM for those links :)
    If I remember correctly then what I was wondering about was the contradiction between two sources (Unity Docs and what _watcher_ said). The docs indicate that both branches are evaluated but watchers comment made me think that a branch node will automatically trigger the creation of multiple shader variants (one for each possible if/else combination) and thus avoid the evaluation of both branches. It now seems to be clear to me that this is not the case. Instead we should use the keywords to force a variation where needed.

    If I look at the initial question and the graph there it now seems obvious to me that both branches need to be evaluated because the program does not know in advance which of the two results (textures) will be needed (evaluates from left to right). It feels like the branch node is designed backwards or, dare I say, misleadingly named. So it's not really an IF/ELSE but more like a filter. Usually when I think of branches it is "one in, two out". This is "two in, one out". Seen like that, the docs make perfect sense.

    What really stumped me initially was the docs mentioning that all branches are evaluated. Calculating both branches when only one is needed seemed like a total wase to me. But again, I think I just pictured the bool node as IF/ELSE with the evaluation of the branches coming afterwards, which is not really how it works.
     
    Last edited: Jan 8, 2021
  11. MagdielM

    MagdielM

    Joined:
    May 27, 2020
    Posts:
    31
    Your first assumption wasn't too far off the mark, actually. In the current version of Shader Graph, Branch nodes are supposed to become conditional statements in the generated code. In earlier versions (pre 7.1, I think), the node would generate a lerp between the two branches where the interpolation point was either 0 or 1, which would work like you just described.

    The real question is whether or not the generated branches encompass all the calculations for each possible condition or just the assignment, and the code you can generate via the Editor seems to show the latter:
    Code (csharp):
    1. // Graph Functions
    2. void Unity_Comparison_Less_float(float A, float B, out float Out)
    3. {
    4.     Out = A < B ? 1 : 0;
    5. }
    6. void Unity_Add_float(float A, float B, out float Out)
    7. {
    8.     Out = A + B;
    9. }
    10. void Unity_Branch_float4(float Predicate, float4 True, float4 False, out float4 Out)
    11. {
    12.     Out = Predicate ? True : False;
    13. }
    14.  
    15. [...]
    16.  
    17. // from SurfaceDescriptionFunction()
    18. float _Float_c46b55e6b3d743d7bfa8cc32bdd4b2af_Out_0 = 2;
    19. float _Comparison_4e5bb68504234ccdb59a35316a6a0445_Out_2;
    20. Unity_Comparison_Less_float(_Float_c46b55e6b3d743d7bfa8cc32bdd4b2af_Out_0, 3, _Comparison_4e5bb68504234ccdb59a35316a6a0445_Out_2);
    21. float4 _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_RGBA_0 = SAMPLE_TEXTURE2D(Texture2D_806f836551da425bb2bf0dfc995dc67e, samplerTexture2D_806f836551da425bb2bf0dfc995dc67e, IN.uv0.xy);
    22. float _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_R_4 = _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_RGBA_0.r;
    23. float _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_G_5 = _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_RGBA_0.g;
    24. float _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_B_6 = _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_RGBA_0.b;
    25. float _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_A_7 = _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_RGBA_0.a;
    26. float _Add_3c7d130e108f4ad8898dacdd97546141_Out_2;
    27. Unity_Add_float(0.1, 0.3, _Add_3c7d130e108f4ad8898dacdd97546141_Out_2);
    28. float4 _Branch_0688a3564d7e41378de784b092bd191c_Out_3;
    29. Unity_Branch_float4(_Comparison_4e5bb68504234ccdb59a35316a6a0445_Out_2, _SampleTexture2D_18b378d59011415f9b9e33f4d175aa6b_RGBA_0, (_Add_3c7d130e108f4ad8898dacdd97546141_Out_2.xxxx), _Branch_0688a3564d7e41378de784b092bd191c_Out_3);
    (Don't worry about the big scary numbers, they're just auto-generated variable names.)

    This is the relevant part of the code generated from a graph that branches into either a texture sample or 0.1 + 0.3 depending on whether 2 is less than 3 or not. Mind you, this is a trivial example. Even though the code would branch statically, executing all of the operations every time would probably be faster than having the branch's overhead in the first place, but it seems like in this particular case, you get the worst of both: all the calculations execute and the shader branches on the assignment. The original lerp implementation would actually perform a bit better.

    Now admittedly, I don't know jack about compilation other that it's usually better at optimizing than I am. Maybe Microsoft's Direct3D compiler would understand that most of these operations can be put into different branches? Again, I wouldn't know personally.
     
    Sinister-Design and _watcher_ like this.
  12. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    151
    Thanks @MagdielM for digging into this.

    Quote: "Now admittedly, I don't know jack about compilation other that it's usually better at optimizing than I am."

    > Haha yes, same here. I still write ++i instead of i++ in my for loops, remembering having learnt once that it's faster that way (premature optimization surely :D).

    I have reformatted the code to make it a little easier to reason about. Now, if the code coming in would be lazily evaluated after the branch then I guess it would still be possible to have a kind of best case scenario but I am not sure if such a feature exists in the shader world. Maybe the shader graph code generator could wrap that code into functions which are then evaluated only if needed. The last (and only) time I wrote a shader from scratch was GLSL in 2010, so I am way out of my comfort zone here. Added some comments out of curiosity.

    Now what comes to my mind is the choice between two textures (like the thread opener wanted) based on a dynamic value (the result of a noise node for example), therefore static variants would not suffice. But again, maybe this dynamic case is not a problem at all because those textures would have been loaded to the GPU anyhow and maybe there is some sort of caching on the hardware which might make sampling textures repeatedly basically free (I am speculating wildly now). Hope that was understandable.

    Code (CSharp):
    1.     // Graph Functions
    2.     void Unity_Comparison_Less_float(float A, float B, out float Out)
    3.     {
    4.         Out = A < B ? 1 : 0;
    5.     }
    6.     void Unity_Add_float(float A, float B, out float Out)
    7.     {
    8.         Out = A + B;
    9.     }
    10.     void Unity_Branch_float4(float Predicate, float4 True, float4 False, out float4 Out)
    11.     {
    12.         Out = Predicate ? True : False;
    13.     }
    14.  
    15.     [...]
    16.  
    17.     // from SurfaceDescriptionFunction()
    18.     float num2 = 2;
    19.     float resultCompare2v3Result1; // will be 1 because (2 < 3)
    20.     Unity_Comparison_Less_float(num2, 3, resultCompare2v3Result1);
    21.     float4 _SampleTexture2D_Texture0_RGBA_0 = SAMPLE_TEXTURE2D(Texture2D_Texture0, samplerTexture2D_Texture0, IN.uv0.xy);
    22.     // float _SampleTexture2D_Texture0_R_4 = _SampleTexture2D_Texture0_RGBA_0.r; // not used in the sample code
    23.     // float _SampleTexture2D_Texture0_G_5 = _SampleTexture2D_Texture0_RGBA_0.g;
    24.     // float _SampleTexture2D_Texture0_B_6 = _SampleTexture2D_Texture0_RGBA_0.b;
    25.     // float _SampleTexture2D_Texture0_A_7 = _SampleTexture2D_Texture0_RGBA_0.a;
    26.     float _Add_Result_Out_2;
    27.     Unity_Add_float(0.1, 0.3, _Add_Result_Out_2);
    28.     float4 _Branch_Result_Out_3;
    29.     Unity_Branch_float4(resultCompare2v3Result1, _SampleTexture2D_Texture0_RGBA_0, (_Add_Result_Out_2.xxxx), _Branch_Result_Out_3); // Since _Add_Result_Out_2 is just a float, I expect a type mismatch here. But the .xxxx part seems like someone "shorthening" the code, or is this valid syntax?
    30.  
     
  13. MagdielM

    MagdielM

    Joined:
    May 27, 2020
    Posts:
    31
    This is actually HLSL syntax for swizzling vector components. In this case, the code builds a temporary
    float4
    out of the X component of
    _Add_Result_Out_2
    .

    As for dynamic branching (branching that diverges because of calculations within the shader operation rather than before or after draw calls), typically you want to avoid it. Because GPUs are massively parallel processors, they do their work best when all threads can execute the same instructions at the same time. Whenever some threads diverge, GPUs need to handle these cases specially. Each GPU vendor has their own proprietary dynamic branching implementation, but one I've heard of is caching the parts of the GPU's state that lead to the change in branch and stalling while waiting for all the current threads to finish executing the operation as it was before the divergence occurred so threads can be re-dispatched under the new condition for the remaining vertices/fragments/what-have-you, which pretty much means the shader needs to run twice.
     
    _geo__ likes this.
  14. _geo__

    _geo__

    Joined:
    Feb 26, 2014
    Posts:
    151
    Thanks for taking the time to explain it in detail. I appreciate it and I will try to avoid dynamic branching :)
     
unityunity