使用永久 Cloud 錨點的 ARCore Cloud Anchors

1. 總覽

ARCore 這個平台可讓您在行動裝置上建構擴增實境應用程式,透過 Cloud Anchors API,您可以建構共用一個參照架構的 AR 應用程式,讓多位使用者可以將虛擬內容放在同一個實際位置。

本程式碼研究室將引導您瞭解 Cloud Anchors API。您將使用現有的 ARCore 應用程式,將其修改為使用 Cloud Anchors,並打造共用 AR 體驗。

ARCore 錨點和永久 Cloud 錨點

ARCore 的一個基本概念是「Anchor」,用於描述現實世界中的固定位置。ARCore 會在 Anchor 中的動作追蹤功能隨著時間逐漸改善時,自動調整姿勢的值。

Cloud Anchors 是雲端託管的錨點。可以由多位使用者解析,建立跨使用者和裝置的共同參考架構。

代管錨點

代管錨定廣告時,會發生以下情況:

  1. Anchor 對世界的姿勢將上傳至雲端,然後取得 Cloud Anchor ID。
    Cloud Anchor ID 是一個字串,必須將要解析這個錨點的任何人傳送給對方。
  2. 系統會將含有錨點圖像資料的資料集上傳到 Google 伺服器。
    這個資料集含有裝置最近看到的影像資料。在主持之前,藉由移動裝置從不同視角擷取錨點周圍的區域,有助於提升本地化品質。

轉移 Cloud Anchor ID

在本程式碼研究室中,您將使用 Firebase 轉移 Cloud Anchor ID。您可以自由使用其他方式共用 Cloud Anchor ID。

解決錨定廣告

您可以使用 Cloud Anchor API,以設定錨點的 Cloud Anchor ID 解析。這會在原始代管錨定標記所在的實際位置上建立新錨定標記。解析時,裝置所呈現的實際環境必須與原始代管錨定廣告相同。

永久 Cloud 錨點

在 1.20 版之前,Cloud Anchors 只能在託管後 24 小時進行解析。只要使用 Persistent Cloud Anchors API,即可建立雲端錨點。這個 API 會在建立後的 1 至 365 天內解決。

建構目標

在這個程式碼研究室中,您將以現有的 ARCore 應用程式為基礎進行建構。完成程式碼研究室後,您的應用程式將:

  • 能夠託管永久的 Cloud Anchors,並取得 Cloud Anchor ID。
  • 將 Cloud Anchor ID 儲存在裝置上,以便使用 Android SharedPreferences 輕鬆擷取。
  • 使用已儲存的 Cloud Anchor ID 來解析先前代管的錨點。如此一來,我們就可以輕鬆模擬單一裝置的多使用者體驗,並用於本程式碼研究室。
  • 將 Cloud Anchor ID 與執行相同應用程式的其他裝置共用,這樣多位使用者就會在相同位置看到 Android 雕像。

系統會在 Cloud Anchor 的位置算繪 Android 雕像:

課程內容

  • 如何使用 ARCore SDK 代管錨點並取得 Cloud Anchor ID。
  • 如何使用 Cloud Anchor ID 來解析錨點。
  • 如何在同一部裝置或不同裝置上的不同 AR 工作階段之間儲存及共用 Cloud Anchor ID。

軟硬體需求

2. 設定開發環境

設定開發機器

使用 USB 傳輸線將 ARCore 裝置連接至電腦。確認裝置允許 USB 偵錯

開啟終端機並執行 adb devices,如下所示:

adb devices

List of devices attached
<DEVICE_SERIAL_NUMBER>    device

<DEVICE_SERIAL_NUMBER> 是裝置專屬的字串。繼續操作前,請確認每個裝置只有一部裝置

下載並安裝程式碼

您可以複製存放區:

git clone https://github.com/googlecodelabs/arcore-cloud-anchors.git

或下載 ZIP 檔案並解壓縮:

請啟動 Android Studio。按一下「Open an existing Android Studio project」。接著前往您解壓縮先前下載的 ZIP 檔案所在的目錄,然後按兩下 arcore-cloud-anchors 目錄。

這是包含多個模組的單一 Gradle 專案。如果 Android Studio 左上方的「Project」窗格未顯示在「Project」窗格中,請按一下下拉式選單中的「Projects」。結果應如下所示:

52282f0415fdbdcb.png

您主要會在 work 模組中工作。其他模組則包含 helpers 模組,內含一組實用的包裝函式類別。另外,程式碼研究室的每個部分都有完整的解決方案。除了 helpers 模組以外,每個模組都是可建構的應用程式。

如果系統顯示建議您升級 Android Gradle 外掛程式的對話方塊,請點選「Don't remember me to this project」(不要再次提醒我這項專案)

31a93c7e9cc58b53.png

按一下「執行」>「執行」執行...&gt;「work」。在隨即顯示的「Select Deployment Target」對話方塊中,裝置應列於「Connected Devices」下方。選取裝置,然後按一下「OK」。Android Studio 會建構初始應用程式,並在您的裝置上執行。

首次執行應用程式時,系統會要求 CAMERA 權限。輕觸「允許」繼續操作。

f7ea81f71a4b969e.png

如何使用應用程式

  1. 將裝置四處移動,協助應用程式尋找飛機。系統找到飛機時,會以虛線的表面顯示。
  2. 輕觸飛機的任一處即可放置錨點,系統會繪製錨定標記位置的 Android 人物。這個應用程式一次只能加入一個錨點。
  3. 移動裝置。即使裝置在移動,人物仍應留在相同位置。
  4. 按下「清除」按鈕移除錨點。這樣您就可以放置另一個錨點。

這個應用程式目前只會使用 ARCore 提供的動作追蹤功能,在應用程式單次執行時追蹤錨點。如果您決定關閉、終止並重新啟動應用程式,將會遺失先前放置的錨點和任何相關資訊,包括姿勢。

在接下來的幾個章節中,您將以這個應用程式為基礎,瞭解在 AR 工作階段之間共用錨點的方式。

3. 代管錨點

在本節中,您將修改 work 專案以代管錨點。在編寫程式碼之前,您需要修改應用程式設定。

宣告 INTERNET 權限

由於 Cloud Anchors 需要與 ARCore Cloud Anchor API 服務通訊,應用程式必須具備網際網路存取權。

AndroidManifest.xml 檔案中,於 android.permission.CAMERA 權限宣告的正下方新增下列程式碼:

<!-- Find this line... -->
<uses-permission android:name="android.permission.CAMERA"/>

<!-- Add the line right below -->
<uses-permission android:name="android.permission.INTERNET"/>

啟用 ARCore API

  1. 前往 ARCore API 服務頁面。
  2. 在專案清單中,選取專案或建立新專案。
  3. 按一下「啟用」

設定無金鑰驗證

如要使用永久 Cloud Anchors,您需要透過無金鑰驗證進行 ARCore API 驗證。

  1. 前往 Google Cloud Platform Console
  2. 在專案清單中選取一個專案。
  3. 如果 API 與服務頁面尚未開啟,請開啟主控台左側選單,然後選取「API 與」服務
  4. 按一下左側的「憑證」
  5. 按一下「建立憑證」,然後選取「OAuth 用戶端 ID」
  6. 填入下列值:
    • 應用程式類型:Android
    • Package name (套件名稱):com.google.ar.core.codelab.cloudanchor
  7. 擷取偵錯簽署憑證指紋:
    1. 在 Android Studio 專案中,開啟 Gradle 工具窗格
    2. 雲端錨點 >工作 >工作 >android,執行 signingReport 工作。
    3. 將 SHA-1 指紋複製到 Google Cloud 中的「SHA-1 憑證指紋」欄位。

設定 ARCore

接著,您將修改應用程式,讓應用程式在使用者輕觸時代管錨定廣告,而非一般的錨定廣告。如要這麼做,您必須設定 ARCore 工作階段,啟用 Cloud Anchors。

CloudAnchorFragment.java 檔案中,新增下列程式碼:

// Find this line...
session = new Session(requireActivity());

// Add these lines right below:
// Configure the session.
Config config = new Config(session);
config.setCloudAnchorMode(CloudAnchorMode.ENABLED);
session.configure(config);

繼續下一步之前,請建構並執行應用程式。請務必只建構 work 模組。您的應用程式應成功建構,且執行方式與先前相同。

代管錨點

該要代管要上傳至 ARCore API 的錨點。

將下列新欄位新增至 CloudAnchorFragment 類別:

// Find this line...
private Anchor currentAnchor = null;

// Add these lines right below.
@Nullable
private Future future = null;

別忘了新增 com.google.ar.core.Future 的匯入內容。

請修改 onClearButtonPressed 方法,如下所示:

private void onClearButtonPressed() {
  // Clear the anchor from the scene.
  if (currentAnchor != null) {
    currentAnchor.detach();
    currentAnchor = null;
  }

  // The next part is the new addition.
  // Cancel any ongoing asynchronous operations.
  if (future != null) {
    future.cancel();
    future = null;
  }
}

接著,將以下方法新增至 CloudAnchorFragment 類別:

private void onHostComplete(String cloudAnchorId, CloudAnchorState cloudState) {
  if (cloudState == CloudAnchorState.SUCCESS) {
    messageSnackbarHelper.showMessage(getActivity(), "Cloud Anchor Hosted. ID: " + cloudAnchorId);
  } else {
    messageSnackbarHelper.showMessage(getActivity(), "Error while hosting: " + cloudState.toString());
  }
}

CloudAnchorFragment 類別中找出 handleTap 方法,然後新增以下幾行內容:

// Find this line...
currentAnchor = hit.createAnchor();

// Add these lines right below:
messageSnackbarHelper.showMessage(getActivity(), "Now hosting anchor...");
future = session.hostCloudAnchorAsync(currentAnchor, 300, this::onHostComplete);

再次透過 Android Studio 執行應用程式。畫面上應該會顯示「Now Hosting anchor...」訊息。顯示錨定標記主機成功完成後,您應該會看到其他訊息。如果看到「存放錨點錯誤: ERROR_NOT_AUTHORIZED」,請確認 OAuth 用戶端設定正確無誤。

任何人只要知道錨點 ID 且與錨定標記位於相同的實際空間,便可使用錨點 ID,以相對於周遭環境的角度 (位置和方向) 建立錨點。

不過,錨定 ID 長度較長,可供其他使用者手動輸入。在以下各節中,您將以易於擷取的方式儲存 Cloud Anchor ID,以便允許在相同或其他裝置上解析錨點。

4. 儲存 ID 及解析錨點

在這個部分,您將指派長碼給較長的 Cloud Anchor ID,以便其他使用者手動輸入。您將使用 Shared Preferences API,將 Cloud 錨點 ID 儲存為鍵/值資料表中的值。即使應用程式終止並重新啟動,這個資料表仍會持續顯示。

系統已為您提供名為 StorageManager 的輔助類別。這是 SharedPreferences API 的包裝函式,具備產生新專屬短碼的方法,以及讀取/寫入 Cloud Anchor ID 的方法。

使用 StorageManager

修改 CloudAnchorFragment,使用 StorageManager 儲存包含短碼的 Cloud Anchor ID,以便輕鬆擷取。

CloudAnchorFragment 中建立下列新欄位:

// Find this line...
private TapHelper tapHelper;

// And add the storageManager.
private final StorageManager storageManager = new StorageManager();

然後修改 onHostComplete 方法:

