RenderTexture to UI - Trying, and failing, to make a portrait camera?

I’ve been beating my head against the desk all day on what I thought would be rather simple, and finally at the end of my wits I’m here to beg for help from some kind souls :slight_smile:

What I am trying to accomplish here is to render a single frame from a special portrait camera rig I have setup, which will be a 2d portrait made from a 3d character. This portrait will then be used in the UI. I imagine this is pretty common stuff and must have been done tons of times.

Eventually this would be fired off on demand in script but for now just to build it I am using a UI button to trigger the process. Unfortunately no matter what I do I either end up with errors, assertions or at best “something” works but instead of getting the expected render in the UI I instead see essentially a screenshot of the full game view which is obviously not correct.

Any help at all would be insanely apprecciated.

public class PortraitCamera : MonoBehaviour
{
    public Camera portraitCamera;
    public RawImage destinationTexture;
    public Texture2D portraitTexture;
    public void TakePortrait()
    {
        RenderTexture rt = new RenderTexture(1024, 1024, 24);
        portraitCamera.targetTexture = rt;
        portraitCamera.aspect = 1f;
        portraitCamera.Render();
        StartCoroutine(SaveCameraView(rt));
    }
   
    public IEnumerator SaveCameraView(RenderTexture rt)
    {
        yield return new WaitForEndOfFrame();
        portraitTexture = new Texture2D(512, 512, TextureFormat.RGB24, false);
        // RenderTexture.active = rt;
        portraitTexture.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
        portraitTexture.Apply();
        destinationTexture.texture = portraitTexture;
    }
}

Double check which camera you’re using? What happens if you render that camera to the screen or a viewport instead? Is it showing the right thing?

Yes the camera is correct and renders correct otherwise. I have already verified all that. I think what is happening is I am getting a corrupted sample back or something and that is why I get the wrong render instead.

EDIT: The portrait camera rig is setup using completely different game layers, so it physically can not render anything but the character and light rig it is setup for.

Here is what the portrait camera sees:

But when I try to grab it with the code above and place it into the UI, this is what I get:

The portrait is supposed to be in that box, but as you can see instead I got a whole copy of the main game view in there instead.

So breaking the problem down into two distinct phases has added some clarity.

At first I had tried that line which is commented out in the original code, RenderTexture.active = rt but I thought that was breaking things as I saw no result, not even a broken image. When I broke the problem down though, I started with “save the render texture to disk” and saw that with that line enabled, I did get an image, it was just all white which is why I didn’t notice it in the UI.

So I know have it broken out in two phases:

  1. Render the image and save it to PNG on disk
  2. Load that PNG into the UI

Doing this I can clearly see that the first part isn’t even working. Everything I try I only get basically a white or a black image rendered to disk. So clearly I have something wrong in my RenderTexture setup.

Finally got this all working. I will post the final code for future reference once I’ve cleaned it up.

1 Like

Here is the final working code

using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;

public class PortraitCamera : MonoBehaviour
{
    public Camera portraitCamera;
    public RawImage destinationTexture;
    public Texture2D portraitTexture;
    public RenderTexture rt;
    public void TakePortrait()
    {
        portraitCamera.aspect = 1f;

        if (rt == null)
        {
            rt = new RenderTexture(1024, 1024, 24);
        }
        portraitCamera.targetTexture = rt;
        RenderTexture.active = rt;
        portraitCamera.Render();
        StartCoroutine(SaveCameraView());
    }
   
    public IEnumerator SaveCameraView()
    {
        yield return new WaitForEndOfFrame();
        portraitTexture = new Texture2D(rt.width, rt.height, TextureFormat.RGB24, false);
        RenderTexture.active = rt;
        portraitTexture.ReadPixels(new Rect(0, 0, rt.width, rt.height), 0, 0);
        RenderTexture.active = null;
        portraitTexture.Apply();
        byte[] bytes = portraitTexture.EncodeToPNG();
        File.WriteAllBytes(Application.dataPath + "/test.png", bytes);
        destinationTexture.texture = portraitTexture;
    }
}
1 Like

Works like it says on the box! Thanks!

As a side note, portraitTexture and rt can just be private. :slight_smile:

It was easier to debug that way :slight_smile:

1 Like