70

Hello,Google ARCore.

 6 years ago
source link: https://mp.weixin.qq.com/s/3a-0GWJPj68OiaqVPSRk7w
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Hello,Google ARCore.

最近我们一直在做AR/VR方面的技术储备,正好Google也推出了自己的AR开放平台库ARCore(用来对抗ARKit),这和原先推出的Project Tango不同,Project Tango是利用高配置的软硬件结合方案,提供诸如环境感知、位置跟踪等服务,对设备要求比较高;而ARCore则是针对移动设备上单目+IMU的增强现实解决方案,试图打造用户量最大的AR开放平台,这就是ARCore。

目前ARCore还处于Preview阶段,装备的机型也比较少,还不能用于商业项目开发。虽然目前不能用于商业项目开发,但我们还是可以拿来学习观摩。

Image

ARCore工作原理

ARCore在移动设备上使用的关键技术有哪些呢?和ARKit类似,主要有如下三点:

  1. 当你的手机在现实世界中移动,ARCore通过一个称为并行测量映射(concurrent odometry and mapping, COM)的处理过程,来理解手机相对于现实世界的位置。ARCore通过检测摄像头捕获的图像数据的视觉差异点(即特征点),使用这些点来计算位置上的改变。这些视觉信息和设备上的IMU的惯性测量值一起,用于计算摄像头随着时间推移而变动的姿势(包括位置和姿势)。

    通过把用于渲染的3D物体的虚拟世界的摄像机姿态对齐ARCore提供的设备上的摄像头的姿态,开发者能够从正确的透视角度来渲染虚拟物体。渲染出来的虚拟物体的图像可以覆盖在摄像头捕获的图像的上面,使得虚拟物体就像现实世界的一部分一样,呈现在屏幕上。

    Image
  2. ARCore通过持续的检测特征点和平面,提升它对现实世界的理解。

    ARCore查找那些看起来像是在同一个水平表面上的特征点,像桌子上的特征点们,然后把识别出来的这些表面作为平面(Plane)提供给你的应用使用。ARCore还能检测每一个平面的边界,并把这些信息提供给你的应用。你可以使用这些信息来防止虚拟物体到这些平面上来。

    由于ARCore使用特征点来识别平面,因此可能无法很好地识别那些表面没有纹理的平面,如纯白色的书桌。

  3. ARCore能够检测环境的光线,并提供当前图像的平均强度信息。这使得你能够用和环境同样的光照来照亮你的虚拟物体,提升AR内容的真实感。

    Image

除了上面的3点关键技术,还有其他,比如在ARCore里面怎么实现人机交互和锚点对象的确认等等。

目前ARCore还处于Preview阶段,相关开发的资料基本上都在官网上面。

官网地址:https://developers.google.com/ar

根据目前的Google ARCore文档描述,当前支持的开发环境还是蛮多的,包括Android Studio、Unity、Unreal、Web这四种。

Image

每一种ARCore开发环境都有一份新手入门指引,就是手把手教你跑个Hello World那种,只要按着上面文档步骤一步一步来操作即可运行一个Hello AR。

Image

这里要注意下,ARCore设计用于运行Android 7及更高版本的Android手机,这是硬性条件,低于Android 7是不可以的。

并且。。。

目前还处于Preview阶段,ARCore只支持以下手机:

  1. Google Pixel and Pixel XL(果然亲儿子…)

  2. Samsung Galaxy S8 (SM-G950U, SM-G950N, SM-G950F, SM-G950FD, SM-G950W, SM-G950U1)(果然好基友…)

Image

ARCore的开发方式比较多,下面就从我们比较熟悉的Android Studio和Unity聊一聊其开发原理和特性。

Android Studio

作为一名Android开发成员,对Android Studio肯定很熟了,这里我们就用Android Studio来体验一下ARCore,体验之前,先打个预防针,要看懂demo里面的代码,最好对OpenGL ES有所了解,不然看其中的绘制代码你绝对会蒙圈!!!