private void onHostComplete(String cloudAnchorId, CloudAnchorState cloudState) {
  if (cloudState == CloudAnchorState.SUCCESS) {
    int shortCode = storageManager.nextShortCode(getActivity());
    storageManager.storeUsingShortCode(getActivity(), shortCode, anchor.getCloudAnchorId());
    messageSnackbarHelper.showMessage(
        getActivity(), "Cloud Anchor Hosted. Short code: " + shortCode);
  } else {
    messageSnackbarHelper.showMessage(getActivity(), "Error while hosting: " + cloudState.toString());
  }
}

現在,請透過 Android Studio 建構並執行應用程式。建立及代管錨點時,畫面上會顯示短碼,而非長 Cloud Anchor ID。

放置錨點後立即顯示

稍等片刻

請注意,StorageManager 產生的短碼目前一律會依遞增順序指派。

接下來,你需要新增幾個 UI 元素,以便輸入短碼並重新建立錨點。

新增「解析」按鈕

您可以在「清除」按鈕旁新增另一個按鈕。這就是「RESOLVE」按鈕。按一下「RESOLVE」按鈕會開啟對話方塊,提示使用者輸入簡短程式碼。這個短程式碼是用來從 StorageManager 擷取 Cloud Anchor ID,並解析錨點。

如要新增按鈕,您必須修改 res/layout/cloud_anchor_fragment.xml 檔案。在 Android Studio 中,按兩下檔案,然後按一下「Text」分頁,即可顯示原始 XML。進行下列修改:

<!-- Find this element. -->
<Button
    android:text="CLEAR"
    android:id="@+id/clear_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

<!-- Add this element right below. -->
<Button
    android:text="RESOLVE"
    android:id="@+id/resolve_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

現在,請在 CloudAnchorFragment 中新增欄位:

private Button resolveButton;

新增方法:

private void onResolveButtonPressed() {
  ResolveDialogFragment dialog = new ResolveDialogFragment();
  dialog.show(getFragmentMagetActivity().getSupportFragmentManagernager(), "Resolve");
}

請在 onCreateView 方法中初始化 resolveButton,如下所示:

// Find these lines...
Button clearButton = rootView.findViewById(R.id.clear_button);
clearButton.setOnClickListener(v -> onClearButtonPressed());

// Add these lines right below.
resolveButton = rootView.findViewById(R.id.resolve_button);
resolveButton.setOnClickListener(v -> onResolveButtonPressed());

找出並修改 handleTap 方法:

private void handleTap(Frame frame, Camera camera) {
  // ...

  // Find this line.
  currentAnchor = hit.createAnchor();

  // Add this line right below.
  getActivity().runOnUiThread(() -> resolveButton.setEnabled(false)); 
}

onClearButtonPressed 方法中新增一行:

private void onClearButtonPressed() {
  // Clear the anchor from the scene.
    if (currentAnchor != null) {
      currentAnchor.detach();
      currentAnchor = null;
    }

    // Cancel any ongoing async operations.
    if (future != null) {
      future.cancel();
      future = null;
    }

  // The next line is the new addition.
  resolveButton.setEnabled(true);
}

透過 Android Studio 建構並執行應用程式。您應該會在「CLEAR」按鈕旁邊看到「RESOLVE」按鈕。按一下「RESOLVE」按鈕後,系統應會彈出對話方塊,如下所示。

現在可以看到「RESOLVE」按鈕

點選按鈕後,這個對話方塊就會出現

輕觸飛機並代管錨點後,系統會停用「RESOLVE」按鈕,但輕觸「CLEAR」按鈕應會再次啟用。這是根據設計,因此一次只有一個錨點。

「解析錨點」對話方塊不會執行任何動作,但您現在就可以變更

解析錨點

CloudAnchorFragment 類別中新增以下方法:

private void onShortCodeEntered(int shortCode) {
  String cloudAnchorId = storageManager.getCloudAnchorId(getActivity(), shortCode);
  if (cloudAnchorId == null || cloudAnchorId.isEmpty()) {
    messageSnackbarHelper.showMessage(
        getActivity(),
        "A Cloud Anchor ID for the short code " + shortCode + " was not found.");
    return;
  }
  resolveButton.setEnabled(false);
  future = session.resolveCloudAnchorAsync(
      cloudAnchorId, (anchor, cloudState) -> onResolveComplete(anchor, cloudState, shortCode));
}

private void onResolveComplete(Anchor anchor, CloudAnchorState cloudState, int shortCode) {
  if (cloudState == CloudAnchorState.SUCCESS) {
    messageSnackbarHelper.showMessage(getActivity(), "Cloud Anchor Resolved. Short code: " + shortCode);
    currentAnchor = anchor;
  } else {
    messageSnackbarHelper.showMessage(
        getActivity(),
        "Error while resolving anchor with short code "
            + shortCode
            + ". Error: "
            + cloudState.toString());
    resolveButton.setEnabled(true);
  }
}

然後修改 onResolveButtonPressed 方法:

private void onResolveButtonPressed() {
  ResolveDialogFragment dialog = ResolveDialogFragment.createWithOkListener(
      this::onShortCodeEntered);
  dialog.show(getActivity().getSupportFragmentManager(), "Resolve");
}

透過 Android Studio 建構並執行應用程式,然後執行下列步驟:

  1. 在平面上建立錨點,並等待代管錨定廣告。
    記住短碼。
  2. 按下「清除」按鈕刪除錨定標記。
  3. 按下「RESOLVE」按鈕。輸入步驟 1 提供的短碼。
  4. 錨定標記應該在你原先放置的相同位置位置上。
  5. 退出並終止應用程式,然後重新開啟。
  6. 重複步驟 (3) 和 (4)。您會在同一個位置看到新的錨定標記。

請輸入短碼

已成功解決錨點

5. 在裝置之間分享

您已經瞭解如何將錨定的 Cloud Anchor ID 儲存在裝置的本機儲存空間,並於日後擷取該 ID 以重新建立相同的錨點。不過,只有在不同裝置之間共用 Cloud Anchor ID 時,才能充分發揮 Cloud Anchor 的潛力。

您可以自行決定應用程式共用 Cloud Anchor ID 的方式。任何在裝置之間傳輸字串都可用於轉移。在本程式碼研究室中,您將使用 Firebase 即時資料庫,在應用程式執行個體之間轉移 Cloud Anchor ID。

設定 Firebase

你必須在 Google 帳戶中設定 Firebase 即時資料庫,才能搭配這個應用程式使用。只要使用 Android Studio 中的 Firebase Assistant,即可輕鬆完成這項作業。

在 Android Studio 中,按一下工具>Firebase:在隨即彈出的 Google 助理窗格中,依序點選「即時資料庫」和「儲存及擷取資料」

68e927cbf324a3b2.png

按一下「連結至 Firebase」按鈕,將 Android Studio 專案連結至新的或現有的 Firebase 專案。

63f3b1ffd6bd263e.png

系統會提示你選取模組。選取 work 模組:

be737c689ad6dd78.png

系統隨即會顯示「Start Connect」對話方塊。請稍候片刻。

b48626f8672551ee.png

使用 Google 帳戶登入,並完成網路工作流程,為應用程式建立 Firebase 專案,直到返回 Android Studio 為止。

接著,在「小幫手」窗格中,按一下「在應用程式中新增即時資料庫」

68e0843fa2531c4c.png

在彈出的對話方塊中,從「Target module」下拉式選單中選取「work」,然後按一下「Accept Changes」

155fd89533c02671.png

系統接著就會:

  1. google-services.json 檔案新增至 work 目錄
  2. 在相同目錄中的 build.gradle 檔案中新增幾行內容。
  3. 建構並執行應用程式 (您可能會看到 Firebase 資料庫版本號碼的解決錯誤)。

work 模組 build.gradle 檔案中,找出並移除以下這行 (xxxx 為最新版本編號的預留位置)

