【Unity】LiDAR+ARFoundation+ShaderGraphで地形メッシュのエフェクトを作る


概要

3/25に発売されたLiDAR搭載のiPad用に地形エフェクトアプリ( https://apps.apple.com/us/app/id1526438768 )の開発を行った際、
リファレンスが探し辛く一部詰まるところがあったので、その部分の解決方法を残しておきます。

今回のアプリは上記の写真のようにLiDARで取った地形メッシュと人物にエフェクトをかけていく形としました。

開発環境

ソフトウェア バージョン
macOS 10.15.6
Xcode 11.6
Unity 2020.1.0f1
ARFoundation 4.0.2
ARKit 3.5
Universal RP 8.2.0
iOS(iPad) 14(preview)

やりたいこと

①PeopleOcclusion、Meshingの機能を保ちながら背景の暗さを変更する。(エフェクトがかかっている部分だけを残して他部分の明度を下げる。)
②LiDARで取得したメッシュにShaderGraphで制作したシェーダでエフェクトやテクスチャを乗せる。

詰まった箇所と解決方法を順番に書いていきます。(ARFoundationでURPを使う方法は割愛します)

①PeopleOcclusion、Meshingの機能を保ちながら背景の暗さを変更する


ARCameraBackgroundにはUseCustomMaterialというチェックボックスがありますが、これにチェックをいれてPublic変数にマテリアルを入れたところで背景にマテリアルは適用されません。

調べたところ、どうやらこのチェックボックスは残されたままで内部の機能だけが廃止されたようです。
ということで、ARCameraBackground.cs内のm_DefaultMaterialがどのマテリアルで初期化されているかを追っていきます。

ARCameraBackground.cs
//省略
void Awake () {
   m_Camera = GetComponent<Camera> ();
   m_CameraManager = GetComponent<ARCameraManager> ();
   m_OcclusionManager = GetComponent<AROcclusionManager> ();
   m_DefaultMaterial = m_CameraManager.cameraMaterial;
}

Awakeでの初期化でm_DefaultMaterialにm_CameraManager.cameraMaterialが代入されています。
ここでcameraMaterialがどのシェーダを使っているのかをTextなどでデバッグするとARKitBackground.shaderというシェーダで背景のメッシュ、人物オクルージョンを画面に描画しているのがわかりました。

このシェーダの中で色々処理をしているようですが、複雑なので必要な箇所だけをピックアップしてコピーを変更していきます。

CustomARKitBackground.shader
Properties
    {
        _textureY ("TextureY", 2D) = "white" {}
        _textureCbCr ("TextureCbCr", 2D) = "black" {}
        _HumanStencil ("HumanStencil", 2D) = "black" {}
        _HumanDepth ("HumanDepth", 2D) = "black" {}
        _Brightness("_Brightness", Float) = 1
    }

まずは明度プロパティを外部スクリプトから変更できるようにBritenessをPropertiesの中に記述します。

CustomARKitBackground.shader

//省略
                float _Brightness;
//省略
#if ARKIT_HUMAN_SEGMENTATION_ENABLED

                if (ARKIT_SAMPLE_TEXTURE2D(_HumanStencil, sampler_HumanStencil, i.texcoord).r > 0.5h)
                {
                    // 人物の処理はこのブロック内。
                    // Sample the human depth (in meters).
                    float humanDistance = ARKIT_SAMPLE_TEXTURE2D(_HumanDepth, sampler_HumanDepth, i.texcoord).r;

                    // Convert the distance to depth.
                    depthValue = ConvertDistanceToDepth(humanDistance);           

                }else{
                    //このブロックを追記。人物以外の処理はここ。
                    c.rgb = fixed3(c.r * _Brightness, c.g * _Brightness, c.b * _Brightness);
                }
#endif // ARKIT_HUMAN_SEGMENTATION_ENABLED

ずっと下にいくとフラグメントシェーダの記述部分がありますが、そこに上記のように追記をします。

HumanStencilが人物の場合は1、背景の場合は0が出力されるようになっており
ここで分岐させて処理を分けているようなので、elseを追記して背景部分を分岐させてあげます。

背景だけを暗くする処理としての追記は簡単で、カメラから拾ってきたRGBにBriteness変数を掛け合わせているだけです。
手書きのshaderに精通している訳ではなく未だ細かい芸はできませんがやりようによっては他にも色々できそうですね。

そしてこれを

     propId = Shader.PropertyToID ("_Brightness");

等で変更可能にしてやると背景の明度をスクリプトから変えたりする事ができます。

②LiDARで取得したメッシュにShaderGraphで制作したシェーダでエフェクトやテクスチャを乗せる

ARFoundationにはLiDARで取得したMeshにPrefabを適用することができます。
(PrefabのMeshFilterがNone以外だと表示がバグります。)

今回はこのMeshPrefabにShaderGraphで作ったシェーダを適用して地形エフェクトを実現しようという作戦です。

ここで注意なのですが、現時点ではシェーダに必要なUV、頂点カラーは提供されていません。
(UVが提供されていない為、Editor上のプレビューでは正常に表示されていても実機では表示できないという仕様を仕様だと知らずにRP周りをごちゃごちゃ数日いじりまわして断念しかけていた)

ここのフォーラムでは、MeshPrefabNormalsConcurrent Queue Size以外はARKit3.5では提供されていないとのこと。
https://forum.unity.com/threads/using-arkit-meshing-with-ar-foundation-4-0.883981/

解決策

メッシュのUVではなく、オブジェクト座標を用いてテクスチャを貼る。
という方法で実現をしました。

PositionノードのSpaceのプルダウンタブが初期値はWorldですが、ここをObjectに変更してUVを欲しているノードの初期値につないでやります。

Z軸の情報を切り捨てている為、XY平面に並行な面以外のテクスチャやエフェクトは少し伸びてしまいますが、UVノードを使わずに表示させることができました。
(SplitやNormal等を用いてUVを補正してあげたりすれば多少マシにはなりますが、伸びずにぴったり貼ることは難しいようです。どなたか情報を求む。。)

あとはShaderGraphで好きなものを作ってMeshPrefabにマテリアルを適用したPrefabを突っ込むだけで地形エフェクトが完成します。

これはシェーダの分岐部分で人物部分に手書きシェーダを適用させたものですが、
・人物オクルージョン(+人物エフェクト)
・地形メッシュにエフェクト
・背景だけ明暗調整
をしっかりと併用する事ができました。

今回開発したアプリはこちらのURLで公開されているのでぜひダウンロードして遊んでみてください。
https://apps.apple.com/us/app/id1526438768

それでは皆様良きARライフを。お疲れ様でした。