A fragment shader for converting to and from HSV. Useful for performing color effects.
The shader supplied here replaces the default image_blend functionality with something like the sprite editor's colorize function, but it can be easily be modified to do other HSV-color-space effects. (I recommend against using it for anything that doesn't require hue though, since saturation and volume can both be manipulated directly in RGB color space)
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
#define EPSILON 0.00001
vec3 toHSV(vec3 col) {
vec3 hsv = vec3(0.0);
hsv.z = max(max(col.r, col.g), col.b); //volume
float difference = hsv.z-min(min(col.r, col.g), col.b);
hsv.y = difference / hsv.z; //saturation
//possible hue ranges
float redmax_hue = (col.g-col.b)/difference;
float greenmax_hue = 2.0+(col.b-col.r)/difference;
float bluemax_hue = 4.0+(col.r-col.g)/difference;
//hue range weights
float redweight = step(hsv.z, col.r);
float greenweight = (1.0-redweight)*step(hsv.z, col.g);
float blueweight = (1.0-redweight)*(1.0-greenweight);
hsv.x = mod((redweight*redmax_hue +
greenweight*greenmax_hue +
blueweight*bluemax_hue)
*60.0,
360.0); //hue
//div by zero handling
//for cases of unsaturated or pure black
//hue is zeroed, and saturation is also zero
//since its gray, and only volume matters,
//and we want to avoid div-by-zero fuckery
hsv.xy *= step(EPSILON, difference)*step(EPSILON, hsv.z);
return hsv;
}
//this function used to be more complicated but then it turns out it was trivial
float rerange(float x, float r) {
return (x-r)/1.0; //div by one ResidentSleeper
}
vec3 toRGB(vec3 hsv) {
//re-normalize hue to 0-6, because color hexagon reasons
hsv.x = mod(hsv.x, 360.0);
hsv.x /= 60.0;
//weights for the possible hue ranges
float ranges[6];
for (int i = 0; i < 6; i++) {
ranges[i] = step(float(i), hsv.x)*step(hsv.x, float(i+1));
}
//manual unrolling bitch
//actually I dont even know how I would do this in a proper loop
//shits going everywhere all at once
//see https://en.wikipedia.org/wiki/File:HSV-RGB-comparison.svg for an explanation
vec3 col = vec3(0.0);
col += vec3(ranges[0], ranges[0]*rerange(hsv.x, 0.0), 0.0);
col += vec3(ranges[1]*(1.0-rerange(hsv.x, 1.0)), ranges[1], 0.0);
col += vec3(0.0, ranges[2], ranges[2]*rerange(hsv.x, 2.0));
col += vec3(0.0, ranges[3]*(1.0-rerange(hsv.x, 3.0)), ranges[3]);
col += vec3(ranges[4]*rerange(hsv.x, 4.0), 0.0, ranges[4]);
col += vec3(ranges[5], 0.0, ranges[5]*(1.0-rerange(hsv.x, 5.0)));
//We have the hue, now we just have to squoosh it into its correct sat/vol
//I'm sure there's a clever one-liner to do this but I haven't found it
col.r = mix(hsv.z-hsv.y, hsv.z, col.r);
col.g = mix(hsv.z-hsv.y, hsv.z, col.g);
col.b = mix(hsv.z-hsv.y, hsv.z, col.b);
//handle fully-desaturated case
//in case of div-by-zero fuckery
col = mix(col, vec3(hsv.z), step(hsv.y, 0.0));
return col;
}
void main()
{
vec4 base_col = texture2D(gm_BaseTexture, v_vTexcoord);
vec3 hsv = toHSV(base_col.rgb);
//you can do HSV fuckery here
//Hue is 0.0-360.0 (degrees)
//Saturation/volume are 0.0-1.0
//access h, s, and v with x, y, and z
hsv.x = toHSV(v_vColour.rgb).x;
//and convert back to RGB here
gl_FragColor = vec4(toRGB(hsv), base_col.a*v_vColour.a);
}