2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

STYLYAdvent Calendar 2024

Day 8

【Unity】StreetscapeGeometryAPIの使い方

Posted at

StreetscapeGeometryAPIとは?

周辺の都市データを3Dモデルとして取得可能です。実際の建造物と同じ位置に表示されるので、オクルージョンやARコンテンツの演出として利用可能です。

参考リンク:Use buildings and terrain around you on Unity

バージョン

ツール、ライブラリ バージョン
Unity 2022.3.46f1
ARCore Extensions 1.46.0

デモ

取得した都市モデルを半透明な状態で表示するサンプル、その都市モデルをオクルージョンに利用するサンプルです。地形のモデルも取れるので、ついでに表示しています。

LOD1が取得される場所

SGOcclusionLOD1.gif

LOD2が取得される場所

SGOcclusionLOD2.gif

サンプルコード

Googleのサンプルだとカメラのfar clipが小さな値になっているので注意が必要です。

using System.Collections.Generic;
using Google.XR.ARCoreExtensions;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR.ARSubsystems;

public class StreetscapeGeometrySample : MonoBehaviour
{
    [SerializeField] private ARStreetscapeGeometryManager _streetscapeGeometryManager;
    [SerializeField] private Button _changeMaterialButton;
    [SerializeField] private Material _streetscapeGeometryMaterialForOcclusion;
    [SerializeField] private Material _streetscapeGeometryMaterial;
    [SerializeField] private GameObject _arObject;

    private readonly Dictionary<TrackableId, GameObject> _streetscapegeometryGOs = new();

    private List<ARStreetscapeGeometry> _addedStreetscapeGeometries = new();
    private List<ARStreetscapeGeometry> _updatedStreetscapeGeometries = new();
    private List<ARStreetscapeGeometry> _removedStreetscapeGeometries = new();

    private enum MaterialType
    {
        Default,
        Occlusion
    }

    private MaterialType _currentMaterialType = MaterialType.Default;

    public void Awake()
    {
        _streetscapeGeometryManager.StreetscapeGeometriesChanged += GetStreetscapeGeometry;
        _changeMaterialButton.onClick.AddListener(ChangeMaterial);
    }

    private void Update()
    {
        UpdateGeometryGOs();

        _arObject.SetActive(_currentMaterialType == MaterialType.Occlusion);
    }

    public void OnDestroy()
    {
        _streetscapeGeometryManager.StreetscapeGeometriesChanged -= GetStreetscapeGeometry;
        _changeMaterialButton.onClick.RemoveListener(ChangeMaterial);
    }

    /// <summary>
    /// StreetscapeGeometryが生成したオブジェクトのマテリアルを変更する。
    /// </summary>
    private void ChangeMaterial()
    {
        var streetscapeGeometryGameObjectList = new List<GameObject>(_streetscapegeometryGOs.Values);
        if (streetscapeGeometryGameObjectList.Count == 0) return;

        if (_currentMaterialType == MaterialType.Default)
        {
            streetscapeGeometryGameObjectList.ForEach(go =>
                SetMaterialToGeometry(go, _streetscapeGeometryMaterialForOcclusion));
            _currentMaterialType = MaterialType.Occlusion;
        }
        else
        {
            streetscapeGeometryGameObjectList.ForEach(go => SetMaterialToGeometry(go, _streetscapeGeometryMaterial));
            _currentMaterialType = MaterialType.Default;
        }
    }


    /// <summary>
    /// StreetscapeGeometryによるオブジェクトの生成、更新、削除を行う。
    /// </summary>
    private void UpdateGeometryGOs()
    {
        foreach (var geometry in _addedStreetscapeGeometries)
        {
            InstantiateRenderObject(geometry);
        }

        foreach (var geometry in _updatedStreetscapeGeometries)
        {
            InstantiateRenderObject(geometry);
            UpdateRenderObject(geometry);
        }

        foreach (var geometry in _removedStreetscapeGeometries)
        {
            DestroyRenderObject(geometry);
        }
    }

