1.Android增強現實(一)-AR的三種方式(展示篇)
2.Android增強現實(二)-支持拖拽控制進度和伸縮的VrGifView
3.Android增強現實(三)-3D模型展示器
前言
前段時間研究了一下增強現實在Android端的實現,目前大體分為兩種,全景立體圖(GIF和全景圖)和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();
//基礎使用方法
STLViewBuilder.init(mStl).Assets(this, "bai.stl").build();
mStl.setTouch(true);//是否可以觸摸
mStl.setScale(true);//是否可以縮放
mStl.setRotate(true);//是否可以拖拽
mStl.setSensor(true);//是否支持陀螺儀
//stl文件讀取過程中的回調
mStl.setOnReadCallBack(new OnReadCallBack() {
@Override
public void onStart() {}
@Override
public void onReading(int cur, int total) {}
@Override
public void onFinish() {}
});
技術分析
對于3D模型的渲染其實對于平常的應用平臺其實涉及的還是比較少的,在游戲平臺應用廣泛,我無意中在京東看到過這樣的功能
起先我平常對于這種效果接觸的比較少,還不太清楚怎么實現,后來才了解到關于OpenGL的相關知識才了解到這種實現方式其實是利用OpenGL和GLSurfaceView進行實現。
大概了解了實現可行性,我們來看一下需求:
1.支持渲染3D模型
2.支持單指拖拽
3.支持雙指縮放
4.支持陀螺儀
5.支持讀取時的異步回調
對于這個的實現方式首先要了解這幾個知識點:
1.3D模型,STL文件格式
2.OpenGL相關知識
3.GLSurfaceView的使用
3D模型,STL文件格式
其實對于3D模型的渲染,這里其實要明白的就是我們要做的就是兩步:
1.3D模型數據文件->模型數據(異步讀取文件過程)
2.模型數據->模型展示(渲染展示過程)
這里只涉及STL文件格式的3D模型數據,不同的文件格式,讀取文件的格式也不一樣,我目前就實現了STL文件格式的,那么問題來了,何為STL文件,為什么要了解STL文件?
我們其實沒必要了解那么深入,這里引入百度百科的介紹其實已經夠我們進行了解;
STL是用三角網格來表現3D CAD模型的一種文件格式。
可能這樣我們理解還是比較困難,那么再加一張圖
上圖可以看到是一個由STL文件描述的貓,就是由一個個小的三角形構成的,所以說STL描述的就是構成這個3D模型所用的所有的三角形的相關數據。
那么我們就需要了解一下STL文件是怎么描述三角形數據的。
STL文件分為兩種格式,一種是ASCII明碼格式,另一種是二進制格式。
ASCII明碼格式:(以下引自百度百科)
ASCII碼格式的STL文件逐行給出三角面片的幾何信息,每一行以1個或2個關鍵字開頭。
在STL文件中的三角面片的信息單元 facet 是一個帶矢量方向的三角面片,STL三維模型就是由一系列這樣的三角面片構成。
整個STL文件的首行給出了文件路徑及文件名。
在一個 STL文件中,每一個facet由7 行數據組成,
facet normal 是三角面片指向實體外部的法矢量坐標,
outer loop 說明隨后的3行數據分別是三角面片的3個頂點坐標,3頂點沿指向實體外部的法矢量方向逆時針排列。
明碼://字符段意義
solidfilenamestl//文件路徑及文件名
facetnormalxyz//三角面片法向量的3個分量值
outerloop
vertexxyz//三角面片第一個頂點坐標
vertexxyz//三角面片第二個頂點坐標
vertexxyz//三角面片第三個頂點坐標
endloop
endfacet//完成一個三角面片定義
......//其他facet
endsolidfilenamestl//整個STL文件定義結束
看到上面的介紹,其實不難發現,其實對于ASCII碼格式的STL文件我們需要怎么讀取哪?其實很簡單,有固定的字段表示文件的開始和結束,有固定的字段表示一個三角的開始和結束,固定每個三角形由7行數據構成,固定每一行表示的含義,這所有的都是固定的,一個for循環,按照文件的格式讀取即可。
二進制格式:(以下引自百度百科)
二進制STL文件用固定的字節數來給出三角面片的幾何信息。
文件起始的80個字節是文件頭,用于存貯文件名;
緊接著用 4 個字節的整數來描述模型的三角面片個數,
后面逐個給出每個三角面片的幾何信息。每個三角面片占用固定的50個字節,依次是:
3個4字節浮點數(角面片的法矢量)
3個4字節浮點數(1個頂點的坐標)
3個4字節浮點數(2個頂點的坐標)
3個4字節浮點數(3個頂點的坐標)個
三角面片的最后2個字節用來描述三角面片的屬性信息。
一個完整二進制STL文件的大小為三角形面片數乘以 50再加上84個字節。
UINT8//Header//文件頭
UINT32//Numberoftriangles//三角面片數量
//foreachtriangle(每個三角面片中)
REAL32[3]//Normalvector//法線矢量
REAL32[3]//Vertex1//頂點1坐標
REAL32[3]//Vertex2//頂點2坐標
REAL32[3]//Vertex3//頂點3坐標
UINT16//Attributebytecountend//文件屬性統計
其實讀取方法和上面的相似,只不過上面的是操作文件的行,這里就是操作字節數了,可以看到每個三角面占用的字節數固定,固定的字節數內數據依次占用固定的字節數,所以還是一個for循環,按照字節的格式讀取即可。
OpenGL相關知識
OpenGL的相關知識怎么說哪,很多渲染過程中的相關api我也沒搞懂,這里只說幾個我們實現過程中需要了解的吧(具體網上資料很多,這方面我反正是個小白,就不充胖子了)。
1.glTranslatef(x,y,z)
2.glRotatef(angle,x,y,z)
3.glScalef(x,y,z)
看到字面意思就很好理解吧,平移,旋轉,縮放,有api就好說了,剩下的就是我們將我們觸摸得到的量轉化成這里面的數值就行。
GLSurfaceView的使用
GLSurfaceView是Android一個專門處理3D模型的的View,他的基本用法和平常的View沒什么差異,唯一需要注意的就是需要調用setRenderer()
傳入一個Renderer
對象。理解起來也比較容易,GLSurfaceView其實就是一個View,也就是一個展示的視圖,而控制展示的也就是Renderer
對象了。Renderer其實是一個接口,對應有三個方法需要我們實現,onSurfaceCreated對應視圖創建時調用,onSurfaceChanged對應視圖改變時調用,onDrawFrame對應視圖繪制時調用。
public interface Renderer {
void onSurfaceCreated(GL10 gl, EGLConfig config);
void onSurfaceChanged(GL10 gl, int width, int height);
void onDrawFrame(GL10 gl);
}
對應配合上面OpenGL的相關知識,其實大概的實現過程已經有個雛形了。
關鍵代碼
1.讀取STL文件(這里以ASCII格式為例)
這里我是定義了一個讀取的接口ISTLReader
public interface ISTLReader {
public STLModel parserBinStl(byte[] bytes);
public STLModel parserAsciiStl(byte[] bytes);
public void setCallBack(OnReadListener listener);
}
可以通過STLViewBuilder.Reader(ISTLReader reader)方法自己實現。
我默認實現的STLReader這里只放上對于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[]轉為了String,接著就通過固定的格式來進行讀取,偽代碼在上面,便于理解讀取過程,可以看到,基本的就是通過對行數,startsWith,split等對字符串處理的函數進行讀取的,讀取規則其實可以仿照上面對于STL文件格式的介紹。
2.自定義Renderer渲染
自定義Renderer其實主要是對于OPenGL函數的調用,由于我對于這塊也不是特別了解,我是在別人的基礎上進行了一定的修改,里面參數的修改影響的就是渲染效果。而對于我們要實現的關于旋轉縮放的函數其實就比較基礎了,這里我還加入了關于縮放范圍的控制。
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.手勢監聽
其實對于縮放和旋轉的處理和前一篇Android增強現實(二)-支持拖拽控制進度和伸縮的VrGifView的處理大同小異,具體大家可以看前一篇博客,而對于陀螺儀的處理其實也比較簡單,只不過用的比較少所以比較陌生,步驟也不比較固定。
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);
}
總結
其實相較于前一篇的對于GIF圖的處理,這里技術上的考慮不是特別多,主要是對于3D文件STL格式的學習,OpenGL基礎知識的學習,還有陀螺儀傳感器使用的學習。
這里對于STL文件的讀取還有Renderer中OpenGL的使用參考學習了以下資料,大家感興趣的可以去查看學習:
1.一個不錯的STL解析器,支持貼紋理,坐標系等
2.Android OpenGL入門系列,一個不錯的系列入門文章
3.Android OpenGL入門系列