Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Several shaders VS single shader with exposed "switch" variables (dynamic branching)

Discussion in 'Shader Graph' started by Opaweynch, Aug 29, 2023.

  1. Opaweynch

    Opaweynch

    Joined:
    Jan 23, 2023
    Posts:
    9
    Hey !

    When a game object switches between several appearances during runtime, should we build a single shader graph with exposed variables that switch between appearances or should we build one shader graph for each appearance and change the shader of the game object for switching?

    Example: if I have a sphere and I want it to be either blue or red and either transparent or opaque, this defines 4 appearances for my sphere. I see two solutions (although in this example shader graph is overkill):
    - In a single shader graph, I can use two float properties between 0 and 1 : the first could control the amount of linear interpolation between blue and red for the color and the second could directly control the alpha value. Then during runtime I can modify the two exposed properties to switch between the 4 states.
    - Or I can create 4 shader graphs corresponding to each state and apply the right one at the right time during runtime.

    I guess the answer depends on the number of game objects we use, what if we have 1000 game objects?

    Thanks!
     
  2. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,108
    It depends on use case and how much calculations you skip. for example lerping between two colors is quite cheap, but if you had some heavy calculations and texture sampling it might be worth to use different one.

    However what you described is shader variants and you should not create new separate shaders, but use keywords instead. Also lerping between values is not dynamic branching.
    If you had one shader then advantage is you can render all of them in several batches without changing shader, so if you have cheap calculations then it is most likely worth it. If you had separate 4 shader variatns used (4x250), then it must change shader 3 times - it's totaly ok to do that. It would be different story if you had no srp batcher or something broke batching, then it would be way slower.
    Still, what I wrote is very general, it's better to profile if this might be important in your project.
     
  3. AcidArrow

    AcidArrow

    Joined:
    May 20, 2010
    Posts:
    10,977
    Shader variants are separate shaders, Unity just generates them for you.

    I would make a huge shader that has all the stuff you want in it (although, I have more in mind writing shader code, I don't know how maintainable it is to have a huge shader graph, so this may be bad advice), so you can animate between the states with lerps and see how that performs. If you have a performance problem, you can try ifdefing stuff out with keywords and creating variants, of flat out splitting the shaders. (if the shaders don't have enough stuff in common it doesn't make sense to make them variants)
     
  4. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,108
    Yes, but you don't need to copy the whole thing to separate file and recreate the same shader with just one different line, and OP wanted to:
     
  5. Opaweynch

    Opaweynch

    Joined:
    Jan 23, 2023
    Posts:
    9
    Thanks for your answers!

    Building shader variants using keywords seems very handy! So if I have a boolean keyword that is set to 'off', does that mean that all the calculation tree that is branched in the 'on' channel will be skipped?

    More details about the use case: I built a shader with 3 states that you can see on the picture below. Basically the game objects start in state 1) (simple texture), then if the player collides with them I animate a transition to state 2) (a drawing appears on the surface), then if the player collides again I animate a dissolve effect that is state 3) (the game object disappears).

    sample_3_states_shader.png

    About calculation, both states 2) and 3) require sampling an additional texture (1 for the drawing and 1 for the dissolve). I have like 1000 game objects in total, simultaneously half of them are in state 1) and the other half are in state 2). Objects in state 3) disappear after 3s of animation and reappear in state 1).

    State 2) is obtained from state 1) by overlaying a semi-transparent texture on the emission channel of the shader. If I understood correctly, for a game object in state 1), I can spare the cost of sampling the texture by using a boolean keyword before branching in the emission channel? (As seen below where the keyword 'Is Emission Enabled' prevents emission when off).

    sample_keyword.jpg
     
  6. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,108
    Yes, but as acid mentioned - they are, becuase it uses different shader, so keywords are just very handy mechanism to generate all shader permutations for you.

    Initially I would create one shader without any keywords for states, and if there is need to optimize I would add keywords for that stuff. And if you really want I would start with the last one as it's used only for 3 seconds, "sometimes".

    The cost of the shader is also related to pixels on the screen, for example if your "boxes" cover 5% of your screen in total and your shader sampled 8 textures, then sampling is performed only for those 5%, but if you cover 90% of your screen, then almost all pixels must sample all 8 textures, for this reason it is worth to first optimize the most common shader that covers most of the screen.
     
    Opaweynch likes this.
  7. Opaweynch

    Opaweynch

    Joined:
    Jan 23, 2023
    Posts:
    9
    Ok, thanks for the advice, I'll definitely implement it this way!

    Just to make it clear: I understand that using keywords is not always better as it saves some computation to the cost of having multiple shaders. Then depending on the tradeoff between the saved computation and the cost of switching between the generated shaders it MAY be more optimized than using no keywords or maybe not (depending on the case). Is that right?
     
  8. Qriva

    Qriva

    Joined:
    Jun 30, 2019
    Posts:
    1,108
    I am not "expert enough" to tell you how much of a difference exactly what operation takes, especially when it's also platform dependent, but to answer you - yes, it may. Long story short in general it's better to create one uber shader with features enabled by keywords, because most of objects will use the same variant, but at the same time it's not worth to push all unrelated features into single shader (for example water shader is applied only to water surface, not normal objects, so usually there is no point to put water functionality into normal shader).

    My main point is that you just might be CPU bound in the end and all things you try to do can end up insignificant. It is good to know general rules what is better and what is worse, but you should not optimize before you reach some milestone, plus there are other ways to optimize stuff. If you notice rendering of your game is lowering your fps, then visit frame debugger first, then use some GPU profiler to see what is the most expansive part to render and optimize it.
     
    Opaweynch likes this.
  9. BenCloward

    BenCloward

    Unity Technologies

    Joined:
    Jul 15, 2021
    Posts:
    93
    My strategy is to create one shader per category of objects. Like one shader for rocks, one shader for foliage, one shader for water, etc, and then within that category I add features to the shader that can be turned on and off where needed. Don't try to make one shader that does everything. It will end up being too large and too slow to deal with
     
    Opaweynch and Qriva like this.
  10. Opaweynch

    Opaweynch

    Joined:
    Jan 23, 2023
    Posts:
    9
    Alright, those seem like the good approach! Thank you all for your advice :)
     
    Last edited: Aug 31, 2023