地理空間アンカーを使用して Unity に現実世界のコンテンツを配置する

ジオ空間アンカーは、3D コンテンツを現実世界に配置できるアンカーの一種です。

地理空間アンカーの種類

地理空間アンカーには 3 つのタイプがあり、高度の扱い方はそれぞれ異なります。

  1. WGS84 アンカー:
    WGS84 アンカーを使用すると、任意の緯度、経度、高度に 3D コンテンツを配置できます。

  2. 地形アンカー:
    地形アンカーを使用すると、緯度と経度のみを使用してコンテンツを配置し、その位置の地形に対する高さを指定できます。高度は、VPS で知られている地面または床に対して測定されます。

  3. 屋上アンカー:
    屋上アンカーを使用すると、緯度と経度のみを使用してコンテンツを配置できます。その位置の建物の屋上に対する高さも指定できます。高度は、街並みのジオメトリで知られている建物の頂点に対して相対的に決定されます。建物に設置しない場合、デフォルトで地形標高に設定されます。

WGS84 地形 ルーフトップ
水平方向 緯度、経度 緯度、経度 緯度、経度
垂直方向 WGS84 高度に相対 Google マップによって決定された地形レベルを基準とする Google マップが決定した屋上のレベルを基準にします
サーバー側で解決する必要があるか いいえ はい

前提条件

続行する前に、Geospatial API が有効になっていることを確認してください。

地理空間アンカーを配置する

各アンカータイプには、それらを作成するための専用 API があります。詳しくは、地理空間アンカーのタイプをご覧ください。

ヒットテストからアンカーを作成する

ヒットテスト結果から地理空間アンカーを作成することもできます。ヒットテストのポーズを使用して、GeospatialPose に変換します。これを使用して、前述の 3 種類のアンカーを配置できます。

AR ポーズから地理空間ポーズを取得する

AREarthManager.Convert(Pose) は、AR ポーズを地理空間ポーズに変換することで、緯度と経度を特定する追加の方法を提供します。

地理空間ポーズから AR ポーズを取得する

AREarthManager.Convert(GeospatialPose) は、地球で指定された水平位置、高度、東北上座標フレームに対するクォータニオン回転を、GL ワールド座標に対する AR ポーズに変換します。

ユースケースに適した方法を選択する

アンカーを作成する方法にはそれぞれトレードオフが伴います。

  • 街並みのジオメトリを使用する場合は、ヒットテストを使用して建物にコンテンツを配置します。
  • WGS84 アンカーよりも、地形アンカーまたは屋上アンカーを使用することをおすすめします。これらのアンカーは、Google マップによって決定された高度値を使用します。

場所の緯度と経度を特定する

場所の緯度と経度は、次の 3 つの方法で計算できます。

  • Geospatial Creator を使用すると、実際にその場所に移動しなくても、3D コンテンツで世界を表示して拡張できます。これにより、Unity Editor で Google マップを使用して、3D の没入型コンテンツを視覚的に配置できます。コンテンツの緯度、経度、回転、高度は自動的に計算されます。
  • Googleマップを使用
  • Google Earth を使用する。Google マップではなく Google Earth を使用して座標を取得する場合、誤差は最大で数メートルになる可能性があります。
  • 物理的な場所に移動する

Googleマップを使用

Google マップを使用して場所の緯度と経度を取得するには:

  1. パソコンで Google マップにアクセスします。

  2. [レイヤ] > [その他] に移動します。

  3. [地図の種類] を [航空写真] に変更し、画面左下にある [地球ビュー] チェックボックスをオフにします。

    これにより、2D の視点が強制され、角度のある 3D ビューから生じる可能性のあるエラーを排除できます。

  4. 地図上で場所を右クリックし、経度/緯度を選択してクリップボードにコピーします。

Google Earth を使用する

Google Earth で場所の緯度と経度を計算するには、UI で場所をクリックし、地図上のマークの詳細からデータを読み取ります。

Google Earth で場所の緯度と経度を取得するには:

  1. パソコンで Google Earth にアクセスします。

  2. ハンバーガー メニュー に移動し、[地図のスタイル] を選択します。

  3. [建物の 3D 表示] スイッチをオフにします。

  4. [建物の 3D 表示] スイッチをオフに切り替えたら、ピンアイコン をクリックして、選択した場所に目印を追加します。

  5. 目印を含むプロジェクトを指定して、[保存] をクリックします。

  6. 目印の [タイトル] フィールドに、目印の名前を入力します。

  7. プロジェクト ペインの戻る矢印 をクリックし、 [その他の操作] メニューを選択します。

  8. メニューから [Export as KML file] を選択します。

