VRChatでLTCGIをビデオプレイヤーに適用してみる

# きっかけ
眠すぎて唐突にスネ夫の自慢話を見るためだけのワールドを作りたくなったので、せっかくなら画面からの反射を取り入れたいなって思ったのがきっかけ。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">スネ夫の自慢話を見るためだけのワールドを作る</p>&mdash; りら (@ni_rilana) <a href="https://twitter.com/ni_rilana/status/1558204393190608896?ref_src=twsrc%5Etfw">August 12, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 概要
RealtimeGIについて調べていったらどうやらLTCGIなるものがあるらしい。

https://qiita.com/shivaduke28/items/ffc5795b93908f29b2d1

shivaduke28さんの記事の追記部にLTCGIについて書かれている。

LTCGIのGithub

https://github.com/PiMaker/ltcgi

もともとの論文。2016年らしい。

https://eheitzresearch.wordpress.com/415-2/

# 環境
- VRCSDK3 最新
- USharp 最新 (1.x系ではない)
(2022/8/13/現在)
- KineLVideoPlayer

LTCGIについて新たな更新などによってこの記事が参考にならない場合があります。今後検証します(2022/11/22)

# セットアップ
基本的には以下のページを参考に構築する。

https://github.com/PiMaker/ltcgi/wiki#quick-instructions


