I am using cocos2dx. I have a Sprite which is set with a custom shader like this:
boss_1 = Sprite::createWithSpriteFrameName("Zombies/normal/0_0_0.png");
boss_1->setPosition(boss_1->getContentSize()/2.0f);
boss_1->setBlendFunc(cocos2d::BlendFunc::ALPHA_NON_PREMULTIPLIED);
boss_1->setGLProgramState(boss_1_state);
I have the following shader:
vec3 hueAdjust(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);
// Convert to YIQ
float YPrime = dot (color, kRGBToYPrime);
float I = dot (color, kRGBToI);
float Q = dot (color, kRGBToQ);
// Calculate the hue and chroma
float hue = atan (Q, I);
float chroma = sqrt (I * I + Q * Q);
// Make the user's adjustments
hue += hueAdjust;
// Convert back to YIQ
Q = chroma * sin (hue);
I = chroma * cos (hue);
// Convert back to RGB
vec3 yIQ = vec3 (YPrime, I, Q);
color.r = dot (yIQ, kYIQToR);
color.g = dot (yIQ, kYIQToG);
color.b = dot (yIQ, kYIQToB);
// Save the result
return color;
}
void main()
{
vec4 v_orColor = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
// Hue
vec3 hueAdjustedColor = hueAdjust(v_orColor.rgb, hue_value);
gl_FragColor = vec4(hueAdjustedColor, v_orColor.a);
}
But the alpha seems to get lost and the sprite is rendered with a black background. (Although the hue shift works perfectly, since i can test it with a slider)
This only happens with the hueAdjust function. If I use this other function to change contrast/saturation/brightness, the alpha is preserved perfectly:
vec3 ContrastSaturationBrightness(vec3 color, float brt, float sat, float con)
{
// Increase or decrease these values to adjust r, g and b color channels seperately
const float AvgLumR = 0.5;
const float AvgLumG = 0.5;
const float AvgLumB = 0.5;
const vec3 LumCoeff = vec3(0.2125, 0.7154, 0.0721);
vec3 AvgLumin = vec3(AvgLumR, AvgLumG, AvgLumB);
vec3 brtColor = color * brt;
vec3 intensity = vec3(dot(brtColor, LumCoeff));
vec3 satColor = mix(intensity, brtColor, sat);
vec3 conColor = mix(AvgLumin, satColor, con);
return conColor;
}
It seems like the algorithm in hueAdjust outputs negative colors or NaNs...
I got it working by replacing this line:
vec3 hueAdjustedColor = hueAdjust(v_orColor.rgb, hue_value);
with this one:
vec3 hueAdjustedColor = max(hueAdjust(v_orColor.rgb, hue_value), 0.0);
BTW, algorithm seems to be overcomplicated. Why do you converting colors like this RGB -> YIQ -> HSV -> YIQ -> RGB? You can convert RGB directly to HSV and vice versa without intermediate YIQ stage. Here is fast branchless algorithm for this: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
Hope, it helps :)
Related
This is the shader i am using to do chroma key , the shader works well but i need to feather the edges of the chroma mask.
How can i do that ?
#version 430 core
uniform sampler2D u_tex;
vec4 keyRGBA = vec4(86.0 / 255.0 , 194.0 / 255.0, 46.0 / 255.0 , 1.0); // key color as rgba
vec2 keyCC; // the CC part of YCC color model of key color
uniform vec2 rangeSpill = vec2(0.1, .52); // the smoothstep range for spill detection
uniform vec2 range = vec2(0.05, 0.21); // the smoothstep range for chroma detection
in vec2 texCoord;
out vec4 FragColor;
vec2 RGBToCC(vec4 rgba) {
float Y = 0.299 * rgba.r + 0.587 * rgba.g + 0.114 * rgba.b;
return vec2((rgba.b - Y) * 0.565, (rgba.r - Y) * 0.713);
}
vec2 RGBAToCC (float r, float g, float b) {
float y = 0.299 * r + 0.587 * g + 0.114 * b;
return vec2((b - y) * 0.565, (r - y) * 0.713);
}
vec3 RGBToYCC( vec3 col )
{
float y = 0.299 * col.r + 0.587 * col.g + 0.114 * col.b;
return vec3( y ,(col.b - y) * 0.565, (col.r - y) * 0.713);
}
vec3 YCCToRGB( vec3 col )
{
float R = col.x + (col.z - 128) * 1.40200;
float G = col.x + (col.y - 128) * -0.34414 + (col.z - 128) * -0.71414;
float B = col.x + (col.y - 128) * 1.77200;
return vec3( R , G , B);
}
vec3 hueShift( vec3 color, float hueAdjust ){
vec3 kRGBToYPrime = vec3 (0.299, 0.587, 0.114);
vec3 kRGBToI = vec3 (0.596, -0.275, -0.321);
vec3 kRGBToQ = vec3 (0.212, -0.523, 0.311);
vec3 kYIQToR = vec3 (1.0, 0.956, 0.621);
vec3 kYIQToG = vec3 (1.0, -0.272, -0.647);
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) );
}
float GetYComponent( vec3 color){
vec3 kRGBToYPrime = vec3 (0.299, 0.587, 0.114);
vec3 kRGBToI = vec3 (0.596, -0.275, -0.321);
vec3 kRGBToQ = vec3 (0.212, -0.523, 0.311);
vec3 kYIQToR = vec3 (1.0, 0.956, 0.621);
vec3 kYIQToG = vec3 (1.0, -0.272, -0.647);
vec3 kYIQToB = vec3 (1.0, -1.107, 1.704);
float YPrime = dot (color, kRGBToYPrime);
return YPrime;
}
void main() {
vec4 src1Color = texture2D(u_tex, texCoord);
keyCC = RGBAToCC( keyRGBA.r , keyRGBA.g , keyRGBA.b );
vec2 CC = RGBToCC(src1Color);
float mask = sqrt(pow(keyCC.x - CC.x, 2.0) + pow(keyCC.y - CC.y, 2.0));
mask = smoothstep(rangeSpill.x + 0.5, rangeSpill.y, mask);
if (mask > 0.0 && mask < .8)
{
src1Color = vec4( hueShift(src1Color.rgb , 1.8 ) , src1Color.a ); // spill remover
}
// Now the spill is removed do the chroma
vec2 CC2 = RGBToCC(src1Color);
float mask2 = sqrt(pow(keyCC.x - CC2.x, 2.0) + pow(keyCC.y - CC2.y, 2.0));
mask2 = smoothstep(range.x, range.y, mask2);
if (mask2 == 0.0) { discard; }
else if (mask2 == 1.0)
{
FragColor = vec4(src1Color.rgb , mask2);
}
else
{
vec4 col = max(src1Color - (1.0 - mask2) * keyRGBA, 0.0);
FragColor = vec4(hueShift(col.rgb , 0.3 ) , col.a); // do color correction
}
}
This is the base image
This is the result after chroma keying.
Also there is not much information avaliable for chroma keying if someone could also give some information about adding more details in the shader.
Effectively, you need to extrude the areas where the Chroma key matched. While you could just sample in a pattern (instead of a single point) in a single render pass, that's not quite efficient.
Instead you should rather write the mask to a 1bit (or as much as you would like for transparency) mask texture first. Then you can run a simple 1D shader in X and Y direction over that mask to extrude the already excluded areas by a fixed amount. You need a temporary texture for playing ping-pong either way, and splitting X and Y dimensions requires far less samples in total.
E.g. the minimum opacity in a range of 5px, or a Gaussian blur with a scaler / clamp to keep already full transparent pixels still transparent.
Ultimately, combine your final mask with the source image as usual.
Im trying to create a GLSL fragment shader which projects an image to a dome. The input would be a sampler2D texture, an elevation and an azimuth.
The result should look like the following gif's.
Elevation between 0 and 90 degree (in this gif its between -90 and 90)
.
Azimuth between 0 and 360 degree
.
Right now my code looks like this:
#ifdef GL_ES
precision mediump float;
#endif
uniform float u_time;
uniform vec2 u_resolution;
uniform sampler2D u_texture_0;
uniform sampler2D u_texture_1;
// INPUT
const float azimuth=0.;// clockwise 360 degree
const float altitude=90.;// 0-90 dregree -> 90 = center
const float scale=1.;
// CALC
const float PI=3.14159265359;
const float azimuthRad=azimuth*PI/180.;
const float altitudeNormalization=sin((1.-(altitude/90.)));
float box(in vec2 _st,in vec2 _size){
_size=vec2(.5)-_size*.5;
vec2 uv=smoothstep(_size,_size+vec2(.001),_st);
uv*=smoothstep(_size,_size+vec2(.001),vec2(1.)-_st);
return uv.x*uv.y;
}
mat2 rotate(float angle){
return mat2(cos(angle),-sin(angle),sin(angle),cos(angle));
}
void main(){
vec2 st=gl_FragCoord.xy/u_resolution;
vec4 color = texture2D(u_texture_1,st); // set background grid
vec2 vPos=st;
float aperture=180.;
float apertureHalf=.5*aperture*(PI/180.);
float maxFactor=sin(apertureHalf);
// to unit sphere -> -1 - 1
vPos=vec2(2.*vPos-1.);
float l=length(vPos);
if(l<=1.){
float x=maxFactor*vPos.x;
float y=maxFactor*vPos.y;
float n=length(vec2(x,y));
float z=sqrt(1.-n*n);
float r=atan(n,z)/PI;
float phi=atan(y,x);
float u=r*cos(phi)+.5;
float v=r*sin(phi)+.5;
vec2 uv=vec2(u,v);
// translate
vec2 translate=vec2(sin(azimuthRad),cos(azimuthRad));
uv+=translate*altitudeNormalization;
// rotate
uv-=.5;
uv=rotate(PI-azimuthRad)*uv;
uv+=.5;
// scale
float size=.5*scale;
float box=box(uv,vec2(.5*size));
uv.x*=-1.;
uv.y*=-1.;
if(box>=.1){
vec3 b=vec3(box);
// gl_FragColor=vec4(b,1.);
//uv *= box;
color += texture2D(u_texture_0,uv);
}
gl_FragColor= color;
}
}
As you can see there are two things wrong, the texture is only displayed partially (I know that I kind of cut it out which is for sure wrong) and the distortion is also wrong. Any help would be apretiated.
The issue is, that you use a scaled uv coordinates for the box test:
float size=.5*scale;
float box=box(uv,vec2(.5*size));
You have to consider this scale when you do the texture look up the texture. Furthermore you wrongly add 0.5 to the uv coordinates:
float u=r*cos(phi)+.5;
float v=r*sin(phi)+.5;
Set up the uv coordinates in range [-1.0, 1.0]:
vec2 uv = vec2(r*cos(phi), r*sin(phi));
Translate, rotate and scale it (e.g. const float scale = 8.0;):
// translate
vec2 translate = vec2(sin(azimuthRad), cos(azimuthRad));
uv += translate * altitudeNormalization;
// rotate
uv = rotate(PI-azimuthRad)*uv;
// scale
uv = uv * scale;
Transform the uv coordinate from range [-1.0, 1.0] to [0.0, 1.0] and do a correct box test:
uv = uv * 0.5 + 0.5;
vec2 boxtest = step(0.0, uv) * step(uv, vec2(1.0));
if (boxtest.x * boxtest.y > 0.0)
color += texture2D(u_texture_0, uv);
Fragment shader main:
void main(){
vec2 st = gl_FragCoord.xy/u_resolution;
vec4 color = texture2D(u_texture_1,st); // set background grid
float aperture=180.;
float apertureHalf=.5*aperture*(PI/180.);
float maxFactor=sin(apertureHalf);
// to unit sphere -> -1 - 1
vec2 vPos = st * 2.0 - 1.0;
float l=length(vPos);
if(l<=1.){
float x = maxFactor*vPos.x;
float y = maxFactor*vPos.y;
float n = length(vec2(x,y));
float z = sqrt(1.-n*n);
float r = atan(n,z)/PI;
float phi = atan(y,x);
float u = r*cos(phi);
float v = r*sin(phi);
vec2 uv = vec2(r*cos(phi), r*sin(phi));
// translate
vec2 translate = vec2(sin(azimuthRad), cos(azimuthRad));
uv += translate * altitudeNormalization;
// rotate
uv = rotate(PI-azimuthRad)*uv;
// scale
uv = uv * scale;
uv = uv * 0.5 + 0.5;
vec2 boxtest = step(0.0, uv) * step(uv, vec2(1.0));
if (boxtest.x * boxtest.y > 0.0)
color += texture2D(u_texture_0, uv);
}
gl_FragColor = color;
}
I have some code I wrote in the book of shaders editor:
#ifdef GL_ES
precision mediump float;
#endif
uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;
float remap(float a, float b, float c, float d, float t) {
return ((t - a) / (b - a)) * (d-c) + c;
}
float outline(vec2 st) {
return smoothstep(0.99, 1.0, st.y) + smoothstep(0.99, 1.0, st.x) + smoothstep(0.01, 0.0, st.y) + smoothstep(0.01, 0.0, st.x);
}
float mouseFoo(vec2 scaledSt, vec2 u_mouse, float scaleVal) {
vec2 scaledMouse = u_mouse * scaleVal;
if(scaledSt.x < ceil(scaledMouse.x) && scaledSt.x > floor(scaledMouse.x) && scaledSt.y < ceil(scaledMouse.y) && scaledSt.y > floor(scaledMouse.y)) {
// if(u_mouse.x < 100.0) {
return 1.0;
} else {
return 0.0;
}
}
void main(){
vec2 st = gl_FragCoord.xy/u_resolution.xy;
vec3 color = vec3(0.03,0.07,0.15);
vec3 redColor = vec3(1.0, 0.0, 0.0);
vec3 outlineColor = vec3(1.0);
float floorSt;
float scaleVal = 5.0;
vec2 scaledSt = st * scaleVal;
// tile
st *= scaleVal;
floorSt = floor(st.x);
st = fract(st);
// inner color
color = mix(color, redColor, mouseFoo(scaledSt, u_mouse/u_resolution.xy, scaleVal));
// outline
color = mix(color, outlineColor, outline(st));
gl_FragColor = vec4(color, 1.0 );
}
I'm wondering if it's possible to have the red color tween to the blue color when a box is hovered off? I think I might have an idea of how to do it if I were to write data to a texture and look that up, but even then I'm not entirely sure.
Use mix
Use mix to interpolate between red and blue. You need another variable that transitions from 0-1 to do the blend, which is mix's 3rd parameter.
ShaderToy example:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
vec3 red = vec3(1,0,0);
vec3 blue = vec3(0,0,1);
// Output to screen
fragColor = vec4(mix(red, blue, uv.x),1.0);
}
which produces:
In your case, you'll want the 3rd parameter (the alpha or lerp parameter) to be driven over some time (say, .2 seconds) after the mouse entered the hover area. You'll need to do one of the following:
Detect the hover entered at a higher level and then pass the mouse down time in as a uniform
Drive the 3rd parameter directly from a uniform
I am reading Inigo Quilez Fog article and I just can't understand few things when he talks about fog based on height.
He has a shader function about height based fog but I have problems understanding how to make it work.
He uses this function to apply fog
vec3 applyFog( in vec3 rgb, // original color of the pixel
in float distance ) // camera to point distance
{
float fogAmount = 1.0 - exp( -distance*b );
vec3 fogColor = vec3(0.5,0.6,0.7);
return mix( rgb, fogColor, fogAmount );
}
then he has the other one based to calculage fog based on height
vec3 applyFog( in vec3 rgb, // original color of the pixel
in float distance, // camera to point distance
in vec3 rayOri, // camera position
in vec3 rayDir ) // camera to point vector
{
float fogAmount = c * exp(-rayOri.y*b) * (1.0-exp( -distance*rayDir.y*b ))/rayDir.y;
vec3 fogColor = vec3(0.5,0.6,0.7);
return mix( rgb, fogColor, fogAmount );
}
I can understand how the shader works but I don't know how to use it with mine. For now I am just trying to learn how the whole fog world in GLSL works but it looks that there is just a lot about it to learn. :D
#version 400 core
in vec3 Position;
in vec3 Normal;
//in vec4 positionToCamera;
//in float visibility;
uniform vec3 color;
uniform vec3 CameraPosition;
uniform float near;
uniform float far;
uniform vec3 fogColor;
uniform bool enableBlending;
uniform float c;
uniform float b;
uniform int fogType;
vec3 applyFogDepth( vec3 rgb, // original color of the pixel
float distance, // camera to point distance
vec3 rayOri, // camera position
vec3 rayDir) // camera to point vector
{
//float cc = 1.0;
//float bb = 1.1;
float fogAmount = c * exp(-rayOri.y*b) * (1.0 - exp(-distance*rayDir.y*b)) / rayDir.y;
return mix(rgb, fogColor, fogAmount );
}
// Fog with Sun factor
vec3 applyFogSun( vec3 rgb,// original color of the pixel
float distance, // camera to point distance
vec3 rayDir, // camera to point vector
vec3 sunDir) // sun light direction
{
float fogAmount = 1.0 - exp(-distance*b);
float sunAmount = max(dot(rayDir, sunDir), 0.0);
vec3 fog = mix(fogColor, // bluish
vec3(1.0, 0.9, 0.7), // yellowish
pow(sunAmount, 8.0));
return mix(rgb, fog, fogAmount);
}
//Exponential fog
vec3 applyFog( vec3 rgb, // original color of the pixel
float distance) // camera to point distance
{
float fogAmount = 1.0 - exp(-distance*b);
return mix(rgb, fogColor, fogAmount);
//return rgb*( exp(-distance*b)) + fogColor*(1.0 - exp(-distance*b));
}
float LinearizeDepth(float depth)
{
float z = depth * 2.0 - 1.0; // Back to NDC
return (2.0 * near * far) / (far + near - z * (far - near));
}
out vec4 gl_FragColor;
void main(void) {
vec3 fog = vec3(0.0);
//-5.0f, 900.0f, 400.0f camera coord
vec3 lightPosition = vec3(0.0, 1200.0, -6000.0);
vec3 lightDirection = normalize(lightPosition - Position);
vec3 direction = normalize(CameraPosition - Position);
float depth = LinearizeDepth(gl_FragCoord.z) / far;
switch (fogType) {
case 0:
fog = applyFog(color, depth);
break;
case 1:
fog = applyFogSun(color, depth, direction, lightDirection);
break;
case 2:
//fog = mix(applyFog(color, depth), applyFogDepth(color, depth, CameraPosition, CameraPosition - Position), 0.5) ;
fog = applyFogDepth(color, depth, CameraPosition, CameraPosition - Position);
break;
}
//calculate light
float diff = max(dot(Normal, lightDirection), 0.0);
vec3 diffuse = diff * color;
float fogAmount = 1.0 - exp(-depth*b);
vec3 finalColor = vec3(0.0);
if (enableBlending)
finalColor = mix(diffuse, fog, fogAmount);
else
finalColor = fog;
gl_FragColor = vec4(finalColor,1.0);
//gl_FragColor = vec4(vec3(LinearizeDepth(visibility) / far), 1.0f);
}
The first image is the first function to apply fog and in the second image is the second function.
Is there a way to efficiently change hue of a 2D OpenGL texture using GLSL (fragment shader)?
Do someone have some code for it?
UPDATE: This is the code resulting from user1118321 suggestion:
uniform sampler2DRect texture;
const mat3 rgb2yiq = mat3(0.299, 0.587, 0.114, 0.595716, -0.274453, -0.321263, 0.211456, -0.522591, 0.311135);
const mat3 yiq2rgb = mat3(1.0, 0.9563, 0.6210, 1.0, -0.2721, -0.6474, 1.0, -1.1070, 1.7046);
uniform float hue;
void main() {
vec3 yColor = rgb2yiq * texture2DRect(texture, gl_TexCoord[0].st).rgb;
float originalHue = atan(yColor.b, yColor.g);
float finalHue = originalHue + hue;
float chroma = sqrt(yColor.b*yColor.b+yColor.g*yColor.g);
vec3 yFinalColor = vec3(yColor.r, chroma * cos(finalHue), chroma * sin(finalHue));
gl_FragColor = vec4(yiq2rgb*yFinalColor, 1.0);
}
And this is the result compared with a reference:
I have tried to switch I with Q inside atan but the result is wrong even around 0°
Have you got any hint?
If needed for comparison, this is the original unmodified image:
While what #awoodland says is correct, that method may cause issues with changes in luminance, I believe.
HSV and HLS color systems are problematic for a number of reasons. I talked with a color scientist about this recently, and his recommendation was to convert to YIQ or YCbCr space and adjust the the chroma channels (I&Q, or Cb&Cr) accordingly. (You can learn how to do that here and here.)
Once in one of those spaces, you can get the hue from the angle formed by the chroma channels, by doing hue = atan(cr/cb) (watching for cb == 0). This gives you a value in radians. Simply rotate it by adding the hue rotation amount. Once you've done that, you can calculate the magnitude of the chroma with chroma = sqrt(cr*cr+cb*cb). To get back to RGB, calculate the new Cb and Cr (or I & Q) using Cr = chroma * sin (hue), Cb = chroma * cos (hue). Then convert back to RGB as described on the above web pages.
EDIT: Here's a solution that I've tested and seems to give me the same results as your reference. You can probably collapse some of the dot products into matrix multiplies:
uniform sampler2DRect inputTexture;
uniform float hueAdjust;
void main ()
{
const vec4 kRGBToYPrime = vec4 (0.299, 0.587, 0.114, 0.0);
const vec4 kRGBToI = vec4 (0.596, -0.275, -0.321, 0.0);
const vec4 kRGBToQ = vec4 (0.212, -0.523, 0.311, 0.0);
const vec4 kYIQToR = vec4 (1.0, 0.956, 0.621, 0.0);
const vec4 kYIQToG = vec4 (1.0, -0.272, -0.647, 0.0);
const vec4 kYIQToB = vec4 (1.0, -1.107, 1.704, 0.0);
// Sample the input pixel
vec4 color = texture2DRect (inputTexture, gl_TexCoord [ 0 ].xy);
// Convert to YIQ
float YPrime = dot (color, kRGBToYPrime);
float I = dot (color, kRGBToI);
float Q = dot (color, kRGBToQ);
// Calculate the hue and chroma
float hue = atan (Q, I);
float chroma = sqrt (I * I + Q * Q);
// Make the user's adjustments
hue += hueAdjust;
// Convert back to YIQ
Q = chroma * sin (hue);
I = chroma * cos (hue);
// Convert back to RGB
vec4 yIQ = vec4 (YPrime, I, Q, 0.0);
color.r = dot (yIQ, kYIQToR);
color.g = dot (yIQ, kYIQToG);
color.b = dot (yIQ, kYIQToB);
// Save the result
gl_FragColor = color;
}
Andrea3000, in comparing YIQ examples on the net, i came across your posting, but i think there is an issue with the 'updated' version of your code.... i'm sure your 'mat3' definitions are flip/flopped on the column/row ordering... (maybe that's why you were still having troubles)...
FYI: OpenGL matrix ordering: "For more values, matrices are filled in in column-major order. That is, the first X values are the first column, the second X values are the next column, and so forth." See: http://www.opengl.org/wiki/GLSL_Types
mat2(
float, float, //first column
float, float); //second column
MSL(Metal Shader Language) version for changing the hue of a texture. I would recommend the MetalPetal pod. This pod will make life a lot easier.
#include <metal_stdlib>
using namespace metal;
typedef struct {
float4 position [[ position ]];
float2 textureCoordinate;
} VertexOut;
fragment float4 hue_adjust_filter(
VertexOut vertexIn [[stage_in]],
texture2d<float, access::sample> inTexture [[texture(0)]],
sampler inSampler [[sampler(0)]],
constant float &hueAdjust [[ buffer(0) ]])
{
const float4 kRGBToYPrime = float4 (0.299, 0.587, 0.114, 0.0);
const float4 kRGBToI = float4 (0.596, -0.275, -0.321, 0.0);
const float4 kRGBToQ = float4 (0.212, -0.523, 0.311, 0.0);
const float4 kYIQToR = float4 (1.0, 0.956, 0.621, 0.0);
const float4 kYIQToG = float4 (1.0, -0.272, -0.647, 0.0);
const float4 kYIQToB = float4 (1.0, -1.107, 1.704, 0.0);
// Sample the input pixel
float2 uv = vertexIn.textureCoordinate;
float4 color = inTexture.sample(inSampler, uv);
// Convert to YIQ
float YPrime = dot (color, kRGBToYPrime);
float I = dot (color, kRGBToI);
float Q = dot (color, kRGBToQ);
// Calculate the hue and chroma
float hue = atan2 (Q, I);
float chroma = sqrt (I * I + Q * Q);
// Make the user's adjustments
hue += hueAdjust;
// Convert back to YIQ
Q = chroma * sin (hue);
I = chroma * cos (hue);
// Convert back to RGB
float4 yIQ = float4 (YPrime, I, Q, 0.0);
color.r = dot (yIQ, kYIQToR);
color.g = dot (yIQ, kYIQToG);
color.b = dot (yIQ, kYIQToB);
return color;
}