KLM ファイルでは、プレースマークの緯度、経度、高度が <coordinates> タグでカンマ区切りで報告されます。次に例を示します。

<coordinates>-122.0755182435043,37.41347299422944,7.420342565583832</coordinates>

<LookAt> タグの緯度と経度は使用しないでください。これは、位置情報ではなくカメラの位置を指定します。

物理的な場所に移動する

場所の高度は、実際にその場所に行き、現地で測定することで計算できます。

回転クォータニオンを取得する

GeospatialPose.EunRotation は、地理空間ポーズから向きを抽出し、ベクトルをターゲットから東北(EUN)座標系に変換する回転行列を表すクォータニオンを出力します。X+ は東、Y+ は重力から上向き、Z+ は北を指します。

WGS84 アンカー

WGS84 アンカーは、任意の緯度、経度、高度に 3D コンテンツを配置できるアンカーの一種です。現実世界に配置するには、ポーズと向きが必要です。位置は、WGS84 座標系で指定される緯度、経度、高度で構成されます。向きはクォータニオン回転で構成されます。

高度は、基準となる WGS84 楕円体からの高さ(メートル単位)で報告されます。地面は 0 ではないためです。作成されたアンカーごとに、これらの座標をアプリが提供する必要があります。

現実世界に WGS84 アンカーを配置する

場所の標高を特定する

アンカーを配置する場所の高さを特定する方法はいくつかあります。

  • アンカーの位置がユーザーの近くにある場合は、ユーザーのデバイスの高度に近い高度を使用できます。
  • 緯度と経度を取得したら、Elevation API を使用して、EGM96 仕様に基づく高度を取得します。GeospatialPose 高度と比較するには、Maps API の EGM96 標高を WGS84 に変換する必要があります。コマンドライン インターフェースと HTML インターフェースの両方がある GeoidEval をご覧ください。Maps API は、WGS84 仕様に従って緯度と経度をデフォルトで報告します。
  • 場所の緯度、経度、高度は Google Earth で取得できます。これにより、最大数 m の誤差が生じます。KML ファイルの <LookAt> タグではなく、<coordinates> タグの緯度、経度、高度を使用します。
  • 既存のアンカーが近くにあり、急斜面でない場合は、Maps API などの別のソースを使用せずに、カメラの GeospatialPose から高度を使用できる場合があります。

アンカーを作成する

緯度、経度、高度、回転クォータニオンを取得したら、ARAnchorManagerExtensions.AddAnchor() を使用して、指定した地理座標にコンテンツを固定します。

if (earthTrackingState == TrackingState.Tracking)
{
  var anchor =
      AnchorManager.AddAnchor(
          latitude,
          longitude,
          altitude,
          quaternion);
  var anchoredAsset = Instantiate(GeospatialAssetPrefab, anchor.transform);
}

地形アンカー

地形アンカーはアンカーの一種で、緯度と経度のみを使用して AR オブジェクトを配置できます。VPS の情報を利用して、地面からの正確な高度を特定できます。

目的の高度を入力するのではなく、地形からの高度を指定します。これがゼロの場合、アンカーは地形と水平になります。

飛行機の検出モードを設定する

平面の検出は任意であり、アンカーを使用する必要はありません。水平面のみが使用されます。水平面は、地面上の地形アンカーの動的な配置に役立ちます。

地形アンカーは HorizontalHorizontal | Vertical の影響を受けます。

[検出モード] プルダウン メニューを使用して、検出モードを設定します。

新しい Async API を使用して地形アンカーを作成する

地形アンカーを作成して配置するには、ARAnchorManagerExtensions.resolveAnchorOnTerrainAsync() を呼び出します。

アンカーはすぐには使用できず、解決する必要があります。解決すると、ResolveAnchorOnTerrainPromise に表示されます。

public GameObject TerrainAnchorPrefab;

public void Update()
{
    ResolveAnchorOnTerrainPromise terrainPromise =
        AnchorManager.ResolveAnchorOnTerrainAsync(
            latitude, longitude, altitudeAboveTerrain, eunRotation);

    // The anchor will need to be resolved.
    StartCoroutine(CheckTerrainPromise(terrainPromise));
}

