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

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

地理空間アンカーの種類

ジオ空間アンカーには 3 種類あり、それぞれ高度を異なる方法で処理します。

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

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

  3. 屋上アンカー:
    屋上アンカーを使用すると、緯度と経度のみを使用してコンテンツを配置できます。その位置にある建物の屋上に対する高さも指定できます。高度は、ストリートビューのジオメトリで知られている建物の上部を基準として決定されます。建物に配置されていない場合は、デフォルトで地形の高さになります。

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

前提条件

続行する前に、Geospatial API を有効にしてください。

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

各アンカータイプには、作成専用の API があります。詳細については、地理空間アンカーの種類をご覧ください。

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

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

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

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

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

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

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

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

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

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

場所の緯度と経度を計算する方法は 3 つあります。

  • Geospatial Creator を使用すると、実際に現地に足を運ばなくても、3D コンテンツで世界を表示して拡張できます。これにより、Unity エディタで 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. メニューから [KML ファイルとしてエクスポート] を選択します。

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

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

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

物理的な場所に移動する

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

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

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

WGS84 アンカー

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

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

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

場所の高度を特定する

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

  • アンカーの位置がユーザーの近くにある場合は、ユーザーのデバイスの高さに近い高度を使用できます。
  • 緯度と経度を取得したら、Elevation API を使用して EGM96 仕様に基づいて標高を取得します。GeospatialPose 高度と比較するには、Maps API の EGM96 標高を WGS84 に変換する必要があります。コマンドライン インターフェースと HTML インターフェースの両方がある GeoidEval をご覧ください。Maps API は、WGS84 仕様に従って緯度と経度を返します。
  • 場所の緯度、経度、標高は Google Earth で確認できます。この場合、誤差は最大で数メートルになります。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 の情報を利用して地面からの正確な高度を特定できます。

目的の高度を入力するのではなく、地形からの高度を指定します。0 に設定すると、アンカーは地形と同じ高さになります。

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

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

地形アンカーは 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 の結果の地形アンカーの状態を確認する

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;
}

次のステップ