Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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) );
}
@Gertkeno

This comment has been minimized.

Copy link

@Gertkeno Gertkeno commented Aug 24, 2018

Do you have any recommended reading to explain this? I don't understand how any of it works, especially the constants. It works perfectly though.

@ForeverZer0

This comment has been minimized.

Copy link

@ForeverZer0 ForeverZer0 commented Aug 2, 2019

For future visitors, the hue value should be in radians, not in degrees (0.0 to 360.0), so if your hue value is in degrees, you will need to convert before passing to the shader add it the shader code.

@powertomato

This comment has been minimized.

Copy link

@powertomato powertomato commented Sep 3, 2019

Do you have any recommended reading to explain this? I don't understand how any of it works, especially the constants. It works perfectly though.

I know this is an old comment, but someone might still find it useful.

Basically the shader uses the YIQ color mode. The constants are conversion matrices between RGB and YIQ, but wikipedia can explain that better than I do:
https://en.wikipedia.org/wiki/YIQ

Hue is the angle on the IQ-plane. In other words you fix the Y value, and get a two dimensional IQ-plane, then calculate the angle between those two values. The shift is simply performed by adding the shift value to this angle.
Once this is done you then simply need to convert it back to RGB, for that you need the new I and Q values. You can split the hue by using sin and cos, you just need to scale them to match the previous length on the IQ plane. That "length" is called "chroma" in the shader and is better known as "saturation". You obtain it by using the pythagorean theorem.

@viruseg

This comment has been minimized.

Copy link

@viruseg viruseg commented Feb 17, 2020

This is an incredibly slow implementation.

Here is the same thing, but it works several times faster. Written on HLSL. Porting to GLSL is easy.

float3 ApplyHue(float3 col, float hueAdjust)
{
    const float3 k = float3(0.57735, 0.57735, 0.57735);
    half cosAngle = cos(hueAdjust);
    return col * cosAngle + cross(k, col) * sin(hueAdjust) + k * dot(k, col) * (1.0 - cosAngle);
}
@ForeverZer0

This comment has been minimized.

Copy link

@ForeverZer0 ForeverZer0 commented Mar 1, 2020

@viruseg Indeed, it is a more elegant solution, and simple to convert to GLSL, thanks.

vec3 hueShift(vec3 color, float hue) {
    const vec3 k = vec3(0.57735, 0.57735, 0.57735);
    float cosAngle = cos(hue);
    return vec3(color * cosAngle + cross(k, color) * sin(hue) + k * dot(k, color) * (1.0 - cosAngle));
}
@Fra-Ktus

This comment has been minimized.

Copy link

@Fra-Ktus Fra-Ktus commented Mar 26, 2020

There was a typo, this one works for me:

vec3 hueShift(vec3 color, float hue)
{
const vec3 k = vec3(0.57735, 0.57735, 0.57735);
float cosAngle = cos(hue);
return vec3(color * cosAngle + cross(k, color) * sin(hue) + k * dot(k, color) * (1.0 - cosAngle));
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.