Saving .png from render texture results in much darker image

I wrote a script that saves a render texture to a .png.

However, the results are not very good, the .png is always much darker than the Render texture is in Unity. How can I fix this so that there is no attempt at color correction?

Script:

 [SerializeField]
RenderTexture rt;

[SerializeField]
string fileName;

[Button("Save png")]
public void SavePNG()
{
var tex = new Texture2D(rt.width, rt.height);
RenderTexture.active = rt;
tex.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
tex.Apply();

var path = "Assets/Textures/Rendered textures/" + fileName + ".png";
File.WriteAllBytes(path, tex.EncodeToPNG());
Debug.Log("Saved file to: " + path);
}

• This looks like the difference between gamma and linear colour space. Have you tried using an SRGB render target? – DMGregory Jul 30 '20 at 19:26
• @DMGregory Ive tried changing around Color format on the render texture, is that the one you mean? – Majs Jul 30 '20 at 19:52
• Yep. Want to list which formats you've tried, and show any differences they made to your test case? – DMGregory Jul 30 '20 at 19:53
• Tbh I just tried 5-6 random once to see if that was the cause, could not see a difference. There is none named SRGB tho – Majs Jul 30 '20 at 19:56
• Hm, that's odd. You might need to set that via code, or set it on the temporary Texture2D you're reading it into. (Don't forget to dispose of this texture at the end of your function, by the way - they're not immediately garbage collected just because they went out of scope) – DMGregory Jul 30 '20 at 20:02

So I managed to get it working thanks to advice from DMGregory. Since sRGB is a readonly field, you have to create a rendertexture in code. Here is my full code:

[SerializeField]
RenderTexture rt;

[SerializeField]
Camera cam;

[SerializeField]
string fileName;

[Button("Save png")]
public void SavePNG()
{
RenderTexture mRt = new RenderTexture(rt.width, rt.height, rt.depth, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB);
mRt.antiAliasing = rt.antiAliasing;

var tex = new Texture2D(mRt.width, mRt.height, TextureFormat.ARGB32, false);
cam.targetTexture = mRt;
cam.Render();
RenderTexture.active = mRt;

tex.ReadPixels(new Rect(0, 0, mRt.width, mRt.height), 0, 0);
tex.Apply();

var path = "Assets/Textures/Rendered textures/" + fileName + ".png";
File.WriteAllBytes(path, tex.EncodeToPNG());
Debug.Log("Saved file to: " + path);

DestroyImmediate(tex);

cam.targetTexture = rt;
cam.Render();
RenderTexture.active = rt;

DestroyImmediate(mRt);
}


There are several possible reasons why this could occur. Here are a few.

1. Unity is displaying the real-time rendered image in a different color space then it uses when drawing the PNG, due to some internal color management quirk. If this is the case, DMGregory's comments should point you in the right direction.

2. Your operating system or image editor (it's not clear how you're viewing the PNG) uses a color profile that isn't applied to Unity. If you're on Windows 10, search for the "Color Management" utility and open it to see what color profile your PC is using (if any). If you're viewing the image in an image editing application such as Photoshop, check the application's color profile settings, and see if the image looks different when viewed with different software (e.g. Windows Photos vs Photoshop vs Paint)

3. Your video card drivers apply color correction settings that aren't the same between desktop and 3D. To determine this, you'll have to check your video card drivers.