hecrj / coffee

An opinionated 2D game engine for Rust
https://docs.rs/coffee
MIT License
1.09k stars 55 forks source link

Handle gamma correction properly #19

Closed hecrj closed 5 years ago

hecrj commented 5 years ago

Yesterday, when I was implementing #18, I noticed text was rendering differently using gfx vs wgpu.

Here is how it looked when using OpenGL:

image

Here is how it looked when using Vulkan:

image

It's a subtle difference, but the OpenGL version is way more readable (look at the uppercase I glyph). The difference? Gamma correction.

This made me start researching about color spaces and how to deal with them, which almost drove me to insanity... But I think I figured out the problem, and the solution is quite simple.

Monitors display colors in the sRGB color space. Therefore, if you have a framebuffer with a (0.5, 0.5, 0.5) color value, it will always display the same grey, independently of the format of the framebuffer.

wgpu_glyph stores a cache of glyphs in a texture of alpha values. This value is then combined with the font color in the wgpu_glyph fragment shader. Then, the blending stage happens.

The blending stage basically combines the color outputs of the fragment shader with the color of the target buffer. If your buffer is not marked as Srgb, then the values from the target buffer will be read as if they were linear and combined with the fragment shader outputs directly. Finally, the combined color will be written directly into the target buffer. Thus, you end up with linear values in your frame buffer, which then are displayed by your monitor as if they were sRGB. Bad! This causes grey values to look darker than they should, like you can observe in the Vulkan example above.

However, when the target buffer is marked as Srgb, the values will be read as Srgb and converted into linear before blending. Additionally, the combined color will be converted back into Srgb on write. This is what the OpenGL implementation was doing.

This PR fixes the wgpu graphics backend to use Srgb buffers for textures and render targets, except for the SwapChain which we are currently copying into. It also fixes font and clear color, by converting the Color type (which is sRGB) into linear. I have also added a colors example to test that colors are rendering properly in both graphics backends.