ARCore 云锚点和持久云锚点

1. 概览

ARCore 是用于在移动设备上构建增强现实 (AR) 应用的平台。借助 Cloud Anchors API,您可以创建共享通用参照系的 AR 应用,以便多个用户在同一个实际位置放置虚拟内容。

此 Codelab 将指导您使用 Cloud Anchors API。您将需要使用一个现有的 ARCore 应用,对其修改以使用云锚点,并打造共享 AR 体验。

ARCore 锚点和持久云锚点

ARCore 中的一个基本概念是锚点,它描述了现实世界中的固定位置。ARCore 会随着动作跟踪技术的不断改进而自动调整锚点姿态的值。

云锚点是指在云端托管的锚点。云锚点可被多个用户解析,这样就可以在用户及其设备之间建立通用参照系。

托管锚点

托管锚点后,会出现以下情况:

  1. 系统会将锚点相对于现实世界的姿态上传到云中,并获取云锚点 ID。
    云锚点 ID 是一个字符串,需要发送给任何想要解析此锚点的人。
  2. 系统会将包含该锚点视觉数据的数据集上传到 Google 服务器。
    该数据集包含设备最近查看的视觉数据。如果稍微移动下设备,以在托管前从不同视角捕获锚点附近的区域,将改善本地化效果。

传送云锚点 ID

在此 Codelab 中,您将使用 Firebase 传送云锚点 ID。您可以随时通过其他方式共享云锚点 ID。

解析锚点

您可以使用 Cloud Anchor API 解析使用其云锚点 ID 的锚点。这将在原始托管锚点所在的同一实际位置创建新锚点。解析时,设备必须与原始托管锚点位于相同的物理环境。

持久云锚点

对于 1.20 之前的版本,云锚点只有在托管 24 小时后才能被解析。借助 Persistent Cloud Anchors API,您可以创建可在创建后的 1 至 365 天内进行解析的云锚点。

构建内容

在此 Codelab 中,您将基于现有的 ARCore 应用构建您的应用。完成该 Codelab 时,您的应用将:

  • 能够托管持久云锚点并获取云锚点 ID。
  • 在设备上保存云锚点 ID,以使用 Android SharedPreferences 实现轻松检索。
  • 使用保存的云锚点 ID 解析之前托管的锚点。这样一来,我们可以使用单个设备轻松模拟多用户体验,以实现本 Codelab 的目的。
  • 与运行同一应用的其他设备共享云锚点 ID,让多位用户在同一位置看到 Android 雕塑。

系统在云锚点的位置渲染了一个 Android 雕塑:

学习内容

  • 如何使用 ARCore SDK 托管锚点并获取云锚点 ID。
  • 如何使用云锚点 ID 解析锚点。
  • 如何在同一设备或不同设备上的不同 AR 会话之间存储和共享云锚点 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(打开一个现有 Android Studio 项目)。然后,转到解压缩先前下载的 ZIP 文件的目录,双击 arcore-cloud-anchors 目录。

这是一个包含多个模块的 Gradle 项目。如果 Project(项目)窗格尚未显示在 Android Studio 左上角的相应位置,请点击下拉菜单中的 Projects(项目)。结果应如下所示:

52282f0415fdbdcb.png

您将主要在 work 模块中进行操作。其他模块包含 helpers 模块,其中包含一组您将会使用的实用封装容器类。我们还提供了 Codelab 的各个部分的完整解决方案。除了 helpers 模块之外,每个模块都是一个可构建的应用。

如果您看到一个对话框,提示您升级 Android Gradle 插件,请点击 Don't remind me again for this project(不要再针对此项目提醒我):

31a93c7e9cc58b53.png

点击 Run(运行)> Run...(运行…)>“work”(工作)。在显示的 Select Deployment Target(选择部署目标)对话框中,您的设备应列在 Connected Devices(已连接的设备)下。选择您的设备,然后点击 OK(确定)。Android Studio 将构建初始应用并在您的设备上运行该应用。

您首次运行该应用时,它会请求 CAMERA 权限。点按 ALLOW(允许)以继续。

f7ea81f71a4b969e.png

