Android增強(qiáng)現(xiàn)實(shí)(三)-3D模型展示器

1.Android增強(qiáng)現(xiàn)實(shí)(一)-AR的三種方式(展示篇)
2.Android增強(qiáng)現(xiàn)實(shí)(二)-支持拖拽控制進(jìn)度和伸縮的VrGifView
3.Android增強(qiáng)現(xiàn)實(shí)(三)-3D模型展示器

前言

前段時(shí)間研究了一下增強(qiáng)現(xiàn)實(shí)在Android端的實(shí)現(xiàn)随常,目前大體分為兩種,全景立體圖(GIF和全景圖)和3D模型圖豆混。這篇博客主要講一下關(guān)于3D模型的展示方式吧映砖。

3D模型

使用方式

1.Add it in your root build.gradle at the end of repositories:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Step 2. Add the dependency

dependencies {
       compile 'com.github.sdfdzx:VRShow:v1.0.2'
}

XML and Java

<com.study.xuan.stlshow.widget.STLView
        android:id="@+id/stl"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

java

//讀取STL文件類
        STLViewBuilder
            .init(STLView stlView)
            .Reader(ISTLReader reader)
            .Byte(byte[] bytes)
            .File(File file)
            .Assets(Context context, String fileName)
            .InputStream(InputStream inputStream)
            .build();
//基礎(chǔ)使用方法
        STLViewBuilder.init(mStl).Assets(this, "bai.stl").build();
        mStl.setTouch(true);//是否可以觸摸
        mStl.setScale(true);//是否可以縮放
        mStl.setRotate(true);//是否可以拖拽
        mStl.setSensor(true);//是否支持陀螺儀
        //stl文件讀取過程中的回調(diào)
        mStl.setOnReadCallBack(new OnReadCallBack() {
            @Override
            public void onStart() {}
            @Override
            public void onReading(int cur, int total) {}
            @Override
            public void onFinish() {}
        });

技術(shù)分析

對(duì)于3D模型的渲染其實(shí)對(duì)于平常的應(yīng)用平臺(tái)其實(shí)涉及的還是比較少的往扔,在游戲平臺(tái)應(yīng)用廣泛颠悬,我無意中在京東看到過這樣的功能


京東3D

起先我平常對(duì)于這種效果接觸的比較少,還不太清楚怎么實(shí)現(xiàn)力穗,后來才了解到關(guān)于OpenGL的相關(guān)知識(shí)才了解到這種實(shí)現(xiàn)方式其實(shí)是利用OpenGL和GLSurfaceView進(jìn)行實(shí)現(xiàn)佑惠。

大概了解了實(shí)現(xiàn)可行性朋腋,我們來看一下需求:

1.支持渲染3D模型
2.支持單指拖拽
3.支持雙指縮放
4.支持陀螺儀
5.支持讀取時(shí)的異步回調(diào)

對(duì)于這個(gè)的實(shí)現(xiàn)方式首先要了解這幾個(gè)知識(shí)點(diǎn):

1.3D模型,STL文件格式
2.OpenGL相關(guān)知識(shí)
3.GLSurfaceView的使用

3D模型膜楷,STL文件格式

其實(shí)對(duì)于3D模型的渲染旭咽,這里其實(shí)要明白的就是我們要做的就是兩步:

1.3D模型數(shù)據(jù)文件->模型數(shù)據(jù)(異步讀取文件過程)
2.模型數(shù)據(jù)->模型展示(渲染展示過程)

這里只涉及STL文件格式的3D模型數(shù)據(jù),不同的文件格式赌厅,讀取文件的格式也不一樣穷绵,我目前就實(shí)現(xiàn)了STL文件格式的,那么問題來了特愿,何為STL文件仲墨,為什么要了解STL文件勾缭?
我們其實(shí)沒必要了解那么深入,這里引入百度百科的介紹其實(shí)已經(jīng)夠我們進(jìn)行了解目养;

STL是用三角網(wǎng)格來表現(xiàn)3D CAD模型的一種文件格式俩由。

可能這樣我們理解還是比較困難,那么再加一張圖


3D模型

上圖可以看到是一個(gè)由STL文件描述的貓癌蚁,就是由一個(gè)個(gè)小的三角形構(gòu)成的幻梯,所以說STL描述的就是構(gòu)成這個(gè)3D模型所用的所有的三角形的相關(guān)數(shù)據(jù)。
那么我們就需要了解一下STL文件是怎么描述三角形數(shù)據(jù)的努释。
STL文件分為兩種格式碘梢,一種是ASCII明碼格式,另一種是二進(jìn)制格式伐蒂。
ASCII明碼格式:(以下引自百度百科)

