HSL Modulation with WebGL

For these prototypes I'm rendering couple of colorful images with WebGL and then I'm using a fragment shader to manipluate their color. I'm going to use HSL to perform the color manipulations because the implementation is extremely simple, but there's no reason you couldn't apply the same techniques to a more modern color modulation like HSB/HSV or LCH.

Hue

Saturation

Lightness

HSL modulation done by converting our RGB color into colors in HSL space, applying a small transformation to the HSL color values, and then converting the color back to RGB space for rendering. WebGL doesn't natively provide color space transformation opertaions so we'll need to write our own conversions from RGB to HSL and back.

8-bit RGB is typically represented by a color cube with dimensions from 0-255. WebGL uses a color cube with dimensions from 0-1, so we have to scale our color math proportionally.

HSL is a cylindrical colorspace, where hue is the angle around a circular cross-section, saturation is distance from the middle of a circular cross-section, and lightness is distance along the height of the cylinder.

Saturation and lightness are linear dimensions so they can be manipulated with proportional transformations. Hue is modified by adding additional distance to travel around the circular cross-section.


vec4 modulate(vec4 imageColor) {
  vec3 hsl = rgbToHsl(imageColor.rgb);
  vec3 rgb = hslToRgb(vec3(hsl.x + hsl_shift.x, hsl.y * hsl_shift.y, hsl.z * hsl_shift.z));
  return vec4(rgb, imageColor.a);
}

void main(void){
  gl_FragColor = modulate(texture2D(sampler0, vTex));
}
      

To avoid unpleasant jumps in our color manipulation animation, we'll reverse direction for our saturation and lightness transformations when we approach acceptable minimum and maximum values. Hue is traveling around a circle so we never need to reverse direction for a smooth animation -- once we've travelled fully around the circle we can just keep going. Just apply a modulus of either 1.0 if you're already in GL math space, 255 if you're working with a 0-255 slider, or 360 if you're thinking in angles.

You'll note that I'm not showcasing darkening with lightness modulation on this page. HSL has some pretty gnarly edge cases that come out of darker colors being more saturated, so near-white dithered colors start to look a bit deep fried in some cases. If you really need to modulate an image to be darker, I highly recommend using HSB/HSV instead.

The source for this page is available here.