1 相關(guān)基礎(chǔ)
1.1 貼紋理原理簡單概述
其實貼紋理的原理非常簡單杯矩,就是給每個三角形貼上圖片即可。那么如何給三角形貼圖片呢袖外?我們知道史隆,既然是給三角形貼圖片曼验,那肯定就需要一張圖泌射,然后在這張圖片上指定三角形的三個頂點(diǎn)對應(yīng)這張圖的位置。這樣就可以準(zhǔn)確的為這個三角形貼好圖片了鬓照。
值得注意的是熔酷,三角形的三個頂點(diǎn)在圖片上的位置取值范圍為[0,1]。即以相對圖片的寬高比例來計算的豺裆。
在加載紋理圖片時拒秘,OpenGL為每張圖片分配好ID,將圖片緩存起來臭猜。在貼圖時躺酒,通過ID來查找圖片。
1.2 相關(guān)API
跟繪制三角形類似蔑歌,如果需要開啟貼紋理功能需要如下代碼:
gl.glEnable(GL10.GL_TEXTURE_2D);
對應(yīng)的關(guān)閉為:
gl.glDisable(GL10.GL_TEXTURE_2D);
前面1.1節(jié)中阴颖,我們提到:在加載紋理圖片時,OpenGL為每張圖片分配好ID丐膝,將圖片緩存起來。在貼圖時钾菊,通過ID來查找圖片帅矗。因此,在我們開始貼圖之前煞烫,需要為當(dāng)前模型綁定好紋理圖片的ID:
//根據(jù)ID綁定對應(yīng)的紋理
gl.glBindTexture(GL10.GL_TEXTURE_2D, model.getTextureIds()[0]);
上面代碼中浑此,我們看到,在Model實體類中滞详,通過getTextureIds函數(shù)來獲取ID數(shù)組凛俱,并取出數(shù)組的第一個數(shù)據(jù)紊馏。而Model類是我們自己自定義的實體類,顯然不可能在我們的自定義的實體類中“無中生有”出一個ID數(shù)組蒲犬。那么這個ID數(shù)組從哪里來朱监?
注意,紋理的ID是保存在一個int[]數(shù)組中原叮,數(shù)組的第一個元素即為ID赫编。
關(guān)于紋理對應(yīng)的ID,后面詳細(xì)說奋隶。我們繼續(xù)往下走擂送,在拿到紋理ID的情況下,如何繪制唯欣。首先你需要啟用紋理坐標(biāo)數(shù)組:
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
然后在繪制三角形之前嘹吨,將紋理坐標(biāo)數(shù)據(jù)設(shè)定好:
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, model.getTextureBuffer());
此時即可繪制紋理。當(dāng)然了境氢,我們現(xiàn)在只是大致講講蟀拷,更完整更詳細(xì)的內(nèi)容第二節(jié)談~。
1.3 pxy文件格式
由于我采用的模型的紋理坐標(biāo)數(shù)據(jù)是pxy格式产还。pxy格式里面保存的是浮點(diǎn)數(shù)的集合匹厘,即每4個字節(jié)為一個數(shù)據(jù)。每2個浮點(diǎn)數(shù)表示一個坐標(biāo)點(diǎn)脐区,每三個坐標(biāo)點(diǎn)對應(yīng)一個三角形在圖片中的紋理區(qū)域愈诚。
1.4 多個模型數(shù)據(jù)
為什么要提多個模型數(shù)據(jù)呢?我們知道牛隅,三維模型中炕柔,自然是包含各個角度的映像圖片。一張圖片往往很難包含整個模型的紋理信息媒佣。因此匕累,我們將一個3D模型分割成多個3D模型,每個模型對應(yīng)一張紋理圖片默伍。理論上說欢嘿,3張圖片即可包含整個模型紋理信息了,即也糊,將一個模型分割成3個3D模型炼蹦。當(dāng)然了,分割的越多狸剃,紋理圖片的構(gòu)造就越簡單(你也可以以一張360°全景紋理圖片掐隐,但是構(gòu)造這樣的圖片成本比較高)。
可能你會說钞馁,我們該如何分割模型虑省?每個模型的坐標(biāo)位置信息我們無法給它分割匿刮,因為坐標(biāo)位置數(shù)據(jù)是一個數(shù)組,是按照三角形頂點(diǎn)的順序指定的探颈。
請注意一點(diǎn)熟丸,我們只負(fù)責(zé)顯示模型,我們不管如何分割膝擂,分割這塊丟個模型的設(shè)計者虑啤!因為設(shè)計者可以通過相關(guān)的3D設(shè)計軟件輕松的分割。我們只需關(guān)注架馋,如何同時顯示多個3D模型狞山,并為每個3D模型貼好對應(yīng)的紋理即可。
2 代碼編寫
2.1 解析pxy文件
前面我們大致介紹了pxy文件格式叉寂,我們知道萍启,pxy保存的就是當(dāng)前stl文件中三角形頂點(diǎn)在紋理圖片上對應(yīng)的坐標(biāo)。每個頂點(diǎn)占2個浮點(diǎn)數(shù)(對應(yīng)x屏鳍、y)勘纯。那么我們的解析就非常簡單了,在STLReader類中钓瞭,添加如下函數(shù):
private void parseTexture(Model model, byte[] textureBytes) {
int facetCount = model.getFacetCount();
// 三角面?zhèn)€數(shù)有三個頂點(diǎn)驳遵,一個頂點(diǎn)對應(yīng)紋理二維坐標(biāo)
float[] textures = new float[facetCount * 3 * 2];
int textureOffset = 0;
for (int i = 0; i < facetCount * 3; i++) {
//第i個頂點(diǎn)對應(yīng)的紋理坐標(biāo)
//tx和ty的取值范圍為[0,1],表示的坐標(biāo)位置是在紋理圖片上的對應(yīng)比例
float tx = Util.byte4ToFloat(textureBytes, textureOffset);
float ty = Util.byte4ToFloat(textureBytes, textureOffset + 4);
textures[i * 2] = tx;
//我們的pxy文件原點(diǎn)是在左下角,因此需要用1減去y坐標(biāo)值
textures[i * 2 + 1] = 1 - ty;
textureOffset += 8;
}
model.setTextures(textures);
}
同時山涡,我們需要在Model類中添加紋理相關(guān)數(shù)據(jù)屬性堤结,并且添加對應(yīng)的setter、getter函數(shù)鸭丛。代碼我就不貼出來了竞穷,后面我會上傳源碼。
現(xiàn)在我們編寫好了解析pxy紋理坐標(biāo)數(shù)據(jù)鳞溉,接下來就是把解析stl文件和pxy文件整合在一起的函數(shù)瘾带,在STLReader中添加:
public Model parseStlWithTexture(InputStream stlInput, InputStream textureInput) throws IOException {
Model model = parseBinStl(stlInput);
int facetCount = model.getFacetCount();
// 三角面片有3個頂點(diǎn),一個頂點(diǎn)有2個坐標(biāo)軸數(shù)據(jù)熟菲,每個坐標(biāo)軸數(shù)據(jù)是float類型(4字節(jié))
byte[] textureBytes = new byte[facetCount * 3 * 2 * 4];
textureInput.read(textureBytes);// 將所有紋理坐標(biāo)讀出來
parseTexture(model, textureBytes);
return model;
}
此時看政,我們的STLReader類就可以通過parseStlWithTexture函數(shù)完美的將stl和pxy數(shù)據(jù)封裝到Model對象中了。
2.2 加載紋理圖片
前面我們提到了抄罕,OpenGL為每張紋理圖片生成一個ID帽衙。接下來我們看看如何將一個紋理圖片加載到OpenGL通道中,并且分配一個ID贞绵。首先,我們需要讀取紋理圖片恍飘,生成Bitmap對象榨崩。然后調(diào)用glGenTextures函數(shù)谴垫,生成ID,并將此ID保存到Model對象中母蛛。此時翩剪,我們已經(jīng)拿到了ID,但是這個ID并沒有綁定Bitmap對象彩郊。在將ID和Bitmap綁定之前前弯,需要調(diào)用glBindTexture函數(shù),將生成的ID綁定到紋理通道秫逝,并且通過glTexParameterf設(shè)定當(dāng)前綁定紋理的相關(guān)屬性恕出。 最后通過GLUtils.texImage2D函數(shù)將Bitmap對象與當(dāng)前紋理通道綁定,而當(dāng)前紋理通道已經(jīng)綁定好了ID违帆,從而達(dá)到了ID與紋理的間接綁定浙巫。以后使用紋理時,就可以直接通過ID來訪問刷后,無需直接訪問Bitmap對象了
的畴。
private void loadTexture(GL10 gl, Model model, boolean isAssets) {
Log.d("GLRenderer", "綁定紋理:" + model.getPictureName());
Bitmap bitmap = null;
try {
// 打開圖片資源
if (isAssets) {//如果是從assets中讀取
bitmap = BitmapFactory.decodeStream(context.getAssets().open(model.getPictureName()));
} else {//否則就是從SD卡里面讀取
bitmap = BitmapFactory.decodeFile(model.getPictureName());
}
// 生成一個紋理對象,并將其ID保存到成員變量 texture 中
int[] textures = new int[1];
gl.glGenTextures(1, textures, 0);
model.setTextureIds(textures);
// 將生成的空紋理綁定到當(dāng)前2D紋理通道
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
// 設(shè)置2D紋理通道當(dāng)前綁定的紋理的屬性
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
GL10.GL_LINEAR);
// 將bitmap應(yīng)用到2D紋理通道當(dāng)前綁定的紋理中
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bitmap != null)
bitmap.recycle();
}
}
2.3 讀取多個模型數(shù)據(jù)
我們前面說過尝胆,將一個模型分割成多個模型丧裁,因此,我們需要讀取多個模型數(shù)據(jù)含衔,并保存起來煎娇。我們在此之前已經(jīng)學(xué)會了讀取一個模型數(shù)據(jù),那么讀取多個模型數(shù)據(jù)通過for循環(huán)即可抱慌。為了讀取上的方便逊桦,我們?yōu)閷⒚總€模型數(shù)據(jù)按序命名。另外抑进,我們知道强经,目前為止,我們的一個模型對應(yīng)三種格式文件:
pxy:三角形對應(yīng)的紋理坐標(biāo)
stl:三角網(wǎng)數(shù)據(jù)
jpg:紋理圖片
為了讀取上的方便寺渗,我們將同一個模型的這三個文件設(shè)為相同的名稱匿情,如:1.pxy
、1.stl信殊、1.jpg炬称。各個模型之間按序命名,格式如下圖:
此時涡拘,我們就可以很輕松的讀取啦~玲躯。在GLRenderer
中:
private List<Model> models = new ArrayList<>();
public GLRenderer(Context context) {
this.context = context;
try {
STLReader reader = new STLReader();
for (int i = 1; i <= 6; i++) {
Model model = reader.parserStlWithTextureInAssets(context, "chuwang/" + i);
models.add(model);
}
} catch (IOException e) {
e.printStackTrace();
}
}
此時我們就完成了將所有的模型數(shù)據(jù)保存在List<Model>類型的models對象中。
2.4 開始繪制
前面所做的鋪墊已經(jīng)完成,接下來就是最后的繪制了跷车!相比上一篇的代碼棘利,我們只需修改onSurfaceCreated
和onDrawFrame
函數(shù)。其實大部分代碼都是相同的朽缴,只是我們通過for
循環(huán)的方式善玫,將所有的模型繪制出來而已。
但是有個區(qū)別需要注意密强,就是我們需要獲取所有模型在xyz坐標(biāo)中的最大值最小值茅郎,以及所有模型加在一起后的中心點(diǎn)位置。前面我們只有一個模型或渤,很快就算好了系冗。多個模型我們也是很簡單,只需根據(jù)每個模型的在xyz坐標(biāo)中的最大值最小值計算即可劳坑,詳情請看我的附件源碼毕谴。
我們看看onSurfaceCreated
函數(shù),onSurfaceCreated
函數(shù)需要負(fù)責(zé)計算所有模型的中心點(diǎn)距芬、所有模型在xyz坐標(biāo)中的最大值最小值涝开,以及加載所有模型對應(yīng)的紋理圖片:
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glEnable(GL10.GL_DEPTH_TEST); // 啟用深度緩存
gl.glClearColor(0f, 0f, 0f, 0f);// 設(shè)置深度緩存值
gl.glDepthFunc(GL10.GL_LEQUAL); // 設(shè)置深度緩存比較函數(shù)
gl.glShadeModel(GL10.GL_SMOOTH);// 設(shè)置陰影模式GL_SMOOTH
//初始化相關(guān)數(shù)據(jù)
initConfigData(gl);
}
private void initConfigData(GL10 gl) {
float r = Util.getR(models);
mScalef = 0.5f / r;
mCenterPoint = Util.getCenter(models);
//為每個模型綁定紋理
for (Model model : models) {
loadTexture(gl, model, true);
}
}
再看看onDrawFrame函數(shù),onDrawFrame函數(shù)需要通過for循環(huán)的方式框仔,繪制出每個模型:
@Override
public void onDrawFrame(GL10 gl) {
// 清除屏幕和深度緩存
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();// 重置當(dāng)前的模型觀察矩陣
//眼睛對著原點(diǎn)看
GLU.gluLookAt(gl, eye.x, eye.y, eye.z, center.x,
center.y, center.z, up.x, up.y, up.z);
//為了能有立體感覺舀武,通過改變mDegree值,讓模型不斷旋轉(zhuǎn)
gl.glRotatef(mDegree, 0, 1, 0);
//將模型放縮到View剛好裝下
gl.glScalef(mScalef, mScalef, mScalef);
//把模型移動到原點(diǎn)
gl.glTranslatef(-mCenterPoint.x, -mCenterPoint.y,
-mCenterPoint.z);
//===================begin==============================//
for (Model model : models) {
//開啟貼紋理功能
gl.glEnable(GL10.GL_TEXTURE_2D);
//根據(jù)ID綁定對應(yīng)的紋理
gl.glBindTexture(GL10.GL_TEXTURE_2D, model.getTextureIds()[0]);
//啟用相關(guān)功能
gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
//開始繪制
gl.glNormalPointer(GL10.GL_FLOAT, 0, model.getVnormBuffer());
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, model.getVertBuffer());
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, model.getTextureBuffer());
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, model.getFacetCount() * 3);
//關(guān)閉當(dāng)前模型貼紋理离斩,即將紋理id設(shè)置為0
gl.glBindTexture(GL10.GL_TEXTURE_2D, 0);
//關(guān)閉對應(yīng)的功能
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
gl.glDisable(GL10.GL_TEXTURE_2D);
}
//=====================end============================//
}
從代碼上也可以看出银舱,相比前幾篇文章,代碼的修改并不大跛梗。細(xì)心的童鞋會發(fā)現(xiàn)寻馏,我這里并沒有開啟光照、材料屬性核偿。主要是我們已經(jīng)貼好紋理了诚欠,并且默認(rèn)上模型會有光照效果。
最后看看效果吧漾岳,其實效果已經(jīng)在最開始已經(jīng)看過了轰绵,我們再看看
https://github.com/changhaismile/OpenGLDemo