如何使用该应用

  1. 移动设备以帮助应用找一个平面。平面在被找到后将显示为虚线表面。
  2. 点按平面上的某个位置以放置锚点。系统将在放置锚点的位置绘制 Android 小人。此应用一次仅允许放置一个锚点。
  3. 移动设备。即使设备在不断移动,小人也应该看起来保持在原地不动。
  4. 按“CLEAR”(清除)按钮以移除锚点。此操作可让您放置另一个锚点。

目前,此应用仅使用 ARCore 提供的动作跟踪来跟踪应用单次运行期间的锚点。如果您决定退出、终止并重启应用,之前放置的锚点及任何相关信息(包括其姿态)都将丢失。

在接下来的几个部分中,您将基于此应用进行构建,了解如何在 AR 会话之间共享锚点。

3. 托管锚点

在本部分中,您将修改 work 项目以托管锚点。在编写代码之前,您将需要对应用配置进行一些修改。

声明 INTERNET 权限

由于云锚点需要与 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 Cloud Anchor API

  1. 访问 ARCore Cloud Anchor API 服务页面。
  2. 在项目列表中,选择一个项目或创建一个新项目。
  3. 点击启用

设置 OAuth

如需使用持久云锚点,您将需要使用 OAuth 向 ARCore 云锚点服务进行身份验证。

  1. 访问 Google Cloud Platform Console
  2. 从项目列表中,选择一个项目。
  3. 如果“API 和服务”页面尚未打开,请打开控制台左侧菜单,然后选择 API 和服务
  4. 点击左侧的凭据
  5. 点击创建凭据,然后选择 OAuth 客户端 ID
  6. 填写以下值:
    • 应用类型:Android
    • 软件包名称com.google.ar.core.codelab.cloudanchor
  7. 检索您的调试签名证书指纹:
    1. 在 Android Studio 项目中,打开 Gradle toolpane(Gradle 工具窗格)。
    2. cloud-anchors(云锚点)> work(工作)> Tasks(任务)> android 中,运行 SigningReport 任务。
    3. 将 SHA-1 指纹复制到 Google Cloud 的 SHA-1 证书指纹字段中。

配置 ARCore

接下来,您将修改该应用,以在用户点按时创建托管的锚点,而不是常规锚点。为此,您需要配置 ARCore 会话以启用云锚点。

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 云锚点服务的托管锚点了。

将以下新字段添加到 CloudAnchorFragment 类中:

// Find this line...
private final SnackbarHelper messageSnackbarHelper = new SnackbarHelper();

// Add this line right below.
private final CloudAnchorManager cloudAnchorManager = new CloudAnchorManager();

系统已经为您提供了 CloudAnchorManagerSnackbarHelper 类。这些是一些封装了样板代码的实用封装容器类,可让您编写的代码更简洁。

onDrawFrame() 方法中,添加下文提到的一行代码:

// Find this line...
Frame frame = session.update();

// Add this line right below:
cloudAnchorManager.onUpdate();

按如下方式修改 onClearButtonPressed 方法:

private synchronized void onClearButtonPressed() {
  // Clear the anchor from the scene.

  // The next line is the new addition.
  cloudAnchorManager.clearListeners();

  currentAnchor = null;
}

接下来,将以下方法添加到 CloudAnchorFragment 类:

private synchronized void onHostedAnchorAvailable(Anchor anchor) {
  CloudAnchorState cloudState = anchor.getCloudAnchorState();
  if (cloudState == CloudAnchorState.SUCCESS) {
    messageSnackbarHelper.showMessage(
        getActivity(), "Cloud Anchor Hosted. ID: " + anchor.getCloudAnchorId());
    currentAnchor = anchor;
  } 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...");
cloudAnchorManager.hostCloudAnchor(session, currentAnchor, /* ttl= */ 300, this::onHostedAnchorAvailable);

在 Android Studio 中重新运行您的应用。放置锚点时,您应该会看到显示“Now hosting anchor...”(现在正在托管锚点…)的消息。托管成功完成后,您应该会看到另一条消息。如果您看到“Error hosting anchor: ERROR_NOT_AUTHORIZED”(托管锚点时出错:),请确认您的 OAuth 客户端是否配置正确。

如果任何人知晓锚点 ID,并且与锚点位于同一物理空间中,则可使用锚点 ID 根据相对于他们的周围环境的完全相同姿态(位置和方向)创建锚点。

但是,锚点 ID 较长,让其他用户手动输入并不容易。在后面的部分中,您将以一种易于检索的方式存储云锚点 ID,以便在同一设备或其他设备上解析锚点。

4. 存储 ID 和解析锚点

在本部分中,您将向长云锚点 ID 分配短代码,以便其他用户更轻松地手动输入。您将使用 Shared Preferences API 将云锚点 ID 以值的形式存储在键值对表中。即使应用被终止并重启,此表也将会保留。

系统已经为您提供了一个名为 StorageManager 的辅助类。这是 SharedPreferences API 的封装容器,其中包含用于生成新的唯一短代码的方法,以及读取/写入云锚点 ID 的方法。

使用 StorageManager

修改 CloudAnchorFragment,以使用 StorageManager 通过短代码存储云锚点 ID,以便轻松检索它们。

CloudAnchorFragment 中创建以下新字段:

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

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

然后修改 onHostedAnchorAvailable 方法:

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

现在,在 Android Studio 中构建并运行应用。创建并托管锚点时,您应该会看到系统显示的是短代码,而非长云锚点 ID。

放置锚点后立即开始

稍等一会儿

请注意,StorageManager 生成的短代码目前一直按递增顺序分配。

接下来,您将添加一些界面元素,以便您输入短代码并重新创建锚点。

添加“Resolve”(解析)按钮

您将在 CLEAR(清除)按钮旁边再添加一个按钮。此按钮将为 RESOLVE(解析)按钮。点击 RESOLVE(解析)按钮会打开一个对话框,提示用户输入短代码。短代码用于从 StorageManager 检索云锚点 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 synchronized 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 synchronized void onClearButtonPressed() {
  // Clear the anchor from the scene.
  cloudAnchorManager.clearListeners();

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

  currentAnchor = null;
}

在 Android Studio 中构建并运行应用。您应该会在 CLEAR(清除)按钮旁边看到 RESOLVE(解析)按钮。点击 RESOLVE(解析)按钮后,系统应该会弹出一个对话框,如下所示。

现在 RESOLVE(解析)按钮会显示出来

点击该按钮后,系统会显示此对话框

点按平面并托管锚点后,系统应该会停用 RESOLVE(解析)按钮,但点按 CLEAR(清除)按钮后,系统应该会将其重新启用。这是特意设计的,以确保场景中一次只有一个锚点。

“Resolve Anchor”(解析锚点)对话框不会执行任何操作,但您现在可以改变这一状况。

解析锚点

CloudAnchorFragment 类中添加以下方法:

private synchronized 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);
  cloudAnchorManager.resolveCloudAnchor(
      session,
      cloudAnchorId,
      anchor -> onResolvedAnchorAvailable(anchor, shortCode));
}