private IEnumerator CheckTerrainPromise(ResolveAnchorOnTerrainPromise promise)
{
    yield return promise;

    var result = promise.Result;
    if (result.TerrainAnchorState == TerrainAnchorState.Success &&
        result.Anchor != null)
    {
        // resolving anchor succeeded
        GameObject anchorGO = Instantiate(TerrainAnchorPrefab,
            result.Anchor.gameObject.transform);
        anchorGO.transform.parent = result.Anchor.gameObject.transform;
    }
    else
    {
       // resolving anchor failed
    }

    yield break;
}

プロミスの状態を確認する

Promise には PromiseState が関連付けられます。

説明
Pending オペレーションはまだ保留中です。
Done オペレーションが完了し、結果が利用可能になっています。
Cancelled オペレーションがキャンセルされました。

Promise の結果の Terrain アンカー状態を確認する

TerrainAnchorState は非同期オペレーションに属し、最終的な Promise の結果の一部です。

switch (result.TerrainAnchorState)
{
    case TerrainAnchorState.Success:
        // Anchor has successfully resolved
        break;
    case TerrainAnchorState.ErrorUnsupportedLocation:
        // The requested anchor is in a location that isn't supported by the Geospatial API.
        break;
    case TerrainAnchorState.ErrorNotAuthorized:
        // An error occurred while authorizing your app with the ARCore API. See
        // https://developers.google.com/ar/reference/unity-arf/namespace/Google/XR/ARCoreExtensions#terrainanchorstate_errornotauthorized
        // for troubleshooting steps.
        break;
    case TerrainAnchorState.ErrorInternal:
        // The Terrain anchor could not be resolved due to an internal error.
        break;
    default:
        break;
}

屋上アンカー

屋上アンカーのヘルプセンター記事

屋上アンカーはアンカーの一種で、上記の地形アンカーとよく似ています。違いは、地形の上の高度ではなく、屋根の上の高度を指定する点です。

新しい Async API を使用して屋上アンカーを作成する

アンカーはすぐには使用できず、解決する必要があります。

屋上アンカーを作成して配置するには、ARAnchorManagerExtensions.resolveAnchorOnRooftopAsync() を呼び出します。地形アンカーと同様に、Promise の PromiseState にもアクセスできます。その後、Promise の結果を確認して RooftopAnchorState にアクセスできます。

public GameObject RooftopAnchorPrefab;

public void Update()
{
    ResolveAnchorOnRooftopPromise rooftopPromise =
        AnchorManager.ResolveAnchorOnRooftopAsync(
            latitude, longitude, altitudeAboveRooftop, eunRotation);

    // The anchor will need to be resolved.
    StartCoroutine(CheckRooftopPromise(rooftopPromise));
}

private IEnumerator CheckRooftopPromise(ResolveAnchorOnTerrainPromise promise)
{
    yield return promise;

    var result = promise.Result;
    if (result.RooftopAnchorState == RooftopAnchorState.Success &&
        result.Anchor != null)
    {
        // resolving anchor succeeded
        GameObject anchorGO = Instantiate(RooftopAnchorPrefab,
            result.Anchor.gameObject.transform);
        anchorGO.transform.parent = result.Anchor.gameObject.transform;
    }
    else
    {
       // resolving anchor failed
    }

    yield break;
}

プロミスの状態を確認する

Promise には PromiseState が関連付けられます(上記のを参照)。

Promise の結果の屋上アンカー状態を確認する

RooftopAnchorState は非同期オペレーションに属し、最終的な Promise の結果の一部です。

switch (result.RooftopAnchorState)
{
    case TerrainAnchorState.Success:
        // Anchor has successfully resolved
        break;
    case RooftopAnchorState.ErrorUnsupportedLocation:
        // The requested anchor is in a location that isn't supported by the Geospatial API.
        break;
    case RooftopAnchorState.ErrorNotAuthorized:
        // An error occurred while authorizing your app with the ARCore API. See
        // https://developers.google.com/ar/reference/unity-arf/namespace/Google/XR/ARCoreExtensions#terrainanchorstate_errornotauthorized
        // for troubleshooting steps.
        break;
    case RooftopAnchorState.ErrorInternal:
        // The Rooftop anchor could not be resolved due to an internal error.
        break;
    default:
        break;
}

次のステップ