Color Spaces
Beyond RGB to HSV and perceptual color
The RGB Problem
We have been working with RGB colors since the beginning. Red, green, blue. Three numbers between 0 and 1. Simple, direct, and how monitors actually work. So why would we ever use anything else?
Try this: what color is halfway between red and green?
In RGB, we would compute (1, 0, 0) + (0, 1, 0) / 2 = (0.5, 0.5, 0). That gives us a muddy yellow-brown. Not the vibrant orange you might expect from blending two bright primary colors.
Interactive: RGB Mixing
RGB mixing produces muddy yellows. HSV mixing through hue gives vibrant oranges.
RGB is designed around how monitors emit light, not how humans perceive color. When we want to reason about color relationships, to smoothly transition between hues or adjust brightness without shifting colors, RGB becomes awkward.
This is where alternative color spaces come in.
HSV: Hue, Saturation, Value
HSV rearranges color into three intuitive dimensions:
Hue is the angle around the color wheel. 0 is red, 0.33 is green, 0.67 is blue, and wrapping back to 1 brings you to red again. Hue answers the question: what color family are we in?
Saturation measures how pure the color is. At saturation 1, colors are vivid and intense. At saturation 0, everything becomes a shade of gray. Saturation is the distance from the center of the color wheel.
Value (sometimes called brightness) is how light or dark the color is. Value 1 gives the full intensity. Value 0 is black, regardless of hue or saturation.
Interactive: HSV Color Wheel
Click anywhere on the wheel to select a color. Angle is hue, distance from center is saturation.
The HSV model makes certain operations trivial. Want to shift a color toward sunset tones? Rotate the hue. Want to make something look faded or washed out? Reduce saturation. Want a darker version of the same color? Lower the value.
Converting RGB to HSV
The conversion from RGB to HSV involves finding which component is dominant (that determines hue), how much they differ (that determines saturation), and what the maximum is (that determines value).
vec3 rgb2hsv(vec3 c) {
vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}This code looks cryptic, but it efficiently computes what would otherwise require multiple conditionals. The step and mix functions replace branching with smooth math, which GPUs prefer.
Converting HSV to RGB
The reverse conversion reconstructs RGB from the angle and magnitude information:
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}The fract(c.xxx + K.xyz) generates three phase-shifted copies of the hue, which creates the RGB components after the absolute value and clamping. The final mix blends between white and the computed color based on saturation, then scales by value.
HSL: A Different Take
HSL is similar to HSV but uses Lightness instead of Value. The difference is subtle but important.
In HSV, a fully saturated color at maximum value is as bright as it can be. In HSL, maximum lightness is always white, regardless of saturation. Lightness 0.5 gives you the pure, vivid color. This makes HSL more symmetric: 0 is black, 0.5 is the pure hue, 1 is white.
Interactive: HSL vs HSV
HSV goes from black to full color. HSL goes from black through full color to white.
Artists often prefer HSL because the middle lightness value gives predictable, vivid results. Programmers often use HSV because the value component maps more directly to perceived brightness.
Converting HSL to RGB
vec3 hsl2rgb(vec3 c) {
vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);
return c.z + c.y * (rgb - 0.5) * (1.0 - abs(2.0 * c.z - 1.0));
}The Perceptual Problem
Neither HSV nor HSL solve a deeper issue: perceptual uniformity. Our eyes do not perceive brightness changes linearly. A jump from 0.1 to 0.2 looks like a bigger change than a jump from 0.8 to 0.9, even though both are the same numeric distance.
Similarly, colors at the same saturation and brightness in HSV do not look equally bright to us. Yellow appears much brighter than blue at identical HSV values.
Interactive: Perceptual Non-Uniformity
Yellow and cyan appear much brighter than blue, even at identical HSV values.
The perceived brightness varies dramatically. Green contributes most to perceived brightness, blue the least. This is why yellow (red + green) looks so bright.
Perceptually uniform color spaces like LAB and OKLAB address this by modeling human vision. In these spaces, equal numeric distances correspond to equal perceived differences. They are invaluable for tasks like generating color palettes or computing color differences, but they are more complex to implement and beyond the scope of this chapter.
For most shader work, HSV and HSL provide enough control to create beautiful color effects without the complexity of perceptual models.
Practical Applications
Palette generation. To create harmonious colors, pick a base hue and generate variations by rotating the hue by fixed amounts (30 degrees for analogous, 180 for complementary) while keeping saturation and value consistent.
Day/night cycles. Shift the hue of sky colors from blue toward orange while lowering saturation as the sun sets. This is far easier than manually computing RGB transitions.
Health bars and indicators. Interpolate hue from green (0.33) to red (0.0) based on a percentage. The transition passes through yellow naturally.
Color grading. Shift all hues by a constant amount to give footage a distinctive tint. Reduce saturation uniformly for a desaturated look.
Interactive: Color Space Explorer
Key Takeaways
- RGB is designed for monitors, not for human intuition about color
- HSV separates hue (color), saturation (purity), and value (brightness) into independent channels
- HSL is similar but treats lightness symmetrically: 0 is black, 0.5 is pure color, 1 is white
- Converting between spaces involves trigonometric relationships between color components
- Perceptual uniformity is a separate concern; HSV/HSL do not guarantee equal numeric distances look equal
- For color manipulation in shaders, HSV and HSL provide intuitive controls that RGB lacks