部落格原文連結:https://glumes.com/post/opengl/opengl-tutorial-import-3d-object/
在使用 OpenGL 繪製時,我們最多繪製的是一些簡單的圖形,比如三角形、圓形、立方體等,因為這些圖形的頂點數量不多,還是可以手動的寫出那些頂點的,可要是繪製一些複雜圖形該怎麼辦呢?
這時候就可以使用 OpenGL 來載入 3D 模型。先使用 3D 建模工具構建物體,然後再將物體匯出成特定的檔案格式,最終通過 OpenGL 渲染模型。
例如如下的 3D 模型檔案影象:
Obj 模型檔案
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” 開頭的行用於存放頂點座標,後面三個數表示一個頂點的 x , y , z 座標 如:
v -0.052045 11.934561 -0.071060
複製程式碼
- "vt" 開頭的行表示存放頂點紋理座標,後面三個數表示紋理座標的 S,T,P 分量,其中 P 指的是深度紋理取樣,主要用於 3D 紋理的取樣,但使用的較少 如:
vt 0.000000 0.000000 0.000000
複製程式碼
- "vn" 開頭的行用於存放頂點法向量,後面三個數分別表示一個頂點的法向量在 x 軸,y 軸,z 軸上的分量。 如:
vn 0.000000 0.000000 1.570796
複製程式碼
- “g” 開頭的行表示一組的開始,後面的字串為此組的名稱。組就是由頂點組成的一些面的集合,只包含 “g” 的行表示一組的結束,與 “g” 開頭的行對應。
- "f" 開頭的行表示組中的一個面,對於三角形圖形,後面有三組用空格分隔的資料,代表三角形的三個頂點。每組資料中包含 3 個數值,用
/
分隔,依次表示頂點座標資料索引、頂點紋理座標資料索引、頂點法向量資料索引,注意這裡都是指索引,而不是指具體資料,索引指向的是具體哪一行對應的座標 如:
f 1/10/1 14/12/11 13/4/12
複製程式碼
如上資料代表了三個頂點,其中三角形 3 個頂點座標來自 1、14、13 號以 "v" 開頭的行, 3 個頂點的紋理座標來自 10、12、4 號以 “vt” 開頭的行,3 個頂點的法向量來自 1、11、12 號以 “vn” 開頭的行。
如果頂點座標沒有法向量和紋理座標,那麼直接可以忽略,用空格將三個頂點座標索引分開就行
f 1 3 4
複製程式碼
最後 OpenGL 在繪製時採用的是 GL_TRIANGLES
,也就是由 ABCDEF 六個點繪製 ABC、DEF 兩個三角形,所以 "f" 開頭的行都代表繪製一個獨立的三角形,最終影象由一個一個三角形拼接組成,並且彼此的點可以分開。
載入 Obj 模型檔案
明白了 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~~
歡迎關注微信公眾號:【紙上淺談】,獲得最新文章推送~~