AR增强现实:ARCore基础、3D模型渲染、平面检测、光照估计
AR增强现实,说白了就是把虚拟物体塞进真实世界里。你拿着手机,摄像头对着桌面,然后一只3D小恐龙就站在桌面上——这就是AR最直观的样子。
我最早接触ARCore是在2018年,那时候Google刚推出1.0版本。说实话,第一版bug不少,平面检测经常飘。但到了现在,ARCore已经非常成熟了。我个人习惯把它当作Android平台上AR开发的标配。
ARCore的核心能力
ARCore不是魔法,它靠三个核心能力来欺骗你的眼睛:
- 运动追踪:手机怎么移动,虚拟物体就怎么跟着动
- 环境理解:检测水平面、垂直面,知道哪里能放东西
- 光照估计:分析真实环境的光线,让虚拟物体有正确的阴影和亮度
这三个能力缺一不可。我在项目中遇到过一个问题:光照估计没做好,虚拟物体在暗光环境下亮得像灯泡,一眼就看穿是假的。
ARCore的工作流程
先看一张整体流程图,帮你快速建立认知:
嗯,这张图把流程讲清楚了。核心就是:先初始化,然后不断追踪、检测、估计,最后把模型渲染上去。
项目配置
要开始用ARCore,首先得在build.gradle里加依赖:
dependencies {
implementation 'com.google.ar:core:1.38.0'
implementation 'com.google.ar.sceneform:sceneform-base:1.17.1'
implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.17.1'
}
注意,SceneForm从1.15.0开始就不在Google官方维护了。我个人建议用SceneForm的社区分支,或者直接用OpenGL/Metal自己渲染。不过对于初学者,SceneForm还是最友好的选择。
AndroidManifest里也要声明:
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.ar" />
<meta-data android:name="com.google.ar.core" android:value="required" />
这里有个坑:如果你把ARCore设为required,那不支持AR的设备直接装不了你的App。我曾经因为这个被产品经理骂了一顿——用户反馈说在旧手机上搜不到我们的App。后来改成了optional,自己代码里判断是否支持。
创建AR Session
AR Session是ARCore的入口。说白了,它就是AR世界的上下文:
// 检查ARCore是否可用
ArCoreApk.Availability availability = ArCoreApk.getInstance().checkAvailability(this);
if (availability.isSupported()) {
// 创建Session
session = new Session(this);
// 配置Session
Config config = new Config(session);
config.setUpdateMode(Config.UpdateMode.LATEST_CAMERA_IMAGE);
session.configure(config);
}
你想想看,Session就像是一个画布,所有AR数据都在上面绘制。每次摄像头帧进来,Session都会更新追踪状态、平面列表、光照信息。
平面检测
平面检测是AR里最常用的功能。用户点击屏幕,我们就在那个位置放一个虚拟物体。但前提是——得先找到平面。
// 获取所有检测到的平面
Collection<Plane> planes = session.getAllTrackables(Plane.class);
for (Plane plane : planes) {
if (plane.getTrackingState() == TrackingState.TRACKING) {
// 获取平面的中心点和法向量
float[] center = plane.getCenterPose().getTranslation();
float[] normal = plane.getCenterPose().getRotationQuaternion();
// 判断是水平面还是垂直面
int type = plane.getType(); // HORIZONTAL_UPWARD_FACING 或 VERTICAL
}
}
我记得有一次做AR家具展示,用户想把沙发放在墙上(垂直面)。结果平面检测一直识别不到垂直面,后来发现是手机离墙太近了。ARCore对距离有要求,太近反而检测不准。
3D模型渲染
有了平面,接下来就是把3D模型放上去。SceneForm里用ModelRenderable来加载模型:
ModelRenderable.builder()
.setSource(this, Uri.parse("models/robot.sfb"))
.build()
.thenAccept(renderable -> {
// 创建AnchorNode,锚定到平面
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setRenderable(renderable);
// 添加到Scene
arFragment.getArSceneView().getScene().addChild(anchorNode);
})
.exceptionally(throwable -> {
Log.e("AR", "模型加载失败", throwable);
return null;
});
这里要注意,SceneForm支持的是.sfb格式(SceneForm Binary),不是.obj或.gltf。你需要用Google提供的工具把模型转成.sfb。或者,你也可以直接用Filament引擎加载.gltf,但代码量会大很多。
我个人的经验是:模型面数不要太高。移动端GPU扛不住几百万面的模型。一个AR场景里,模型控制在1-3万面比较合适。纹理贴图也尽量用512x512或1024x1024。
光照估计
光照估计是AR里最容易忽略但又最重要的环节。没有光照估计,虚拟物体就像P上去的贴纸,毫无立体感。
// 获取当前帧的光照估计
Frame frame = session.update();
LightEstimate lightEstimate = frame.getLightEstimate();
// 获取环境光强度
float pixelIntensity = lightEstimate.getPixelIntensity();
// 获取环境光颜色
float[] colorCorrection = lightEstimate.getColorCorrection();
// 获取环境球谐光照(用于反射)
float[] sphericalHarmonics = lightEstimate.getEnvironmentalHdrEnvironmentalLighting();
SceneForm会自动应用光照估计到模型上。但如果你用OpenGL自己渲染,就需要手动处理了。
我曾经踩过一个坑:在室内暖黄光下,虚拟物体偏蓝,看起来特别假。后来发现是颜色校正没处理好。ARCore返回的颜色校正是针对当前场景的,你得把它应用到模型的材质上。
用户交互:点击放置
用户点击屏幕,我们在点击位置放置模型。这个逻辑看起来简单,但实现起来有几个细节:
arFragment.setOnTapArPlaneListener((hitResult, plane, motionEvent) -> {
// 1. 创建Anchor
Anchor anchor = hitResult.createAnchor();
// 2. 创建AnchorNode
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arFragment.getArSceneView().getScene());
// 3. 创建TransformableNode(支持手势缩放旋转)
TransformableNode transformableNode = new TransformableNode(
arFragment.getTransformationSystem());
transformableNode.setParent(anchorNode);
transformableNode.setRenderable(renderable);
transformableNode.select();
});
嗯,这里有个细节:hitResult.createAnchor()创建的Anchor会随着ARCore的追踪优化而自动调整位置。但如果你用hitResult.createAnchor(pose)指定位置,那模型就固定死了,不会自动修正。
TrackingState.STOPPED状态。这时候模型会消失。你需要监听状态变化,必要时重新创建。
性能优化建议
AR应用对性能要求极高。30fps是底线,低于这个帧率用户会明显感到眩晕。我总结了几条优化经验:
- 控制模型面数:单个模型不超过5万面
- 纹理压缩:使用ETC2或ASTC格式
- 减少Draw Call:合并材质,减少渲染批次
- 降低分辨率:如果设备发热,可以降低图形分辨率
- 及时清理:不再需要的Anchor和Node要及时移除
我记得有一次在低端机上跑AR,手机烫得能煎鸡蛋。后来把模型从3万面降到1万面,纹理从1024降到512,帧率从18fps提到了30fps。效果虽然差了点,但至少能用了。
完整示例代码
最后,给一个完整的Activity骨架。你直接拿去改改就能用:
public class ArActivity extends AppCompatActivity {
private ArFragment arFragment;
private ModelRenderable renderable;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ar);
arFragment = (ArFragment) getSupportFragmentManager()
.findFragmentById(R.id.ar_fragment);
// 加载模型
ModelRenderable.builder()
.setSource(this, Uri.parse("models/robot.sfb"))
.build()
.thenAccept(renderable -> this.renderable = renderable);
// 点击放置
arFragment.setOnTapArPlaneListener((hitResult, plane, motionEvent) -> {
if (renderable == null) return;
Anchor anchor = hitResult.createAnchor();
AnchorNode anchorNode = new AnchorNode(anchor);
anchorNode.setParent(arFragment.getArSceneView().getScene());
TransformableNode node = new TransformableNode(
arFragment.getTransformationSystem());
node.setParent(anchorNode);
node.setRenderable(renderable);
});
}
}
这个代码虽然简单,但已经包含了ARCore的核心流程:初始化、平面检测、模型渲染、用户交互。光照估计是SceneForm自动处理的,你不需要额外写代码。
AR开发的门槛其实不高,难的是做出让人信服的体验。光照、阴影、遮挡、物理碰撞——这些细节才是决定AR应用好坏的关键。我建议你从最简单的模型放置开始,慢慢加上交互、动画、音效,一步步把体验做丰富。
好了,这一章的内容就到这里。ARCore的世界很大,平面检测只是第一步。后面还有云锚点、多人共享AR、深度API等等,都是很有意思的方向。
公众号:蓝海资料掘金营,微信deep3321