SoMuch SMx
SoMuch SMx

Reputation: 13

Unity Texture2D.ReadPixels not reading correctly from rendertexture

Basically I want a function that can take screenshot to a thumbnail in runtime.

here is my code run after RenderPipelineManager.endCameraRendering in URP in 2022.3:

    private void TakeScreenshot(int w, int h)
    {
        shouldTakeScreenshot = false;

        RenderTexture rt = RenderTexture.GetTemporary(w, h, 16, RenderTextureFormat.ARGB32);
        Camera.main.targetTexture = rt;

        Texture2D t = new Texture2D(rt.width, rt.height, TextureFormat.ARGB32, false);

        //RenderTexture.active = rt;
        //Graphics.CopyTexture(rt, t);
        t.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);

        RenderTexture.ReleaseTemporary(Camera.main.targetTexture);
        Camera.main.targetTexture = null;
        //RenderTexture.active = null;

        //t.Apply();
        //ri.texture = t;

        screenshotBytes = ImageConversion.EncodeToJPG(t, 25);
    }

I had a fully working TakeScreenshot() to take a Screen size Screenshot, the code is same as above without all the comment.

the main problem is:

without all the comment, it did output the size i want but it's not scaled like this

enter image description here

which it try to not release the RenderTexture by commenting

        //RenderTexture.ReleaseTemporary(Camera.main.targetTexture);
        //Camera.main.targetTexture = null;

the rendertexture in main cam seems correct, what should i do?

enter image description here


the other problems I'm experiencing is:

I saw so many solution, they all have RenderTexture.active = rt;, but once i uncomment it, it just a black screen, same as Graphics.CopyTexture(rt, t); but it's gray.

Upvotes: 1

Views: 756

Answers (1)

derHugo
derHugo

Reputation: 90813

Afaik without setting the RenderTexture active the ReadPixels will directly read from your screen instead.

The result you observed in the Inspector is actually only being rendered into the temporary render texture at the end of the frame after your code already finished.

Usually you need these additional steps:

  • force the camera to render into the render texture - otherwise you would need to wait until the next frame so the camera at least once rendered to the render texture.
  • make the render texture active before ReadPixels in order to read from the rt instead of the screen

so something like e.g.

private void TakeScreenshot(int w, int h)
{
    shouldTakeScreenshot = false;

    var rt = RenderTexture.GetTemporary(w, h, 16, RenderTextureFormat.ARGB32);
    Camera.main.targetTexture = rt;
    
    // Required to actually fill the rt
    Camera.Render();

    var t = new Texture2D(w, h, TextureFormat.ARGB32, false);

    // Required in order to not read from screen but from rt
    var previousRT = RenderTexture.active;
    RenderTexture.active = rt;

    t.ReadPixels(new Rect(0, 0, w, h), 0, 0);
    RenderTexture.ReleaseTemporary(rt);
    Camera.main.targetTexture = null;
    RenderTexture.active = previousRT;

    // Not sure if required if not to be displayed in the scene
    // might be able to be omitted 
    t.Apply();

    screenshotBytes = ImageConversion.EncodeToJPG(t, 25);

    // Important! Without this your memory will bit by bit be filled
    // as textures (as other UnityEngine.Object based types) are not GC collected
    Destroy(t);
}

Might still want to performance check again whether ReadPixels is faster than Graphics.CopyTexture(rt, t);.

Upvotes: 1

Related Questions