dependencies {
  ...
  implementation 'com.google.firebase:firebase-database:xxxx'

接著,請查看「設定公開存取權規則」頁面中連結的操作說明,將 Firebase 即時資料庫設為可寫入世界。這有助於簡化本程式碼研究室中的測試:

666ebefd39019c05.png

Firebase 控制台中,選取您已連結 Android Studio 專案的專案,然後依序選取「BUILD」>即時資料庫

Firebase 即時資料庫位置

按一下「建立資料庫」,設定並設定即時資料庫

建立資料庫

選擇任一資料庫位置。

在下一個步驟中選取「測試模式」安全性規則,然後按一下「啟用」

資料庫安全性

應用程式現已設為使用 Firebase 資料庫。

使用 FirebaseManager

您現在會以 FirebaseManager 取代 StorageManager

在 Android Studio 的 work 目錄下找到 CloudAnchorFragment 類別。將 StorageManager 替換為 FirebaseManager

// Find this line.
private final StorageManager storageManager = new StorageManager();

// And replace it with this line.
private FirebaseManager firebaseManager;

onAttach 方法中初始化 firebaseManager

public void onAttach(@NonNull Context context) {
  super.onAttach(context);
  tapHelper = new TapHelper(context);
  trackingStateHelper = new TrackingStateHelper(requireActivity());

  // The next line is the new addition.
  firebaseManager = new FirebaseManager(context);
}

請修改 onShortCodeEntered 方法,如下所示:

private void onShortCodeEntered(int shortCode) {
  firebaseManager.getCloudAnchorId(shortCode, cloudAnchorId -> {
    if (cloudAnchorId == null || cloudAnchorId.isEmpty()) {
      messageSnackbarHelper.showMessage(
          getActivity(),
          "A Cloud Anchor ID for the short code " + shortCode + " was not found.");
      return;
    }
    resolveButton.setEnabled(false);
    future = session.resolveCloudAnchorAsync(
        cloudAnchorId, (anchor, cloudState) -> onResolveComplete(anchor, cloudState, shortCode));
  });
}

然後,按照以下方式修改 onHostComplete 方法:

private void onHostComplete(String cloudAnchorId, CloudAnchorState cloudState) {
  if (cloudState == CloudAnchorState.SUCCESS) {
    firebaseManager.nextShortCode(shortCode -> {
      if (shortCode != null) {
        firebaseManager.storeUsingShortCode(shortCode, cloudAnchorId);
        messageSnackbarHelper.showMessage(getActivity(), "Cloud Anchor Hosted. Short code: " + shortCode);
      } else {
        // Firebase could not provide a short code.
        messageSnackbarHelper.showMessage(getActivity(), "Cloud Anchor Hosted, but could not "
            + "get a short code from Firebase.");
      }
    });
  } else {
    messageSnackbarHelper.showMessage(getActivity(), "Error while hosting: " + cloudState.toString());
  }
}

建構並執行應用程式您應該會看到與上一節相同的 UI 流程,只不過,系統現在會使用線上 Firebase 資料庫儲存 Cloud Anchor ID 和短碼,而不是裝置本機儲存空間。

多使用者測試

如要測試多使用者體驗,請使用兩支手機:

  1. 在兩部裝置上安裝該應用程式。
  2. 使用單一裝置代管錨定廣告並產生短碼。
  3. 再使用另一部裝置,運用該短代碼解析錨點。

你應該能在某部裝置上代管錨定廣告、取得簡短程式碼,然後在另一部裝置上使用短碼,就能在相同位置看到錨點!

6. 總結

恭喜!您已完成本程式碼研究室的課程!

涵蓋內容

  • 如何使用 ARCore SDK 代管錨點並取得 Cloud Anchor ID。
  • 如何使用 Cloud Anchor ID 來解析錨點。
  • 如何在同一部裝置或不同裝置上,在不同 AR 工作階段之間儲存及共用 Cloud Anchor ID。

瞭解詳情