1

Suppose I have a texture, what I want to do is for each pixel, return only the biggest channel as 1 and the rest as 0.

For instance:

      |         INPUT           |      OUTPUT
------|-------------------------|-----------------
RGBA  |  (0.5, 0.3, 0.4, 0.3)   |   (1, 0, 0, 0)
RGBA  |  ( 0 , 0.8, 0.9,  1 )   |   (0, 0, 0, 1)

I'm using shader graph and I'm searching for an optimal approach to avoid using a lot of nodes.

My thought was taking the maximum m of all channels, then let each channel ci = (ci >= m), so the channel greater equal to m would be 1 and the rest would be 0, but I'm guessing there might be a better/more performant way.

PS: If there are 2 or more channels with the same value, the correctness doesn't matter, is a problem of the texture. It's possible to suppose there will always be a channel with biggest value.

Daniel
  • 7,357
  • 7
  • 32
  • 84
  • I'm not sure about Shader Graph but what about the shader function [`max(x,y)`](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-max)? –  Nov 10 '22 at 03:40
  • In my question I mentioned I'm using max. I'm searching for a probably better approach. Using max, I have to perform 3 max operations, then perform 4 floors and 4 divisions. I'm hoping there's something better than this. – Daniel Nov 10 '22 at 03:42
  • All those operations you describe are _trivial._ –  Nov 10 '22 at 04:59

3 Answers3

1

One possible option would be to add a small custom function node.

enter image description here

float check = -1;
if ( inputColour.x > check )
{
  check = inputColour.x;
  outputColour = float4(1,0,0,0);
}
if ( inputColour.y > check )
{
  check = inputColour.y;
  outputColour = float4(0,1,0,0);
}
if ( inputColour.z > check )
{
  check = inputColour.z;
  outputColour = float4(0,0,1,0);
}
if ( inputColour.w > check )
{
  outputColour = float4(0,0,0,1);
}

When the colour is closer to "red", the node produces:enter image description here

But as the closer gets closer to "blue", the node produces:

enter image description here


EDIT

Added in recognition of a better answer. As per @Daniel's comment, this code will produce the same results, but with NO if statements. AND it's easier to read! Win-win.

float m = inputColour.x;
m = max(m, inputColour.y);
m = max(m, inputColour.z);
m = max(m, inputColour.w);

outputColour = float4 ( inputColour.x = inputColour.x >= m,
inputColour.x = inputColour.y >= m,
inputColour.x = inputColour.z >= m,
inputColour.x = inputColour.w >= m );
Milan Egon Votrubec
  • 3,696
  • 2
  • 10
  • 24
  • if-else statements in shaders usually slow things down because they kind of break the flow of the parallel processing of the GPU. But anyways, this looks a better solution than mine. I'll mark it as correct if nothing better appear. – Daniel Nov 10 '22 at 03:58
  • @Daniel and MickyD, you're both right ... But... I spent a few seconds trying to figure out how we'd step up the individual components to 1, while determine WHICH component to step up... and I drew a blank. – Milan Egon Votrubec Nov 10 '22 at 04:06
  • 2
    I thought about a good solution: let `m` be the max of `r,g,b,a`, then each channel `c` can be set to `c = c >= m`. As HLSL behaves like C, the `>=` operation returns 1 if true, 0 otherwise. – Daniel Nov 10 '22 at 04:38
  • 1
    @Daniel that's actually quite nice. BUT .. it leaves the possibility that two or more components might be the same value of 1. THOUGH in your OP, you DID say that two components should never have the same value (a problem with the input texture). So.. with that stipulation, nice! – Milan Egon Votrubec Nov 10 '22 at 04:43
0

You could use a Shader Graph Maximum Node.

Returns the largest of the two inputs values A and B.

      Red
RGBA -------- 
  |          \ 
  |            Maximum -------- Maximum --- (max of R, G, B)
  |   Green   /               /
  |----------/               /
  |                         /
  |   Blue                /
  |----------------------/

Alternative

If you are worried about the overhead of Shader Graph, you can always drop down to HLSL-level and use the max(x,y) intrinsic function. It's a simple operation if you need two (2) of them. Much better than using slow-and-nasty if statements.

Such code can even be invoked from Shader Graph should you wish to use it elsewhere.

  • This alone gives you the biggest of the components values ... but no information about whih component it actually belongs to – derHugo Nov 10 '22 at 07:51
0

If i understand well what you want is not the max value, intead you want to know what texture is actually the one with max value, so i came with this...

A thing about divisions is that if both numbers are the same it always equals to one. And that also means that if the dividend is smaller than the divisor it will always return <1 and if the divisor is the smaller one it will return >1. Keep in mind 0 on the divisor.

With that I think you can get rid of the max() and use just 4 divisions and 4 floors + possibly some control of 0 and ∞ cases.

I would try something like this: (didnt test anything)

inputTex1 = float4(r,g,b,a);
inputTex2 = float4(r,g,b,a);


outputTex1 = float4 ( 

    r = ((inputTex1.r / inputTex2.r) >= 1),
    g = ((inputTex1.r / inputTex2.r) >= 1),
    b = ((inputTex1.r / inputTex2.r) >= 1),
    a = ((inputTex1.r / inputTex2.r) >= 1));

outputTex2 = float4 ( 

    r = ((inputTex1.r / inputTex2.r) <= 1),
    g = ((inputTex1.r / inputTex2.r) <= 1),
    b = ((inputTex1.r / inputTex2.r) <= 1),
    a = ((inputTex1.r / inputTex2.r) <= 1));

`

uberflut
  • 120
  • 1
  • 7
  • My first idea was exactly what you said, but I took the max from all channels and them divided each channel by this maximum, taking the floor right after. The idea in the comments os Milan's answer seems a little faster since it doesn't do divisions, which are quite slow. – Daniel Nov 10 '22 at 05:28
  • I had not seen it, that approach look better indeed. – uberflut Nov 10 '22 at 05:35