夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

MixedRealityToolkitGraphicsToolsのDistantLightを理解する。 その①BaseLightの理解

本日はMRGT調査枠です。

〇MixedRealityToolkit GraphicsToolsとは?

MixedRealityToolkit GraphicsTools(MRGT)とはMicrosoftによってUnity、UnrealEngine向けに提供されているMixedRealityアプリケーション開発のための開発ツールです。

本ブログでは特に明言しない限りUnityでのMRGTを紹介しています。

github.com

MRGTにはDistantLightと呼ばれる機能が搭載されています。

これはUnityのライトとして機能するスクリプトで、DirectionalLightとは別に定義、機能するライトです。

手前のSphereがDistantLight、奥のSphereがDirectionalLight

〇DistantLightコンポーネント

DistanceLightはCustomDirectionalLightとしてUIなどでDirectionalLightと光源を区別して使用するために実装されました。

github.com

BaseLightクラスを継承するクラスとなっています。

using System.Collections.Generic;
using UnityEngine;

namespace Microsoft.MixedReality.GraphicsTools
{
    [ExecuteInEditMode]
    [AddComponentMenu("Scripts/GraphicsTools/DistantLight")]
    public class DistantLight : BaseLight
    {
  ・・・
    }
}

まずはBaseLightクラスに関して触れていきます。

〇BaseLight

BaseLightはMRGTで使用されるすべてのライトクラスで使用される抽象クラスです。

github.com

ExecuteInEditModeが定義されているためUnityEditor上でも実行されます。

    [ExecuteInEditMode]
    public abstract class BaseLight : MonoBehaviour
    {

BaseLightが行っていることはBaseLightクラスを継承する全ライトクラスで共通して処理を定義しています。

つまり継承クラスではBaseLightの定義する以下の関数を定義、それぞれ同じように使用されるということになります。

・初期化:Initialize()

・ライトの追加:AddLight()

・ライトの削除:RemoveLight()

・ライトの更新:UpdateLights()

〇Update

BaseLightでは常にLateUpdate()によってUpdateLights()が走るようになっていますがUnityEditor上でのExecuteInEditModeではLateUpdate()は実行されません。このためEditor上ではプリプロセッサによってUpdateが走るようになっています。

 #if UNITY_EDITOR
        protected virtual void Update()
        {
            if (Application.isPlaying)
            {
                return;
            }

            Initialize();
            UpdateLights();
        }
#endif // UNITY_EDITOR
        protected virtual void LateUpdate()
        {
            UpdateLights();
        }

ここではUpdateLights()を行っています。

BaseLightクラスはこのようになります。では実際のDistantLightクラスを見ていきます。

〇DistantLightクラスの理解

DistantLightクラスでは前述のとおりBaseLightクラスで定義されたインスタンスを使用しています。

〇UpdateLight

UpdateLightはBaseLightのLateUpdateで実行されているためマイフレーム実行されています。

この処理がDistantLightの実質的なコア処理になります。

  protected override void UpdateLights(bool forceUpdate = false)
        {
            if (lastDistantLightUpdate == -1)
            {
                Initialize();
            }

            if (!forceUpdate && (Time.frameCount == lastDistantLightUpdate))
            {
                return;
            }

            for (int i = 0; i < distantLightCount; ++i)
            {
                DistantLight light = (i >= activeDistantLights.Count) ? null : activeDistantLights[i];
                int dataIndex = i * distantLightDataSize;

                if (light)
                {
                    Vector4 direction = -light.transform.forward;
                    distantLightData[dataIndex] = new Vector4(direction.x,
                                                              direction.y,
                                                              direction.z,
                                                              1.0f);
                    distantLightData[dataIndex + 1] = new Vector4(light.Color.r * intensity,
                                                                  light.Color.g * intensity,
                                                                  light.Color.b * intensity, 
                                                                  1.0f);
                }
                else
                {
                    distantLightData[dataIndex] = invalidLightDirection;
                    distantLightData[dataIndex + 1] = Vector4.zero;
                }
            }

            Shader.SetGlobalVectorArray(_DistantLightDataID, distantLightData);

            lastDistantLightUpdate = Time.frameCount;
        }

〇lastDistantLightUpdateと初期化

        private static int lastDistantLightUpdate = -1;
・・・

 protected override void UpdateLights(bool forceUpdate = false)
        {
            if (lastDistantLightUpdate == -1)
            {
                Initialize();
            }
    if (!forceUpdate && (Time.frameCount == lastDistantLightUpdate))
            {
                return;
            }
 ・・・
            lastDistantLightUpdate = Time.frameCount;
  }

lastDistantLightUpdateは名前の通り前回のライト更新時の時間が記録されます。

初期状態では-1が定義されており、最初の更新時にInitialize()が走る仕組みとなっています。

移行は実行されてからのフレーム数が代入されます。

また処理を見るに同じフレームに二回処理が行われないようになっています。

これはまだ筆者自身が追えていない部分ではあるのですが、例えばDistantLightコンポーネントはシーンに対して常に1つで排他的に実行されており、この仕組みがそれを担っている可能性があります。

〇初期化

        protected override void Initialize()
        {
            _DistantLightDataID = Shader.PropertyToID("_DistantLightData");
        }

処理としてはDistantLightDataIDをShaderの持つDistantLightDataのユニークIDに更新しています。

ユニークIDとはマテリアルの持つ固有のIDで、これを取得することで何度もシェーダー側のプロパティへアクセスを行う場合軽量な処理になります。

docs.unity3d.com

長くなってしまったため今回はここまでです。