ASCII碼格式的STL文件逐行給出三角面片的幾何信息煞躬,每一行以1個(gè)或2個(gè)關(guān)鍵字開頭。
在STL文件中的三角面片的信息單元 facet 是一個(gè)帶矢量方向的三角面片逸邦,STL三維模型就是由一系列這樣的三角面片構(gòu)成恩沛。
整個(gè)STL文件的首行給出了文件路徑及文件名。
在一個(gè) STL文件中缕减,每一個(gè)facet由7 行數(shù)據(jù)組成复唤,
facet normal 是三角面片指向?qū)嶓w外部的法矢量坐標(biāo),
outer loop 說明隨后的3行數(shù)據(jù)分別是三角面片的3個(gè)頂點(diǎn)坐標(biāo)烛卧,3頂點(diǎn)沿指向?qū)嶓w外部的法矢量方向逆時(shí)針排列。

明碼://字符段意義
solidfilenamestl//文件路徑及文件名
facetnormalxyz//三角面片法向量的3個(gè)分量值
outerloop
vertexxyz//三角面片第一個(gè)頂點(diǎn)坐標(biāo)
vertexxyz//三角面片第二個(gè)頂點(diǎn)坐標(biāo)
vertexxyz//三角面片第三個(gè)頂點(diǎn)坐標(biāo)
endloop
endfacet//完成一個(gè)三角面片定義
 
......//其他facet
 
endsolidfilenamestl//整個(gè)STL文件定義結(jié)束

看到上面的介紹,其實(shí)不難發(fā)現(xiàn),其實(shí)對(duì)于ASCII碼格式的STL文件我們需要怎么讀取哪妓局?其實(shí)很簡單总放,有固定的字段表示文件的開始和結(jié)束,有固定的字段表示一個(gè)三角的開始和結(jié)束好爬,固定每個(gè)三角形由7行數(shù)據(jù)構(gòu)成局雄,固定每一行表示的含義,這所有的都是固定的存炮,一個(gè)for循環(huán)炬搭,按照文件的格式讀取即可。
二進(jìn)制格式:(以下引自百度百科)

二進(jìn)制STL文件用固定的字節(jié)數(shù)來給出三角面片的幾何信息穆桂。
文件起始的80個(gè)字節(jié)是文件頭宫盔,用于存貯文件名;
緊接著用 4 個(gè)字節(jié)的整數(shù)來描述模型的三角面片個(gè)數(shù)享完,
后面逐個(gè)給出每個(gè)三角面片的幾何信息灼芭。每個(gè)三角面片占用固定的50個(gè)字節(jié),依次是:
3個(gè)4字節(jié)浮點(diǎn)數(shù)(角面片的法矢量)
3個(gè)4字節(jié)浮點(diǎn)數(shù)(1個(gè)頂點(diǎn)的坐標(biāo))
3個(gè)4字節(jié)浮點(diǎn)數(shù)(2個(gè)頂點(diǎn)的坐標(biāo))
3個(gè)4字節(jié)浮點(diǎn)數(shù)(3個(gè)頂點(diǎn)的坐標(biāo))個(gè)
三角面片的最后2個(gè)字節(jié)用來描述三角面片的屬性信息般又。
一個(gè)完整二進(jìn)制STL文件的大小為三角形面片數(shù)乘以 50再加上84個(gè)字節(jié)彼绷。

UINT8//Header//文件頭
UINT32//Numberoftriangles//三角面片數(shù)量
//foreachtriangle(每個(gè)三角面片中)
REAL32[3]//Normalvector//法線矢量
REAL32[3]//Vertex1//頂點(diǎn)1坐標(biāo)
REAL32[3]//Vertex2//頂點(diǎn)2坐標(biāo)
REAL32[3]//Vertex3//頂點(diǎn)3坐標(biāo)
UINT16//Attributebytecountend//文件屬性統(tǒng)計(jì)

其實(shí)讀取方法和上面的相似巍佑,只不過上面的是操作文件的行,這里就是操作字節(jié)數(shù)了寄悯,可以看到每個(gè)三角面占用的字節(jié)數(shù)固定萤衰,固定的字節(jié)數(shù)內(nèi)數(shù)據(jù)依次占用固定的字節(jié)數(shù),所以還是一個(gè)for循環(huán)猜旬,按照字節(jié)的格式讀取即可脆栋。

OpenGL相關(guān)知識(shí)

OpenGL的相關(guān)知識(shí)怎么說哪,很多渲染過程中的相關(guān)api我也沒搞懂昔馋,這里只說幾個(gè)我們實(shí)現(xiàn)過程中需要了解的吧(具體網(wǎng)上資料很多筹吐,這方面我反正是個(gè)小白,就不充胖子了)秘遏。
1.glTranslatef(x,y,z)
2.glRotatef(angle,x,y,z)
3.glScalef(x,y,z)
看到字面意思就很好理解吧丘薛,平移,旋轉(zhuǎn)邦危,縮放洋侨,有api就好說了,剩下的就是我們將我們觸摸得到的量轉(zhuǎn)化成這里面的數(shù)值就行倦蚪。

GLSurfaceView的使用

GLSurfaceView是Android一個(gè)專門處理3D模型的的View希坚,他的基本用法和平常的View沒什么差異,唯一需要注意的就是需要調(diào)用setRenderer()傳入一個(gè)Renderer對(duì)象陵且。理解起來也比較容易裁僧,GLSurfaceView其實(shí)就是一個(gè)View,也就是一個(gè)展示的視圖,而控制展示的也就是Renderer對(duì)象了慕购。Renderer其實(shí)是一個(gè)接口聊疲,對(duì)應(yīng)有三個(gè)方法需要我們實(shí)現(xiàn),onSurfaceCreated對(duì)應(yīng)視圖創(chuàng)建時(shí)調(diào)用沪悲,onSurfaceChanged對(duì)應(yīng)視圖改變時(shí)調(diào)用获洲,onDrawFrame對(duì)應(yīng)視圖繪制時(shí)調(diào)用。

public interface Renderer {
        void onSurfaceCreated(GL10 gl, EGLConfig config);
        void onSurfaceChanged(GL10 gl, int width, int height);
        void onDrawFrame(GL10 gl);
    }

對(duì)應(yīng)配合上面OpenGL的相關(guān)知識(shí)殿如,其實(shí)大概的實(shí)現(xiàn)過程已經(jīng)有個(gè)雛形了贡珊。

關(guān)鍵代碼

1.讀取STL文件(這里以ASCII格式為例)
這里我是定義了一個(gè)讀取的接口ISTLReader


public interface ISTLReader {
    public STLModel parserBinStl(byte[] bytes);

    public STLModel parserAsciiStl(byte[] bytes);

    public void setCallBack(OnReadListener listener);
}

可以通過STLViewBuilder.Reader(ISTLReader reader)方法自己實(shí)現(xiàn)。
我默認(rèn)實(shí)現(xiàn)的STLReader這里只放上對(duì)于ASCII格式文件讀取的偽代碼吧涉馁。

public STLModel parserAsciiStl(byte[] bytes) {
        ...
        String stlText = new String(bytes);
        String[] stlLines = stlText.split("\n");
        vertext_size = (stlLines.length - 2) / 7;
        ...
        for (int i = 0; i < stlLines.length; i++) {
            String string = stlLines[i].trim();
            if (string.startsWith("facet normal ")) {
                string = string.replaceFirst("facet normal ", "");
                String[] normalValue = string.split(" ");
                for (int n = 0; n < 3; n++) {
                    ...
                }
            }
            if (string.startsWith("vertex ")) {
                string = string.replaceFirst("vertex ", "");
                String[] vertexValue = string.split(" ");
                ...
            }

            ...
        }
      ...
    }

