How to blend from two axis in GDShader

Godot Version

4.4.1

Question

I am trying out writing my own shaders for the first time, and have ran into an issue (which may be more of a mathematical issue then shader issue) when it comes to my shader;

I am attempting to make a shader which highlights the edge of a ColorRect so the user can easily know if it is the active one. After some tinkering I got this working;

shader_type canvas_item;

uniform vec4 highlight_colour = vec4(0, 0, 0, 1);
uniform float highlight_cutoff = 0.025;
uniform bool highlight = false;

void fragment() {
	// If highlighting is enabled and either we are within a threshold on the horizontal or vertical edges.
	if (highlight && ((UV.x < highlight_cutoff || UV.x > (1.0 - highlight_cutoff)) || (UV.y < highlight_cutoff || UV.y > (1.0 - highlight_cutoff)))) {
		float dif;
		// If we are handling the horizontal edges
		if (UV.x < highlight_cutoff || UV.x > (1.0 - highlight_cutoff)) {
			// If we are on the left side, get the percentage of how far along we are from the edge, otherwise do that but from the right side.
			dif = (UV.x < 0.5) ? (highlight_cutoff - UV.x) / highlight_cutoff : (((1.0 - highlight_cutoff) - UV.x) / highlight_cutoff) * -1.0;
		} else {
			// If we are at the top of the page, get the percentage of how far along we are from the top, otherwise do that but from the bottom.
			dif = (UV.y < 0.5) ? (highlight_cutoff - UV.y) / highlight_cutoff : (((1.0 - highlight_cutoff) - UV.y) / highlight_cutoff) * -1.0;
		}
		// Blend from base colour and highlight colour based on difference
		COLOR = mix(COLOR, highlight_colour, dif);
	}
}

This mostly works, except for the fact that I don’t know how to handle the edges where the X and Y intersect (Where we would want gradient from both sides in the corners of the page), example;

My question is how this could be done so that in the corners it blends from the top and bottom evenly, similar to using blend on a stylebox border (Though ideally without the weird lighter diagonal-line seen below);

I am unsure as mix only takes one input so I would need to get the value of how much to mix between the two colours when dealing with both X and Y into one percentage, but I don’t know the equation in-order to get that result.

Thanks.

Hi!

Here’s a way you can do that:

shader_type canvas_item;

uniform vec4 highlight_colour = vec4(0, 0, 0, 1);
uniform float highlight_cutoff = 0.025;
uniform bool highlight = false;

void fragment()
{
	if (highlight)
	{
		vec2 remap_uv = abs((UV - 0.5) * 2.0);
		vec2 highlight_smoothstep = smoothstep(vec2(0.0), vec2(highlight_cutoff), 1.0 - remap_uv);
		float mask = (highlight_smoothstep.x * highlight_smoothstep.y);
		
		COLOR.rgb = vec3(mask); // Mask applied to color for demo purpose, use it however needed.
	}
}

The idea is to remap the UV (from [0,1], to [-1,1], and then passed in abs to get a [1,0,1] range, if that makes sense), and then compute the highlight with a smoothstep function (being very efficient to compute gradients based on threshold values).
And then, multiplying both axis gives you a single float value being a mask you can use however you want.

Let me know if that works!

1 Like

Thank you so much!

It works great, I just swapped the COLOR.rgb = vec3(mask) to use the mix() function instead and it worked great, makes my old code look so overcomplicated XD

(This is what I ended up using if any future people want to steal it;)

shader_type canvas_item;

uniform vec4 highlight_colour = vec4(0, 0, 0, 1);
uniform float highlight_cutoff = 0.025;
uniform bool highlight = false;

void fragment() {
	if (highlight) {
		vec2 remap_uv = abs((UV - 0.5) * 2.0);
		vec2 highlight_smoothstep = smoothstep(vec2(0.0), vec2(highlight_cutoff), 1.0 - remap_uv);
		COLOR = mix(highlight_colour, COLOR, float(highlight_smoothstep.x * highlight_smoothstep.y));
	}
}

Thanks!

I would not say it was overcomplicated, but the biggest flaw of your initial code was the intensive use of if/else branching, which you typically want to avoid in shaders. It’s a more complex topic than it seems, so don’t be afraid of using a few conditions, mostly if you’re beginning in shaders, but keep in mind that when you can replace conditions with maths, you should do it.

For instance, your highlight boolean could be replaced with a highlight_percentage float, being 0 or 1, that you’d multiply with your mask to nullify it. Something like:

vec2 highlight_smoothstep = smoothstep(vec2(0.0), vec2(highlight_cutoff), 1.0 - remap_uv);
highlight_smoothstep *= highlight_percentage;

You may ask: wouldn’t it result in too much useless computations, if in the end everything is multiplied by 0? And I would answer that shaders, since they run on the GPU, are incredibly fast when it comes to do lots of maths instructions, but not so good with if/else branching, so it may be faster to do it that way in your case (not asserting anything here as it would require performance testing, but you get the point!).

Also, using a float percentage instead of a boolean would allow you to fade in and out your highlight :smile: same code, but with a bonus feature!

Again: don’t worry too much on all this stuff. Just sharing a few info on the fly since you’re learning, but your code is perfectly fine for what you’re doing right now.