使用地理空間錨點在 Unity 上定位實際內容

地理空間錨點是一種錨點,可讓您將 3D 內容放置在現實世界中。

地理空間錨點類型

地理空間錨點分為三種類型,每種類型處理高度的方式各不相同:

  1. WGS84 錨點
    WGS84 錨點可讓您在任何指定的緯度、經度和高度上放置 3D 內容。

  2. 地形錨點
    地形錨點可讓您只使用緯度和經度來放置內容,並以該位置相對於地形的高度為準。高度會根據 VPS 所知的地上或樓層來決定。

  3. 屋頂錨點
    屋頂錨點可讓您只使用經緯度和相對於該位置建築物屋頂的高度來放置內容。高度會根據 街景幾何圖形所知的建築物頂端來決定。未放置在建築物上時,預設為地形高度。

WGS84 地形 屋頂
水平位置 緯度, 經度 緯度, 經度 緯度, 經度
垂直位置 相對於 WGS84 高度 相對於 Google 地圖所判定的地形高度 相對於 Google 地圖所判定的屋頂樓層
是否需要由伺服器解析?

必要條件

請務必先啟用 Geospatial API,再繼續操作。

放置地理空間錨點

每個錨點類型都有專屬的 API 可用來建立錨點,詳情請參閱「地理空間錨點類型」。

根據命中測試建立錨點

您也可以使用命中測試結果建立地理空間錨點。使用命中測試的 Pose,並將其轉換為 GeospatialPose。可用來放置上述任一錨點類型。

從 AR 姿勢取得地理空間姿勢

AREarthManager.Convert(Pose) 提供另一種方法,可透過將 AR 姿勢轉換為地理空間姿勢,判斷經緯度。

從地理空間姿勢取得 AR 姿勢

AREarthManager.Convert(GeospatialPose) 會將地球指定的水平位置、高度和四元數旋轉 (相對於東-上-南座標架構) 轉換為 AR 姿勢 (相對於 GL 世界座標)。

選擇最適合用途的方法

每種建立錨點的方法都會帶來相關的取捨,請留意:

  • 使用 街景地形時,請使用命中測試將內容附加至建築物。
  • 請優先使用地形或屋頂錨點,而非 WGS84 錨點,因為前者會使用 Google 地圖決定的高度值。

判斷位置的經緯度

計算地點的經緯度有三種方法:

  • 使用地理空間創作者,即可透過 3D 內容查看及擴增世界,無須實際前往該地。這樣一來,您就能在 Unity 編輯器中使用 Google 地圖,以視覺化方式放置 3D 沈浸式內容。系統會自動計算內容的緯度、經度、旋轉角度和高度。
  • 使用 Google 地圖
  • 使用 Google 地球。請注意,使用 Google 地球 (而非 Google 地圖) 取得這些座標時,誤差範圍最多可達數公尺。
  • 前往實際地點

使用 Google 地圖

如要使用 Google 地圖取得某個地點的經緯度,請按照下列步驟操作:

  1. 在電腦上前往 Google 地圖

  2. 依序前往「圖層」 >「更多」

  3. 將「地圖類型」變更為「衛星」,然後取消勾選畫面左下角的「地球儀檢視」核取方塊。

    這會強制使用 2D 視角,並消除可能來自角度 3D 檢視畫面的錯誤。

  4. 在地圖上按一下要複製的位置,然後選取經度/緯度,將其複製到剪貼簿。

使用 Google 地球

您可以按一下使用者介面中的某個位置,然後從地標詳細資料中讀取資料,藉此計算 Google 地球中位置的緯度和經度。

如要使用 Google 地球取得地點的經緯度,請按照下列步驟操作:

  1. 在電腦上前往 Google 地球

  2. 前往漢堡選單 ,然後選取「地圖樣式」

  3. 關閉「3D 建築物」切換鈕。

  4. 關閉「3D 建築物」切換鈕後,按一下圖釘圖示 ,即可在所選位置新增地標。

  5. 指定要包含地標的專案,然後按一下「儲存」

  6. 在地標的「Title」欄位中,輸入地標名稱。

  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 橢圓體為基準,因此地面高度「不會」為零。您的應用程式負責為每個建立的錨點提供這些座標。

在現實世界中放置 WGS84 錨點

判斷位置的高度

您可以透過下列幾種方式,判斷地點的高度以便放置錨點:

  • 如果錨點位置位於使用者附近,您可以使用與使用者裝置高度相近的高度。
  • 取得經緯度後,請使用 Elevation API,根據 EGM96 規格取得海拔高度。您必須將 Maps API EGM96 海拔高度轉換為 WGS84,以便與 GeospatialPose 高度進行比較。請參閱同時提供指令列和 HTML 介面的 GeoidEval。Maps API 會根據預設的 WGS84 規格回報經緯度。
  • 您可以透過 Google 地球取得位置的緯度、經度和高度。這會產生最多數公尺的誤差範圍。使用 KML 檔案中 <coordinates> 標記而非 <LookAt> 標記的經緯度和高度。
  • 如果附近有現有的錨點,而且你並未處於陡峭的斜坡上,則可以使用相機的 GeospatialPose 提供的高度資訊,而不需要使用其他來源,例如 Maps API。

建立錨點

取得經緯度、高度和旋轉四元數後,請使用 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 的狀態

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

屋頂錨點

屋頂固定點 Hero

屋頂固定點是一種固定點,與上述地形固定點非常相似。差別在於你要提供屋頂以上的高度,而不是地形以上的高度。

使用新的 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 的狀態

Promise 會具有相關的 PromiseState,請參閱上方的表格

檢查 Promise 結果的 Rooftop 錨點狀態

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

後續步驟