Ternaries and branches in UE (and graphics generally)

View in #engine_unreal on Slack

probiner @probiner: I’m surprised there’s not a comparison node in Materials. How do you achieve it?

archo5 @archo5: should work by passing constants (0/1) to the last 3 parameters
the implementation does seems a bit unusual though

speuzer @speuzer: FWIW, its not really a branch, you pay the cost of all inputs for every pixel. There might be some minor savings, but this isn’t a “Dynamic Branch”

probiner @probiner: I see, cause I was concerned about doing this.

And usually to do some branchless design one would just would do a chain or tree of Multiply-Adds.

BUT… I still need to feed the condition 0/1 to the multiply.
So you’re saying @speuzer that in this context one doesn’t need to have the same concerns as it would with CPU code?

speuzer @speuzer: Yeah, different concerns. I wouldn’t be surprised if you second example is cheaper, though on modern hardware you probably aren’t to notice any difference with this simple of an example :slightly_smiling_face:

Doing a true branch in a shader is a relatively new idea (like last 5-10 years or so?), and its used sparingly, performing the branch has cost, you need to make sure what you are saving by branching makes up for it. But again, the IF statement in unreal material editor isn’t one of these branches

probiner @probiner: But again, the IF statement in unreal material editor isn't one of these branches
So internally it’s also just a multiply-add branchless design? Interesting gonna check more on it
{A>BVal} * A>B + {A<BVal} * A<B + {A==BVal} * A==B

bob.w @bob.w: I believe the material editor has a way so that you can visualize the generated code.
I know in the past I’ve seen it use an actual if branch, but that was also some wacky complicated shit that the artist in question had constructed

archo5 @archo5: > Doing a true branch in a shader is a relatively new idea (like last 5-10 years or so?)
dynamic branching has existed for at least 17 years afaict (since SM3.0)

bob.w @bob.w: My how the time flys

probiner @probiner: I believe the material editor has a way so that you can visualize the generated code.
I’ll check it out! Thanks! Sounds like it should perform similar to a LERP then.

teessider @teessider: unless you have a custom node with the BRANCH macro above the if statement, the material node version is an inline ternary conditional :slightly_smiling_face:
with a custom node though, you would have to handle basically everything - maybe this has now changed with multiple outputs and such
@probiner the generated code is not the FULL version but it covers the nodes from the graph…however it is easier to copy that text and view it in an external editor :disappointed:

probiner @probiner: Yeah I guess I should check if I can see the node’s code in VSCode.

ozzmeister00 @ozzmeister00: So in my TA-level understanding of ternary operators and GPUs, you’re always evaluating both the A and B inputs, but you don’t necessarily evaluate all three of the outputs depending on the results of the comparisons ina give… wave? quad? I gotta double-check with my rendering guy
if y’all really want to get your hands dirty, I’ve got a couple KBs:

How the Unreal Engine translates your Material Graph to HLSL
and, for those of you on UDN: Creating Material Expressions

archo5 @archo5: MaterialFloat3(0.00000000,0.00000000,0.00000000) I sometimes wonder how much faster the shaders would compile if someone got rid of all the excess zeroes :nerd_face:

bob.w @bob.w: They need those extra 0’s to weed out he pesky -0.0

theodox @theodox: By default most “ifs” are really treated as lerps with 1 or 0 coefficients. If the output is highly variable within a wave that’s generally more efficient unless one of the output options is very expensive. If on the other hand the if’s tend to cohere in screen space, if can make sense to force the if to be a [branch] which can really short-circuit evaluations . The classic case is a terrain texture which is or is not sampled based on a distance check, since the distance checks will tend to be all true in one part of the screen and all false in another with relatively few tiles or waves containing both true and false pixels.

ozzmeister00 @ozzmeister00: TIL!, thanks Steve!

probiner @probiner: So yeah it seems to be this @Christian , thanks. Two ternaries:
((abs({A} - {B}) > {epsilon}) ? ({A} >= {B} ? {A>B_input} : {A<B_input}) : {A==B_input}) ;
On that note, static switch, apparently for stuff that doesn’t change during run time, necessitates a boolean… Unsure if there’s a way to set some stratic int attributes that could be used as such, but hey… I think I got my answer for now :slightly_smiling_face:

ozzmeister00 @ozzmeister00: There’s a Bool and Boolean Parameter you could use, as well as Static Switch Parameters that expose those to the users, but there’s no way to convert Floats -> Bools in the graph.

il_berna @il_berna: Save this good stuff on forum! :smiley: