Saturation and Hue in GLSL

How do I go about changing the hue or saturation of an RGB color by the mean of a GLSL shader without the RGB to HSV back to RGB cycle?
I already figured how to modify the contrast and brightness through this shader, and I need only the last two components to finish my demo.

<Shaders 
   linkProcessors    = "true" 
   vertexProcessor   = "VERTEX_SHADER"
   fragmentProcessor = "FRAGMENT_SHADER">

  <VERTEX_SHADER>
    <RawData>
      void main(void)
      { 
        gl_TexCoord[0] = gl_MultiTexCoord0;
        gl_Position    = ftransform();
      }
    </RawData>
  </VERTEX_SHADER>

  <FRAGMENT_SHADER>

    <Uniform name = "videoParams"  size = "2" type = "float" x = "0.5"
                                                             y = "1.0"/>

    <Uniform name = "proccessMatRow0" type = "float" size = "4"/>
    <Uniform name = "proccessMatRow1" type = "float" size = "4"/>
    <Uniform name = "proccessMatRow2" type = "float" size = "4"/>

    <Uniform name = "first"      size = "1" type = "int"   x =   "0"  />
    <Uniform name = "second"     size = "1" type = "int"   x =   "1"  />

    <RawData>
      uniform sampler2D second;
      uniform sampler2D first;

      uniform vec2  videoParams;
      uniform vec4  proccessMatRow0;
      uniform vec4  proccessMatRow1;
      uniform vec4  proccessMatRow2;

      const   vec4  halfVector = vec4(0.5, 0.5, 0.5, 0.0);

      vec4 applyPostProcessingMatrix(vec4 color)
      {
        vec4 outColor;
 
        outColor.x  = dot(proccessMatRow0, color);
        outColor.y  = dot(proccessMatRow1, color);
        outColor.z  = dot(proccessMatRow2, color);

        outColor   -= halfVector;
        outColor   *= videoParams.y;
        outColor   += halfVector;
        return outColor;
      }

      void main(void)
      {
        vec4  firstColor  = texture2D(first , gl_TexCoord[0].xy)*(1.0 - videoParams.x),
              secondColor = texture2D(second, gl_TexCoord[0].xy)*videoParams.x;

        gl_FragColor = applyPostProcessingMatrix(firstColor + secondColor);
      }
    </RawData>
  </FRAGMENT_SHADER>
</Shaders>

The contrast value is loaded in the y component of videoParams and the brightness is in x;

Screenshot

Binaries
Demo source
Engine source

Am I posting this on the wrong forum? :confused:

Originally posted by Java Cool Dude:
How do I go about changing the hue or saturation of an RGB color by the mean of a GLSL shader without the RGB to HSV back to RGB cycle?
Is this an absolute requirement, or are you just concerned that the pretty large chunk of math for that would slow you down? If the latter, then consider using two 3D textures as a lookup table. With linear filter a 32x32x32 is usually good enough. There’s a sample in the Radeon SDK called PostProcessing that does this. The shader looks like this:

uniform sampler2D Image;
uniform sampler3D RGB2HSI;
uniform sampler3D HSI2RGB;

uniform float hueShift;
uniform float satBoost;

varying vec2 texCoord;

void main(){
	// Sample the image
	vec3 rgb = texture2D(Image, texCoord).rgb;
	// Look up the corresponding HSI value
	vec3 hsi = texture3D(RGB2HSI, rgb).xyz;

	// Manipulate hue and saturation
	hsi.x = fract(hsi.x + hueShift);
	hsi.y *= satBoost;

	// Look up the corresponding RGB value
	gl_FragColor = texture3D(HSI2RGB, hsi);
}

Man you’re awesome, how ya been? Haven’t seen ya around in a while, how’s your job at ATi’s? I myself might be going to Nvidia this summer and I’m really looking forward to it :slight_smile:
Peace.

The RGB to HSI back to RGB conversion through the 3D textures look ups appear to be quite hideous… to be honest :stuck_out_tongue:

All settings including Saturation and Hue are now working. The thing is, I compute a single post processing matrix on the CPU that applies the brightness contrast and hue alteration before uploading it to the GPU via uniforms.

<Shaders 
   linkProcessors    = "true" 
   vertexProcessor   = "VERTEX_SHADER"
   fragmentProcessor = "FRAGMENT_SHADER">

  <VERTEX_SHADER>
    <RawData>
      void main(void)
      { 
        gl_TexCoord[0] = gl_MultiTexCoord0;
        gl_Position    = ftransform();
      }
    </RawData>
  </VERTEX_SHADER>

  <FRAGMENT_SHADER>

    <Uniform name = "videoParams"  size = "2" type = "float" x = "0.5"
                                                             y = "1.0"/>

    <Uniform name = "proccessMatRow0" type = "float" size = "4"/>
    <Uniform name = "proccessMatRow1" type = "float" size = "4"/>
    <Uniform name = "proccessMatRow2" type = "float" size = "4"/>

    <Uniform name = "first"      size = "1" type = "int"   x =   "0"  />
    <Uniform name = "second"     size = "1" type = "int"   x =   "1"  />

    <RawData>
      uniform sampler2D second;
      uniform sampler2D first;

      uniform vec2  videoParams;
      uniform vec4  proccessMatRow0;
      uniform vec4  proccessMatRow1;
      uniform vec4  proccessMatRow2;

      const   vec4  halfVector = vec4(0.5, 0.5, 0.5, 0.0);

      vec4 applyPostProcessingMatrix(vec4 color)
      {
        vec4 outColor;
 
        outColor.x  = dot(proccessMatRow0, color);
        outColor.y  = dot(proccessMatRow1, color);
        outColor.z  = dot(proccessMatRow2, color);

        outColor   -= halfVector;
        outColor   *= videoParams.y;
        outColor   += halfVector;
        return outColor;
      }

      void main(void)
      {
        vec4  firstColor  = texture2D(first , gl_TexCoord[0].xy)*(1.0 - videoParams.x),
              secondColor = texture2D(second, gl_TexCoord[0].xy)*videoParams.x;

        gl_FragColor = applyPostProcessingMatrix(firstColor + secondColor);
      }
    </RawData>
  </FRAGMENT_SHADER>
</Shaders>

Originally posted by Java Cool Dude:
Man you’re awesome, how ya been? Haven’t seen ya around in a while, how’s your job at ATi’s? I myself might be going to Nvidia this summer and I’m really looking forward to it :slight_smile:
Peace.

Hi, I’m fine, and things are going fine at ATI too. :slight_smile: I’ve been around here pretty much on a daily basis with a few exceptions for quite a long time. I haven’t seen you around though in quite a while.

Originally posted by Java Cool Dude:
The RGB to HSI back to RGB conversion through the 3D textures look ups appear to be quite hideous… to be honest :stuck_out_tongue:
But it works pretty darn well. With tables as small as 32x32x32 I’ve not been able to see any visual difference compared to using the full math. 16x16x16 gives good quality as well. 32x32x32 32bit is only 128kb, which is less than a regular 256x256 texture.

Hmmm, I noticed a lot of artifacts when I retrieved the HSV values and then used them immidiately to look up the source RGB.
Could be the way I load up the volume textures (my DDS loader is still in its infancy).
Besides, computing the hue processing matrix on the CPU and then uploading its first 3 rows to the fragment shader seems to give quite a performance boost. That and you don’t have to recompute the matrix at every frame, the need to do so only arises when the Hue or Saturation values are modified.

Here’s the code snippet

void SceneFrame::updatePostProcessMatrix()
{
  float c            = fastCos(hue),
        valSquare    = 0.57735f*0.57735f *(1.0f - c),
        oneMinusSat  = 1.0f - saturation,
        valTimesSine = 0.57735f*fastSin(hue);

  float weightX      = oneMinusSat*0.3086f,
        weightY      = oneMinusSat*0.6094f,
        weightZ      = oneMinusSat*0.0820f;

  postProcessingMatrix.setScale(brightness, brightness, brightness);
  temp1.setElements(weightX + saturation, weightX, weightX, 0.0f,
                    weightY, weightY + saturation, weightY, 0.0f,
                    weightZ, weightZ, weightZ + saturation, 0.0f,
                    0.0f, 0.0f, 0.0f, 1.0f);

  postProcessingMatrix *= temp1;

  temp1.setElements(valSquare + c,
                    valSquare - valTimesSine,
                    valSquare + valTimesSine,
                    0.0f,
                    valSquare + valTimesSine,
                    valSquare + c,
                    valSquare - valTimesSine,
                    0.0f,
                    valSquare - valTimesSine,
                    valSquare + valTimesSine,
                    valSquare + c,
                    0.0f,
                    0.0f, 0.0f, 0.0f, 1.0f);

  postProcessingMatrix *= temp1;
  postProcessingMatrix.setTranspose();
}

PS: yeah I’ve been busy with school lately, and I went to Europe for about 10 days :slight_smile:

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.