4
votes

I'd like to write a pixel shader that takes an input image, and converts all of the colors of one Hue range (i.e. HSV) into another Hue Range.

My motivation is simple: I want to color a bunch of different textures differently, but i don't want to color the entire texture, just the portion with a hue in a specific range. That way, I can draw one image of a racing car, and then change the color of just the stripes and the logo on the car with a pixel shader.

I looked at the HLSL documentation online and couldn't find anything to deal with hues. Is there a library of HLSL code available online?

Here's some pseudocode for what i'm trying to accomplish:

external float SrcMinHue,SrcMaxHue,TargetMin
void changeHues(image source)
{
   foreach x,y in image:
   {
      float sourceHue = getHue(source,x,y)
      if (SrcMinHue < sourceHue < SrcNaxHue):
          setHue(source,x,y,(TargetMin + (sourceHue - MinHue))
   }
}

I'm working with XNA Game Studio, if that information matters to anyone.

4

4 Answers

6
votes

Take a look at the "post RGB to HSV" sample over at the NVidia Shader Library page. That might give you some inspiration.

Otherwise I guess you could "simply" convert an RGB color to HSV in your pixel shader using the formula from Wikipedia and then take it from there.

3
votes

Make a matrix that rotates an RGB color around the [1,1,1] axis (the luminance axis)* by an angle that would rotate your source color onto your target color. Set this matrix as a pixel shader constant.

In the pixel shader, transform the pixel color by this matrix. Then, lerp between the untransformed and transformed colors, according to the angle between the untransformed pixel's hue and the source color's hue. When the angle is small, use the transformed color. When the angle is large, use the untransformed color.

To determine the hue of a pixel, subtract the average of its RGB* from the pixel. The angle between this vector and [1,0,0] is the hue.

*I'm aware that luminance is not this simple, but this is a good enough approximation for this case.

2
votes

Hue/Saturation/Brightness/Contrast HLSL pixel shader (shazzam .fx)

/// <class>7Aliens HSBC Hue/Saturation/Brightness/Contrast</class>
/// <description>Blend modes Brightness/Contrast (Photoshop CS) with Hue and Saturation.</description>

sampler2D input : register(s0);

/// <summary>The brightness offset.</summary>
/// <minValue>-2</minValue>
/// <maxValue>2</maxValue>
/// <defaultValue>0</defaultValue>
float SliderBrightnes : register(C0);

/// <summary>The brightness offset.</summary> 
/// <minValue>-1</minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0</defaultValue>
float SliderContrast : register(C1);

/// <summary>The brightness offset.</summary>
/// <minValue>-1</minValue>
/// <maxValue>1</maxValue>
/// <defaultValue>0</defaultValue>
float sliderSaturation : register(C2);

/// <summary>The brightness offset.</summary>
/// <minValue>-180</minValue>
/// <maxValue>180</maxValue>
/// <defaultValue>0</defaultValue>
float sliderHue : register(C3);

float3x3 QuaternionToMatrix(float4 quat)
{
    float3 cross = quat.yzx * quat.zxy;
    float3 square= quat.xyz * quat.xyz;
    float3 wimag = quat.w * quat.xyz;

    square = square.xyz + square.yzx;

    float3 diag = 0.5 - square;
    float3 a = (cross + wimag);
    float3 b = (cross - wimag);

    return float3x3(
    2.0 * float3(diag.x, b.z, a.y),
    2.0 * float3(a.z, diag.y, b.x),
    2.0 * float3(b.y, a.x, diag.z));
}

const float3 lumCoeff = float3(0.2125, 0.7154, 0.0721);

float4 Desaturate(float3 color, float Desaturation)
{
    float3 grayXfer = float3(0.3, 0.59, 0.11);
    float grayf = dot(grayXfer, color);
    float3 gray = float3(grayf, grayf, grayf);
    return float4(lerp(color, gray, Desaturation), 1.0);
}

float4 main(float2 uv : TEXCOORD) : COLOR 
{ 
    float4  cInput; 
    cInput = tex2D( input , uv.xy);

    float4 inputColor;
    inputColor = cInput;
    float4 blendColor;
    blendColor = cInput;
    float4 resultColor;
    resultColor = cInput;

    float3 hsv; 
    float3 intensity;           
    float3 root3 = float3(0.57735, 0.57735, 0.57735);
    float half_angle = 0.5 * radians(sliderHue); // Hue is radians of 0 tp 360 degree
    float4 rot_quat = float4( (root3 * sin(half_angle)), cos(half_angle));
    float3x3 rot_Matrix = QuaternionToMatrix(rot_quat);     
    resultColor.rgb = mul(rot_Matrix, inputColor.rgb);

    resultColor = Desaturate(resultColor, -sliderSaturation);

    inputColor = resultColor;`enter code here`
    blendColor = resultColor;
    resultColor = resultColor;

    blendColor.rgb = clamp(blendColor.rgb / blendColor.a, 0, 1);
    if (resultColor.r > 0.5) resultColor.r = 1 - (1 - 2 * (resultColor.r - 0.5)) * (1 - blendColor.r); else resultColor.r = (2 * resultColor.r) * blendColor.r;
    if (resultColor.g > 0.5) resultColor.g = 1 - (1 - 2 * (resultColor.g - 0.5)) * (1 - blendColor.g); else resultColor.g = (2 * resultColor.g) * blendColor.g;
    if (resultColor.b > 0.5) resultColor.b = 1 - (1 - 2 * (resultColor.b - 0.5)) * (1 - blendColor.b); else resultColor.b = (2 * resultColor.b) * blendColor.b;

    float4 colorOverlay = resultColor;
    colorOverlay = colorOverlay * SliderContrast;
    resultColor.rgb = (1 - (colorOverlay.a)) * inputColor.rgb + colorOverlay.rgb;       

    inputColor = resultColor;
    blendColor = resultColor;

    float4 colorScreen = resultColor;
    colorScreen.rgb = (1.0f - (1.0f - inputColor.rgb) * (1.0f - blendColor.rgb));
    colorScreen = -(colorScreen * SliderBrightnes * -(1 - inputColor.r));
    resultColor.rgb = (1 - (colorScreen.a)) * inputColor.rgb + colorScreen.rgb;

    return resultColor; 
}