Help us understand the problem. What is going on with this article?

AsyncGPUReadbackでRenderTextureをTexture2Dに変換する

More than 1 year has passed since last update.

概要

RenderTextureをTexture2Dに変換するときに良く使われるTexture2d.ReadPixelsが重すぎてカッとなったので他の方法を調べてみた所、AsyncGPUReadbackが見つかったのでこれを試してみた話。

これまでの方法

RenderTextureをTexture2Dに変換する方法としてよくあるのがTexture2d.ReadPixelsを用いる方法です。ただしこの方法だとめちゃめちゃ処理が遅いです。
"Texture2d.ReadPixels slow"でググってみると泣き言書いてる人が結構います。
https://stackoverflow.com/questions/45100993/rendertexture-to-texture2d-is-very-slowly
https://forum.unity.com/threads/encodetopng-super-slow-readpixels-also-quite-slow-any-faster-ways-to-capturing-camera-shots.464813/

super slowとかvery slowとか言われてますね。

試しに下記のコードで毎フレームRenderTexture->Texture2D変換を手持ちのiPhoneXで試してみましたが15FPSぐらいしか出ませんでした。まあiPhoneX解像度高いし。。

SyncCapture.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SyncCapture : MonoBehaviour {

    Texture2D _tex;
    RenderTexture _renderTex;

    private void Start()
    {
        _tex = new Texture2D(Screen.currentResolution.width, Screen.currentResolution.height, TextureFormat.RGBA32, false);
        _renderTex = new RenderTexture(_tex.width, _tex.height, 24, RenderTextureFormat.ARGB32);

    }


    private void OnPostRender()
    {
        Graphics.Blit(null, _renderTex);
        RenderTexture.active = _renderTex;
        //ここがめっちゃ重い!
        _tex.ReadPixels(new Rect(0, 0, _tex.width, _tex.height), 0, 0);
        _tex.Apply();
    }
}

AsyncGPUReadbackを使う

Unity2018.1からAsyncGPUReadbackが追加されてGPUから非同期でデータを取得する事ができるようになりました。なおUnity2018.1時点ではWindowsしか対応してませんでしたが、Unity2018.2のいつ頃から分かりませんがMetal対応され、MacでもiOSでも動作するようになっています。また、AsyncGPUReadbackに対応しているかどうかは下記で確認できるようです。

if(SystemInfo.supportsAsyncGPUReadback){
    //AsyncGPUReadback使える!
}

自分の環境だとUnity2018.2.9f1ですがMacでもiOSでもTrueを返してました。
というわけでAsyncGPUReadbackを試してみます。

処理の流れとしては以下のようにAsyncGPUReadback.Requestでリクエストを投げ、

var reqest = AsyncGPUReadback.Request(renderTexture)

Request.doneがtrueになるまで待機し、

void Update()
{
 if(reqest.done){
 }
}

Requestからbufferを受け取ってTexture2Dに設定でいけます。
取得するデータはNativeArrayなのでTexture2D.LoadRawTextureData()でロードしてあげるのが一番効率良いです。

Unity.Collections.NativeArray<Color32> buffer = reqest.GetData<Color32>();
_tex.LoadRawTextureData(buffer);
_tex.Apply();

全コードは以下のようになります。これをビルドしてiosでFPSを確認すると20FPSだったのが30FPSまで改善しました。でもやっぱiPhoneXの解像度高い分重いっすね。。毎フレームTexture2Dに変換するのはやめて、数フレーム毎に1回しか変換しない等はした方が良さげ。

あと注意点として、Unityのマニュアルにもあるけどリクエスト投げてからデータ取得できるまで数フレームの遅延があるとの事。遅延しても影響ないような使い方(スクリーンショット撮影とか)にしておくのが無難かも。
https://docs.unity3d.com/ScriptReference/Rendering.AsyncGPUReadback.html

AsyncCapture.cs
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.Rendering;

public class AsyncCapture : MonoBehaviour
{

    Queue<AsyncGPUReadbackRequest> _requests = new Queue<AsyncGPUReadbackRequest>();

    Texture2D _tex;
    RenderTexture _renderTex;

    void Update()
    {
        while (_requests.Count > 0)
        {
            var req = _requests.Peek();

            if (req.hasError)
            {
                Debug.Log("GPU readback error detected.");
                _requests.Dequeue();
            }
            else if (req.done)
            {
                Unity.Collections.NativeArray<Color32> buffer = req.GetData<Color32>();
                _tex.LoadRawTextureData(buffer);
                _tex.Apply();
                _requests.Dequeue();
            }
            else
            {
                break;
            }
        }
    }

    private void Start()
    {
        _tex = new Texture2D(Screen.currentResolution.width, Screen.currentResolution.height, TextureFormat.RGBA32, false);
        _renderTex = new RenderTexture(_tex.width, _tex.height, 24, RenderTextureFormat.ARGB32); 

    }


    private void OnPostRender()
    {
        Graphics.Blit(null, _renderTex);
        if (_requests.Count < 8)
        {
            _requests.Enqueue(AsyncGPUReadback.Request(_renderTex));
        }
        else
            Debug.Log("Too many requests.");
    }


}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした