你是否曾梦想过像钢铁侠托尼·斯塔克那样,在现实的空气中操作全息数据?增强现实(AR)技术正在将这种科幻构想变为触手可及的科学现实。通过将数字信息——如声音、文本、3D 模型——叠加到我们的物理世界中,AR 打破了虚拟与现实的边界。想想风靡全球的“Pokémon GO”,你就能感受到 AR 的魅力。
在这篇文章中,我们将作为一名探索者,一起深入 Android AR 开发的世界。我们将不依赖复杂的 3D 引擎知识,使用 Java 语言在 Android Studio 中构建一个功能完整、能够识别平面并展示 3D 模型的 AR 应用。准备好你的手机,让我们开始这段神奇的代码之旅吧!
为什么选择 ARCore 与 Sceneform?
在开始敲代码之前,我们需要理解背后的核心工具。Google 的 ARCore 是 Android AR 体验的基石,而 Sceneform 则是我们手中的画笔。
#### 深入了解 ARCore
ARCore 是一个平台,它让我们的智能手机能够“感知”周围的环境。要在手机上实现稳定的 AR 体验,ARCore 主要依赖三个核心原则:
- 运动追踪:利用手机的摄像头和惯性传感器,ARCore 能在用户移动时追踪手机的位置和姿态。这确保了虚拟物体在屏幕中看起来是“钉”在现实世界中的,而不是随着手机晃动而乱飞。
- 环境理解:这是 AR 的关键。ARCore 会检测水平或垂直的平面(如地板、桌子或墙壁)。通过识别这些特征点,手机能知道哪里有“地面”,从而让虚拟物体有地方放置。
- 光照估算:为了逼真,虚拟物体必须有阴影。ARCore 会分析环境的光线条件,让我们的 3D 模型看起来真的像是处于当前的灯光下。
> 注意:ARCore 仅在支持它的设备上运行。虽然现在大部分新手机都支持,但开发前最好查阅 Google 的设备支持列表。
#### Sceneform 的魅力
以前,在 Android 上写 AR 需要懂 OpenGL,这对许多应用开发者来说是一道高墙。Sceneform 是一个由 Google 开发的 3D 框架,它的存在就是为了打破这道墙。它让我们可以在不懂 OpenGL 的情况下,像操作普通 Android 视图一样处理 3D 资产。它甚至自带了处理 UI 手势(如点击旋转、缩放)的功能。
第一步:搭建项目地基
首先,我们需要一个干净的起点。打开 Android Studio,创建一个新项目(请参考标准的新建项目流程)。但在配置时,请注意以下几点:
- 语言选择:选择 Java。虽然 Kotlin 现在很流行,但为了教程的通用性和稳定性,我们沿用 Java。
- Minimum SDK:设置为 API 24: Android 7.0 (Nougat)。这是保证 ARCore 兼容性的基础。
- 记住路径:记下你的项目保存路径,稍后我们需要在那里放入一些特殊的文件。
第二步:准备 3D 资产(.glb 文件)
AR 应用需要内容。在这个极简示例中,我们需要一个 3D 模型。
#### 关于文件格式:glTF 与 .glb
Sceneform 1.16.0 主要使用 glTF(GL Transmission Format)格式。它是 3D 界的“JPEG”。而 .glb 文件则是 glTF 的二进制版本,它将纹理、模型数据打包在一个文件中,非常适合移动端传输和加载。
#### 如何获取模型?
你有两个选择:下载现成的或者自己制作。
- 下载资源:有很多优秀的 3D 模型库(Sketchfab 等以前提到的 Poly 平台虽已整合,但社区资源依然丰富)。搜索
.glb格式的模型即可。 - 自己动手:如果你喜欢创造,可以使用 Blender(一款免费且强大的开源 3D 软件)制作模型,然后导出为
.glb格式。
#### 导入项目
这是一个关键步骤,请务必仔细操作:
- 命名规范:确保你的文件名仅包含小写字母、数字或下划线。例如
my_model.glb。避免使用大写字母,这可能会在资源编译中引发错误。 - 创建 INLINECODE5be8890d 目录:在 Android Studio 左侧项目视图中,右键点击 INLINECODE67e97f74 目录,选择
New > Android Resource Directory。在弹出的窗口中,资源类型选择 Raw,点击 OK。 - 放置文件:将你下载或制作的 INLINECODE26afdf2a 文件复制到 INLINECODE2879c88a 文件夹下。
第三步:集成 Sceneform SDK(手动配置)
这里我们可能会遇到一些挑战。早期的 Sceneform 插件在最新版 Android Studio 中可能会出现兼容性问题。为了保证我们的项目能跑起来,我们将采用更稳妥的方式:手动集成 Sceneform 1.16.0 SDK。
#### 1. 获取 SDK 文件
我们需要从 GitHub 的 Sceneform 仓库中下载源码包(下载链接指向 v1.16.0 版本的 Release)。解压 ZIP 文件,找到其中的 INLINECODE521e154b 和 INLINECODE43d54390 文件夹。
#### 2. 配置 Settings.gradle
将解压后的 INLINECODEa2b9de25 和 INLINECODE90a7bc06 文件夹直接移动到你的项目根目录下(与 INLINECODEbc876d26 文件夹同级)。然后,打开 INLINECODEeeec9433 文件,添加以下配置。这将告诉 Gradle 去哪里找这两个库。
// 将 sceneformsrc 文件夹作为模块引入
include ‘:sceneform‘
project(‘:sceneform‘).projectDir = new File(‘sceneformsrc/sceneform‘)
// 将 sceneformux 文件夹作为模块引入
include ‘:sceneformux‘
project(‘:sceneformux‘).projectDir = new File(‘sceneformsrc/sceneformux‘)
#### 3. 配置 Build.gradle
接下来,我们需要在 INLINECODEb30baf83 模块中引用这些库。打开 INLINECODE958c8c9e。
在 dependencies 闭包中添加依赖:
dependencies {
...
// 依赖我们刚才导入的 sceneformux 库
implementation project(‘:sceneformux‘)
...
}
> 实用见解:由于 Java 8 的特性在 AR 开发中非常常用,建议在同一文件的 android 闭包中,确保开启了 Java 8 支持(虽然现代 AS 版本通常默认开启,但检查一下总是好的)。
第四步:编写核心代码——从布局到逻辑
现在,地基已经打好,是时候建造大楼了。我们将分三步来完成代码编写。
#### 1. 设计用户界面
我们需要一个全屏的视图来显示摄像头画面。打开 INLINECODEcee6d931,并将代码修改如下。这里我们使用了 INLINECODEae704518,它是 Sceneform 提供的一个现成的 Fragment,帮我们处理了权限请求、相机开启和背景渲染等繁琐工作。
#### 2. 实现主逻辑
接下来是 MainActivity.java。这是我们将一切串联起来的地方。我们要做的是:
- 获取
ArFragment的引用。 - 添加一个监听器,等待 ARCore 检测到平面。
- 当检测到平面后,添加另一个监听器,等待用户点击屏幕。
- 当用户点击屏幕上的平面时,显示 3D 模型。
// MainActivity.java
package com.example.simplearapp; // 替换为你的包名
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.view.MotionEvent;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.ar.core.HitResult;
import com.google.ar.core.Plane;
import com.google.ar.sceneform.AnchorNode;
import com.google.ar.sceneform.rendering.ModelRenderable;
import com.google.ar.sceneform.ux.ArFragment;
import com.google.ar.sceneform.ux.TransformableNode;
public class MainActivity extends AppCompatActivity {
private ArFragment arFragment;
// 使用 URI 格式引用我们在 res/raw 中放入的模型文件
// 注意:格式固定为 android.resource://包名/raw/文件名(不含扩展名)
private Uri modelUri = Uri.parse("android.resource://" + getPackageName() + "/raw/your_model_filename");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化 ArFragment
arFragment = (ArFragment) getSupportFragmentManager().findFragmentById(R.id.arFragment);
// 开始构建我们的 AR 场景
setUpModel();
setUpPlane();
}
// 辅助方法:动态获取包名(因为 onCreate 中 getPackageName() 可能还未完全初始化)
@Override
protected String getPackageName() {
return super.getPackageName();
}
// 步骤 1: 异步加载 3D 模型
private void setUpModel() {
// 动态修正 URI,确保在运行时获取正确的包名
modelUri = Uri.parse("android.resource://" + getPackageName() + "/raw/your_model_filename");
// 请确保将 ‘your_model_filename‘ 替换为你真实的文件名,例如 ‘gfg_model‘
ModelRenderable.builder()
.setSource(this, modelUri)
.setIsFilamentGltf(true) // Sceneform 1.16+ 使用 Filament 引擎加载 glTF
.build()
.thenAccept(renderable -> {
// 加载成功,将 renderable 存起来(这里简化处理,直接在点击事件中引用)
// 在实际开发中,建议将其设为成员变量以便复用
Toast.makeText(this, "模型加载成功", Toast.LENGTH_SHORT).show();
})
.exceptionally(throwable -> {
Toast.makeText(this, "无法加载 3D 模型,请检查文件名和格式", Toast.LENGTH_LONG).show();
return null;
});
}
// 步骤 2: 设置平面检测和点击监听
private void setUpPlane() {
arFragment.setOnTapArPlaneListener((HitResult hitResult, Plane plane, MotionEvent motionEvent) -> {
// 检查点击的是否是水平面(地板或桌子)
if (plane.getType() != Plane.Type.HORIZONTAL_UPWARD_FACING) {
return;
}
// 创建锚点
AnchorNode anchorNode = new AnchorNode(hitResult.createAnchor());
anchorNode.setParent(arFragment.getArSceneView().getScene());
// 再次加载模型(为了代码逻辑清晰,这里简化,实际应复用已加载的对象)
// 注意:真实项目中应避免在点击时重复加载,这里仅为演示流程
ModelRenderable.builder()
.setSource(this, modelUri)
.setIsFilamentGltf(true)
.build()
.thenAccept(renderable -> {
createNode(anchorNode, renderable);
});
});
}
// 步骤 3: 创建可交互的节点
private void createNode(AnchorNode anchorNode, ModelRenderable renderable) {
// TransformableNode 让我们可以通过手指拖拽、旋转、缩放模型
TransformableNode node = new TransformableNode(arFragment.getTransformationSystem());
node.setParent(anchorNode);
node.setRenderable(renderable);
node.select(); // 激活节点,显示控制手柄
}
}
> 重要提示:在代码中,请务必将 INLINECODEa48e8808 替换为你放在 INLINECODE5d47309d 文件夹下的实际文件名(不需要 INLINECODEc2e15154 后缀)。例如,如果你的文件是 INLINECODE30cde459,这里就写 teddy。
#### 3. 配置权限
AR 应用必须使用摄像头。虽然 ArFragment 会自动请求权限,但我们必须在 INLINECODEc927099d 中声明它。打开该文件,在 INLINECODE47c56dc6 标签外添加:
常见陷阱与优化建议
在实际开发中,你可能会遇到一些坑。这里分享几点实战经验:
- 模型不显示?:最常见的原因是模型尺寸不合适。现实世界非常大,如果你的模型太小(例如 0.001 单位),它可能就在你眼皮底下,你却看不见。反之,如果太大(100 单位),它可能包围了整个相机。在 Blender 中导出时,注意调整模型的缩放比例。
- APK 体积过大:3D 模型文件通常很重。如果 App 启动慢,请检查
.glb文件的大小。优化模型的纹理和面数是减小 APK 体积的关键。 - 灯光与环境:为了让效果更逼真,我们还可以在代码中开启光照估计。Sceneform 支持根据环境光调整模型的亮度。只需一行代码:
arFragment.getArSceneView().getSession().setLightEstimationEnabled(true);(需在 Session 创建后调用)。
结语
恭喜你!你已经完成了从 0 到 1 的突破。通过这篇教程,我们不仅构建了一个简单的 AR 应用,更重要的是,我们掌握了 ARCore 的基本原理(环境感知、锚点建立)和 Sceneform 的核心用法(模型加载、节点交互)。
但这只是冰山一角。接下来,你可以尝试探索更多功能:比如让 3D 模型动起来(动画)、支持识别垂直平面(像海报一样的墙面)、或者使用 Cloud Anchors 让不同用户看到同一个虚拟物体。AR 的未来充满无限可能,而你已经拿到了开启这扇大门的钥匙。快去创建属于你的增强现实世界吧!