3
\$\begingroup\$

I have been reading the Real Time Rendering book and stumbled upon this paragraph:

For textures encoded in a nonlinear space (such as most color textures), ignoring gamma correction when filtering will modify the perceived bright- ness of the mipmap levels [121]. As you get farther away from the object and the uncorrected mipmaps get used, the object can look darker overall, and contrast and details can also be affected. For this reason, it is impor- tant to convert such textures into linear space, perform all mipmap filtering in that space, and convert the final results back into nonlinear space for storage.

I have never taken into account gamma correction for color textures using mipmapping.

For generating mipmaps I have always used the GPU-implementation (OpenGL):

glGenerateMipmap

In the resources I have learned from, gamma correction has not been mentioned as something that has to be taken into account for mipmapping.

I was wondering, since color textures that include gamma correction seem to be the norm, are the GPU-provided filtering functions already taking into account gamma correction?

\$\endgroup\$
3
  • \$\begingroup\$ This sounds like something you could test for yourself: make a test pattern with lots of adjacent bright/dark regions, save & import as an sRGB texture. Generate mipmaps using your most familiar method. Inspect the resulting texel values of the mips, and compare against the expected values you computed by hand using linear and gamma space intermediates. Which one matches? \$\endgroup\$
    – DMGregory
    Nov 13, 2019 at 22:28
  • \$\begingroup\$ This answer on StackOverflow deals with how sRGB textures are dealt with by OpenGL, including how they're filtered, but doesn't address mipmap generation specifically: stackoverflow.com/questions/10347995/… \$\endgroup\$
    – Ross Ridge
    Nov 14, 2019 at 1:54
  • \$\begingroup\$ @DMGregory sRGB support in OpenGL is notoriously flaky. All this would tell you is if your GPU/driver does this properly, but not if your customers may end up seeing a different result. \$\endgroup\$
    – Tara
    Jul 6, 2023 at 3:19

2 Answers 2

3
\$\begingroup\$

Taking from the OpenGL 4.4 specification:

The contents of the derived arrays are computed by repeated, filtered reduction of the levelbase array. For one- and two-dimensional array and cube map array textures, each layer is filtered independently. No particular filter algorithm is required, though a box filter is recommended as the default filter.

So we can see that this is farily weakly-specified, and details of how submips are filtered are left up to the implementation.

In other words: if you require gamma-correction you can't make any assumptions about how, or even if, your GL implementation will do it, so you will need to write your own miplevel reduction in software.

\$\endgroup\$
2
\$\begingroup\$

Some people in the comments pointed out some mistakes in my previous answer. This answer tries to fix all those issues. Thanks for the feedback!

Here is the new code.

I have chosen better images for the experiment this time.

This image has more high-frequency contrast changes.

enter image description here

And this is a extreme case: black and white checker 1-pixel grid.

enter image description here

Now the program uses more correct linear<->srgb conversion formula:

#define SIMPLIFIED_SRGB_FORMULA 0
// https://stackoverflow.com/questions/61138110/what-is-the-correct-gamma-correction-function
static float linearToGamma(float x) {
    #if SIMPLIFIED_SRGB_FORMULA
        return powf(x, 2.2f);
    #else
        return x <= 0.0031308f
            ? x * 12.92f
            : powf(x, 1.0f / 2.4f) * 1.055f - 0.055f;
    #endif
};
static float gammaToLinear(float x) {
    #if SIMPLIFIED_SRGB_FORMULA
        return powf(x, 1.f/2.2f);
    #else
        return x <= 0.04045f
            ? x / 12.92f
            : powf((x + 0.055f) / 1.055f, 2.4f);
    #endif
};

The new program generates 4 images and compares them:

  1. cpu_no_conversion.png: box filtering performed on the CPU, without any gamma conversions
  2. cpu_conversion.png: box filtering performed on the CPU, converting from gamma to linear, and back from linear to gamma
  3. gpu_no_conversion.png: let OpenGL create the mipmaps, RGB format
  4. gpu_conversion.png: let OpenGL create the mipmaps, SRGB format

cpu_no_conversion.png cpu_conversion.png gpu_no_conversion.png gpu_conversion.png

cpu_no_conversion.png cpu_conversion.png gpu_no_conversion.png gpu_conversion.png

As you can see in the previous images, there are some visual differences if we do the gamma conversion or not. This is very obvious for the checkers sample image. But noticeable for the tent sample image as well.

OpenGL seems to be doing something similar to our gamma<->linear conversion. So, at least in my system, OpenGL seems to be doing things right.

The program also prints a table with the difference between the generated images.

Meaning of the labels:

  • CPU: downscale performed in the CPU
  • GPU: downsample computed with OpenGL's glGenerateMipmap
  • NG: doesn't do any gamma conversion
  • G: converts from gamma space to linear when reading, and back to gamma when writing

For the tent image:

CPU, NG CPU, G GPU, NG GPU, G
CPU, NG 0 2102348 49224 2121146
CPU, G 2102348 0 2276882 10106
GPU, NG 49224 2276882 0 2295830
GPU, G 2121146 10106 2295830 0

For the checker image:

CPU, NG CPU, G GPU, NG GPU, G
CPU, NG 0 176947200 0 176947200
CPU, G 176947200 0 176947200 0
GPU, NG 0 176947200 0 176947200
GPU, G 176947200 0 176947200 0

For the checkers sample image, OpenGL perfectly matches the output of our CPU box-filtering in all cases (with conversion and without)!

For the tent sample image, it's doesn't 100% match but it's very close.

I performed the tests in Windows, and the RX6600 GPU.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Nice job! Rare to see someone putting this much effort into an old question. Your findings match what I discovered with Photoshop before you finished your updated answer. I compared mipmap generation using linear 32 bit float workflow against 8 bit per channel non-linear workflow. I also used a checkerboard texture and got a result very similar to yours. Your code also looks correct to me. If I may make one small suggestion: It is not entirely clear to me what your tables are showing (especially which value corresponds to which image). \$\endgroup\$
    – Tara
    Aug 8, 2023 at 23:57
  • 1
    \$\begingroup\$ Thanks for the suggestion: I've added labels to the tables now. I'll delete the old answer \$\endgroup\$
    – tuket
    Aug 9, 2023 at 10:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .