WebGL Color Spaces and models

Exploring the intricacies of color spaces and color models

I've had the same problem in too many of my projects. Using photoshop to slightly modify a texture, a normal map, or a light map, magically makes the texture slightly different and my render doesn't look it's supposed to look. So, what is happening to my images?

Turns out there's more to color than meets the eye. And there's definitely more color than meets your computer screen.

Color spaces are like the range of colors. Some spaces can represent more colors than others: sRGB represents the range of colors most screens use, while Adobe RGB represents a larger range commonly used for printing.

Color models are different ways of representing colors: RGB, HSL, HSB, CMYK. They may represent the same color but in different ways.

For example: sRGB is a color space that uses RGB as it's color model. Hella confusing, but they are slightly different things, terminology is important.

Another Example: Adobe RGB is a color space that has a larger color range which works well for the CMYK color model(used for printing).

Getting started


sRGB vs Linear sRGB Color spaces

sRGB color space is the most common space used for monitors, it looks good our viewing experience, and is good for storage. So, most color images (png, jpg, etc..) are in the sRGB color space.

Linear-sRGB represents the same color range as sRGB, but without the gamma correction that makes it look good to us. Linear-sRGB is best suited for lighting calculations and other render math. You can see the difference here. The top is sRGB and the bottom is Linear-sRGB

So when calculating lighting for an object in a shader, all sRGB textures usually transformed to Linear-sRGB when used in light calculations and the result of the shader is transformed into sRGB as a final step on the shader.

In short, The bulk of the work is done in Linear-sRGB for light calculations to look correctly, but at the end our renderer should output sRGB (in most cases)

In ThreeJS this is handled all for yollu. But once in a while, you will run into your textures looking weird. Understanding the difference helps a lot to fix the issue.

Shadertoy demo by rasterbars

Photoshop messes with images.

By default, photoshop is a program used to modify color images, and output color images, usually using sRGB.

Some of the textures we use for 3D aren't color at all images (roughness maps) or they use Linear-sRGB directly instead of sRGB.

So, if you open those images in Photoshop, it may incorrectly assume they use sRGB (if the color profile is missing). And it may incorrectly assume that you want them exported in sRGB as well.

To fix these issues, make sure when you import to Photoshop select the correct color format. And when exporting make sure the "convert to sRGB" isn't checked if not needed.

Image PBR material by Bers

RGB Cosine Palettes

Cosine palettes are function made by Inigo Quilez that creates a palette in the RGB color space. They are an easy and performant way to create infinite color palettes with a wide or short range of change.

vec3 palette( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d )
return a + b*cos( 6.28318*(c*t+d) );

I'm a big fan of these cosine palettes, they are all over my demos. They are just easy to use, and get some decent results with.

Cosine Palettes by iq

Audio Reactive HSV

(If the demo doesn't play right away for you, click on the play icon on iChannel0 square)

While RGB is the color model for sRGB, we most commonly use HSV/HSB as our color pickers because they are more intuitive to use. However, internally each image still uses RGB.

This demo by airtight places points around the circle and uses HSV to give each dot its colors sequentially. Then, he increases and decreases the size of the dots using the music

Audio visualizer by Airtight

HSB Analogous colors

In beervgeer's demo, he creates the color in RGB space. Then, transformes it to HSB to change the hue of the color with time, and keeps the saturation at 0.5. Then, transforms the HSB back into RGB. And merges the initial, and modified color together.

vec3 hue = rgb2hsb(powerColor);
hue.x = mod(time/10.,1.);
hue.y = 0.5;
float d = 1.-distance(vec2(.5),st)*2.;

fragColor = vec4( (hsb2rgb(hue)*d )+(powerColor*d*0.),1.0);

Giving the HSB the same hue makes the color have the same color, but varying brightness. And adding the RGB at the end makes it vary slightly creating a mix of analogous colors.

There's a lot more to say about actual color management which I'm going to focus on the next issue. More about color management in WebGL, how threeJS manages colors for you, and how to not be confused about what color format, or color output in your WebGL scenes anymore!

Audio visualizer by beervgeer

Further reading / Inspiration