ARCore-普及篇

5,052 阅读11分钟

ARCore 是一个用于在 Android 上构建增强现实应用的平台

ARCore 三个特点

  • 运动跟踪 (让手机可以理解和跟踪它相对于现实世界的位置)
    1. ARCore 会检测捕获的摄像头图像中的视觉差异特征(称为特征点),并使用这些点来计算其位置变化
    2. 这些视觉信息将与设备 IMU, IMU, 惯性测量单元 (Inertial measurement unit , 可以通过这个视频直观得感受下 的惯性测量结果结合,估测摄像头随着时间推移而相对于周围世界的姿态
    3. 通过将渲染 3D 内容的虚拟摄像头的姿态与 ARCore 提供的设备摄像头的姿态对齐,渲染的虚拟图像可以叠加到从设备摄像头获取的图像上,让虚拟内容看起来就像现实世界的一部分一样(实际就是采集摄像头图像,获取到摄像头的姿势状态后再把虚拟物体进行相应转换)
  • 环境理解 (让手机可以检测平坦水平表面(例如地面或咖啡桌)的大小和位置)
    1. 通过检测特征点和平面检测真实世界的平面等(因此无法检测没有特征点的物体,例如:纯色平面)
  • 光估值 (让手机可以估测环境当前的光照条件)
    1. 主要通过采集的图像的平均色值以及手机传感器来进行综合计算
    2. 这样把光照值应用到虚拟物体上就会更加真实。

ArCore 是如何把真实和虚拟结合起来的

使用opengl es 渲染

如何渲染纹理贴图

接触过opengl的同学应该很容易知道,opengl最大的特点就是模拟真实视角以及渲染场景模型,

你可以用 ==3DMax==、==Maya== 创建自己的模型并贴图,绑定、 输出成opengl支持的文件,并展示。 当然这是opengl最重要的应用。 现在市面上也有很多美颜相机,包括抖音的一些变脸特效, 他们的做法是==(以Android平台为例)==通过Camera获取图像,并对通过识别算法捕捉到特定部位, 用特殊设计的矩阵算法进行变换,比如拉伸、挤压等, 然后把==处理过后的图像== 以贴图的方式呈现在屏幕上。 当然图像并不是简简单单得直接像平时开发一样

  imageview.setImage(...)

因为要考虑到物体本身坐标、世界坐标、视图坐标 等,所以要进行转换才能最终对应到屏幕中的坐标,因为摄像头的姿势一直在变。摄像头每一次变更,图像跟着转换,就好像真的是通过摄像头在观察真实世界。

opengl生态

所以,google是聪明的,它利用 opengl的生态来发展自己的生态,==几乎所有能在opengl es上应用的库、模型,都可以直接拿来使用==, 只不过要换成ARCore自己的视图矩阵转换。

再者,毫不夸张得说, 如果有人说 ==ARCore(android)是opengl es的一个库==,Android原生开发者应该无言以对吧。====

环境理解以及运动跟踪和opengl es结合

ARCore提供可跟踪对象信息

ARCore通过特征点识别,能够鉴别出纹理平面、位置、光线等, 这里暂时只考虑平面识别。 得到平面信息(不管是哪个朝向,上平面OR下平面),平面是有姿态和边界的,平面是一个可跟踪对象, 可以在平面上创建Anchor,这是我们用来标定具体对象的位置信息,可以把平面想象成桌子,可以在桌子上放任何像放的东西, 比如放一个苹果,苹果的位置必须由平面提供接口创建, 因为它和桌子保持相对性,这里的Anchor相当于苹果的抽象,因为Anchor只是包含世界坐标的信息和姿态并不代表具体物体, 我们可以把Anchor的坐标姿态信息应用到具体物体上(如 苹果), 那么苹果就会出现在桌子上。 ==opengl es==就是利用Anchor信息来在真实世界中放置自己的模型,模型叠加在真实世界视图中,这就是增强现实。

ARCore提供其他信息

光有Anchor还不够, 上面的Anchor只是物体本身位置信息, 并没有考虑到 摄像头、投影等因素, 其实这和opengl里是一样的, 物体本身的坐标要经过 视图矩阵==ViewMatrix==和投影矩阵==ProjectionMatrix==的转换才能最终转换成==屏幕坐标==, 这些信息ARCore也会提供,除此之外还会提供 ==光照信息==、==摄像头姿态==等

这些信息以前其实都是由opengl 自己产生指定, 现在变更为由ARCore提供而已。

运动跟踪

前面说过 运动跟踪是他的特点之一, 意思就是你在摄像头的当前位置方式一些物体 镜头位置转移,再回来的时候物体还在, 这就是他对环境的理解对物体的跟踪。

ARCore支持情况

前言-指纹适配

接触过Android中指纹Api的人应该知道, 6.0之前虽然各厂商都有指纹支持,但是并没有统一Api,如果硬是要做,只能判断具体机型,调用对应Api。

  • 但会有很多问题:
    1. 机型太多,不可能覆盖
    2. 兼容问题,api说变就变
    3. 维护困难

因此很多大厂致力于这些事情,比如联想的Fido指纹库, 原理是和每一个手机厂商合作,厂商会提供特定的api给联想去调用, 所以合作的厂商在手机出厂时会有预置的 APK, Android app开发者利用联想提供的jar来实现和内置apk以及手机底层通信,开发者就可以把关注点从繁琐的适配中转移到Fido的使用上。

google的做法

google也是这个策略, 但是也有不同的地方, 不同在于,他并没有预置应用在厂商手机上, 而是在特定时候才会去下载该特殊apk(后面会详细说明),至于为什么要采取这个做法,后面可以探讨 [^preLoad], 所以一个设备不光需要这歌特殊的apk同样也需要厂商的接口支持。

  • 因此一个机型是否支持ARCore取决于两个条件:
    1. 厂商支持^supportDevice,提供了特定API
    2. 手机中安装了google特殊的apk

针对开法者

今天4月初的时候,ARCore release了1.1版本, 相对于之前版本改动点不是特别多,但是提供了 特殊apk,开发者可以在支持ARCore的手机上安装它(考虑到墙的问题,某些开发者无法从google商店获取),或者安装在 ==3.1 Studio==的模拟器上。

目前google正和国内华为、小米、 魅族等各大厂商进行合作, 4月初的时候,小米上架了特殊apk ==ARCore== 被我逮到了,不过还处在试验期,不支持下载,还无聊得举报了一下。

ARCore集成

依赖包引入

implementation 'com.google.ar:core:1.0.0'

menifest申明

google把ARcore应用分为两类,==AR Required== 或者 ==AR Optional==,what?

==AR Required==

意味着您的应用在没有AR的情况下不可用。Play商店仅在支持ARCore的设备上提供您的应用。

当用户安装AR需求应用时,Play商店将自动安装ARCore。但是,如果用户卸载了ARCore ,您的应用仍然必须执行其他运行时检查。
<uses-sdk android:minSdkVersion = “ {24或更高版本}“ /> 
   ... 
<uses-feature android:name = ”android.hardware.camera.ar​​“ android:required = ”true“ />    
<application>     ... 
	<meta-data android:name = ”com.google .ar.core“ android:value = ”required“ />

==AR Optional==

您的应用程序包含一个或多个AR功能,如果用户的设备支持ARCore,则会激活该功能。但是该应用程序可以在不支持ARCore的设备上安装和运行。

当用户安装AR可选应用程序时,Play商店不会自动安装ARCore。

要将您的应用作为AR可选,需要修改menifest以包含以下条目:

<uses-sdk android:minSdkVersion = “ {14或更高版本}“ /> 
   ... 
<application> 
    ... 
<meta-data android:name = ”com.google.ar.core“ android:value = ”optional“ />   
     ...

opengl es部分

宿主Activity实现GLSurfaceView.Renderer接口

	public class MainActivity extends AppCompatActivity implements GLSurfaceView.Renderer{
	//初始化opengl的一些参数,例如初始化着色器
	void onSurfaceCreated(GL10 gl, EGLConfig config){}
	//接收视口的变化,在传统opengl应用中如果涉及到Camera,可以设置
	//projection matrix
	void onSurfaceChanged(GL10 gl, int width, int height){}
	//具体渲染的地方,在ARCore中会按一定频率被调用,
	//这个方法中我们可以获取摄像头信息、投影/视图矩阵信息、光照信息等
	void onDrawFrame(GL10 gl){}
	}

初始化画布并且设置上面的Render接口

	// Set up renderer.
    surfaceView.setPreserveEGLContextOnPause(true);
    surfaceView.setEGLContextClientVersion(2);
    surfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending.
    surfaceView.setRenderer(this);
    surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);

