October 23, 2015

Dynamic Colors

To generate a galaxy full of interesting variety, I knew I would need to reuse textures. This was one of the reasons I switched to OpenGL 2.0, to use shaders to dynamically color textures.

Fragment Shader

To facilitate this, I needed a fragment shader that combined a grayscale texture (0-255) with a hue value (0-360) and a saturation value (0-100). However, OpenGL needs an RGB value, not an HSL value. Writing shaders is new to me, but thankfully I found some help online and came up with this:

  precision mediump float;
  uniform sampler2D u_TextureUnit;
  uniform float u_Alpha;
  uniform float u_Hue;
  uniform float u_Sat;
  varying vec2 v_TextureCoordinates;
  vec3 hsl2rgb(vec3 HSL)
    float R = abs(HSL.x * 6.0 - 3.0) - 1.0;
    float G = 2.0 - abs(HSL.x * 6.0 - 2.0);
    float B = 2.0 - abs(HSL.x * 6.0 - 4.0);
    vec3 RGB = clamp(vec3(R,G,B), 0.0, 1.0);
    float C = (1.0 - abs(2.0 * HSL.z - 1.0)) * HSL.y;
    return (RGB - 0.5) * C + HSL.z;
  void main()
     gl_FragColor = texture2D(u_TextureUnit, v_TextureCoordinates);
     vec3 rgb = hsl2rgb(vec3(u_Hue / 360.0, u_Sat / 100.0, gl_FragColor.x));
     gl_FragColor = vec4(rgb.r, rgb.g, rgb.b, gl_FragColor.a*u_Alpha);


Now using a single texture for a star like this:


I am able to dynamically color stars any hue.


And here is what changing the saturation for a particular hue looks like:


Texture Tools

To help create and test dynamically colored textures, I wrote some tools which can be found here. The following is an example using the current spacedust water texture.

The first script creates a grayscale image from a colored image.

./bin/extract-lightness examples/water.png examples/water-lightness.png

water lightness

The second script applies a hue (and saturation) to the grayscale image, like the shader does.

./bin/apply-hue 300 39 examples/water-lightness.png examples/water-hue-300.png ./bin/apply-hue 100 39 examples/water-lightness.png examples/water-hue-100.png

lightness hue300 hue100


My first attempt at a shader, I mistakenly used HSV instead of HSL. I honestly didn’t know there was a difference. HSV seems to be used more often, and many image editors have HSV color pickers. So that’s what I initially went with.

HSV color solid cylinder alpha lowgamma HSL color solid cylinder alpha lowgamma

Simply put, the problem with HSV is the range of colors for a particular hue/saturation does not go from white at its lightest, to black at its darkest, leaving you with a smaller range of color to work with.

So, for example, using this star texture:


Stars colored different hues ended up looking like this:


Not a lot I could do with the limited range of colors. Thankfully HSL solved that problem!

If you are interested in learning more, there is a useful page on Wikipedia about HSV and HSL.

And if you are looking for a good HSL color picker (since many image editors don’t have one), I recommend this web tool, written by Brandon Mathis. You can read about his discovery of HSL here.

tagged: resource code