> ・Drag the prefab into your scene
> ・Optional: Set video render texture on the controller object (use lox9973's VideoRT or similar for AVPro)
> ・Add LTCGI_Screen component to a quad or other MeshRenderer with 4 vertices
> ・Hit CTRL-S or use Tools/LTCGI/Force Update to update the editor representation
> ・Make sure you are using a supported shader! You should already see the light working in the editor if everything is set up correctly.
> ・Use GUI settings to configure your light
> ・Read the rest of this wiki, especially the "Performance optimization" section!
> ・Upload and test in-game

引用(PiMaker / ltcgi, Quick instructions, 2022/8/19)

1. LTCGI Controllerをシーンに設置
2. 反射させたいObjectに対応シェーダーを適用させる。(https://github.com/PiMaker/ltcgi#supported-shaders)
   - 私はORL Shader Family を使いました
3. 今回はビデオプレイヤーのスクリーン画像を適用させたいのであとは特に何もしない



# スクリーンへの適用
スクリーンのMeshがあるGameObjectにLTCGI ScreenをアタッチしてUdonBehaviourに変換。

これで動くと思ったけれど、そう簡単には動作してくれないようである。

Wikiを見ていくと、どうやらUdonAdapter内に```_SetVideoTexture```があるということでこれを使用すればいい感じになるのではないかと推測。

KineLVideoPlayerはScreenの適用にTextureを使っているので、このTextureを取得するUdonを流用してLTCGIに流し込むといい感じになるのでは！

## UDON
これを実現するUdonは以下の通り。
このコードを徹夜して朝7時くらいに書いているので適当なのは許してほしい。~~(そのうち直す)~~(ちょっと直した)

```KinelLTCGIScreenController.cs
using System;
using Kinel.VideoPlayer.Udon.Module;
using UdonSharp;
using UnityEngine;

// namespaceが多分エラーを吐くので適時削除するか移動するか対応してください。
namespace Kinel.VideoPlayer.Udon.Controller
{
    public class KinelLTCGIScreenController : UdonSharpBehaviour
    {
     
        //// 一部不要な変数あり
        // 必要なコンポーネントをアタッチできるように設定   
        [SerializeField] private KinelVideoPlayer _videoPlayer;
        [SerializeField] private LTCGI_UdonAdapter _ltcgi;
        [SerializeField] private string propertyName = "_MainTex";

        // テクスチャ取得するときに使う変数        
        private MaterialPropertyBlock _propertyBlockInternal;
        private Renderer _screenRenderer, _internalVideoRenderer, _internalAvProRenderer;

        // VideoかStreamか
        private const int VIDEO_MODE = 0;
        private const int STREAM_MODE = 1;
        
        // 待機中(再生されていないとき)に表示させる画像
        public Texture StandbyTexture;
        public CustomRenderTexture CRT;
        private Material sharedMaterial;

        // 必要なパラメータなどをここで初期化する
        public void Start()
        {
            _videoPlayer.RegisterListener(this);
            _propertyBlockInternal = new MaterialPropertyBlock();
            _screenRenderer = gameObject.GetComponent<Renderer>();
            _internalVideoRenderer = _videoPlayer.GetVideoPlayerController().GetInternalScreen(VIDEO_MODE);
            _internalAvProRenderer = _videoPlayer.GetVideoPlayerController().GetInternalScreen(STREAM_MODE);
            sharedMaterial = _screenRenderer.material;
            UpdateLTCGIController();
        }

        // KineLの場合のみ。ほかのプレイヤーの場合は適時変える
        public void OnKinelVideoStart()
        {
            SendCustomEventDelayedFrames(nameof(UpdateLTCGIController), 10);
        }

        // KineLの場合のみ。ほかのプレイヤーの場合は適時変える
        public void OnKineLVideoModeChange()
        {
            UpdateLTCGIController();
        }

        // 再生が始まった時とモードが変わった時と初期化時に呼ばれる
        public void UpdateLTCGIController()
        {

            Texture texture = null;

            if (_videoPlayer.IsPlaying)
            {
                if (_internalVideoRenderer == null)
                    _internalVideoRenderer = _videoPlayer.GetVideoPlayerController().GetInternalScreen(VIDEO_MODE);
            
                if(_internalAvProRenderer == null)
                    _internalAvProRenderer = _videoPlayer.GetVideoPlayerController().GetInternalScreen(STREAM_MODE);
            
                texture = _videoPlayer.GetCurrentVideoMode() == VIDEO_MODE
                    ? GetTexture(_internalVideoRenderer, false)
                    : GetTexture(_internalAvProRenderer, true);
            
                if (texture == null)
                {
                    SendCustomEventDelayedFrames(nameof(UpdateLTCGIController), 5);
                    return;
                }
            }
            
            ////// ここから
            if (texture != null)
            {
                // ここで前述の関数を使う
                _ltcgi._SetVideoTexture(texture);
            }
            else
            {
                _ltcgi._SetVideoTexture(StandbyTexture);
            }
            ////// ここまで
        }
        
        // テクスチャ取得する関数
        private Texture GetTexture(Renderer renderer, bool avPro)
        {
            if (avPro)
            {
                return renderer.material.GetTexture(propertyName);
            }
            renderer.GetPropertyBlock(_propertyBlockInternal);
            return _propertyBlockInternal.GetTexture(propertyName);
        }
        
    }
}
```

これをScreenにアタッチしてビデオプレイヤーとかLTCGIのUdonAdapterをアタッチしてあげてください。

UdonAdapterは多分LTCGI Controllerに存在していると思います。

# 成果物

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">暗い方が映える <a href="https://t.co/9MzvSSczWN">pic.twitter.com/9MzvSSczWN</a></p>&mdash; りら (@ni_rilana) <a href="https://twitter.com/ni_rilana/status/1558376292419923968?ref_src=twsrc%5Etfw">August 13, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Unity上で動いているのでたぶんVRCでも動く()


# おわりに
たぶんRenderTextureを使うのが前提なのかなとWikiとかDiscord見た感じ思いました。

Twitterで調べてみるとみんな苦しめられているので生き返ってほしい。

間違った情報や不適切な表現を見つけた際はTwitter(@ni_rilana)までお知らせいただけると幸いです。

# iwasyncでは
iwasyncはよくわからなかったのですが、フレンドさんが教えてくれました！(リンク先削除)

この点について、私の方でも動作確認をしたいところ。(と思ってたのですがリンク先が消えてしまって完全に忘れてしまった。後でもう一度聞きたいと思います。)

閲覧ありがとうございました。