`float3 HSVtoRGB(float3 HSV)`

{

float3 RGB = 0;

float C = HSV.z * HSV.y;

float H = HSV.x * 6;

float X = C * (1 - abs(fmod(H, 2) - 1));

if (HSV.y != 0)

{

float I = floor(H);

if (I == 0) { RGB = float3(C, X, 0); }

else if (I == 1) { RGB = float3(X, C, 0); }

else if (I == 2) { RGB = float3(0, C, X); }

else if (I == 3) { RGB = float3(0, X, C); }

else if (I == 4) { RGB = float3(X, 0, C); }

else { RGB = float3(C, 0, X); }

}

float M = HSV.z - C;

return RGB + M;

}

float3 RGBtoHSV(float3 RGB)

{

float3 HSV = 0;

float M = min(RGB.r, min(RGB.g, RGB.b));

HSV.z = max(RGB.r, max(RGB.g, RGB.b));

float C = HSV.z - M;

if (C != 0)

{

HSV.y = C / HSV.z;

float3 D = (((HSV.z - RGB) / 6) + (C / 2)) / C;

if (RGB.r == HSV.z)

HSV.x = D.b - D.g;

else if (RGB.g == HSV.z)

HSV.x = (1.0/3.0) + D.r - D.b;

else if (RGB.b == HSV.z)

HSV.x = (2.0/3.0) + D.g - D.r;

if ( HSV.x < 0.0 ) { HSV.x += 1.0; }

if ( HSV.x > 1.0 ) { HSV.x -= 1.0; }

}

return HSV;

}

{

float3 RGB = 0;

float C = HSV.z * HSV.y;

float H = HSV.x * 6;

float X = C * (1 - abs(fmod(H, 2) - 1));

if (HSV.y != 0)

{

float I = floor(H);

if (I == 0) { RGB = float3(C, X, 0); }

else if (I == 1) { RGB = float3(X, C, 0); }

else if (I == 2) { RGB = float3(0, C, X); }

else if (I == 3) { RGB = float3(0, X, C); }

else if (I == 4) { RGB = float3(X, 0, C); }

else { RGB = float3(C, 0, X); }

}

float M = HSV.z - C;

return RGB + M;

}

float3 RGBtoHSV(float3 RGB)

{

float3 HSV = 0;

float M = min(RGB.r, min(RGB.g, RGB.b));

HSV.z = max(RGB.r, max(RGB.g, RGB.b));

float C = HSV.z - M;

if (C != 0)

{

HSV.y = C / HSV.z;

float3 D = (((HSV.z - RGB) / 6) + (C / 2)) / C;

if (RGB.r == HSV.z)

HSV.x = D.b - D.g;

else if (RGB.g == HSV.z)

HSV.x = (1.0/3.0) + D.r - D.b;

else if (RGB.b == HSV.z)

HSV.x = (2.0/3.0) + D.g - D.r;

if ( HSV.x < 0.0 ) { HSV.x += 1.0; }

if ( HSV.x > 1.0 ) { HSV.x -= 1.0; }

}

return HSV;

}

Even to my eyes, these looked far than optimal. However, a quick glance at the HSV graph on the Wiki page suggests an optimisation for 'HSVtoRGB' which doesn't involve branching or conditional predicates.

`float3 Hue(float H)`

{

float R = abs(H * 6 - 3) - 1;

float G = 2 - abs(H * 6 - 2);

float B = 2 - abs(H * 6 - 4);

return saturate(float3(R,G,B));

}

float3 HSVtoRGB(in float3 HSV)

{

return ((Hue(HSV.x) - 1) * HSV.y + 1) * HSV.z;

}

{

float R = abs(H * 6 - 3) - 1;

float G = 2 - abs(H * 6 - 2);

float B = 2 - abs(H * 6 - 4);

return saturate(float3(R,G,B));

}

float3 HSVtoRGB(in float3 HSV)

{

return ((Hue(HSV.x) - 1) * HSV.y + 1) * HSV.z;

}

This is particularly efficient because 'abs' and 'saturate' are "free" operations on a lot of GPU pipelines.

The reverse conversion is a bit more tricky, and if you're really obsessed with performance on Xbox360, or the like, you'll need to drop down into shader assembler to utilise the vector 'max4' instruction instead of the scalar alternatives:

`float3 RGBtoHSV(in float3 RGB)`

{

float3 HSV = 0;

#if NO_ASM

HSV.z = max(RGB.r, max(RGB.g, RGB.b));

float M = min(RGB.r, min(RGB.g, RGB.b));

float C = HSV.z - M;

#else

float4 RGBM = RGB.rgbr;

asm { max4 HSV.z, RGBM };

asm { max4 RGBM.w, -RGBM };

float C = HSV.z + RGBM.w;

#endif

if (C != 0)

{

HSV.y = C / HSV.z;

float3 Delta = (HSV.z - RGB) / C;

Delta.rgb -= Delta.brg;

Delta.rg += float2(2,4);

if (RGB.r >= HSV.z)

HSV.x = Delta.b;

else if (RGB.g >= HSV.z)

HSV.x = Delta.r;

else

HSV.x = Delta.g;

HSV.x = frac(HSV.x / 6);

}

return HSV;

}

{

float3 HSV = 0;

#if NO_ASM

HSV.z = max(RGB.r, max(RGB.g, RGB.b));

float M = min(RGB.r, min(RGB.g, RGB.b));

float C = HSV.z - M;

#else

float4 RGBM = RGB.rgbr;

asm { max4 HSV.z, RGBM };

asm { max4 RGBM.w, -RGBM };

float C = HSV.z + RGBM.w;

#endif

if (C != 0)

{

HSV.y = C / HSV.z;

float3 Delta = (HSV.z - RGB) / C;

Delta.rgb -= Delta.brg;

Delta.rg += float2(2,4);

if (RGB.r >= HSV.z)

HSV.x = Delta.b;

else if (RGB.g >= HSV.z)

HSV.x = Delta.r;

else

HSV.x = Delta.g;

HSV.x = frac(HSV.x / 6);

}

return HSV;

}

Although we haven't managed to get rid of the 'if' statements, they typically compile down to one conditionally predicated block and two conditional assignments.

Even against the startlingly successful optimisations produced by the current batch of HLSL compilers, these refactorings produce excellent results. The round-trip conversions (RGB-to-HSV-to-RGB) are typically three times faster than the simplistic implementations. For a pixel shader, that's not to be sneezed at.