private synchronized void onResolvedAnchorAvailable(Anchor anchor, int shortCode) {
  CloudAnchorState cloudState = anchor.getCloudAnchorState();
  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 synchronized void onResolveButtonPressed() {
  ResolveDialogFragment dialog = ResolveDialogFragment.createWithOkListener(
      this::onShortCodeEntered);
  dialog.show(getActivity().getSupportFragmentManager(), "Resolve");
}

在 Android Studio 中构建和运行应用,然后执行以下步骤:

  1. 在平面上创建锚点,并等待该锚点被托管。
    记住短代码。
  2. CLEAR(清除)按钮可删除锚点。
  3. RESOLVE(解析)按钮。输入在第 1 步中获得的短代码。
  4. 您应该会在相对于最初放置锚点的环境的同一位置看到锚点。
  5. 退出并终止该应用,然后重新打开。
  6. 重复第 3 步和第 4 步。您应该会看到新锚点在同一位置再次显示。

输入短代码

已成功解析锚点

5. 在设备之间共享

您已经了解了如何将锚点的云锚点 ID 存储在您的设备的本地存储空间中,并稍后检索以重新创建同一个锚点。但是,只有在您可以在不同设备之间共享云锚点 ID 时,才能充分发挥云锚点的潜力。

您的应用共享云锚点 ID 的方式由您自行决定。可使用任何方式将字符串从一个设备传送到另一个设备。在此 Codelab 中,您将使用 Firebase Realtime Database 在应用的实例之间传送云锚点 ID。

设置 Firebase

您需要使用自己的 Google 帐号设置 Firebase Realtime Database 才能将其与此应用搭配使用。通过 Android Studio 中的 Firebase Assistant 可以轻松完成。

在 Android Studio 中,依次点击 Tools(工具)> Firebase。在弹出的 Assistant 窗格中,点击 Realtime Database,然后点击 Save and retrieve data(保存并检索数据):

68e927cbf324a3b2.png

点击 Connect to Firebase(关联到 Firebase)按钮,将您的 Android Studio 项目与新的或现有的 Firebase 项目相关联。

63f3b1ffd6bd263e.png

系统会提示您选择模块。选择 work 模块:

be737c689ad6dd78.png

此时会显示“Starting Connect”(开始连接)对话框。这可能需要一点时间。

b48626f8672551ee.png

使用您的 Google 帐号登录,并完成 Web 工作流,以便为您的应用创建 Firebase 项目,直至您返回 Android Studio 为止。

接下来,在 Assistant 窗格中,点击 add the Realtime Database to your app(将 Realtime Database 添加到您的应用):

68e0843fa2531c4c.png

在弹出的对话框中,从 Target module(目标模块)下拉菜单中选择 work(工作),然后点击 Accept Changes(接受更改)。

155fd89533c02671.png

这样会:

  1. google-services.json 文件添加到您的 work 目录
  2. 将几行代码添加到同一目录中的 build.gradle 文件中。
  3. 构建并运行应用(您可能会看到有关 Firebase 数据库版本号的解析错误)。

build.gradle 文件的 work 模块中,找到并移除以下行(xxxx 是最新版本号的占位符)

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

接下来,查看(适用于尚未进行操作的用户)配置公开访问权限规则页面中链接的说明,以将您的 Firebase Realtime Database 配置为全局可写。这有助于简化此 Codelab 中的测试:

666ebefd39019c05.png

Firebase 控制台中,选择您与 Android Studio 项目关联的项目,然后依次选择构建 > Realtime Database

Firebase Realtime Database 位置

点击创建数据库,以配置和设置 Realtime Database

创建数据库

选择任意数据库位置。

在下一步中,选择测试模式安全规则,然后点击启用

数据库安全性

您的应用现已配置为使用 Firebase 数据库。

使用 FirebaseManager

您现在可以将 StorageManager 替换为 FirebaseManager

在 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 synchronized 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);
    cloudAnchorManager.resolveCloudAnchor(
        session,
        cloudAnchorId,
        anchor -> onResolvedAnchorAvailable(anchor, shortCode));
  });
}

然后,按如下方式修改 onHostedAnchorAvailable 方法:

private synchronized void onHostedAnchorAvailable(Anchor anchor) {
  CloudAnchorState cloudState = anchor.getCloudAnchorState();
  if (cloudState == CloudAnchorState.SUCCESS) {
    String cloudAnchorId = anchor.getCloudAnchorId();
    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.");
      }
    });
    currentAnchor = anchor;
  } else {
    messageSnackbarHelper.showMessage(getActivity(), "Error while hosting: " + cloudState.toString());
  }
}

构建和运行您的应用。您应该会看到与上一部分相同的界面流程,只是现在使用在线 Firebase 数据库(而非设备本地存储空间)存储云锚点 ID 和短代码。

多用户测试

如需测试多用户体验,请使用两部不同的手机:

  1. 在两台设备上安装应用。
  2. 使用一台设备托管锚点并生成短代码。
  3. 使用其他设备通过该短代码解析该锚点。

您应该可以通过一台设备托管锚点,获取一个短代码,然后在另一台设备上使用短代码,以便在同一位置查看锚点!

6. 小结

恭喜!您已完成了此 Codelab!

所学内容

  • 如何使用 ARCore SDK 托管锚点并获取云锚点 ID。
  • 如何使用云锚点 ID 解析锚点。
  • 如何在同一设备或不同设备上的不同 AR 会话之间存储和共享云锚点 ID。

了解详情