Cory Beutler: The Blog

Helpful shader functions for Godot

Color is sometimes strange to work with. For example, you may have noticed that pure blue (0.0,0.0,1.0) is darker than pure red(0.0,0.0,1.0). This "perceived luminance difference" makes it not totally straightforward when working with color. There is a quick and dirty way to get a luminance value closer to what us humans would perceive. Unfortunately, it is different for SD and HD displays, but you can usually get away with just using SD. (Color spaces get complex fast, so if you want to learn more... good luck: https://en.wikipedia.org/wiki/Color_space)


const vec3 Y_SD = vec3(0.299, 0.587, 0.114);
const vec3 Y_HD = vec3(0.2126, 0.7152, 0.0722);
float rgb_to_y( vec3 c ) { return dot(c, Y_SD); }
float rgb_to_y_hd( vec3 c ) { return dot(c, Y_SD); }
vec3 to_black_and_white( vec3 c ) { return vec3(rgb_to_y(c)); }
vec3 to_black_and_white_hd( vec3 c ) { return vec3(rgb_to_y_hd(c)); }

Currently missing from the stock functions is a 'wrap' function. Each of these have different small tweaks for choosing how to wrap. The 'wrap_floor' and 'wrap_ceil' variants don't require a or b to be in any particular order, it's just a range, which may be needed.


// Wraps [mn, mx)
float wrapf(float x, float mn, float mx) { return mod(x+mn, mx)-mn; }
vec2 wrapvec2(vec2 x, float mn, float mx) { return mod(x+mn, mx)-mn; }
vec3 wrapvec3(vec3 x, float mn, float mx) { return mod(x+mn, mx)-mn; }

// Wraps [a, b)
float wrap_floor(float x, float a, float b) { return (a-b)*floor((a-x)/(a-b)) + x; }

// Wraps (a, b]
float wrap_ceil(float x, float a, float b) { return (a-b)*ceil((a-x)/(a-b)) + x + b - a; }
// Wraps (0,1]
float wrap_ceil_0_1(float x) { return 1.0 + x - ceil(x); }
// Wraps (-1,1]
float wrap_1_1(float x) { return x + 2.0 - 2.0 * ceil( 0.5 + 0.5*x ); }

Another function I have used more than I thought I would is toroidal distance. This function takes in two points with values between 0.0 and 1.0. It then finds how close those points are to each other if the 1x1 world were to wrap back around on itself (like Pacman.)


float toroidal_distance( vec2 a, vec2 b ) {
	vec2 d = abs(a - b);
	if (d.x > 0.5) d.x -= 1.0;
	if (d.y > 0.5) d.y -= 1.0;
	return length(d);
}

I will likely return to this post and edit/add functions as I use them.