检查设备是否支持ARCore

在展示我们ARCore相关UI元素之前,我们需要检查当前设备是否支持,否则Session会创建失败。

void maybeEnableArButton() {
  // Likely called from Activity.onCreate() of an activity with AR buttons.
  ArCoreApk.Availability availability = ArCoreApk.getInstance().checkAvailability(this);
  if (availability.isTransient()) {
    // re-query at 5Hz while we check compatibility.
    new Handler().postDelayed(new Runnable() {
      @Override
      public void run() {
        maybeEnableArButton();
      }
    }, 200);
  }
  if (availability.isSupported()) {
    mArButton.setVisibility(View.VISIBLE);
    mArButton.setEnabled(true);
    // indicator on the button.
  } else { // unsupported or unknown
    mArButton.setVisibility(View.INVISIBLE);
    mArButton.setEnabled(false);
  }
}

由于该检查方式会启用远程服务查询,所以不管设备是否支持都会立即返回UNKNOWN_CHECKING, 所以要循环查询,但不会重复请求查询服务,只是获取查询结果, 上段代码中:

 if (availability.isTransient())

该方法返回true表示当前状态知识临时状态,也就是说查询服务还没有返回,你应该继续去捞结果, 直到 返回 ==SUPPORTED== 或者 ==UNSUPPORTED==,大致流程如下图:

