How to do nothing in HDR




Hi,

Today's post is about how to do nothing in HDR. That's just a clickbait way of saying I want to transform an SDR/SRGB gamma encoded (hereafter referred to as just SDR) image such that it appears like an SDR image after PQ encoding. The buffer you'll retreive from an SDR swapchain on windows is precisely that: an SDR/SRGB gamma encoded texture. Essentially, I want to press the button that turns on HDR and see nothing happen.

This is useful because maybe you want to toggle or lerp between SDR/HDR, so that you can compare the two. In the HDR Injector, I need to do this because once I've created the HDR swapchain, windows doesn't want to go back to the SDR swapchain for some reason. Also, I need to be able to sample parts of the SDR backbuffer to determine which parts had UI on them, or if color grading/tonemapping/post-processing occurred.

Why is this even a problem?

As I mentioned in my previous posts, HDR images are encoded in a different way than SDR images. If you take an SDR image and pretend it's an HDR image, you will see some really bright/saturated stuff. This doesn't work for 3 reasons in combination:

  1. SDR images are gamma encoded.
  2. HDR uses Rec. 2020 color primaries, SDR uses Rec. 709 color primaries.
  3. HDR images are PQ encoded.

In order to Do Nothing, we need to account for each of these three things.

Gamma decoding

For the first issue, we really just need to de-gamma the SRGB image. The result is a linear image. This is fairly simple, just use this code taken from Microsoft's utility:

Once we've applied this code, our colors are in linear space, but still using Rec. 2020 primaries

Color space conversion

HDR images use the Rec. 2020 color space, so if we just naively say "ok, our linear SDR image is an HDR image" then that means our SDR Rec. 709 color primaries just became Rec. 2020 primaries. This is a bad thing. Our image would not only be super saturated, but it would also be hue shifted! Lets look at this color space image from my previous post again:

If you took the colors that were at the 709 green primary, and snapped it to the 2020 green primary, you'd shift everything away from yellow. Same goes for the red primary: everything would be less orange. Blue doesn't change that much. All of this is bad because it doesn't preserve the artistic intent of the image.

Luckily, there is a simple fix for this color primary problem. A matrix multiply will shrink your 2020 colors into 709 colors. The code is here, taken from Microsoft's utility:

Once you've shifted the colors back to 709 (inside 2020) then you shouldn't see any extra saturation or hue shifting.

PQ encoding

Ok, now we've got a linear Rec. 2020 color space image (though all the colors are within Rec. 709 triangle). In order to get this to output to an HDR screen, we need to do PQ encoding. But wait, the brightness is all wrong. Your monitor looks like this.

And you now have a nice rectangular sunburn.
In SDR, a value of (1,1,1) should not be 10,000 nits, but in HDR according to the specification, it is. So, we need to fix this up by setting a maximum brightness. This part probably depends on a few things, like how your monitor or windows or your game outputs SDR signals. For me and my settings, 180 nits is SDR "paper white." That's the brightness of (1,1,1) in an SDR image. I found this out by tweaking the following code until I saw no change in brightness after switching over to HDR. Here's the code for PQ encoding with a maximum brightness:

Using 180 nits for maxLuminance on my display, and windows settings caused no change in the output for me. Finally, I have Done Nothing.

Here's all the code together in one gist:

The next step for me is try to create a gamut expansion that preserves hue, and I  have some ideas about how to do that, but I'll save that for next time~!

Hit me up on the tweeto @pyromuffin if you got any questions, comments, hate-mail, sexts, etc.

XOXO
Kelly

Popular posts from this blog

Reverse Engineering Unity Games

Rotating a vector using integer math