Skip to content

Instantly share code, notes, and snippets.

@mairod
Last active February 10, 2024 16:57
Show Gist options
  • Star 26 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mairod/a75e7b44f68110e1576d77419d608786 to your computer and use it in GitHub Desktop.
Save mairod/a75e7b44f68110e1576d77419d608786 to your computer and use it in GitHub Desktop.
Optimised Hue shift function in GLSL
vec3 hueShift( vec3 color, float hueAdjust ){
const vec3 kRGBToYPrime = vec3 (0.299, 0.587, 0.114);
const vec3 kRGBToI = vec3 (0.596, -0.275, -0.321);
const vec3 kRGBToQ = vec3 (0.212, -0.523, 0.311);
const vec3 kYIQToR = vec3 (1.0, 0.956, 0.621);
const vec3 kYIQToG = vec3 (1.0, -0.272, -0.647);
const vec3 kYIQToB = vec3 (1.0, -1.107, 1.704);
float YPrime = dot (color, kRGBToYPrime);
float I = dot (color, kRGBToI);
float Q = dot (color, kRGBToQ);
float hue = atan (Q, I);
float chroma = sqrt (I * I + Q * Q);
hue += hueAdjust;
Q = chroma * sin (hue);
I = chroma * cos (hue);
vec3 yIQ = vec3 (YPrime, I, Q);
return vec3( dot (yIQ, kYIQToR), dot (yIQ, kYIQToG), dot (yIQ, kYIQToB) );
}
@vmedea
Copy link

vmedea commented Jan 16, 2023

Thank you so much for this! Contrary to what you seem to think though, your original function produces much more natural looking/beautiful results than the cheaper hueshift function provided by @viruseg.

I had the same conclusion. The idea to use Rodrigues rotation formula is clever, but the problem is that the RGB to YIQ translation isn't a pure rotation. It also has skew and scaling components. So the output won't be the same.

The most compact version I could come up with, with some symbolic manipulation (along the same lines as Rodrigues' but replacing the cross product), that should be equivalent, is:

vec3 hue_shift(vec3 color, float dhue) {
	float s = sin(dhue);
	float c = cos(dhue);
	return (color * c) + (color * s) * mat3(
		vec3(0.167444, 0.329213, -0.496657),
		vec3(-0.327948, 0.035669, 0.292279),
		vec3(1.250268, -1.047561, -0.202707)
	) + dot(vec3(0.299, 0.587, 0.114), color) * (1.0 - c);
}

(Godot shader, but should be compatible to GLSL)

@l375cd
Copy link

l375cd commented Aug 30, 2023

vmedea, thank you for the code.

How to adapt your code fro RGBA (alpha) image, vec4?

@ForeverZer0
Copy link

@l375cd It doesn't do anything with alpha channel, so just use GLSL swizzling to only pass in the RGB channels, and then re-apply the alpha channel to the result. Something like the pseudo-code below.

in vec2 texCoord;
out vec4 outFrag;

uniform sampler2D image;
uniform float hue

void main()
    vec4 color = texture(image, texCoord);
    outFrag = vec4(hueShift(color.rgb, hue), color.a);
}

@l375cd
Copy link

l375cd commented Aug 30, 2023

@ForeverZer0, thank you so much!

@MikeKotys
Copy link

Will it work with sRGB color format, or should I convert my color to RGB before using this function?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment