细数Android原生工程接入EasyAR-SurfaceTracking遇到的坑

背景:该篇文章总结于在原生的工程中接入EasyAR的SurfaceTracking的过程中遇到的一些问题,有一些问题是知识性的问题,有一些问题是可以称之为是坑的问题。

关键词:

Android原生中有关OpenGL的渲染设置

EasyAR初始化失败

EasyAR照相机黑屏

在Android Studio中进行有关EasyAR矩阵的调试(矩阵数据变的很诡异)

如何在自己的引擎中进行设置EasyAR提供的Projection Matrix(投影矩阵)和View Matrix(观察矩阵)

点击人物无法命中,或者没有办法正确命中

低配机进入AR模式之后,拍摄的视频背景卡顿问题

1、Android原生中有关OpenGL的渲染设置

这一块内容其实和AR的东西不是很相关,但是一般来说,工程里面接入ARSDK的话。肯定是想绘制自己的虚拟人物,在移动平台上,OpenGL又是不可或缺的一个部分,所以这一部分应该是有一些可以借鉴的内容。

因为之前工程里面的ARSDK接入的是Vuforia的GroundPlane(以下简称Vuforia),所以按照Vuforia的官网提供的Android事例,自己工程中的结构完全按照其官网事例进行设置,包括里面的session控制Vuforia的生命周期以及子类View继承GLSurface来进行渲染的设置。

所以接入EasyAR-SurfaceTracking(以下简称EasyAR)的第一步,就是先去将Vuforia从原来的工程中移除掉。

这里吐槽一下自己的感想:Vuforia关于这里的结构的设计,真的很严密,有专门控制功能的类,有专门掌管Vuforia生命周期的类,有专门进行渲染设置的类,以及功能的逻辑,错误的判断等真的很值得借鉴。相比于EasyAR,就会更加的简易,也更容易我们去理解,对于我这种新手还是比较友好的。

当然也可以把EasyAR的结构设计成和Vuforia的一样,但是有点小题大做了,因为相比于EasyAR,Vuforia里面的概念还是比较多,设备跟踪、智能地形等等,但是程序还是应该越简单越好(大佬说的,照做就是了)。

所以在我的程序里面,关于这里的设置就只有一个MainActivity.java、GLView.java、以及EasyAR官网提供的例子的中的HelloAR.java和BackGroundRender.java(最后一个文件名字我记不清了,就是用来渲染拍摄的视频流背景的那个文件)。

Tip:官网的例子中还有一个BoxRender.java的文件,用来渲染一个立方体,这个其实在接入的过程中可以留着,可以先去将盒子画出来,然后再去一步步的接入自己的渲染部分。然后完全接入的时候,再去将这部分删除掉就可以了。

EasyAR里面关于SurfaceTracking总共就只有三个文件:HelloAR(管理AR的相关接口),BoxRender(渲染盒子),BGroundRender(背景的渲染)。

下面说一下关于这几个文件结构的设计:

MainActivity.java文件里面是主要的Activity,并且只有这么一个Activity,负责进行EasyAR的最外层的管理,因为在我的工程里面,还有一些通过JNI和C++交互的部分,所以我将EasyAR最外层的管理放在这个类里面。

和Activity匹配的页面布局(XML文件)我使用的很简单,就只是写了一个进入AR的按钮,和一个退出AR的按钮,在这个XML里面,将XML的上下文配置为MainActivity,MainActivity类继承的是:Application类,没什么主义的地方。

在MainActivity的成员变量里面,有一个GLView类型的成员变量:mView。

在OnCreate方法里面,使用setContentView函数将这个成员变量mView设置为当前Activity的View视图。

这里其实我刚开始的时候不是很明白通过xml设置和通过这个View来设置有什么区别,总结一下:可以在某一些层面上将这个xml和mView理解为同一个东西,都是用来设置界面的长相的,只不过如果我们在代码里面一直使用代码来添加按钮,拖动条,进度条之类的,会让我们的代码文件显的特别的臃肿,所以此时,我们将所有有关控件的设置,都抽离出来,组成一个xml文件,这个文件里面只用来设置UI控件的样式,然后我们在代码文件里面就只需要使用就可以了,或者在需要的时候做一些更改,这样子我们的代码结构就会变的很简单明了。

 

GLView.java文件

这个文件就是主要和OpenGl相关,这个GLView我们需要让其继承自GLSurfaceView类 ,有关GLSurfaceView这个类是Android对于SurfaceView类的更高的一层封装,目的就是为了让我们能在Android原生更方便的使用OpenGL。

