細數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的開發者,有點難搞哦。

 

 

 

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