該原創文章首發於微信公衆號:字節流動
OpenGLES 3D 模型
OpenGLES 3D 模型本質上是由一系列三角形在 3D 空間(OpenGL 座標系)中構建而成,另外還包含了用於描述三角形表面的紋理、光照、材質等信息。
構建一些規則的 3D 物體,如立方體、球體、椎體等,我們自己可以手動輕易實現,但是在實際開發中往往會用到複雜的 3D 物體,如人體、汽車等,這就需要設計師和專業的建模工具軟件(像 3DS Max、Maya )來生成。
利用 3D 建模軟件,設計師可以構建一些複雜的形狀,並將貼圖應用到形狀上去,不需要去關注圖像技術細節。最後在導出模型文件時,建模工具會自己生成所有的頂點座標、頂點法線和紋理座標。
常用的模型文件格式有 .obj、.max、.fbx .3ds 等,其中 .obj 是 Wavefront 科技開發的一種幾何體圖形文件格式,包含每個頂點的位置、紋理座標、法線,以及組成面(多邊形)的頂點列表等數據,應用較爲廣泛。
OBJ 文件的結構
本文主要介紹 obj 3D 模型文件及其附屬文件 mtl ,下面是 obj 模型文件的數據結構(爲了方便展示部分數據被略過)。
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2019 guruware
# File Created: 25.01.2019 02:22:51
mtllib earth.mtl
v -4.4189 340.8998 -4.2436
v -4.4189 339.2582 -37.6576
v -7.6941 339.2582 -37.4967
v -10.9377 339.2582 -37.0156
v -14.1185 339.2582 -36.2188
v -17.2059 339.2582 -35.1141
...
# 1986 vertices
vn -0.0000 1.0000 0.0000
vn 0.0000 0.9950 -0.0995
vn -0.0098 0.9950 -0.0991
vn -0.0194 0.9950 -0.0976
vn -0.0289 0.9950 -0.0952
vn -0.0381 0.9950 -0.0920
vn -0.0469 0.9950 -0.0878
...
# 1986 vertex normals
vt 0.0000 1.0000 0.0000
vt 0.0000 0.9688 0.0000
vt 0.0156 0.9688 0.0000
vt 0.0156 1.0000 0.0000
vt 0.0313 0.9688 0.0000
vt 0.0313 1.0000 0.0000
...
# 2143 texture coords
usemtl 01___Default
s 1
f 1/1/1 2/2/2 3/3/3
f 1/4/1 3/3/3 4/5/4
f 1/6/1 4/5/4 5/7/5
f 1/8/1 5/7/5 6/9/6
f 1/10/1 6/9/6 7/11/7
...
OBJ 文件數據結構的簡單說明:
#
開頭的行表示註釋行;mtllib
表示指定該 OBJ 文件所使用的 mtl 文件(材質文件);v
開頭的行表示存放的是頂點座標,後面三個數分別表示一個頂點的(x,y,z)座標值;vn
開頭的行表示存放的是頂點法向量,後面三個數分別表示一個頂點法向量的三維(x,y,z)分量值;vt
開頭的行表示存放的是紋理座標,後面三個數分別表示一個紋理座標的(s,t,p)分量值,其中 p 分量一般用於 3D 紋理;usemtl 01___Default
表示使用指定 mtl 文件中名爲01___Default
的材質;s 1
表示開啓平滑渲染;f
開頭的行表示存放的是一個三角面的信息,後面有三組數據分別表示組成三角面的三個頂點的信息,每個頂點信息的格式爲:頂點位置索引/紋理座標索引/法向量索引。
mtl 文件的結構:
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2019 guruware
# File Created: 25.01.2019 02:22:51
newmtl 01___Default
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 0.0000 0.0000 0.0000
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka 4096_earth.jpg
map_Kd 4096_earth.jpg
map_Ke 4096_night_lights.jpg
map_bump 4096_bump.jpg
bump 4096_bump.jpg
newmtl 02___Default
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5882 0.5882 0.5882
Kd 0.5882 0.5882 0.5882
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000
map_Ka 4096_clouds.jpg
map_Kd 4096_clouds.jpg
map_d 4096_clouds.jpg
mtl 文件結構說明:
newmtl 01___Default
表示定義一個名爲01___Default
的材質;Ns
表示材質的反射指數,反射指數越高則高光越密集,取值範圍在一般爲 [0,1000];Ni
表示材質的折射值(折射率),一般取值範圍是 [0.001,10] ,取值爲 1.0,表示光在通過物體的時候不發生彎曲,玻璃的折射率爲 1.5 ;d
表示材質的漸隱指數(通透指數),取值爲 1.0 表示完全不透明,取值爲 0.0 時表示完全透明;Tr
表示材質的透明度(與d
的取值相反),默認值爲0.0(完全不透明);Tf
表示材質的濾光折射率,三維向量表示;illum
表示材質的光照模型;Ka
表示材質的環境光(Ambient Color)(r,g,b);Kd
表示材質的散射光(Diffuse Color)(r,g,b);Ks
表示材質的鏡面光(Apecular Color)(r,g,b);Ke
表示材質的發射光,它與環境光,散射光和鏡面光並存,代表材質發出的光量;map_Ka
表示爲材質的環境反射指定紋理文件(紋理採樣值與環境光相乘作爲輸出顏色的一部分加權);map_Kd
表示爲材質的漫反射指定紋理文件;map_Ke
表示爲材質的發射光指定紋理文件;map_d
表示爲材質的透明度指定紋理文件;bump
表示指定材質的凹凸紋理文件,凹凸紋理修改表面法線,用於凹凸紋理的圖像表示相對於平均表面的表面拓撲或高度(沒用過)。
模型加載庫 Assimp
Assimp 全稱爲 Open Asset Import Library,可以支持幾十種不同格式的模型文件的解析(同樣也可以導出部分模型格式),Assimp 本身是 C++ 庫,可以跨平臺使用。
Assimp 可以將幾十種模型文件都轉換爲一個統一的數據結構,所有無論我們導入何種格式的模型文件,都可以用同一個方式去訪問我們需要的模型數據。
當導入一個模型文件時,Assimp 將加載該模型文件所包含的所有模型和場景數據到一個 scene 對象,爲這個模型文件中的所有場景節點、模型節點都生成一個具有對應關係的數據結構,如下圖所示:
一個模型往往是由很多小模型組成,這些小模型在 Assimp 中稱之爲 Mesh ,Mesh 進行獨立渲染,Mesh 對象本身包含渲染所需的所有相關數據,比如頂點位置、法向量、紋理座標以及物體的材質。
編譯模型加載庫 Assimp
Assimp 源代碼地址:https://github.com/assimp/assimp
環境準備:
Windows 7
Android Studio 版本> 2.3.0 (帶有 NDK 和 CMake)
Python 3.5
- 從 Github 下載 Assimp(本文使用的是 assimp-v.5.0.0) 源碼並解壓到一個新建文件夾 BuildAssimp 中;
- 在 BuildAssimp 文件夾新建一個 make_standalone_toolchain.bat 文件用於創建編譯所需的工具鏈,該文件內容如下:
python D:/AndroidSDK/Sdk/ndk-bundle/build/tools/make_standalone_toolchain.py --arch=arm --stl=libc++ --api=24 --install-dir=android-toolchain-24-llvm-arm
D:/AndroidSDK/Sdk/ndk-bundle/build/tools/make_standalone_toolchain.py
需要換成你本地的 Android SDK 中的工具鏈構建腳本。
雙擊腳本,執行完成後,會在當前目錄下(BuildAssimp 文件夾中)生成一個新的文件夾 android-toolchain-24-llvm-arm ,它是我們用於編譯 32 位的 assimp 庫用到的工具鏈。
- 在 BuildAssimp 文件夾中新建一個 build_assimp.bat 文件,用於編譯生成 assimp.so 文件,其內容如下:
@echo off
cls
REM *NOTE* Change these based on
SET ASSIMP_DIR=assimp-v.5.0.0
SET OUTPUT_DIR=assimp-build-arm
SET ANDROID_PATH=D:\AndroidSDK\Sdk
SET NDK_PATH=D:\AndroidSDK\Sdk\ndk-bundle
SET NDK_TOOLCHAIN=%~dp0android-toolchain-24-llvm-arm
SET CMAKE_TOOLCHAIN=%NDK_PATH%\build\cmake\android.toolchain.cmake
SET CMAKE_PATH=%ANDROID_PATH%\cmake\3.6.4111459
REM *NOTE* Careful if you don't want rm -rf, I use it for testing purposes.
del /F /S /Q %OUTPUT_DIR%
mkdir %OUTPUT_DIR%
REM pushd doesn't seem to work ):<
cd %OUTPUT_DIR%
if not defined ORIGPATH set ORIGPATH=%PATH%
SET PATH=%CMAKE_PATH%\bin;%ANDROID_PATH%\tools;%ANDROID_PATH%\platform-tools;%ORIGPATH%
cmake ^
-GNinja ^
-DCMAKE_TOOLCHAIN_FILE=%CMAKE_TOOLCHAIN% ^
-DASSIMP_ANDROID_JNIIOSYSTEM=ON ^
-DANDROID_NDK=%NDK_PATH% ^
-DCMAKE_MAKE_PROGRAM=%CMAKE_PATH%\bin\ninja.exe ^
-DCMAKE_BUILD_TYPE=Release ^
-DANDROID_ABI="armeabi-v7a" ^
-DANDROID_NATIVE_API_LEVEL=24 ^
-DANDROID_FORCE_ARM_BUILD=TRUE ^
-DCMAKE_INSTALL_PREFIX=install ^
-DANDROID_STL=c++_static ^
-DCMAKE_CXX_FLAGS=-Wno-c++11-narrowing ^
-DANDROID_TOOLCHAIN=clang ^
-DASSIMP_BUILD_TESTS=OFF ^
../%ASSIMP_DIR%
cmake --build .
cd ..
pause
這裏需要配置你的 Android SDK 和 NDK path 。
雙擊腳本,執行完成後,會在 BuildAssimp 文件夾下生成一個新的文件夾 android-toolchain-24-llvm-arm ,編譯通過後會在 android-toolchain-24-llvm-arm/code/ 下生成 32 位的 assimp.so 文件。
下一節中將會使用 assimp.so 去加載 obj 模型文件,然後利用 OpenGL ES 渲染模型。