Spatial Mapping Renderer
空間マッピングのよくあるトラブルシューティングの問題

空間マッピングの低レベル API

空間マッピングの Renderer コンポーネントと Collider コンポーネントを使用すると、システムの細かいことを気にせずに空間マッピングの機能を簡単に使用できます。アプリケーションの空間マッピングを精細に制御したい場合は、Unity が空間マッピング用に提供する低レベル API を使用してください。

API は、HoloLens が収集する空間マッピング情報にアクセスするためのデータの構造体とオブジェクトの型を多数提供します。

SurfaceObserver

SurfaceObserver を使用して空間マッピングデータにアクセスできます。SurfaceObserver はAPI クラスで、アプリケーションによって空間マッピングデータが必要とされる任意の現実空間を監視します。 SurfaceObserver は、現実世界の物理的な領域を観察し、それが交差する一連の空間サーフェスについて、Spatial Mapping システムによる追加、変更、削除を報告します。

SurfaceObservers は、Unity スクリプト API を通して直接、空間マッピングと相互作用したい場合にのみ使用します。

Unity は SurfaceObserver API を基盤に構築された独自の 空間マッピングの Renderer コンポーネントと Collider コンポーネントを提供しており、空間マッピング機能に簡単にアクセスできます。これらの詳細については Spatial Mapping コンポーネント のドキュメントを参照してください。

SurfaceObserver を使用すると、アプリケーションは物理衝突データの有無にかかわらず、メッシュデータを非同期的に要求できます。要求が完了すると、別のコールバックで、アプリケーションにデータの使用が可能になったことを通知します。

SurfaceObserver は、アプリケーションに以下の機能を提供します。

  1. サーフェスの変更 (追加、削除、更新) の要求に関し、コールバック を発信します。

  2. 既知のサーフェスに対応するメッシュデータを要求するためのインターフェースを提供します。

  3. 要求するメッシュデータの使用が可能になった時に、コールバック を発信します。

  4. SurfaceObserver の位置と容積を定義する方法を提供します。

SurfaceData

SurfaceData は、サーフェス のメッシュデータを構築してレポートするために空間マッピングシステムが必要とするすべての情報を含むクラスです。

RequestMeshAsync メソッドを使用して、収集した SurfaceData オブジェクトを空間マッピングシステムに渡す必要があります。初めて RequestMeshAsync メソッドを呼び出すときは、それに SurfaceDataReadyDelegate を渡す必要があります。 メッシュデータの準備が整うと、SurfaceDataReadyDelegate は一致する SurfaceData オブジェクトを報告します。

これにより、アプリケーションはデータがどのサーフェスに対応するのかを正確に判断できます。

アプリケーションが必要とする情報を使用して SurfaceData ゲームオブジェクトを設定する必要があります。これには、以下のコンポーネントとデータが含まれます。

  • WorldAnchor コンポーネント

  • MeshFilter コンポーネント

  • MeshCollider コンポーネント (アプリケーションに物理データが必要な場合)

  • 生成したメッシュの立方メートルあたりの三角形の数

  • サーフェス ID

正しく設定されていない SurfaceData オブジェクトを使用して RequestMeshAsync メソッドを呼び出すと、システムは引数の例外をスローします。RequestMeshAsync メソッドの呼び出しが引数の例外をスローしない場合でも、空間マッピングがメッシュデータを正常に作成して返しているかどうかを確認する他の方法はありません。スクリプトを使用して手動で作成したメッシュデータは、追跡することが推奨されます。

API の使用例

以下のサンプルスクリプトは API の重要な箇所の基本的な使用例を示しています。


using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.WSA;
using UnityEngine.Rendering;
using UnityEngine.Assertions;
using System;
using System.Collections;
using System.Collections.Generic;

public enum BakedState 
{
    NeverBaked = 0,
    Baked = 1,
    UpdatePostBake = 2
}

// このクラスは、システムがサーフェスのベイクの優先順位を決めるためのデータを保存します
class SurfaceEntry 
{
    public GameObject  m_Surface; // サーフェスに関連するゲームオブジェクト
    public int         m_Id; // サーフェスの ID 
    public DateTime    m_UpdateTime; // システムによって報告されるアップデート時間
    public BakedState  m_BakedState;
    public const float c_Extents = 5.0f;
}

