StreetscapeGeometryAPIとは?
周辺の都市データを3Dモデルとして取得可能です。実際の建造物と同じ位置に表示されるので、オクルージョンやARコンテンツの演出として利用可能です。
参考リンク:Use buildings and terrain around you on Unity
バージョン
ツール、ライブラリ | バージョン |
---|---|
Unity | 2022.3.46f1 |
ARCore Extensions | 1.46.0 |
デモ
取得した都市モデルを半透明な状態で表示するサンプル、その都市モデルをオクルージョンに利用するサンプルです。地形のモデルも取れるので、ついでに表示しています。
LOD1が取得される場所
LOD2が取得される場所
サンプルコード
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が存在する可能性を確認できます。
もし、建物の場所に3次元表示された都市モデルではなく、2Dの平面的な表示(以下画像赤枠部分)となっている場合、その近辺はLOD1の存在する可能性が高くなるそうです。