1. 概览
ARCore 是用于在移动设备上构建增强现实 (AR) 应用的平台。借助 Cloud Anchors API,您可以创建共享通用参照系的 AR 应用,以便多个用户在同一个实际位置放置虚拟内容。
此 Codelab 将指导您使用 Cloud Anchors API。您将需要使用一个现有的 ARCore 应用,对其修改以使用云锚点,并打造共享 AR 体验。
ARCore 锚点和持久云锚点
ARCore 中的一个基本概念是锚点,它描述了现实世界中的固定位置。ARCore 会随着动作跟踪技术的不断改进而自动调整锚点姿态的值。
云锚点是指在云端托管的锚点。云锚点可被多个用户解析,这样就可以在用户及其设备之间建立通用参照系。
托管锚点
托管锚点后,会出现以下情况:
- 系统会将锚点相对于现实世界的姿态上传到云中,并获取云锚点 ID。
云锚点 ID 是一个字符串,需要发送给任何想要解析此锚点的人。 - 系统会将包含该锚点视觉数据的数据集上传到 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。
所需条件
- 通过 USB 线将受支持的 ARCore 设备连接到开发机器。
- 面向 AR 的 Google Play 服务 1.22 或更高版本。
- 安装了 Android Studio(v3.0 或更高版本)的开发机器。
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(项目)。结果应如下所示:
您将主要在 work
模块中进行操作。其他模块包含 helpers
模块,其中包含一组您将会使用的实用封装容器类。我们还提供了 Codelab 的各个部分的完整解决方案。除了 helpers
模块之外,每个模块都是一个可构建的应用。
如果您看到一个对话框,提示您升级 Android Gradle 插件,请点击 Don't remind me again for this project(不要再针对此项目提醒我):
点击 Run(运行)> Run...(运行…)>“work”(工作)。在显示的 Select Deployment Target(选择部署目标)对话框中,您的设备应列在 Connected Devices(已连接的设备)下。选择您的设备,然后点击 OK(确定)。Android Studio 将构建初始应用并在您的设备上运行该应用。
您首次运行该应用时,它会请求 CAMERA
权限。点按 ALLOW(允许)以继续。
如何使用该应用
- 移动设备以帮助应用找一个平面。平面在被找到后将显示为虚线表面。
- 点按平面上的某个位置以放置锚点。系统将在放置锚点的位置绘制 Android 小人。此应用一次仅允许放置一个锚点。
- 移动设备。即使设备在不断移动,小人也应该看起来保持在原地不动。
- 按“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
- 访问 ARCore Cloud Anchor API 服务页面。
- 在项目列表中,选择一个项目或创建一个新项目。
- 点击启用。
设置 OAuth
如需使用持久云锚点,您将需要使用 OAuth 向 ARCore 云锚点服务进行身份验证。
- 访问 Google Cloud Platform Console。
- 从项目列表中,选择一个项目。
- 如果“API 和服务”页面尚未打开,请打开控制台左侧菜单,然后选择 API 和服务。
- 点击左侧的凭据。
- 点击创建凭据,然后选择 OAuth 客户端 ID。
- 填写以下值:
- 应用类型:Android
- 软件包名称:
com.google.ar.core.codelab.cloudanchor
- 检索您的调试签名证书指纹:
- 在 Android Studio 项目中,打开 Gradle toolpane(Gradle 工具窗格)。
- 在 cloud-anchors(云锚点)> work(工作)> Tasks(任务)> android 中,运行 SigningReport 任务。
- 将 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();
系统已经为您提供了 CloudAnchorManager
和 SnackbarHelper
类。这些是一些封装了样板代码的实用封装容器类,可让您编写的代码更简洁。
在 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 中构建和运行应用,然后执行以下步骤:
- 在平面上创建锚点,并等待该锚点被托管。
记住短代码。 - 按 CLEAR(清除)按钮可删除锚点。
- 按 RESOLVE(解析)按钮。输入在第 1 步中获得的短代码。
- 您应该会在相对于最初放置锚点的环境的同一位置看到锚点。
- 退出并终止该应用,然后重新打开。
- 重复第 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(保存并检索数据):
点击 Connect to Firebase(关联到 Firebase)按钮,将您的 Android Studio 项目与新的或现有的 Firebase 项目相关联。
系统会提示您选择模块。选择 work
模块:
此时会显示“Starting Connect”(开始连接)对话框。这可能需要一点时间。
使用您的 Google 帐号登录,并完成 Web 工作流,以便为您的应用创建 Firebase 项目,直至您返回 Android Studio 为止。
接下来,在 Assistant 窗格中,点击 add the Realtime Database to your app(将 Realtime Database 添加到您的应用):
在弹出的对话框中,从 Target module(目标模块)下拉菜单中选择 work(工作),然后点击 Accept Changes(接受更改)。
这样会:
- 将
google-services.json
文件添加到您的work
目录 - 将几行代码添加到同一目录中的
build.gradle
文件中。 - 构建并运行应用(您可能会看到有关 Firebase 数据库版本号的解析错误)。
在 build.gradle
文件的 work
模块中,找到并移除以下行(xxxx
是最新版本号的占位符)
dependencies {
...
implementation 'com.google.firebase:firebase-database:xxxx'
接下来,查看(适用于尚未进行操作的用户)配置公开访问权限规则页面中链接的说明,以将您的 Firebase Realtime Database 配置为全局可写。这有助于简化此 Codelab 中的测试:
从 Firebase 控制台中,选择您与 Android Studio 项目关联的项目,然后依次选择构建 > 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 和短代码。
多用户测试
如需测试多用户体验,请使用两部不同的手机:
- 在两台设备上安装应用。
- 使用一台设备托管锚点并生成短代码。
- 使用其他设备通过该短代码解析该锚点。
您应该可以通过一台设备托管锚点,获取一个短代码,然后在另一台设备上使用短代码,以便在同一位置查看锚点!
6. 小结
恭喜!您已完成了此 Codelab!
所学内容
- 如何使用 ARCore SDK 托管锚点并获取云锚点 ID。
- 如何使用云锚点 ID 解析锚点。
- 如何在同一设备或不同设备上的不同 AR 会话之间存储和共享云锚点 ID。