ScreenCapture.CaptureScreenshot fails when HDR is enabled

I’m using CaptureScreenshot at specific points in my game.

Everything works fine usually, but I’ve recently added HDR support for the game and the above function returns “Failed to capture screen shot” in the player.log when HDR Mode is enabled.

Can anyone confirm that the function should be able to capture HDR screenshots and have it working ? Thanks

(Running Unity 2020.3.25 Standalone build on Windows 10 desktop)

CaptureScreenshot saves the screenshot as a png file. PNGs are not HDR capable.

Do you want a .HDR or .EXR file? Or do you want Unity to convert the HDR colors to SDR before saving (run tonemapping)?

Ah ok I see… makes sense.

im looking for a PNG file output so that all screenshots are in the same format. I’m only using them as large thumbnails. If you could offer any hints how to achieve that, would be much appreciated. thanks for your help.

Can’t say for sure, unfortunately, because I haven’t tried this myself.

This could work if you are using HDR rendering with SDR output:

Maybe combining WaitForEndOfFrame with CaptureScreenshot would also work in that case.

If you are using HDR rendering with HDR output, it’s more complicated because then you probably have to add a temporary tonemapping pass for the screenshot.

Maybe somebody else can help.

PS: This is how it would work if you wanted an exr: Unity - Scripting API: ImageConversion.EncodeToEXR

Just had an idea which shouldn’t be too hard to implement:

  • Read the floating point HDR colors with Texture2D.ReadPixels
  • Run a manual tonemapping pass on the CPU (ACES, Reinhard or Uncharted operators are easy to implement)
  • Store the converted colors in a new Texture2D with regular RGB24 format.
  • Call ImageConversion.EncodeToPNG on it
float3 ACESFilmOperator(float3 x)
{
    const float a = 2.51f;
    const float b = 0.03f;
    const float c = 2.43f;
    const float d = 0.59f;
    const float e = 0.14f;
    return saturate((x * (a * x + b)) / (x * (c * x + d) + e));
}

float3 ReinhardOperator(float3 hdrColor)
{
    return hdrColor / (1.0 + hdrColor);
}

float3 Uncharted2Operator(float3 x)
{
    const float A = 0.15;
    const float B = 0.50;
    const float C = 0.10;
    const float D = 0.20;
    const float E = 0.02;
    const float F = 0.30;
    const float W = 11.2;
    return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}

(you have to convert this to C# code, of course)
Also, you probably have to call Color.gamma on the result because PNGs are stored in SRGB format.

Many thanks! Looks straightforward as you say - I’ll give it a shot