检查是否安装了ARCore

上面我们说过,一个设备能否使用ARCore,有两个条件,==设备本身支持== 和 ==安装了特殊apk(ARCore)== 因此,我们还需要检查==是否安装了ARCore==

// Set to true ensures requestInstall() triggers installation if necessary.
private boolean mUserRequestedInstall = true;

// in onResume:
try {
  if (mSession == null) {
    switch (ArCoreApk.getInstance().requestInstall(this, mUserRequestedInstall)) {
      case INSTALLED:
        mSession = new Session(this);
        // Success.
        break;
      case INSTALL_REQUESTED:
        // Ensures next invocation of requestInstall() will either return
        // INSTALLED or throw an exception.
        mUserRequestedInstall = false;
        return;
    }
  }
} catch (UnavailableUserDeclinedInstallationException e) {
  // Display an appropriate message to the user and return gracefully.
  return;
} catch (...) {  // current catch statements
  ...
  return;  // mSession is still null
}

也许有人困惑第2行的变量有什么用,我也困惑了一顿饭的时间,先不急,我们先看看上面代码对应的流程图:

对于上面的流程,你有没有困惑过,当设备不支持时,什么时候返回 INSTALL_REQUESTED 什么时候返回exception, 这就取决于代码中第2行的变量啦, 如果为false时,系统则认为你已经提示过用户安装了,因此会跑出一个异常,这时候你可以简单得Toast提示, 为true时会引导用户去各市场下载==ARCore==, 当然目前只有google市场,其他厂商的市场还在商谈中。

渲染

前面说过,渲染请求都是在public void onDrawFrame(GL10 gl)发起的,在这个方法里我们可以获取 sdk提供给我们的信息传递给==opengl es==进行渲染

//session是在上文创建的ARCore回话,是贯穿于整个宿主Activity生命周期的
session.setCameraTextureName(backgroundRenderer.getTextureId());
//获取到当前帧的信息
Frame frame = session.update();
//获取Camera对象,Camera对象有我们所需要的绝大部分渲染信息
Camera camera = frame.getCamera();

//这个就是投影矩阵
float[] projmtx = new float[16];
camera.getProjectionMatrix(projmtx, 0, 0.1f, 100.0f);
//这个就是视图矩阵
float[] viewmtx = new float[16];
camera.getViewMatrix(viewmtx, 0);
//光照信息
final float lightIntensity = frame.getLightEstimate().getPixelIntensity();

//pikaqiuRender是我定义的opengl的渲染类,把这三个要素丢给opengl es其实就没有ARCore啥事了,
//其余工作就交给opengl es了
pikaqiuRender.draw(viewmtx, projmtx, lightIntensity);

Session和Activity生命周期

由于ARCore检测以及opengl es渲染实在是太耗资源了, 我的mac开启ARCore应用,小风扇就直接转起来了,实在太恐怖, 所以在不必要的时候尽量避免渲染,这里要特别提醒的是,注意 ==session== 和 ==surfaceView== 的次序。

onPause()

@Override
    protected void onPause() {
        super.onPause();
        if (session != null) {
            // Note that the order matters - GLSurfaceView is paused first so that it does not try
            // to query the session. If Session is paused before GLSurfaceView, GLSurfaceView may
            // still call session.update() and get a SessionPausedException.
            displayRotationHelper.onPause();
            surfaceView.onPause();
            session.pause();
        }
    }

onResume()

  @Override
    protected void onResume() {
        super.onResume();
        if(session == null){
            checkAndInitSession();
        }
        showLoadingMessage();
        try{
            session.resume();
        }catch (CameraNotAvailableException e){
            Log.d(TAG, "CameraNotAvailableException");
            e.printStackTrace();
        }
        surfaceView.onResume();
        displayRotationHelper.onResume();
    }

demo

模型没有添加灯光参数, 添加灯光后会更加真实一点

普及篇就写到这里了,大家如果感兴趣的话后面可以针对官方demo以及api做一些解读

[^IMU]: IMU, 惯性测量单元 (Inertial measurement unit , 可以通过这个视频直观得感受下

[^preload]: 不采用预置方法,可能有两个原因: 1. 特殊apk可以被卸载,一旦卸载手机则无法支持ARCore,2. 采用特殊时机下载的方式,可以防止这种情况,并且支持升级。