46
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

AsyncGPUReadbackでRenderTextureをTexture2Dに変換する

Last updated at Posted at 2018-10-07

#概要
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.");
    }

   
}
46
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
46
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?