OpenGL ES

OpenGL(全写Open Graphics Library)是指定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它用于三维图像(二维的亦可),是一个功能强大,调用方便的底层图形库。

OpenGL在不同的平台上有不同的实现,但是它定义好了专业的程序接口,不同的平台都是遵照该接口来进行实现的,思想完全相同,方法名也是一致的,所以使用时也基本一致,只需要根据不同的语言环境稍有不同而已。OpenGL这套3D图形API从1992年发布的1.0版本到目前最新2014年发布的4.5版本,在众多平台上多有着广泛的使用。

OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计。

OpenGL ES相对于OpenGL来说,减少了许多不是必须的方法和数据类型,去掉了不必须的功能,对代价大的功能做了限制,比OpenGL更为轻量。在OpenGL ES的世界里,没有四边形、多边形,无论多复杂的图形都是由点、线和三角形组成的,也去除了glBegin/glEnd等方法。

OpenGL ES是手机、PDA和游戏主机等嵌入式设备三维(二维也包括)图形处理的API,当然是用来在嵌入式设备上的图形处理了,OpenGL ES 强大的渲染能力使其成为我们在嵌入式设备上进行图形处理的优良选择。我们经常使用的场景有:

  • 图片处理。比如图片色调转换、美颜等。

  • 摄像头预览效果处理。比如美颜相机、恶搞相机等。

  • 视频处理。摄像头预览效果处理可以,这个自然也不在话下了。

  • 3D游戏。比如神庙逃亡、都市赛车等。

  • AR/VR

OpenGL ES当前主要版本有1.0/1.1/2.0/3.0/3.1。这些版本的主要情况如下:

  • OpenGL ES1.0是基于OpenGL 1.3的,OpenGL ES1.1是基于OpenGL 1.5的。Android
     1.0和更高的版本支持这个API规范。OpenGL ES 1.x是针对固定硬件管线的。

  • OpenGL ES2.0是基于OpenGL 2.0的,不兼容OpenGL ES 1.x。Android 2.2(API 8)和更高的版本    支持这个API规范。OpenGL ES 2.x是针对可编程硬件管线的。

  • OpenGL ES3.0的技术特性几乎完全来自OpenGL 3.x的,向下兼容OpenGL ES 2.x。Android     4.3(API 18)及更高的版本支持这个API规范。

  • OpenGL ES3.1基本上可以属于OpenGL 4.x的子集,向下兼容OpenGL ES3.0/2.0。Android     5.0(API 21)和更高的版本支持这个API规范。

Image

这里我通过OpenGL ES绘制了一个三角形。

点、线、三角形是OpenGL ES世界的图形基础,无论多么复杂的几何物体,在OpenGL ES的世界里都可以用三角形拼成。

更多详细的资料可以查看官网,官方文档地址:https://developer.android.google.cn/guide/topics/graphics/opengl.html

配置开发环境

下面我们开始最基础的开发环境配置:

  • 安装 Android Studio version 2.3 或者更高版本 ; Android SDK Platform version 7.0 (API level 24) 或者更高版本.

  • 一部支持ARCore的手机

  • 下载ARCore SDK:

    • 直接下载 SDK preview for Android Studio或者

    • 在Github直接clone

      git clone https://github.com/google-ar/arcore-android-sdk.git
  • 安装arcore-preview.apk作为一个基础服务,手机安装完后在应用程序里面会有一个Tango Core服务。

Image

神秘的Tango Core

我们通过Android Studio来分析下上面必须安装的Tango Core基础服务arcore-preview.apk

Image

可以看到,里面一堆的so包,AR核心计算代码都在这些so包里面,由ARCore SDK封装向上提供JNI调用接口。从so包的名字里面都出现了Tango的字眼,由此不难猜测ARCore其实是在Tango的基础上进行改造而来的,Tango需要特定的软硬件标准才能够实现。

我们上层怎么调用这些JNI接口呢?ARCore提供了arcore_client.aar和obj-0.2.1.jar等包来供Java层访问。

一切准备就绪后,导入实例项目,编译项目并在设备上运行,打开相机权限,移动拍摄位置,会出现很多星点,经过计算之后会识别出平面位置,点击平面会放置绿色的Android logo,效果如下:

Image

ARCore Samples导读

HelloArActivity是示例应用的入口。这个入口简单演示了ARCore的使用方法。这里主要做了以下四件事:

  1. 配置ARCore SDK

  2. 配置绘制环境

  3. 往画面绘制信息,如摄像头数据、点云、菱形平面、Android小机器人

可以看到,ARCore还是比较简单易用的。SDK以尽可能简单的方式封装了一系列API。连平时最让人头疼的摄像头API使用也不需要我们操心了。But,这里简单容易的前提是AR素材都已经准备好,并且非常熟悉OpenGL ES的前提下。

既然是ARCore的示例工程,那么最核心的当然是ARCore的使用了。

SDK暴露在外的主要接口类为Session类。ARCore的功能通过这个类提供,开发者通过这个类和ARCore进行交互。

Session类的使用很简单:

  1. 构造一个和当前Activity绑定的Session

  2. 对这个Session进行配置

  3. 将onPause、onResume生命周期事件通知给这个Session

  4. 从Session中获取frame

接下来看看,ARCore是怎么进行图形绘制的,这也是AR开发过程中我们开发者最关心的部分。在ARCore里面,我们需要绘制这几个对象:

  1. BackgroundRenderer 用于绘制摄像头采集收的数据

  2. ObjectRenderer 用于绘制Android机器人和阴影

  3. PlaneRenderer 用于绘制识别出来的平面

  4. PointCloudRenderer 用于绘制识别出来的点云,即ARCore识别到的特征点

负责绘制的对象就是以上这几位仁兄了。但具体在哪里进行绘制?应该怎么进行绘制呢?这时候GLSurfaceView要登场了。

在Android上开发过OpenGL ES相关应用的同学们知道,要在Android上进行绘制,需要准备一个GLSurfaceView作为绘制的目标,ARCore里也不例外。

布局文件里准备了一个GLSurfaceView控件,GLSurfaceView会为我们准备好OpenGL的绘制环境,并在合适的时候回调给我们。

OpenGL ES配置

// Set up renderer.
mSurfaceView.setPreserveEGLContextOnPause(true);  //在Pause状态下,保留EGL上下文
mSurfaceView.setEGLContextClientVersion(2);  //设置OpenGL ES的版本为2
mSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0); // Alpha used for plane blending. 绘制表面配置选择RGBA分别为8位,深度为16位,模板为0的配置
mSurfaceView.setRenderer(this);  //设置自身为渲染器,即处理逻辑在这个类里面实现
mSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); //渲染模式为持续模式,即一帧渲染完,继续渲染下一帧

上面就是ARCore里面OpenGL ES基础配置,代码里面我做了详尽的标注。更多OpenGL ES的知识,可以参考官网相关文档Displaying graphics with OpenGL ES。

设置完GLSurfaceView的配置之后,接下来需要我们实现我们的绘制逻辑了。要实现在GLSurfaceView上绘制内容,需要实现GLSurfaceView.Renderer接口。这个接口的定义如下:

public interface Renderer {
    void onSurfaceCreated(GL10 gl, EGLConfig config);  //这个方法在创建或重新创建的时候回调,这个方法里面可以做一些初始化操作
    void onSurfaceChanged(GL10 gl, int width, int height);  //这个方法在可绘制表面发生大小变化的时候被调用,这个时候我们需要更新窗口信息,以便图形能准确绘制到屏幕中来
    void onDrawFrame(GL10 gl);  // 绘制的核心方法,每绘制一帧,就会调用一次
}