继承了这个GLSurfaceView类之后,我们必需要做的就是重载里面的三个函数:

OnSurfaceCreate、OnSurfaceChanged、OnDrawFrame

这三个函数的实现必须放在setRenderer函数里面,这个函数传进去的参数是一个GLSurfaceView里面的Renderer类型的变量。

在这个变量里面,我们要重载里面的三个函数。

第一个函数:OnSurfaceCreate

作用:用来创建渲染OpenGL界面的函数,也就说第一次执行就会先执行这个函数

第二个函数:OnSurfaceChanged

作用:当手机横竖屏转的时候,就会调用这个函数进行宽高切换

第三个函数:OnDrawFrame

这个函数是我们的重点函数,因为这个函数会每一帧都会去调,然后就是作为我们的render驱动,我渲染我们的虚拟人物

setRenderer(new GLSurfaceView.Renderer() {
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {

            }

            @Override
            public void onSurfaceChanged(GL10 gl, int w, int h) {

            }

            @Override
            public void onDrawFrame(GL10 gl) {
            }
        });

除了使用setRenderer函数重载这3个函数之外,我们还可以设置下面这些:

setEGLContextFactory(new ContextFactory());
setEGLConfigChooser( translucent ?
                new ConfigChooser(8, 8, 8, 8, depth, stencil) :
                new ConfigChooser(5, 6, 5, 0, depth, stencil) );
 setEGLWindowSurfaceFactory(new WindowSurfaceFactory());
setPreserveEGLContextOnPause(true);

这些就是设置OpenGL的上下文有关的东西,里面的参数可以使用默认的,也可以使用自己写的类,我这里是使用的自己写的类,下面可以将这些代码贴上来。应该可以直接在你们的工程里面去用,因为这里主要还是记录和EasyAR相关的东西,所以这里的GLSurfaceView就不多说了,网上其他博客还是有很多更详细的资料。

这一部分的最后:按照EasyAR官方例子上,我们可以在这个GLView的类里面声明一个HelloAR类型的变量,然后使用这个变量去调用HelloAR类里面的render函数,再将这个render函数放在刚才的onDrawFrame里面就可以启动我们的render循环。

在这一个部分,我遇到了一些问题:就是关于有关OpenGL的包,

import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL10;

我只有使用这些包的时候,才不会报错,之前使用下面这些包的时候,会有一些奇怪的符号不能识别的问题,查了一下,说是两个包差别不大,上面的算是升级版,但是具体升级什么了,我没有太细搞,有知道的大佬可以分享一下。

import android.opengl.GLES30;
import android.opengl.GLSurfaceView;

2、EasyAR初始化失败

初始化EasyAR的引擎需要去官网申请一个开发者账号,然后在里面申请一个免费版的Key,自己工程里面用的是哪一个版本的,就申请那个版本的key,Key只能向下兼容,不能向上兼容,然后将自己app的ID填进去

ID在自己的工程的Grandle里面,之后就会给你一个字符串,这个字符串,我们在就可以用来初始化EasyAR的引擎,如果key和ID没有对应上的话,就会在log里面提示

Log.e("HelloAR", "Initialization Failed.");

可以在控制台搜索相关字符,看看有没有这个提示,来判断引擎有没有初始化成功。

这个初始化,完全可以将例子中的初始化搬过来,我是放在MainActivity的onCreate里面直接初始化的。这一部分只要注意一下,应该没什么大问题。

3、EasyAR照相机黑屏

进入AR模式之后的黑屏就比较好搞了,因为上一步我们已经确定了引擎的初始化成功,现在出现黑屏的问题很有可能就是我们的app摄影机的权限没有申请成功,我们可以打开app的详情信息来看一下 ,里面有没有摄影机的权限。

有了这个权限之后,拍摄应该正常了。

4、在Android Studio中进行有关EasyAR矩阵的调试(矩阵数据变的很诡异)

因为我是的工程是使用Android Studio进行开发的。所以在Engine初始化完成,摄影机正常打开之后,此时我们就应该进行自己的虚拟人物的绘制,如果只是绘制他的一个小盒子,就没什么意思了。

这个时候我建议大家,先正确的绘制出来他自己的盒子,然后再去将程序改成绘制自己的虚拟人物,一步一步来。

而绘制虚拟人物的话,我们就需要从EasyAR中获取两个矩阵:Projection 矩阵和 View 矩阵。