    /// <summary>
    /// StreetscapeGeometryの機能を利用して地形/ビル群のオブジェクトを生成する。
    /// </summary>
    private void InstantiateRenderObject(ARStreetscapeGeometry streetscapeGeometry)
    {
        if (streetscapeGeometry.mesh == null) return;
        if (_streetscapegeometryGOs.ContainsKey(streetscapeGeometry.trackableId)) return;

        var geometry = new GameObject("StreetscapeGeometryMesh", typeof(MeshFilter), typeof(MeshRenderer));
        geometry.transform.position = streetscapeGeometry.pose.position;
        geometry.transform.rotation = streetscapeGeometry.pose.rotation;
        geometry.GetComponent<MeshFilter>().mesh = streetscapeGeometry.mesh;
        SetMaterialToGeometry(geometry, _streetscapeGeometryMaterial);
        _currentMaterialType = MaterialType.Default;
        _streetscapegeometryGOs.Add(streetscapeGeometry.trackableId, geometry);
    }

    /// <summary>
    /// StreetscapeGeometryが生成した既存のオブジェクトの位置と回転を更新する。
    /// </summary>
    private void UpdateRenderObject(ARStreetscapeGeometry streetscapeGeometry)
    {
        if (_streetscapegeometryGOs.TryGetValue(streetscapeGeometry.trackableId, out var geometry))
        {
            geometry.transform.position = streetscapeGeometry.pose.position;
            geometry.transform.rotation = streetscapeGeometry.pose.rotation;
        }
    }

    /// <summary>
    /// StreetscapeGeometryが生成したオブジェクトを削除する。
    /// </summary>
    private void DestroyRenderObject(ARStreetscapeGeometry streetscapeGeometry)
    {
        if (_streetscapegeometryGOs.TryGetValue(streetscapeGeometry.trackableId, out var geometry))
        {
            _streetscapegeometryGOs.Remove(streetscapeGeometry.trackableId);
            Destroy(geometry);
        }
    }

    /// <summary>
    /// 対象のオブジェクトにマテリアルを設定する。
    /// </summary>
    private void SetMaterialToGeometry(GameObject go, Material material)
    {
        go.GetComponent<MeshRenderer>().material = material;
    }

    /// <summary>
    /// StreetscapeGeometryの変更イベントを受け取り、リストを更新する。
    /// </summary>
    private void GetStreetscapeGeometry(ARStreetscapeGeometriesChangedEventArgs eventArgs)
    {
        _addedStreetscapeGeometries = eventArgs.Added;
        _updatedStreetscapeGeometries = eventArgs.Updated;
        _removedStreetscapeGeometries = eventArgs.Removed;
    }
}

LOD1/LOD2について

StreetscapeGeometryで取得できるモデルにはLOD1、LOD2の2種類が存在します。
LOD2について、冒頭で紹介したようなオクルージョンやARコンテンツの演出として利用可能な品質ですが、LOD1は正直使えません。デモで示した通り、形状はただの立方体で、高さも合っていません。

そして、これは開発者側でコントロールできるものではなく、Googleの気まぐれでどちらが取得できるかが決まっています。

よって、アプリ側ではジオメトリのqualityの値をチェックして、LOD1は弾くような実装があると良いかと思います。

ただ、未解決の問題として、"事前にそのエリアのLODの品質を判別できないこと"が残されています。この件について、issueを立てて問い合わせてみたところ、どうやら、"LOD1が存在する可能性"の高い場所はGoogleMapから読み取ることができるようです。

issue:How can I check LOD before using Streetscape Geometry API?

以下GIFのようにGoogleMapを航空写真として表示し、マップを斜めにずらすことでそのエリアにLOD1が存在する可能性を確認できます。

SG_Map.gif

もし、建物の場所に3次元表示された都市モデルではなく、2Dの平面的な表示(以下画像赤枠部分)となっている場合、その近辺はLOD1の存在する可能性が高くなるそうです。

image.png

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?