unity-game-engine8thwall-xr

8th Wall XR for Unity: Saving RealityTexture to disk


I'm trying to save the camera's feed to a png using this code but it seems not to work correctly:

void RecordAndSaveFrameTexture()
{
    if (mXr.ShouldUseRealityRGBATexture())
    {
        SaveTextureAsPNG(mXr.GetRealityRGBATexture(), mTakeDirInfo.FullName + "/texture_" + mFrameInfo.mFrameCount.ToString() + ".png");
    }
    else
    {
        SaveTextureAsPNG(mXr.GetRealityYTexture(), mTakeDirInfo.FullName + "/texture_y_" + mFrameInfo.mFrameCount.ToString() + ".png");
        SaveTextureAsPNG(mXr.GetRealityUVTexture(), mTakeDirInfo.FullName + "/texture_uv_" + mFrameInfo.mFrameCount.ToString() + ".png");
    }

}

public static void SaveTextureAsPNG(Texture2D aTexture, string aFullPath)
{
    byte[] bytes = aTexture.EncodeToPNG();
    System.IO.File.WriteAllBytes(aFullPath, bytes);
    Debug.Log("Image with Instance ID:" + aTexture.GetInstanceID() + "with dims of " + aTexture.width.ToString() + " px X " + aTexture.height + " px " + " with size of" + bytes.Length / 1024 + "Kb was saved as: " + aFullPath);
}

Testing on a Non-ArCore android I get black images (with random colored pixels) with the correct size of 480 x 640 pixels. With ArCore it's a 0kb black image.


Solution

  • Creating a image from these textures directly does not work properly as this data is passed through the XRCameraYUVShader and XRARCoreCamera before a valid image can be rendered.

    One option is to create a custom version of the XRVideoController which provides access to the material used to render the camera feed. From there, your code could be modified as so:

    void RecordAndSaveFrameTexture() {
      Texture2D src = xr.ShouldUseRealityRGBATexture()
          ? xr.GetRealityRGBATexture()
          : xr.GetRealityYTexture();
    
      RenderTexture renderTexture = new RenderTexture (src.width, src.height, 0, RenderTextureFormat.ARGB32);
      Graphics.Blit (null, renderTexture, xrMat);
      SaveTextureAsPNG(renderTexture, Application.persistentDataPath + "/texture.png");
    }
    
    public static void SaveTextureAsPNG(RenderTexture renderTexture, string aFullPath) {
      Texture2D aTexture = new Texture2D(renderTexture.width, renderTexture.height, TextureFormat.RGBA32, false);
      RenderTexture.active = renderTexture;
      aTexture.ReadPixels(new Rect(0, 0, aTexture.width, aTexture.height), 0, 0);
      RenderTexture.active = null;
    
      byte[] bytes = aTexture.EncodeToPNG();
      System.IO.File.WriteAllBytes(aFullPath, bytes);
    }
    

    Where xrMat is the material used to render the camera feed.

    That will give an camera image without any Unity game objects. If you'd like game objects to be displayed in the saved image, you could override OnRenderImage to achieve that:

    void OnRenderImage(RenderTexture src, RenderTexture dest) {
        if (shouldSaveImageFrame) {
          SaveTextureAsPNG(src, Application.persistentDataPath + "/_rgbatexture.png");
          shouldSaveImageFrame = false;
        }
        Graphics.Blit (src, dest);
    }
    

    Where shouldSaveImageFrame is a flag you've set elsewhere to prevent an image from being saved on every frame.