🎞️

VRChatで動画のフレームをTexture2Dとして取得する

2024/12/08に公開

この記事はUniMagic Advent Calendar 2024の7日目の記事です
相変わらずゆにまじに関係なさそうな話題ですが、一応関係あります
(そろそろ本当にネタが尽きてきました)


皆さんきっと動画プレイヤーの映像の特定フレームをTexture2Dとして取得したいと思ったことがあると思います
しかし、VRChatで利用可能なコンポーネントであるAVProPlayerにおいて、出力されるmaterialから映像に紐づくテクスチャを取得することはできますが、取得できるテクスチャは断続的に内容が更新されるものであり、特定のフレームを単体で取得することはできません
そこで、VRCGraphicsというAPIを用いてTexture2dへ焼く処理を実装してみました

ソースコード

public class VideoLoader : UdonSharpBehavior
{
    private RenderTexture _tmpRenderTexture;
    private int _vlTextureWidth;
    private int _vlTextureHeight;
    protected virtual void CopyToRenderTexture(Texture2D texture, bool flipHorizontal = false,
        bool flipVertical = false)
    {
        _tmpRenderTexture = new RenderTexture(texture.width, texture.height, 0, RenderTextureFormat.ARGB32,
            RenderTextureReadWrite.Linear);
        _tmpRenderTexture.Create();
        _vlTextureWidth = texture.width;
        _vlTextureHeight = texture.height;
        VRCGraphics.Blit(texture, _tmpRenderTexture, new Vector2(flipHorizontal ? -1 : 1, flipVertical ? -1 : 1),
            new Vector2(flipHorizontal ? 1 : 0, flipVertical ? 1 : 0));
        VRCAsyncGPUReadback.Request(_tmpRenderTexture, 0, (IUdonEventReceiver)this);
    }

    public override void OnAsyncGpuReadbackComplete(VRCAsyncGPUReadbackRequest request)
    {
        Destroy(_tmpRenderTexture);
        _tmpRenderTexture.Release();
        var data = new byte[_vlTextureWidth * _vlTextureHeight * 4];
        request.TryGetData(data);
        var readableText = new Texture2D(_vlTextureWidth, _vlTextureHeight, TextureFormat.RGBA32, false);
        readableText.LoadRawTextureData(data);
        readableText.Apply();
        //readableTextをどうにかする
    }
}

解説

_tmpRenderTexture = new RenderTexture(texture.width, texture.height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
_tmpRenderTexture.Create();

書き出し先のRenderTextureを生成します
Unity公式はRenderTexture.GetTemporaryを使うことを推奨していますが残念ながらVRChatでは使えません

VRCGraphics.Blit(texture, _tmpRenderTexture, new Vector2(flipHorizontal ? -1 : 1, flipVertical ? -1 : 1),

Graphics.Blitの代替としてVRChat向けに提供されているVRCGraphics.Blitを使用します
おそらく純粋なラッパーだと考えて大丈夫です

RenderTextureで良い場合はここで完了です

VRCAsyncGPUReadback.Request(_tmpRenderTexture, 0, (IUdonEventReceiver)this);

Texture2Dに変換を行うため、RenderTextureのピクセルデータを読み取ります
ReadPixelsは重いのでVRCAsyncGPUReadbackを用いて非同期で読み取ります

Destroy(_tmpRenderTexture);
_tmpRenderTexture.Release();

読み取りが完了した(=ピクセルデータがすでに配列として取得できる)ため、不要になったRenderTextureを削除します

var data = new byte[_vlTextureWidth * _vlTextureHeight * 4];
request.TryGetData(data);

ピクセルデータを配列に読み取ります

var readableText = new Texture2D(_vlTextureWidth, _vlTextureHeight, TextureFormat.RGBA32, false);

最終的な書き出し対象のTexture2Dを生成します

readableText.LoadRawTextureData(data);
readableText.Apply();

ピクセルデータを読み込ませ、反映します

以上でReadableなTexture2Dを生成することに成功しました

おわりに

以上のコードは実際にスライドシステムで使用しているものです
VRChatにおいて、複数の画像を読み込む方法として動画はとても優秀なので、外部からのデータの取得を検討される場合は活用してみてください

宣伝

魔術学舎United、略してUniMagic(ユニマジック)とは、VRの世界に関係の深い技術を学ぶ学園空間型のイベントです。
15名~18名程度で構成されるクラスで、2週間の授業と1週間の修了制作を通して、技術についての知見を深めていきます。
https://twitter.com/UniMagicVRC

Discussion