我们去ARCore里面查看对应上面接口的方法实现:

先来看下初始化部分:

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    GLES20.glClearColor(0.1f, 0.1f, 0.1f, 1.0f); // 设置清除屏幕的时候颜色,这里设置的是一个接近黑色的颜色

    // Create the texture and pass it to ARCore session to be filled during update().
    mBackgroundRenderer.createOnGlThread(/*context=*/this); //在GL线程初始化背景绘制器,即摄像头采集的数据,入参为Context,内部需要Context来读取资源
    mSession.setCameraTextureName(mBackgroundRenderer.getTextureId()); // 设置摄像头纹理句柄,ARCore会将摄像头数据更新到这个纹理上

    // Prepare the other rendering objects.
    try {
        mVirtualObject.createOnGlThread(/*context=*/this, "andy.obj", "andy.png"); //绿色的android小机器人
        mVirtualObject.setMaterialProperties(0.0f, 3.5f, 1.0f, 6.0f); //设置渲染模型的一些表面材质信息,比如:表面光强度、表面反射率等

        mVirtualObjectShadow.createOnGlThread(/*context=*/this,
                "andy_shadow.obj", "andy_shadow.png"); // android小机器人阴影
        mVirtualObjectShadow.setBlendMode(BlendMode.Shadow); //配置混合模式
        mVirtualObjectShadow.setMaterialProperties(1.0f, 0.0f, 0.0f, 1.0f); //设置表面材质信息
    } catch (IOException e) {
        Log.e(TAG, "Failed to read obj file");
    }
    try {
        mPlaneRenderer.createOnGlThread(/*context=*/this, "trigrid.png"); //平面
    } catch (IOException e) {
        Log.e(TAG, "Failed to read plane texture");
    }
    mPointCloud.createOnGlThread(/*context=*/this); //点云
}

然后,是配置绘制表面的大小,把绘制表面的size信息通知给ARCore。

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    GLES20.glViewport(0, 0, width, height);
    // Notify ARCore session that the view size changed so that the perspective matrix and
    // the video background can be properly adjusted.
    mSession.setDisplayGeometry(width, height); //通知ARCore显示区域大小变了,以便ARCore内部调整透视矩阵,以及视图背景
}

最后,就是核心的绘制部分了,挺住,一大坨代码马上翻涌而来。。。