public class SMSample : MonoBehaviour 
{
    // この Observer は空間マッピング世界を覗く窓です
    SurfaceObserver m_Observer;

    // このディクショナリには、一連の既知の空間マッピングサーフェスが含まれています
    // サーフェスは、定期的にシステムによって更新、追加、削除されます
    Dictionary<int, SurfaceEntry> m_Surfaces;

    // このマテリアルを使って、システムがベイクしたサーフェスを描画します
    public Material m_drawMat;

    // このフラグは、ベイクが途中の場合に、要求を延期するために、空間マッピングシステムによって使用されます    
   // メッシュデータをベイクするのに、複数のフレームが必要なことがあります
    // このサンプルでは、Serface データに基づくベイク要求の順序を優先します
    // 現在、システムによって処理中の要求がない場合にのみ、新しい要求が発信されます
    bool m_WaitingForBake;

    // これは、SurfaceObserver がシステムによって最後に更新された時刻です
    // 2 秒ごとに 1 回より多く更新されることはありません
    float m_lastUpdateTime;

    void Start () 
    {
        m_Observer = new SurfaceObserver ();
        m_Observer.SetVolumeAsAxisAlignedBox (new Vector3(0.0f, 0.0f, 0.0f), 
            new Vector3 (SurfaceEntry.c_Extents, SurfaceEntry.c_Extents, SurfaceEntry.c_Extents));
        m_Surfaces = new Dictionary<int, SurfaceEntry> ();
        m_WaitingForBake = false;
        m_lastUpdateTime = 0.0f;
    }
    
    void Update () 
    {
        // SurfaceObserver に対して Update をあまり頻繁に呼び出すことを避けます        
  if (m_lastUpdateTime + 2.0f < Time.realtimeSinceStartup) 
        {
            // ここでは、Observer が観察する容積がカメラに追従するように設定します
            Vector3 extents;
            extents.x = SurfaceEntry.c_Extents;
            extents.y = SurfaceEntry.c_Extents;
            extents.z = SurfaceEntry.c_Extents;
            m_Observer.SetVolumeAsAxisAlignedBox (Camera.main.transform.position, extents);

            try 
            {
                m_Observer.Update (SurfaceChangedHandler);
            } 
            catch 
            {
                // 指定したコールバックが悪い場合は、Update は例外をスローします
                Debug.Log ("Observer update failed unexpectedly!");
            }

            m_lastUpdateTime = Time.realtimeSinceStartup;
        }
        if (!m_WaitingForBake) 
        {
            // 更新よりも古い追加を優先します
            SurfaceEntry bestSurface = null;
            foreach (KeyValuePair<int, SurfaceEntry> surface in m_Surfaces) 
            {
                if (surface.Value.m_BakedState != BakedState.Baked) 
                {
                    if (bestSurface == null) 
                    {
                        bestSurface = surface.Value;
                    } 
                    else 
                    {
                        if (surface.Value.m_BakedState < bestSurface.m_BakedState) 
                        {
                            bestSurface = surface.Value;
                        } 
                        else if (surface.Value.m_UpdateTime < bestSurface.m_UpdateTime) 
                        {
                            bestSurface = surface.Value;
                        }
                    }
                }
            }
            if (bestSurface != null) 
            {
                // 要求を作成してディスパッチします
                SurfaceData sd;
                sd.id.handle = bestSurface.m_Id;
                sd.outputMesh = bestSurface.m_Surface.GetComponent<MeshFilter> ();
                sd.outputAnchor = bestSurface.m_Surface.GetComponent<WorldAnchor> ();
                sd.outputCollider = bestSurface.m_Surface.GetComponent<MeshCollider> ();
                sd.trianglesPerCubicMeter = 300.0f;
                sd.bakeCollider = true;
                try 
                {
                    if (m_Observer.RequestMeshAsync(sd, SurfaceDataReadyHandler)) 
                    {
                        m_WaitingForBake = true;
                    } 
                    else 
                    {
                        // メッシュを要求したときに、返された値が false の場合は
                        // たいてい、指定した Surface ID が無効であることを                      
                  // 示しています。
                        Debug.Log(System.String.Format ("Bake request for {0} failed.  Is {0} a valid Surface ID?", bestSurface.m_Id));
                    }
                }
                catch 
                {
                    // データ struct を適切に作成しないと、要求は失敗します
                    Debug.Log (System.String.Format("Bake for id {0} failed unexpectedly!", bestSurface.m_Id));
                }
            }
        }
    }

