博客原文連接:https://glumes.com/post/opengl/opengl-tutorial-import-3d-object/java
在使用 OpenGL 繪製時,咱們最多繪製的是一些簡單的圖形,好比三角形、圓形、立方體等,由於這些圖形的頂點數量很少,仍是能夠手動的寫出那些頂點的,可要是繪製一些複雜圖形該怎麼辦呢?git
這時候就可使用 OpenGL 來加載 3D 模型。先使用 3D 建模工具構建物體,而後再將物體導出成特定的文件格式,最終經過 OpenGL 渲染模型。github
例如以下的 3D 模型文件圖像:正則表達式
obj 模型文件是衆多 3D 模型文件中的一種,它的格式比較簡單,本質上就是文本文件,只是格式固定了格式。數組
obj 文件將頂點座標、三角形面、紋理座標等信息以固定格式的文本字符串表示。微信
截取一小段 obj 文件內容:函數
# Max2Obj Version 4.0 Mar 10th, 2001
#
# object (null) to come ...
#
v -0.052045 11.934561 -0.071060
v -0.052045 11.728649 1.039199
...
# 288 vertices
vt 0.000000 0.000000 0.000000
vt 1.000000 0.000000 0.000000
...
vt 1.000000 1.000000 0.000000
# 122 texture vertices
vn 0.000000 0.000000 -1.570796
vn 0.000000 0.000000 -1.570796
...
vn 0.000000 0.000000 1.570796
# 8 vertex normals
g (null)
f 1/10/1 14/12/11 13/4/11
f 1/11/4 2/12/3 14/12/11
...
f 5/4/5 7/3/7 3/1/3
# 576 faces
g
複製代碼
v -0.052045 11.934561 -0.071060
複製代碼
vt 0.000000 0.000000 0.000000
複製代碼
vn 0.000000 0.000000 1.570796
複製代碼
/
分隔,依次表示頂點座標數據索引、頂點紋理座標數據索引、頂點法向量數據索引,注意這裏都是指索引,而不是指具體數據,索引指向的是具體哪一行對應的座標 如:f 1/10/1 14/12/11 13/4/12
複製代碼
如上數據表明了三個頂點,其中三角形 3 個頂點座標來自 一、1四、13 號以 "v" 開頭的行, 3 個頂點的紋理座標來自 十、十二、4 號以 「vt」 開頭的行,3 個頂點的法向量來自 一、十一、12 號以 「vn」 開頭的行。工具
若是頂點座標沒有法向量和紋理座標,那麼直接能夠忽略,用空格將三個頂點座標索引分開就行post
f 1 3 4
複製代碼
最後 OpenGL 在繪製時採用的是 GL_TRIANGLES
,也就是由 ABCDEF 六個點繪製 ABC、DEF 兩個三角形,因此 "f" 開頭的行都表明繪製一個獨立的三角形,最終圖像由一個一個三角形拼接組成,而且彼此的點能夠分開。ui
明白了 Obj 模型文件表明的含義,接下來把它加載並用 OpenGL 進行渲染。
Obj 模型文件實質上也就是文本文件了,經過讀取每一行來進行加載便可,假設加載的模型文件只有頂點座標,實際代碼以下:
// 加載全部的頂點座標數據,把 List 容器的 index 當成 索引
ArrayList<Float> alv = new ArrayList<>();
// 表明繪製圖像的每個小三角形的座標
ArrayList<Float> alvResult = new ArrayList<>();
// 最終要傳入給 OpenGL 的數組
float[] vXYZ;
try {
InputStream in = context.getResources().getAssets().open(fname);
InputStreamReader isr = new InputStreamReader(in);
BufferedReader br = new BufferedReader(isr);
String temps = null;
// 遍歷每一行來讀取內容
while ((temps = br.readLine()) != null) {
// 正則表達式 用空格分開
String[] tempsa = temps.split("[ ]+");
// 先把全部的頂點座標加入到 List 中,這樣就有了索引
if (tempsa[0].trim().equals("v")) {
alv.add(Float.parseFloat(tempsa[1]));
alv.add(Float.parseFloat(tempsa[2]));
alv.add(Float.parseFloat(tempsa[3]));
} else if (tempsa[0].trim().equals("f")) {
// 根據 f 指示的索引,找到對應的頂點座標,
// 這裏 -1 的操做是由於 List 從 0 開始,f 開頭的行的索引從 1 開始
// *3 是由於要跳過 3 的倍數個頂點
int index = Integer.parseInt(tempsa[1].split("/")[0]) - 1;
alvResult.add(alv.get(3 * index));
alvResult.add(alv.get(3 * index + 1));
alvResult.add(alv.get(3 * index + 2));
index = Integer.parseInt(tempsa[2].split("/")[0]) - 1;
alvResult.add(alv.get(3 * index));
alvResult.add(alv.get(3 * index + 1));
alvResult.add(alv.get(3 * index + 2));
index = Integer.parseInt(tempsa[3].split("/")[0]) - 1;
alvResult.add(alv.get((3 * index)));
alvResult.add(alv.get((3 * index + 1)));
alvResult.add(alv.get((3 * index + 2)));
}
}
// 把面的座標轉換爲最終要傳遞給 OpenGL 的數組
// 根據這個數組,而後按照 GL_TRIANGLES 方式進行繪製
int size = alvResult.size();
vXYZ = new float[size];
for (int i = 0; i < size; i++) {
vXYZ[i] = alvResult.get(i);
}
return vXYZ;
} catch (IOException e) {
return null;
}
複製代碼
經過上面的函數就計算出了最終的頂點座標位置,並將此頂點座標位置傳入給 GPU ,經過 FloatBuffer 進行轉換等等,這就和以前的文章內容相同了。
若是隻是單純的導入了全部頂點,並決定了要繪製的顏色,就會出現相似上面的單一顏色的繪製狀況,事實上能夠經過修改片斷着色器來給 3D 模型添加條紋着色效果。
經過修改片斷着色器來給 3D 形狀添加條紋着色效果。
precision mediump float;
varying vec3 vPosition; //頂點位置
void main() {
vec4 bColor=vec4(0.678,0.231,0.129,0);//條紋的顏色
vec4 mColor=vec4(0.763,0.657,0.614,0);//間隔的顏色
float y=vPosition.y;
y=mod((y+100.0)*4.0,4.0);
if(y>1.8) {
gl_FragColor = bColor;//給此片元顏色值
} else {
gl_FragColor = mColor;//給此片元顏色值
}
// 默認使用單一顏色進行繪製
// vec4 white = vec4(1,1,1,1);
// gl_FragColor = white;
}
複製代碼
實現的方式也是根據片斷的 y 座標所在位置來決定該片斷是採樣條紋的顏色仍是間隔的顏色。
最後,加載 3D 模型就先了解到這了,若是想要加載更多效果,卻是能夠繼續深挖,只是沒有 MAC 版本的 3ds Max 軟件,倒是少了一些樂趣~~
具體代碼詳情,能夠參考個人 Github 項目,求一波 star~~
歡迎關注微信公衆號:【紙上淺談】,得到最新文章推送~~