@Override
public void onDrawFrame(GL10 gl) {
    // Clear screen to notify driver it should not load any pixels from previous frame.
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);  //清屏

    try {
        // Obtain the current frame from ARSession. When the configuration is set to
        // UpdateMode.BLOCKING (it is by default), this will throttle the rendering to the
        // camera framerate.
        Frame frame = mSession.update(); //从Session中获取当前帧,ARCore的核心API之一

        // Handle taps. Handling only one tap per frame, as taps are usually low frequency
        // compared to frame rate.
        MotionEvent tap = mQueuedSingleTaps.poll();  // 从点击事件队列里面获取事件,这种设计是一种优化,一次只能处理一个点击事件,用来减轻绘制过程的计算量
        if (tap != null && frame.getTrackingState() == TrackingState.TRACKING) {
            for (HitResult hit : frame.hitTest(tap)) { // 遍历手势事件和平面碰撞的结果集合
                // Check if any plane was hit, and if it was hit inside the plane polygon.
                if (hit instanceof PlaneHitResult && ((PlaneHitResult) hit).isHitInPolygon()) { //检测是否点击到了平面
                    // Cap the number of objects created. This avoids overloading both the
                    // rendering system and ARCore.
                    if (mTouches.size() >= 16) { //如果大于16个,移除最开始的那个
                        mSession.removeAnchors(Arrays.asList(mTouches.get(0).getAnchor()));
                        mTouches.remove(0);
                    }
                    // Adding an Anchor tells ARCore that it should track this position in
                    // space. This anchor will be used in PlaneAttachment to place the 3d model
                    // in the correct position relative both to the world and to the plane.
                    mTouches.add(new PlaneAttachment(
                            ((PlaneHitResult) hit).getPlane(),
                            mSession.addAnchor(hit.getHitPose())));  //将新添加的碰撞点结果添加到待绘制的集合中

                    // Hits are sorted by depth. Consider only closest hit on a plane.
                    break;
                }
            }
        }

        // Draw background.
        mBackgroundRenderer.draw(frame);  //绘制背景,即摄像头捕获的图像数据

        // If not tracking, don't draw 3d objects.
        if (frame.getTrackingState() == TrackingState.NOT_TRACKING) { //如果没有处于运动追踪状态,下面的机器人、平面、点云就都不绘制了
            return;
        }

        // Get projection matrix.
        float[] projmtx = new float[16];
        mSession.getProjectionMatrix(projmtx, 0, 0.1f, 100.0f);  // 获取当前摄像头相对于世界坐标系的投影矩阵

        // Get camera matrix and draw.
        float[] viewmtx = new float[16];
        frame.getViewMatrix(viewmtx, 0);  //获取视图矩阵,这个矩阵和上面的投影矩阵,决定了虚拟世界里哪些物体能够被看见

        // Compute lighting from average intensity of the image.
        final float lightIntensity = frame.getLightEstimate().getPixelIntensity(); // 根据图片的平均强度来计算光照强度

        // Visualize tracked points.
        mPointCloud.update(frame.getPointCloud()); //通过getPointCloud来获取追踪的特征点云
        mPointCloud.draw(frame.getPointCloudPose(), viewmtx, projmtx); //绘制点云,即ARCore识别到的特征点,姿态、视图矩阵决定了哪些点能够看到

        // Check if we detected at least one plane. If so, hide the loading message.
        if (mLoadingMessageSnackbar != null) {
            for (Plane plane : mSession.getAllPlanes()) {
                if (plane.getType() == com.google.ar.core.Plane.Type.HORIZONTAL_UPWARD_FACING &&
                        plane.getTrackingState() == Plane.TrackingState.TRACKING) {
                    hideLoadingMessage();
                    break;
                }
            }
        }

        // Visualize planes.
        mPlaneRenderer.drawPlanes(mSession.getAllPlanes(), frame.getPose(), projmtx); // 通过所有平面的位置和姿态信息,结合投影矩阵,绘制平面

        // Visualize anchors created by touch.
        float scaleFactor = 1.0f;
        for (PlaneAttachment planeAttachment : mTouches) {   //遍历上面的集合,即开始绘制绿色的Android小机器人
            if (!planeAttachment.isTracking()) {
                continue;
            }
            // Get the current combined pose of an Anchor and Plane in world space. The Anchor
            // and Plane poses are updated during calls to session.update() as ARCore refines
            // its estimate of the world.
            planeAttachment.getPose().toMatrix(mAnchorMatrix, 0); // 将姿态信息转换成矩阵,包含姿态、位置等信息

            // Update and draw the model and its shadow. 根据矩阵等参数绘制Android小机器人和它的阴影
            mVirtualObject.updateModelMatrix(mAnchorMatrix, scaleFactor);
            mVirtualObjectShadow.updateModelMatrix(mAnchorMatrix, scaleFactor);
            mVirtualObject.draw(viewmtx, projmtx, lightIntensity); //绘制Android小机器人
            mVirtualObjectShadow.draw(viewmtx, projmtx, lightIntensity);  //绘制阴影
        }

    } catch (Throwable t) {
        // Avoid crashing the application due to unhandled exceptions.
        Log.e(TAG, "Exception on the OpenGL thread", t);
    }
}

绘制代码虽然有点多,但梳理后还是蛮简单的,无非就是上面几个图形的绘制而已,代码里我也做了详细的标注。总结一下就是:

这里用mBackgroundRenderer绘制了摄像头拍到的内容,用mPointCloud绘制了ARCore识别出来的特征点云,用mPlaneRenderer绘制ARCore识别出来的平面,用mVirtualObject、mVirtualObjectShadow绘制虚拟物体和它的阴影。

可以看到,绘制相关的方法都是draw或drawXXX。正是这些调用,使得界面上有东西显示出来。具体的绘制逻辑,都封装在了对应的类里,而这些都是通过OpenGL ES来绘制的,有兴趣的同学可以深入研究下。

整个的流程都清楚了,我们就可以做很多很多的事情,而不仅仅局限于示例程序上面绘制的小东西。知道了如何获取这些信息,我们可以把绘制相关的代码都替换掉,比如用别的3D图形框架来进行绘制,只需要把这些信息给到对应的API即可。有兴趣的同学可以试一试,也就是把上文提到的绘制内容的部分替换掉罢了。

总的来说,ARCore的API设计还是很精简的,以尽可能少的暴露API的方式,提供了它最核心的功能。使用起来难度不大。但要用好ARCore,还需要开发者有一定的OpenGL基础,以及一丢丢游戏开发的基础知识,比如坐标系,投影透视矩阵,视图矩阵,纹理等基础概念。

Unity

讲完了Android Studio开发方式,下面我们来讲下Unity开发AR,因为Unity开发AR才是当前主流。Unity是一个让开发者轻松创建诸如三维视频游戏、建筑可视化、实时三维动画等类型互动内容的多平台的综合型游戏开发工具,是一个全面整合的专业游戏引擎,当然也可以开发AR相关的3D交互!

在Unity 2017.2.0b9之后,Unity添加了对ARCore的支持,低于这个版本是不支持ARCore开发的,所以建议开发者下载2017.2.0f3版本,这个版本类似一个稳定发布版本。

Image

具体Unity支持ARCore的版本详细信息可以看这里:https://unity3d.com/partners/google/arcore

大家都知道,Unity并不是完全免费的,所以我们需要去破解,Mac破解和Windows还不一样,Windows任何版本都可以一劳永逸的破解,而Mac由于架构不同需要对特定的版本进行一一破解。目前最新的Unity Mac版本已经破解到了2017.2.0f3版本,正好满足我们的需求,下面我们也是基于2017.2.0f3版本讲解。

  1. 下载破解包并解压,下载地址:链接: https://pan.baidu.com/s/1eSISYH0 密码: mzu6

  2. 下载2017.2.0f3版本的Unity

  3. 替换里面的两个核心文件

    首先 - 替换破解补丁Unity文件:

       1. 找到- 应用程序 
       2.双击Unity文件夹 
       3.右键单击Unity文件, 并选择显示包内容 
       4.双击contents文件夹 
       5.双击MacOS文件夹 
       6.右键Unity文件选择拷贝, 并粘贴到桌面作为你的备份 
       7.返回刚才的文件夹, 里面的Unity文件可以删掉了. 
       8.从下载的破解压缩包里拖拽Unity破解补丁文件到刚才的文件夹内即可. 
    

    然后- 替换破解补丁*.ulf 文件:

       1.打开Finder, 在顶部菜单出选择 前往>电脑, 打开后选择磁盘, 双击资源库(不是隐藏的那个资源库,请看清楚) 
       2.找到双击 Application Support文件夹 
       3.找到并双击Unity文件夹, 如果没有该文件夹, 请创建名为Unity的文件夹 
       4.右键文件夹内的 *.ulf文件, 选择拷贝, 并粘贴到桌面作为你的备份 
       5.返回刚才的文件夹, 里面的*.ulf文件可以删掉了. 
       6.从下载的破解压缩包里拖拽 *.ulf到刚才的文件夹内即可. 
    

