How to set shader constants for GUI? (SOLVED)

I’ve already hijacked another thread for this, but since that one is officially SOLVED I thought it would make more sense to start this one.

I want to achieve colour palette swapping in GUI. I can’t pass shader constants in the same way as you would do for a sprite (using go.set()) because gui.set() doesn’t exist. I can’t change materials at runtime, which would otherwise be a viable workaround (for my use case, anyway, as I could make materials for each individual palette).

Is there a reasonable workaround or is this just not possible currently?

4 Likes

You should be able to set a constant in the render script before drawing your GUI:

2 Likes

What about render.enable_material()?

2 Likes

Great suggestions! Both were exactly what I needed. Voila - a fully dynamic GUI palette swapper.

(Caveat: There’s a fixed number of colours to replace. In this case 8.)

replace_color_shader.zip (33.8 KB)

10 Likes

TL;DR: I don’t understand how the alpha channel works in shaders. How can I make my shader respect the alpha specified in my GUI?

Re-opening this topic because I was trying to update the project so that the palette swap shader maintained the alpha of the images.

Here is my setup. I have a GUI with a custom material and fragment program. In this GUI I have two nodes. One is the greyscale which is what was used in the video above to palette swap. The second node is a copy of the first, except I move it a bit, flip it horizontally, and set the alpha to 0.5.

Here’s what happens when you apply the shader I’ve already shared. You can see that the alpha channel gets overwritten to 1 (no surprise - the shader explicitly overwrites alpha to whatever is provided, in this case 1):

The shader code (simplified - I’ve removed color replacements 2 through 7 as they are basically identical).:

varying mediump vec2 var_texcoord0;
varying lowp vec4 var_color;

uniform lowp sampler2D texture_sampler;

vec4 color_1 = vec4(1, 1, 1, 1);

uniform lowp vec4 replace_1;

vec4 eps = vec4(0.009, 0.009, 0.009, 0.009);

void main()
{
    lowp vec4 pixel = texture2D(texture_sampler, var_texcoord0.xy);

    // color_1, replace_1
    if (all(greaterThanEqual(pixel, vec4(color_1 - eps))) &&
    all(lessThanEqual(pixel, vec4(color_1 + eps))))
    pixel = vec4(replace_1);

    gl_FragColor = pixel;
}

I tried modifying it to capture pixel’s original alpha value, then setting it back at the end. I’ve tried both “a” and “w” by the way, with no difference. It just doesn’t change the picture from the last one.

varying mediump vec2 var_texcoord0;
varying lowp vec4 var_color;

uniform lowp sampler2D texture_sampler;

vec4 color_1 = vec4(1, 1, 1, 1);

uniform lowp vec4 replace_1;

vec4 eps = vec4(0.009, 0.009, 0.009, 0.009);

void main()
{
    lowp vec4 pixel = texture2D(texture_sampler, var_texcoord0.xy);
    float alph = pixel.a;

    // color_1, replace_1
    if (all(greaterThanEqual(pixel, vec4(color_1 - eps))) &&
    all(lessThanEqual(pixel, vec4(color_1 + eps))))
    pixel = vec4(replace_1);

    pixel.a = alph;
    gl_FragColor = pixel;
}

My fragment program is a kind of hybrid of the default GUI and sprite fp’s, since I was modifing Pkeod’s original shader. I thought perhaps I’ve messed something up on the way, so I went back to basics. The default GUI fragment program:

varying mediump vec2 var_texcoord0;
varying lowp vec4 var_color;

uniform lowp sampler2D texture_sampler;

void main()
{
    lowp vec4 tex = texture2D(texture_sampler, var_texcoord0.xy);
    gl_FragColor = tex * var_color;
}

To test that I was changing the alpha channel, I added this line at the end:

gl_FragColor.a = 0.1;

You can see here that definitely does something to the alpha channel, but not the way I’d expect. The transparency is more pronounced on the darker shades, seemingly not at all on the whites (at least the fully opaque white):

To make it clearer, I added a red node underneath (also in the same GUI):

What’s going on?

How does alpha work in shaders?

How can I modify my shader so that it keeps the alpha set in GUI?

What blend mode do you have on the node(s) ?
How the blend modes are setup is found here

2 Likes

Oh, didn’t think of this.

All the screenshots and descriptions above are with the default setting - Alpha.

Which one would you think is appropriate? I cycled through all of them and none behaved as I would expect.

Here’s the setup:

  • Render clear colour is red.
  • I’ve modified the default GUI fp to add gl_FragColor.a = 0.25; at the end
  • Default render script is used.

Alpha:

Add:

Add Alpha:

Multiply:

Screen:

Compare the above to what I think should happen if I force the fp to set the alpha channel to 0.25 for every single pixel (switched back to the default gui material and just set the alpha of both GUI nodes to 0.25):

1 Like

Perhaps if you also share the latest version of the test project, someone can play with it

Good idea.

There’s a lot going on in the project as I’ve done a lot of different variants, but it’s actually currently set up much simpler than it may appear.

  • Default render script is used.
  • The bootstrap collection is main_alpha_test.collection
  • The collection contains one game object with a GUI object (described in previous post), with material gui_alpha (which has fp gui_alpha associated with it)
  • The gui_alpha fp has the line gl_FragColor.a = 0.25; added to the end.

That should be it I think.

replace_color_shader.zip (89.9 KB)

1 Like

From the OpenGL documentation of the blend functions.

If our blend mode is Alpha, then we set these constants constants are:

Source Factor = ONE = (1,1,1,1)
Destination Factor = ONE_MINUS_SOURCE_ALPHA = (1-src.a, 1-src.a, 1-src.a, 1-src.a)

Let’s call them srcf and dstf

The blend formula is:

out.r = src.r * srcf + dst.r * dstf
out.g = src.g * srcf + dst.g * dstf
out.b = src.b * srcf + dst.b * dstf
out.a = src.a * srcf + dst.a * dstf

The source color is the one output from the fragment shader.

The source factor is ONE=(1,1,1,1)
The destination blend factor is (1-src.a)

out.r = src.r * 1 + dst.r * (1-src.a)
out.g = src.g * 1 + dst.g * (1-src.a)
out.b = src.b * 1 + dst.b * (1-src.a)
out.a = src.a * 1 + dst.a * (1-src.a)

If we plugin your example alpha (0.25) into this and,
if we use the white area src=(1,1,1,0.25) as an example, with our red background dst=(1,0,0,1)

out.r = 1.0  * 1 + 1 * 0.75 = 1.75 -> 1
out.g = 1.0  * 1 + 0 * 0.75 = 1.0  -> 1
out.b = 1.0  * 1 + 0 * 0.75 = 1.0  -> 1
out.a = 0.25 * 1 + 1 * 0.75 = 1.0  -> 1

(Values larger than 1.0 are clamped to 1.0)

So, we see that we still get full white, even if we added 0.25 as our alpha value!
What to do?

Well, we can use premultiplied alpha (just like we do for our textures).

If we multiply the src.rgb with the src.a we scale down its influence on the final color.

float a = 0.25
src.a *= a
src.rgb *= src.a

Given the white source color, the value is now src=(0.25, 0.25, 0.25, 0.25)
And, now we calculate it again:

out.r = 0.25 * 1 + 1 * 0.75 = 1.0 
out.g = 0.25 * 1 + 0 * 0.75 = 0.25
out.b = 0.25 * 1 + 0 * 0.75 = 0.25
out.a = 0.25 * 1 + 1 * 0.75 = 1.0 

I hope this explains things a bit, and there’s more information online ofc.
Here’s another reference explaining this (blending)

In your example, it would mean something like this:

float alpha = 0.5;
gl_FragColor.rgb *= alpha;
gl_FragColor.a *= alpha;
6 Likes

Thanks for this detailed answer. This is much more complicated than I thought! Damn you, Dunning-Kruger…!

First things first, your suggestion works for the super simplified example. :partying_face:

I wasn’t able to get it working in my actual code, and wrote out a very long post detailing step by step how it should work. I was reaching the point where I was concluding that OpenGL or Defold is broken… Fat chance, huh. So I stepped back a bit and compared the builtin to my fp. The difference is:

 varying lowp vec4 var_color;

I still don’t know what this is, but it was missing in my code. I think it’s because the sprite.fp (which served as the foundation of my fp) does it slightly differently (variable tint_pm and “// Pre-multiply alpha since all runtime textures already are”) and so I just didn’t make the connection that this was a necessary part of the fp (see reference to Dunning-Kruger above).

So, all I had to do was this at the end:

gl_FragColor = pixel * var_color;

Forget about storing and re-applying the alpha entirely.

Happy days:

I still don’t quite understand what var_color is (where does it get information from?), but thanks to my silly mistake I have learned a little about OpenGL, as I spent time trying to decipher @Mathias_Westerdahl 's links. Particularly this one (LearnOpenGL - Blending) I found at least slightly approachable. :sweat_smile:

Updated project:

replace_color_shader.zip (95.7 KB)

2 Likes

The “varying” means it is a variable you declare in the vertex shader, that is then interpolated automatically for each pixel when rasterizing a triangle, and it is then available for you in the fragment shader.

https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/varying.php

3 Likes