◀️ 🔼 🔽 ▶️

3D Game Shaders For Beginners

Gamma Correction

Gamma Correction

Correcting for gamma will make your color calculations look correct. This isn't to say they'll look amazing but with gamma correction, you'll find that the colors meld together better, the shadows are more nuanced, and the highlights are more subtle. Without gamma correction, the shadowed areas tend to get crushed while the highlighted areas tend to get blown-out and over saturated making for a harsh contrast overall.

If you're aiming for realism, gamma correction is especially important. As you perform more and more calculations, the tiny errors add up making it harder to achieve photorealism. The equations will be correct but the inputs and outputs will be wrong leaving you frustrated.

It's easy to get twisted around when thinking about gamma correction but essentially it boils down to knowing what color space a color is in and how to convert that color to the color space you need. With those two pieces of the puzzle, gamma correction becomes a tedious yet simple chore you'll have to perform from time to time.

Color Spaces

sRGB vs RGB

The two color spaces you'll need to be aware of are sRGB (standard Red Green Blue) and RGB or linear color space.

identify -format "%[colorspace]\n" house-diffuse-srgb.png
sRGB

identify -format "%[colorspace]\n" house-diffuse-rgb.png
RGB

Knowing what color space a color texture is in will determine how you handle it in your shaders. To determine the color space of a texture, use ImageMagick's identify. You'll find that most textures are in sRGB.

convert house-diffuse-srgb -colorspace rgb house-diffuse-rgb.png

To convert a texture to a particular color space, use ImageMagick's convert program. Notice how a texture is darkened when transforming from sRGB to RGB.

Decoding

The red, green, and blue values in a sRGB color texture are encoded and cannot be modified directly. Modifying them directly would be like running spellcheck on an encrypted message. Before you can run spellcheck, you first have to decrypt the message. Similarly, to modify the values of an sRGB texture, you first have to decode or transform them to RGB or linear color space.

  // ...

  color     = texture(color_texture, uv);
  color.rgb = pow(color.rgb, 2.2);

  // ...

To decode a sRGB encoded color, raise the rgb values to the power of 2.2. Once you have decoded the color, you are now free to add, subtract, multiply, and divide it.

By raising the color values to the power of 2.2, you're converting them from sRGB to RGB or linear color space. This conversion has the effect of darkening the colors.

Color Darkening

For example, vec3(0.9, 0.2, 0.3) becomes vec3(0.793, 0.028, 0.07).

Gamma Curves

The 2.2 value is known as gamma. Loosely speaking, gamma can either be 1.0 / 2.2, 2.2, or 1.0. As you've seen, 2.2 is for decoding sRGB encoded color textures. As you will see, 1.0 / 2.2 is for encoding linear or RGB color textures. And 1.0 is RGB or linear color space since y = 1 * x + 0 and any base raised to the power of 1.0 is itself.

Non-color Data

Non-color Data

One important exception to decoding is when the "colors" of a texture represent non-color data. Some examples of non-color data would be the normals in a normal map, the alpha channel, the heights in a height map, and the directions in a flow map. Only decode color related data or data that represents color. When dealing with non-color data, treat the sRGB color values as RGB or linear and skip the decoding process.

Encoding

Perceptually versus Actually Linear

The necessity for encoding and decoding stems from the fact that humans do not perceive lightness linearly and most displays (like a monitor) lack the precision or number of bits to accurately show both lighter and darker tonal values or shades. With only so many bits to go around, colors are encoded in such a way that more bits are devoted to the darker shades than the lighter shades since humans are more sensitive to darker tones than lighter tones. Encoding it this way uses the limited number of bits more effectively for human perception. Still, the only thing to remember is that your display is expecting sRGB encoded values. Therefore, if you decoded a sRGB value, you have to encode it before it makes its way to your display.

  // ...

  color     = texture(color_texture, uv);
  color.rgb = pow(color.rgb, 1.0 / 2.2);

  // ...

To encode a linear value or convert RGB to sRGB, raise the rgb values to the power of 1.0 / 2.2. Notice how 1.1 / 2.2 is the reciprocal of 2.2 or 2.2 / 1.0. Here you see the symmetry in decoding and encoding.

Not Gamma Corrected versus Gamma Corrected

Source

(C) 2020 David Lettier
lettier.com

◀️ 🔼 🔽 ▶️