然后在设置矩阵的开发过程中,我们一定会去调试程序,这个时候就会有有一个十分严重的问题,通过调试,我们在观察器里面监视我们的矩阵里面的数字。

而这个监视的过程无论是附加断点还是直接Debug启动,中间AS(Android Studio)都会为我们做一步动作:将我们的.so文件一起打包进app,然后还有其他的一些有关build的操作。这些操作在稍微大一点的工程里面是需要一些时间的,这个时间根据自己工程的大小而决定,我这个工程,附加一次断点,中间的这些操作耗时大概在20s左右。

A:当你进入了AR模式,然后打了断点,这个时候进行附加断点调试操作。中间经历了大概20s左右的空白,此时AR拍摄的画面会卡顿,前一帧和后一帧的画面就会不连贯,而我们的EasyAR的平面特征点计算的依据就是前一帧和后一帧画面一起的计算结果,所以中间附加断点的20s,就会让EasyAR的计算结果不准确,这个时候你观测你的矩阵数据,就会发现:矩阵的数据十分诡异,变得十分的大,正常来说,EasyAR里面的单位是:米。应该数据最大在10左右,就可以了,再大的话,你就应该注意一下是不是出了问题。

B:当我们使用Debug启动App调试的时候,进入AR之前我们已经将断点打好了,这个时候我们第一次命中断点的时候,就会发现,这个矩阵里面的数据全是0。是因为EasyAR在刚开始的几帧画面里面,正在计算这个画面的特征点,此时还没有计算出来对应的Projection Matrix和 View Matrix。所以多跑几帧就可以了。我的大概跑5帧左右(也就是断点命中5次左右)就会发现矩阵有数据了。然后这个时候,你继续调试,就会发现数据有可能也变的越来越大。这和上面的原因是一样的,因为打断点的时候,我们要观察监视器里面的数据,然后再去跑程序,中间就有会有很长时间的停顿,这也会影响EasyAR的引擎判断。

所以我建议的方法就是:打log咯,这个时候,EasyAR计算的是连续帧,所以数据比较准确,就不会出现上面的问题了。

这个两个问题搞了我好久。最后发现只能将锅甩给我自己。没有完全的理解EasyAR,冒然调试,锅从天降。

5、如何在自己的引擎中进行设置EasyAR提供的Projection Matrix(投影矩阵)和View Matrix(观察矩阵)

6、点击人物无法命中,或者没有办法正确命中

这两个问题可以放在一起总结,因为都是关于矩阵的设置,但是这两个问题是我在不同的时间遇到的,所以就抽出来成两个问题。

因为AR的效果呈现主要靠的是View Matrix ,这是刚开始想法,所以开始的时候我就只去设置EasyAR提供的ViewMatrix,效果正确之后,我就没有去管EasyAR提供的ProjectionMatrix,而是让自己的渲染引擎计算ProjectionMatrix去给人物用。这个时候,就会出现第六个问题。

所以ProjectionMatrix一定是要设置的。

然后关于ViewMatrix,按照文档的说法,EasyAR提供的矩阵是行主序,和OpenGL的列主序是不一样的,所以首先我们使用到自己的OpenGL引擎中的时候,就需要行列转置,剩余的呢,就按照正常的处理就可以,

因为我自己的渲染引擎里面虽然使用的是OpenGL,但是矩阵的方式都是按照DX的方式来的,也就是出了行列不一样之外,还有左右手也不一样,所以我在我的矩阵的时候时候,要经过行列转置和左右手座标系互换两步。

关于ViewMatrix,我是通过解析里面的观察点座标和观察方向的向量,然后通过这两个数据计算出一个新的投影矩阵。