    // サーフェスが変化するとき、このハンドラーはイベントを受け取り、
    // SurfaceObserver の Update メソッドを使ってそれらのイベントを通知します。
    void SurfaceChangedHandler (SurfaceId id, SurfaceChange changeType, Bounds bounds, DateTime updateTime) 
    {
        SurfaceEntry entry;
        switch (changeType)  
        {
            case SurfaceChange.Added:
            case SurfaceChange.Updated:
            if (m_Surfaces.TryGetValue(id.handle, out entry)) 
            {
                // システムがすでにサーフェスをベイク済の場合は
                // 「ベイク処理する次のサーフェス」のロジックが正しく順番づけするように、 
                // ベイクが必要であるとマークし、時間を更新します。  
                if (entry.m_BakedState == BakedState.Baked) 
                {
                    entry.m_BakedState = BakedState.UpdatePostBake;
                    entry.m_UpdateTime = updateTime;
                }
            } 
            else 
            {
                // 新しいサーフェスなので、エントリーを作成します
                entry = new SurfaceEntry ();
                entry.m_BakedState = BakedState.NeverBaked;
                entry.m_UpdateTime = updateTime;
                entry.m_Id = id.handle;
                entry.m_Surface = new GameObject (System.String.Format("Surface-{0}", id.handle));
                entry.m_Surface.AddComponent<MeshFilter> ();
                entry.m_Surface.AddComponent<MeshCollider> ();
                MeshRenderer mr = entry.m_Surface.AddComponent<MeshRenderer> ();
                mr.shadowCastingMode = ShadowCastingMode.Off;
                mr.receiveShadows = false;
                entry.m_Surface.AddComponent<WorldAnchor> ();
                entry.m_Surface.GetComponent<MeshRenderer> ().sharedMaterial = m_drawMat;
                m_Surfaces[id.handle] = entry;
            }
            break;

            case SurfaceChange.Removed:
            if (m_Surfaces.TryGetValue(id.handle, out entry)) 
            {
                m_Surfaces.Remove (id.handle);
                Mesh mesh = entry.m_Surface.GetComponent<MeshFilter> ().mesh;
                if (mesh) 
                {
                    Destroy (mesh);
                }
                Destroy (entry.m_Surface);
            }
            break;
        }
    }

    void SurfaceDataReadyHandler(SurfaceData sd, bool outputWritten, float elapsedBakeTimeSeconds) 
    {
        m_WaitingForBake = false;
        SurfaceEntry entry;
        if (m_Surfaces.TryGetValue(sd.id.handle, out entry)) 
        {
            // これらの 2 つの assert は、返された Filter と WorldAnchor が
            // データを要求するためにシステムが使用したものと同じであることを確認します。
            // それらの置き換えや破棄のためにコードを変更しない限り、常に true のはずです。
            Assert.IsTrue (sd.outputMesh == entry.m_Surface.GetComponent<MeshFilter>());
            Assert.IsTrue (sd.outputAnchor == entry.m_Surface.GetComponent<WorldAnchor>());
            entry.m_BakedState = BakedState.Baked;
        } 
        else 
        {
            Debug.Log (System.String.Format("Paranoia:  Couldn't find surface {0} after a bake!", sd.id.handle));
            Assert.IsTrue (false);
        }
    }
}

ノート: SurfaceObserver の Update メソッドの呼び出しはリソースに負荷がかかることがあります。そのため、アプリケーションが必要とする以上に、呼び出さないようにします。たいていのアプリケーションでは、3 秒に 1 回このメソッドを呼び出せば十分です。

  • 2018–05–01 編集レビュー を行ってパブリッシュされたページ

  • Hololens の空間マッピングは 2017.3 に更新

Spatial Mapping Renderer
空間マッピングのよくあるトラブルシューティングの問題