這里可以看到我是將byte[]轉(zhuǎn)為了String门岔,接著就通過固定的格式來進(jìn)行讀取,偽代碼在上面烤送,便于理解讀取過程固歪,可以看到,基本的就是通過對(duì)行數(shù),startsWith牢裳,split等對(duì)字符串處理的函數(shù)進(jìn)行讀取的逢防,讀取規(guī)則其實(shí)可以仿照上面對(duì)于STL文件格式的介紹。
2.自定義Renderer渲染
自定義Renderer其實(shí)主要是對(duì)于OPenGL函數(shù)的調(diào)用蒲讯,由于我對(duì)于這塊也不是特別了解忘朝,我是在別人的基礎(chǔ)上進(jìn)行了一定的修改,里面參數(shù)的修改影響的就是渲染效果判帮。而對(duì)于我們要實(shí)現(xiàn)的關(guān)于旋轉(zhuǎn)縮放的函數(shù)其實(shí)就比較基礎(chǔ)了,這里我還加入了關(guān)于縮放范圍的控制局嘁。

        gl.glRotatef(angleX, 0, 1, 0);
        gl.glRotatef(angleY, 1, 0, 0);
        gl.glPopMatrix();

        scale_rember = scale_now * scale;
        if (scaleRange) {
            if (scale_rember > SCALE_MAX) {
                scale_rember = SCALE_MAX;
            }
            if (scale_rember < SCALE_MIN) {
                scale_rember = SCALE_MIN;
            }
        }
        gl.glScalef(scale_rember, scale_rember, scale_rember);

3.手勢監(jiān)聽
其實(shí)對(duì)于縮放和旋轉(zhuǎn)的處理和前一篇Android增強(qiáng)現(xiàn)實(shí)(二)-支持拖拽控制進(jìn)度和伸縮的VrGifView的處理大同小異,具體大家可以看前一篇博客晦墙,而對(duì)于陀螺儀的處理其實(shí)也比較簡單悦昵,只不過用的比較少所以比較陌生,步驟也不比較固定晌畅。

private void initSensor() {
        sensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
        gyroscopeSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
        sensorEventListener = new SensorEventListener() {
            @Override
            public void onSensorChanged(SensorEvent sensorEvent) {
                if (sensorEvent.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
                    if (timestamp != 0) {
                        final float dT = (sensorEvent.timestamp - timestamp) * NS2S;
                        stlRenderer.angleX += sensorEvent.values[0] * dT * 180.0f % 360.0f;
                        stlRenderer.angleY += sensorEvent.values[1] * dT * 180.0f % 360.0f;
                        stlRenderer.requestRedraw();
                        requestRender();
                    }
                    timestamp = sensorEvent.timestamp;
                }
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {

            }
        };
        sensorManager.registerListener(sensorEventListener, gyroscopeSensor, SensorManager
                .SENSOR_DELAY_GAME);
    }

總結(jié)

其實(shí)相較于前一篇的對(duì)于GIF圖的處理但指,這里技術(shù)上的考慮不是特別多,主要是對(duì)于3D文件STL格式的學(xué)習(xí)抗楔,OpenGL基礎(chǔ)知識(shí)的學(xué)習(xí)棋凳,還有陀螺儀傳感器使用的學(xué)習(xí)。

這里對(duì)于STL文件的讀取還有Renderer中OpenGL的使用參考學(xué)習(xí)了以下資料连躏,大家感興趣的可以去查看學(xué)習(xí):

1.一個(gè)不錯(cuò)的STL解析器剩岳,支持貼紋理,坐標(biāo)系等
2.Android OpenGL入門系列入热,一個(gè)不錯(cuò)的系列入門文章
3.Android OpenGL入門系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拍棕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子勺良,更是在濱河造成了極大的恐慌莫湘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件郑气,死亡現(xiàn)場離奇詭異,居然都是意外死亡腰池,警方通過查閱死者的電腦和手機(jī)尾组,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來示弓,“玉大人讳侨,你說我怎么就攤上這事∽嗍簦” “怎么了跨跨?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我勇婴,道長忱嘹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任耕渴,我火速辦了婚禮拘悦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘橱脸。我一直安慰自己础米,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布添诉。 她就那樣靜靜地躺著屁桑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栏赴。 梳的紋絲不亂的頭發(fā)上蘑斧,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音艾帐,去河邊找鬼乌叶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛柒爸,可吹牛的內(nèi)容都是我干的准浴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼捎稚,長吁一口氣:“原來是場噩夢啊……” “哼乐横!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起今野,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤葡公,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后条霜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體催什,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年宰睡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蒲凶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拆内,死狀恐怖旋圆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情麸恍,我是刑警寧澤灵巧,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響刻肄,放射性物質(zhì)發(fā)生泄漏瓤球。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一肄方、第九天 我趴在偏房一處隱蔽的房頂上張望冰垄。 院中可真熱鬧,春花似錦权她、人聲如沸虹茶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蝴罪。三九已至,卻和暖如春步清,著一層夾襖步出監(jiān)牢的瞬間要门,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國打工廓啊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欢搜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓谴轮,卻偏偏與公主長得像炒瘟,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子第步,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345