经过上面破解之后,开始启动Unity,就没有那个讨厌的输入账号密码的页面了

创建HelloAR工程

接下来我们就可以根据官方教程来创建我们的第一个HelloAR项目了,官方教程在这:https://developers.google.com/ar/develop/unity/getting-started

我们需要下载Unity的ARCore SDK,这个SDK是帮助我们来使用ARCore提供相关的API来完成AR渲染、展示和交互的,下载地址:https://github.com/google-ar/arcore-unity-sdk/releases/download/sdk-preview/arcore-unity-sdk-preview.unitypackage

在Unity里面,选择File->New Project
命名项目名为HelloAR,选择3D模式,点击create Project按钮完成项目的创建

导入ARCore SDK

导入我们上面下载的arcore-unity-sdk-preview.unitypackage,怎么导入呢?点击菜单Assets->Import package->Custom Package并把所有文件都导入到工程里面,导入之后,在Project窗口下面就可以看到SDK和HelloARExample了

Image

来个导入后的项目全景图:

Image

配置构建设置选项

点击File > Build settings打开Build Settings窗口

Image

可以看到,有很多平台可以选择,这里我们只关注Android平台,切换到Android 平台,点击Player Settings做更进一步的配置,这里我们主要关心两个配置,一个是Other Settings,一个是XR Settings,我们先看下Other Settings:

Image

然后再来看下XR Settings:

Image

为了使用ARCore,必须在XR里面勾选上ARCore Supported

最后一步,因为我们要例子中HelloAR场景打包到我们的apk中,所以讲HelloAR拖到Scenes In Build中即可.

点击Build and Run按钮,进行构建和运行,如果项目没有报错,稍等一会,apk就会安装到你的设备上。

Image

手指不停的在识别出来的平面上点击,它会一直不停的添加绿色的Android小机器人,这是因为在控制脚本里面并没有添加个数限制。

Image

除了SDK里面和Shaders文件,Example里面一共自定义有四个脚本文件,分别是:

  1. HelloARController.cs

  2. PlaneAttachment.cs

  3. PointcloudVisualizer.cs

  4. TrackedPlaneVisualizer.cs

核心文件入口就是HelloARController.cs,我们去看看里面有啥神奇之处:

这是一个C#程序文件,其继承了MonoBehavior这个类,我们知道,在MonoBehavior里面,Start()方法主要做一些初始化的操作,而每一帧刷新的时候都会回调Update方法,那我们直接去查看Update方法即可:

/// <summary>
        /// The Unity Update() method.
        /// </summary>
        public void Update ()
        {
            _QuitOnConnectionErrors(); // 连接失败App直接退出

            // 当前状态必须是帧序列跟踪状态
            if (Frame.TrackingState != FrameTrackingState.Tracking)
            {
                const int LOST_TRACKING_SLEEP_TIMEOUT = 15;
                Screen.sleepTimeout = LOST_TRACKING_SLEEP_TIMEOUT;
                return;
            }

            Screen.sleepTimeout = SleepTimeout.NeverSleep;
            Frame.GetNewPlanes(ref m_newPlanes);

            // 遍历Frame上所有的平面,实例化成GameObject并显示出来
            for (int i = 0; i < m_newPlanes.Count; i++)
            {
                // Instantiate a plane visualization prefab and set it to track the new plane. The transform is set to
                // the origin with an identity rotation since the mesh for our prefab is updated in Unity World
                // coordinates.
                GameObject planeObject = Instantiate(m_trackedPlanePrefab, Vector3.zero, Quaternion.identity,
                    transform);
                planeObject.GetComponent<TrackedPlaneVisualizer>().SetTrackedPlane(m_newPlanes[i]);

                // Apply a random color and grid rotation.
                planeObject.GetComponent<Renderer>().material.SetColor("_GridColor", m_planeColors[Random.Range(0,
                    m_planeColors.Length - 1)]);
                planeObject.GetComponent<Renderer>().material.SetFloat("_UvRotation", Random.Range(0.0f, 360.0f));
            }

            // 有效平面存在时,隐藏搜索条UI
            bool showSearchingUI = true;
            Frame.GetAllPlanes(ref m_allPlanes);
            for (int i = 0; i < m_allPlanes.Count; i++)
            {
                if (m_allPlanes[i].IsValid)
                {
                    showSearchingUI = false;
                    break;
                }
            }

            m_searchingForPlaneUI.SetActive(showSearchingUI);

            // 触摸事件相关
            Touch touch;
            if (Input.touchCount < 1 || (touch = Input.GetTouch(0)).phase != TouchPhase.Began)
            {
                return;
            }

            TrackableHit hit;
            TrackableHitFlag raycastFilter = TrackableHitFlag.PlaneWithinBounds | TrackableHitFlag.PlaneWithinPolygon;

            if (Session.Raycast(m_firstPersonCamera.ScreenPointToRay(touch.position), raycastFilter, out hit))
            {
                // Create an anchor to allow ARCore to track the hitpoint as understanding of the physical
                // world evolves.
                var anchor = Session.CreateAnchor(hit.Point, Quaternion.identity);

                // 实例化渲染Android绿色小机器人
                var andyObject = Instantiate(m_andyAndroidPrefab, hit.Point, Quaternion.identity,
                    anchor.transform);

                // Andy should look at the camera but still be flush with the plane.
                andyObject.transform.LookAt(m_firstPersonCamera.transform);
                andyObject.transform.rotation = Quaternion.Euler(0.0f,
                    andyObject.transform.rotation.eulerAngles.y, andyObject.transform.rotation.z);

                // Use a plane attachment component to maintain Andy's y-offset from the plane
                // (occurs after anchor updates).
                andyObject.GetComponent<PlaneAttachment>().Attach(hit.Plane);
            }
        }

C#代码和Java非常像,所以整体逻辑看起来不是很困难。

/// <summary>
        /// Quit the application if there was a connection error for the ARCore session.
        /// </summary>
        private void _QuitOnConnectionErrors()
        {
            // Do not update if ARCore is not tracking.
            if (Session.ConnectionState == SessionConnectionState.DeviceNotSupported)
            {
                _ShowAndroidToastMessage("This device does not support ARCore.");
                Application.Quit();
            }
            else if (Session.ConnectionState == SessionConnectionState.UserRejectedNeededPermission)
            {
                _ShowAndroidToastMessage("Camera permission is needed to run this application.");
                Application.Quit();
            }
            else if (Session.ConnectionState == SessionConnectionState.ConnectToServiceFailed)
            {
                _ShowAndroidToastMessage("ARCore encountered a problem connecting.  Please start the app again.");
                Application.Quit();
            }
        }

_QuitOnConnectionErrors 方法就是检测手机是不是支持ARCore、有没有相机权限、连接Tango服务是否成功,如果有一项不满足,则调用Android的Toast方法Toast一下,然后结束Application。

其他的代码就不细看了,绘制逻辑步骤总体是一致的,使用Unity的方式有种在Android里面写页面的赶脚。事实上也确实是,Unity的一些控制逻辑在C#脚本里面,而Android一般写到Activity里面,其实都是差不多的。

总体来说,ARCore实现AR的效果还是蛮不错的,提高了移动端AR应用体验,降低了开发门槛。但是,ARCore也有致命的缺陷,比如说:手机需要安装几十M的Tango服务;只能在Android 7系统上才能使用;预览阶段只支持部分手机,目前还不知道正式版放开这个限制后有没有其他问题;官方教程文档不是很多,开发资料少,没有一套完备的开发流程体系等等。

结论就是,暂时还不能用于商业项目。

不过,随着Google后期的发力,我们相信,ARCore会逐步完善,Android移动端AR的春天马上来了。。。

Image

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK