Advertisement

Component-wise function/if-statement

Started by June 11, 2020 05:10 PM
8 comments, last by Juliean 4 years, 7 months ago

Hello,

does anyone know of a method for applying a function or if-statement component-wise in HLSL (prefered; bonus points if it also/or works in GLSL)?

My example is a function that should allow me to modulate the color of game-sprites, by making them completely white (1.0f), completely black (-1.0f) and lerping everything in between.

float targetValue;
float alpha;
if (target > 0.0f)
{
	targetValue = 1.0f;
	alpha = target;
}
else
{
	targetValue = 0.0f;
	alpha = -target;
}
return lerp(value, targetValue, alpha);

That is the base function for a single value, and now I want to have this applied to all components of a float2-4. I know that intrinsic functions can be somehow declared scalar/vector, and I know that I could just write functions that overload on input of float2-float4, but I wanted to see if there is a more advanced solution. Maybe some way of applying the if-check per component or so.

Any ideas?

Juliean said:
My example is a function that should allow me to modulate the color of game-sprites, by making them completely white (1.0f), completely black (-1.0f) and lerping everything in between. float targetValue;

If it's not about a short time pulsing effect but something presistent, it would be worth to experiment with changing saturation before you change brightness. Changing brightness reduces saturation perceptually, so the result from your trivial approach can look dull.

About the question, i think there would be optimization with CPU simd instructuions, but GPUs are scalar currently and vecX is just x unique registers internally. So your solution should be fine, but not sure if this still applies to any GPU.

Advertisement

JoeJ said:
If it's not about a short time pulsing effect but something presistent, it would be worth to experiment with changing saturation before you change brightness. Changing brightness reduces saturation perceptually, so the result from your trivial approach can look dull.

Thanks for the suggestion, for now I'm only emulating an effect that I “reverse engineered” from an emulated game, so the result is fine in that regard ?

JoeJ said:
About the question, i think there would be optimization with CPU simd instructuions, but GPUs are scalar currently and vecX is just x unique registers internally. So your solution should be fine, but not sure if this still applies to any GPU.

Oh, I might not have made my question clear. Its not about efficiency but code-redundancy. I have a shader-code generator which in combinations with most intrinsic functions accepting anything from scalar to float4 allows me to write most operations only once, while for here I would have to physically write 4 helper-functions to cover all cases (potentially also ending up with conversion-warnings along the way because of the permutations of (float1-4 value, float1-4 target) that the function has. I'm also confident it will be optimized in the end, but I'd like to save writing all those functions, if possible.

For example, I could write a double-lerp (lerp between 3 values, -1.0f for a 0.0f for b and 1.0f for c) once by replacing the type on compilation:

%TYPE% result;
if(alpha >= 0.0f)
    result = lerp(a, b, alpha);
 else
    result = lerp(a, b2, -alpha);

I would like to do that same with this new lerp-function, but can't since “alpha” now consists of 1-4 floats).

int4 signs = sign(alpha);
uint4 ander = signs >> 1; // 0 if >0, 0 if ==0, 0x7FFFFFFF if <0
float4 result = lerp(a,  // for positive b/b2, 0x7FFFFFFF is fine
       asfloat(asuint(b) &amp;amp; (~ander)) + asfloat(asuint(b2) &amp;amp; ander),
       ((float)signs) * alpha); // or just "abs(alpha)"

Ah, totally forgot about sign. Thats what I've been looking for, thx

FYI, comparisons performed on vector types actually return a vector of booleans rather than a single boolean. You can feed this into a ternary operator to do component-wise comparison + select:

struct PSInput
{
    float4 check : CHECK;
    float4 color0 : COLOR0;
    float4 color1 : COLOR1;
};

float4 PSMain(PSInput input) : SV_TARGET
{
    bool4 comparison = input.check > 0.0;
    float4 value = comparison ? input.color0 : input.color1;
    return value;
}

http://shader-playground.timjones.io/8fe6bf0ddae00788b6df6b31cb180a82

Advertisement

MJP said:
FYI, comparisons performed on vector types actually return a vector of booleans rather than a single boolean. You can feed this into a ternary operator to do component-wise comparison + select:

Nice, I like that one even better! Didn't think that comparisons and ternaries work component-wise, thats sweet.

The only problem with both solutions that I have is that since there are no “templates” in HLSL, I cannot pack all variants into one function but eigther have to paste the full code every time my meta-function is used (think visual material editor), or generate all super-sets of types eigther way. Well, it still saves me a little bit of duplicate so I'm still happy.

Juliean said:
The only problem with both solutions that I have is that since there are no “templates” in HLSL

Good old macros?

JoeJ said:
Good old macros?

Huh, those might actually do that job. You (or anyone else) don't know a trick for having sort of a dynamic return type in HLSL now, right? Thats the only problem, as (looking at MJPs) code now the return of the comparison is bool-bool4. I suppose I could calculate that based on the input float types and pass it to the macro as well if everything else fails.

This topic is closed to new replies.

Advertisement