然后还要设置一个upDir向量,这个向量决定的是你怎么去观察,是脑袋立起来观察(0,1,0),还是脑袋朝右,然后观察(1,0,0),其他的话就可以根据自己的渲染引擎进行推算,我现在将我的设置方法放出来。希望对你设置自己的渲染引擎的矩阵有所帮助。

    void SetMat(const float* pfProjMat, const float* pfViewMat)
    {
        // 传进来的两个参数就是EasyAR的Projection Matrix和View Matrix
        KVec3 vLookAt;
        KVec3 vEyePos;
        KVec3 vUp;
        KMATRIX viewMatrix;
        KMATRIX proMatrix;
        KMATRIX pfProjMatrix;

        // 进行View Matrix的设置
        memcpy(&viewMatrix, pfViewMat, sizeof(viewMatrix));
        MatrixTranspose(viewMatrix,viewMatrix);// 行列转置
        // 因为我自己渲染引擎的矩阵,使用的是DX的矩阵,所以需要转成DX的矩阵
        _MatrixDXToOpengl(viewMatrix,viewMatrix);// 转成DX的矩阵
        // 从View Matrix里面解析出来Eye Positon和 LookAt的数据
        DecomposeMarixLookAtLH(viewMatrix,vEyePos,vLookAt);
        SetPosition(vEyePos * 100);
        SetLookAt(vLookAt * 100);
        SetUpDir(KVec3(1,0,0));


        // Projection Matrix的设置
        memcpy(&pfProjMatrix, pfProjMat, sizeof(pfProjMatrix));
        MatrixTranspose(pfProjMatrix,pfProjMatrix);
        _MatrixDXToOpengl(pfProjMatrix,pfProjMatrix);

        float fNear = 10.0f;
        float fFar = 40000.0f;
        float a = (fFar) / (fFar - fNear);
        float b = -fFar * fNear / (fFar - fNear);
        proMatrix.f[0] = pfProjMatrix.f[0];
        proMatrix.f[1] = pfProjMatrix.f[1];
        proMatrix.f[4] = pfProjMatrix.f[4];
        proMatrix.f[5] = pfProjMatrix.f[5];
        proMatrix.f[10] = a;
        proMatrix.f[11] = 1;
        proMatrix.f[14] = b;

        SetARMode();
        SetProjMat(proMatrix.f);
    }

上面的设置里面还用到了转置函数和OpenGL矩阵转DX矩阵的函数,以及解析Eye Posotion、LookAt的函数,

因为这些函数都是使用我自己封装的矩阵类型,所以就不上传了,网上也可以找得到,不想写的话,就从网上找一个,也不难。

Tip:传进来的ViewMatrix参数是经过求逆的,这个求逆的函数是EasyAR里面自带的:

传进来的时候使用这个函数对View Matrix进行求逆一下:

像这样(官网例子里面的)

 

7、低配机进入AR模式之后,拍摄的视频背景卡顿问题

这个我问题,刚开始我以为是adb的问题,但是后来发现低配机器上这个问题一直存在,所以就差了一下文档,如下图:

文档就说InputFrame的数量太少,导致渲染卡住,然后InputFrame的默认数量是8,这个时候我们就可以扩大这个数量,我是直接扩大到32.为什么扩大到32我不是很清楚但是问题是解决了。

目前我不是很清楚,什么样的大小算是一个合适的大小,因为我在高配机上使用的默认的大小就正常。这个文档里也没有说过。

然后我们点开上面的蓝色的文字:CameraDevice,然后我们就进入了CameraDevice类里面,发现了这么一段话:

就会更加的印证我的想法是正确的,接着我们在下面找一找接口咯。会发现,有这么一个接口,有点意思:

然后使用CameraDevide类的对象(HelloAR初始化的那个部分的时候,就可以找得到)调用这个接口,将InputFrame的数量搞大一些。具体数值可以看你咯,我设置的32;文档也没有说怎么设置合适。

放在最后的吐槽:

关于EasyAR的文档,确实让我有点琢磨不同,因为之前有使用过Vuforia,所以Vuforia的文档之详细,简直真的有点强了,每一个接口都会有对应的小例子。除了都是英文,真的没啥好吐槽的,可是EasyAR的文档,关于接口的说明就只是每一种语言列出来,然后统一一个解释,有时候万一有那个接口没有理解,压根没有什么其他的资料可以让你查找。

关于AR,大家使用的Vuforia会更多一些,因为毕竟是高通爸爸搞的,有钱有人有力。但是关于Vuforia的GroundPlane功能支持的国产的手机,一个牌子竟然不超过5部,清楚的记得某米手机,只有最新的几台才支持,但是三星的几乎是全系列都支持了,很难搞。但是Vuforia的效果确实的很不错,还支持多人物,多锚点。但是还是抵不住EasyAR支持几乎全部的Android机的诱惑。毕竟产品还是得让大部分人都能使用的上。

EasyAR的技术人员也说了,他们没有向其他的大公司的技术方向靠,所以平面识别这里,他们自己也承认做的一般。但是我觉得够用,毕竟也没有水印,基础版本全免费。Vuforia的基础班还有个大大的水印。但是EasyAR给人的感觉像是疯狂的向Unity靠拢。但是究其原因,毕竟Unity也是另一个爸爸,能在Unity用,可以让更多的游戏开发者使用这个EasyAR。

总结:

关于(Android / iOS)原生本地接EasyAR的开发者,有点难搞哦。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章