1术羔、概述
前面幾篇關于OpenGLES的文章:
前面討論了利用矩陣的變換來對所有頂點進行變換。OpenGL ES希望在每次頂點著色器運行后,可見的所有頂點都為標準化設備坐標(Normalized Device Coordinate, NDC)淀零。也就是說,每個頂點的x,y,z坐標都應該在-1.0到1.0之間赞厕,超出這個坐標范圍的頂點都將不可見。開發(fā)時候通可以自己設定一個坐標的范圍定硝,之后再在頂點著色器中將這些坐標變換為標準化設備坐標皿桑。然后將這些標準化設備坐標傳入光柵器,將它們變換為屏幕上的二維坐標或像素蔬啡。
將坐標變換為標準化設備坐標诲侮,接著再轉化為屏幕坐標的過程通常是分步進行的。物體的頂點在最終轉化為屏幕坐標之前還會被變換到多個坐標系星爪。將物體的坐標變換到幾個過渡坐標系的好處在于浆西,在這些特定的坐標系統中粉私,一些操作或運算更加方便和容易顽腾。一般來說比較重要的總共有5個不同的坐標系統:
局部空間(Local Space,或者稱為物體空間(Object Space))
世界空間(World Space)
觀察空間(View Space诺核,或者稱為視覺空間(Eye Space))
裁剪空間(Clip Space)
屏幕空間(Screen Space)
這就是一個頂點在最終被轉化為片段之前需要經歷的所有不同狀態(tài)抄肖。
2、坐標系總覽
為了將坐標從一個坐標系變換到另一個坐標系窖杀,需要用到幾個變換矩陣漓摩,最重要的幾個分別是模型(Model)、觀察(View)入客、投影(Projection)三個矩陣管毙。頂點坐標起始于局部空間(Local Space),在這里它稱為局部坐標(Local Coordinate)桌硫,它在之后會變?yōu)槭澜缱鴺?World Coordinate)夭咬,觀察坐標(View Coordinate),裁剪坐標(Clip Coordinate)铆隘,并最后以屏幕坐標(Screen Coordinate)的形式結束卓舵。下面的這張圖展示了整個流程以及各個變換過程做了什么:
① 局部坐標系:對象相對于局部原點的坐標,也是物體起始的坐標膀钠。
② 世界坐標系:世界空間坐標是處于一個更大的空間范圍的掏湾。這些坐標相對于世界的全局原點,它們會和其它物體一起相對于世界的原點進行擺放肿嘲。
③ 觀察坐標系:觀察坐標下每個坐標都是從攝像機或者說觀察者的角度進行觀察的融击。
④ 剪裁坐標系:坐標到達觀察空間之后,需要將其投影到裁剪坐標雳窟。裁剪坐標會被處理至-1.0到1.0的范圍內尊浪,并判斷哪些頂點將會出現在屏幕上。
⑤ 屏幕坐標系:使用一個叫做視口變換(Viewport Transform)的過程,將剪裁坐標變成屏幕坐標际长。視口變換將位于-1.0到1.0范圍的坐標變換到由glViewport()所定義的坐標范圍內耸采。最后變換出來的坐標將會送到光柵器,將其轉化為片段工育。
上面就是每個坐標系大致的作用虾宇,之所以將頂點變換到各個不同的空間的原因是有些操作在特定的坐標系統中才有意義且更方便。例如如绸,當需要對物體進行修改的時候嘱朽,在局部空間中來操作會更合理;如果要對一個物體做出一個相對于其它物體位置的操作時怔接,在世界坐標系中來做這個才更合理搪泳,等等。其實也可以定義一個直接從局部空間直接變換到裁剪空間的變換矩陣扼脐,但那樣會失去很多靈活性岸军。
3、各坐標系空間詳述
3.1 局部坐標系空間
局部坐標系空間是指物體所在的坐標系空間瓦侮,即對象最開始所在的地方艰赞。例如在一個建模軟件中創(chuàng)建了一個立方體。創(chuàng)建的立方體的原點有可能位于(0, 0, 0)肚吏,即便它有可能最后在程序中處于完全不同的位置方妖。甚至有可能創(chuàng)建的所有模型都以(0, 0, 0)為初始位置,然而它們會最終出現在世界的不同位置罚攀。所以党觅,模型的所有頂點都是在局部系空間中,它們相對于物體來說都是局部的斋泄。
3.2 世界坐標系空間
如果將所有的物體導入到程序當中杯瞻,它們有可能會全擠在世界的原點(0, 0, 0)上,這并不是想要的結果是己。理想狀態(tài)下是為每一個物體定義一個位置又兵,從而能在更大的世界當中放置它們。世界空間中的坐標系正如其名:是指頂點相對于世界的坐標卒废。物體的坐標將會從局部變換到世界空間沛厨;該變換一般是由模型矩陣(Model Matrix)實現。模型矩陣是一種變換矩陣摔认,它能通過對物體進行位移逆皮、縮放、旋轉來將它置于它本應該在的位置或朝向参袱。
3.3 觀察坐標系空間
觀察坐標系空間經常被稱之OpenGL ES的攝像機視角电谣,所以有時也稱為攝像機坐標系空間(Camera Space)或視覺坐標系空間(Eye Space)秽梅。觀察坐標系空間是將世界空間坐標轉化為用戶視野前方的坐標而產生的結果。因此觀察坐標系空間就是從攝像機的視角所觀察到的空間剿牺。而這通常是由一系列的位移和旋轉的組合來完成企垦,平移/旋轉場景從而使得特定的對象被變換到攝像機的前方。這些組合在一起的變換通常存儲在一個觀察矩陣(View Matrix)里晒来,它被用來將世界坐標變換到觀察空間钞诡。更詳細的情況之后會用專門的一篇文章來進行討論。
3.4 剪裁坐標系空間
在一個頂點著色器運行的最后湃崩,OpenGL ES期望所有的坐標都能落在一個特定的范圍內荧降,且任何在這個范圍之外的點都應該被裁剪掉。被裁剪掉的坐標就會被忽略攒读,所以剩下的坐標就將變?yōu)槠聊簧峡梢姷钠味浣搿_@也就是裁剪坐標系空間名字的由來。
因為將所有可見的坐標都指定在-1.0到1.0的范圍內不是很直觀薄扁,所以會指定自己的坐標集并將它變換回標準化設備坐標系剪返,就像OpenGL ES期望的那樣。為了將頂點坐標從觀察坐標系空間變換到裁剪坐標系空間泌辫,需要定義一個投影矩陣(Projection Matrix)随夸,它指定了一個范圍的坐標,比如在每個維度上的-1000到1000震放。投影矩陣接著會將在這個指定的范圍內的坐標變換為標準化設備坐標的范圍(-1.0, 1.0)。所有在范圍外的坐標不會被映射到在-1.0到1.0的范圍之間驼修,所以會被裁剪掉殿遂。在上面這個投影矩陣所指定的范圍內,坐標(1250, 500, 750)將是不可見的乙各,這是由于它的x坐標超出了范圍墨礁,它被轉化為一個大于1.0的標準化設備坐標,所以被裁剪掉了耳峦。
如果只是圖元(Primitive)恩静,例如三角形,的一部分超出了裁剪體積蹲坷,則OpenGL ES會重新構建這個三角形為一個或多個三角形讓其能夠適合這個裁剪范圍驶乾。
由投影矩陣創(chuàng)建的觀察箱(Viewing Box)被稱為平截頭體,每個出現在平截頭體范圍內的坐標都會最終出現在用戶的屏幕上循签。將特定范圍內的坐標轉化到標準化設備坐標系的過程(而且它很容易被映射到2D觀察空間坐標)被稱之為投影级乐,因為使用投影矩陣能將3D坐標投影到很容易映射到2D的標準化設備坐標系中。
一旦所有頂點被變換到裁剪空間县匠,最終的操作——透視除法將會執(zhí)行风科,在這個過程中將位置向量的x撒轮,y,z分量分別除以向量的齊次w分量贼穆;透視除法是將4D裁剪空間坐標變換為3D標準化設備坐標的過程题山。這一步會在每一個頂點著色器運行的最后被自動執(zhí)行。在這一階段之后故痊,最終的坐標將會被映射到屏幕空間中(使用glViewport中的設定)召边,并被變換成片段。
將觀察坐標變換為裁剪坐標的投影矩陣可以為兩種不同的形式炮车,每種形式都定義了不同的平截頭體衷笋。可以選擇創(chuàng)建一個正射投影矩陣(Orthographic Projection Matrix)或一個透視投影矩陣(Perspective Projection Matrix)豫领。
3.4.1 正射投影
正射投影矩陣定義了一個類似立方體的平截頭箱抡柿,它定義了一個裁剪空間,在這空間之外的頂點都會被裁剪掉等恐。創(chuàng)建一個正射投影矩陣需要指定可見平截頭體的寬洲劣、高和長度。在使用正射投影矩陣變換至裁剪空間之后處于這個平截頭體內的所有坐標將不會被裁剪掉课蔬。它的平截頭體看起來像一個容器:
上面的平截頭體定義了可見的坐標囱稽,它由由寬、高二跋、近(Near)平面和遠(Far)平面所指定战惊。任何出現在近平面之前或遠平面之后的坐標都會被裁剪掉。正射平截頭體直接將平截頭體內部的所有坐標映射為標準化設備坐標扎即,因為每個向量的w分量都沒有進行改變吞获;如果w分量等于1.0,透視除法則不會改變這個坐標谚鄙。
要創(chuàng)建一個正射投影矩陣各拷,可以使用android.opengl.Matrix 下面的內置函數orthoM():
public static void orthoM(float[] m, int mOffset,
float left, float right, float bottom, float top,
float near, float far)
上面第一個參數是需要變換的矩陣存儲數組,第二個參數從第一個參數數組中的偏移位置闷营,第三烤黍、四、五傻盟、六分別對應平截頭的左右下上邊界速蕊,第七、八個參數對應近平面和遠平面距離莫杈。這個投影矩陣會將處于這些x互例,y,z值范圍內的坐標變換為標準化設備坐標筝闹。
其映射原理如下圖:
如上圖所示剪裁空間中的 所有x媳叨,y和z分量線性映射到NDC腥光。只需要將矩形體積縮放到立方體,然后將觀察坐標系的原點移動到標準化設備坐標原點糊秆。最終呈現出來的圖像就是標準化過后的效果武福。
正射投影矩陣直接將坐標映射到2D平面中,即屏幕上痘番,但實際上一個直接的投影矩陣會產生不真實的結果捉片,因為這個投影沒有將透視(Perspective)考慮進去。所以需要透視投影矩陣來解決這個問題汞舱。
3.4.2 透視投影
對于肉眼直觀的感受是伍纫,近大遠小的,這種視覺效果稱之為透視昂芜。透視投影要模仿肉眼的這種效果莹规,是使用透視投影矩陣來完成的。這個透視投影矩陣將給定的平截頭體范圍映射到裁剪空間泌神,除此之外還修改了每個頂點坐標的w值良漱,從而使得離觀察者越遠的頂點坐標w分量越大。被變換到裁剪空間的坐標都會在-w到w的范圍之間(任何大于這個范圍的坐標都會被裁剪掉)欢际。OpenGL ES要求所有可見的坐標都落在-1.0到1.0范圍內母市,作為頂點著色器最后的輸出,因此损趋,一旦坐標在裁剪空間內之后患久,透視除法就會被應用到裁剪空間坐標上:
頂點坐標的每個分量都會除以它的w分量,距離觀察者越遠頂點坐標就會越小舶沿。這是也是w分量非常重要的另一個原因墙杯,它能夠幫助開發(fā)者進行透視投影。最后的結果坐標就是處于標準化設備空間中的括荡。
在android.opengl.Matrix 可以這樣創(chuàng)建一個透視投影矩陣:
public static void perspectiveM(float[] m, int offset,
float fovy, float aspect, float zNear, float zFar)
perspectiveM() 所做的其實就是創(chuàng)建了一個定義了可視空間的大平截頭體,任何在這個平截頭體以外的東西最后都不會出現在裁剪空間體積內溉旋,并且將會受到裁剪畸冲。一個透視平截頭體可以被看作一個不均勻形狀的箱子,在這個箱子內部的每個坐標都會被映射到裁剪空間上的一個點观腊。下面是一張透視平截頭體的圖片:
上面的函數第一個參數是需要變換的矩陣存儲數組邑闲,第二個參數從第一個參數數組中的偏移位置,第三個參數表示的是視角梧油。如果想要一個真實的觀察效果苫耸,它的值通常設置為45.0f。第四個參數設置了寬高比儡陨,由視口的寬除以高所得褪子。第三和第四個參數設置了平截頭體的近和遠平面量淌。通常設置近距離為0.1f,而遠距離設為100.0f嫌褪。所有在近平面和遠平面內且處于平截頭體內的頂點都會被渲染呀枢。當把透視矩陣的 near 值設置太大時(如10.0f),OpenGL ES會將靠近攝像機的坐標(在0.0f和10.0f之間)都裁剪掉笼痛,這會導致游戲中常見那種太靠近物體時候視線直接穿過物體的情況裙秋。
透視投影映射到標準化設備坐標的原理如下圖:
在透視投影中,截斷的金字塔平截頭體(觀察坐標)中的3D點被映射到立方體(NDC)缨伊。從[l摘刑,r]到[-1,1]的x坐標范圍,從[b刻坊,t]到[-1,1]的y坐標范圍和[-n枷恕,-f]到到[-1,1]的z的坐標范圍。這邊需要注意的是在OpenGL ES觀察坐標是在右手坐標系中定義的紧唱,但NDC使用左手坐標系活尊。也就是說,原點處的相機沿著觀察空間中的-Z軸看漏益,但它在NDC中沿著+ Z軸看蛹锰。
3.5 坐標系組合
上述的每一個步驟都創(chuàng)建了一個變換矩陣:模型矩陣、觀察矩陣和投影矩陣绰疤。一個頂點坐標將會根據以下過程被變換到裁剪坐標:
注意矩陣運算的順序是相反的即需要從右往左閱讀矩陣的乘法铜犬。最后的頂點應該被賦值到頂點著色器中的gl_Position,OpenGLES將會自動進行透視除法和裁剪轻庆。頂點著色器的輸出要求所有的頂點都在裁剪空間內癣猾,這正是剛才使用變換矩陣所做的。OpenGL ES然后對裁剪坐標執(zhí)行透視除法從而將它們變換到標準化設備坐標余爆。OpenGL ES會使用glViewPort內部的參數來將標準化設備坐標映射到屏幕坐標纷宇,每個坐標都關聯了一個屏幕上的點。
4蛾方、OpenGL ES中的操作
有來前面的坐標系的鋪墊像捶,可以正式用OpenGL ES創(chuàng)建三維物體了,而不是前面的兩維物體桩砰。
4.1 二維平面傾斜
首先創(chuàng)建一個模型矩陣拓春。這個模型矩陣包含了位移、縮放與旋轉操作亚隅,它們會被應用到所有物體的頂點上硼莽,以變換它們到全局的世界空間。變換一下前面幾篇文章所展示的平面煮纵,將其繞著x軸旋轉懂鸵,使它看起來像放在地上一樣偏螺。這里為了和下面兩節(jié)內容所一致,將模型的生成操作放在draw()中這個模型矩陣看起來是這樣的:
//Triangle.kt
private val mModelMatrix = FloatArray(16)
fun draw(){
...
Matrix.setIdentityM(mModelMatrix, 0)
Matrix.rotateM(mModelMatrix, 0, -55f, 1.0f, 0.0f, 0f)
...
}
通過將頂點坐標乘以這個模型矩陣矾瑰,將該頂點坐標變換到世界坐標砖茸。原理的平面看起來就是在地板上,代表全局世界里的平面殴穴。
接下來需要創(chuàng)建一個觀察矩陣凉夯。想要在場景里面稍微往后移動,以使得物體變成可見的采幌,當在世界空間時劲够,默認觀察點也就是相機所處位置位于原點(0,0,0)。所以將攝像機向后移動休傍,和將整個場景向前移動是一樣的征绎。
這正是觀察矩陣所做的,以相反于攝像機移動的方向移動整個場景磨取。因為想要往后移動人柿,并且OpenGL ES是一個右手坐標系,所以需要沿著z軸的正方向移動忙厌。則要通過將場景沿著z軸負方向平移來實現凫岖。它會給我們一種在往后移動的感覺。所以這邊觀察矩陣如下所示:
//Triangle.kt
private val mViewMatrix = FloatArray(16)
fun draw(){
...
Matrix.setIdentityM(mViewMatrix,0)
Matrix.translateM(mViewMatrix,0,0f,0.0f, -2.5f)
...
}
最后需要做的是定義一個投影矩陣逢净。在場景中使用透視投影哥放,所以像這樣聲明一個投影矩陣:
//Triangle.kt
private val mProjectionMatrix = FloatArray(16)
fun draw(){
...
val displayMetrics = mContext.resources.displayMetrics
Matrix.setIdentityM(mProjectionMatrix, 0)
Matrix.perspectiveM(mProjectionMatrix, 0,45f, displayMetrics.widthPixels * 1.0f / displayMetrics.heightPixels,0.1f,100f)
...
}
現在已經創(chuàng)建了變換矩陣,應該將它們傳入著色器爹土。首先甥雕,在頂點著色器中聲明三個uniform變換矩陣然后將它乘以頂點坐標:
//Triangle.kt
private val vertexShaderCode =
"#version 300 es \n" +
...
"uniform mat4 model;" +
"uniform mat4 view;" +
"uniform mat4 projection;" +
"void main() {" +
" gl_Position = projection * view * model * vec4(aPos, 1.0);" +
...
"}"
還應該將矩陣傳入著色器這通常在每次的渲染迭代中進行,因為變換矩陣會經常變動:
//Triangle.kt
fun draw(){
...
Matrix.setIdentityM(mModelMatrix, 0)
Matrix.rotateM(mModelMatrix, 0, -55f, 1.0f, 0.0f, 0f)
Matrix.setIdentityM(mViewMatrix, 0)
Matrix.translateM(mViewMatrix, 0, 0f, 0.0f, -2.5f)
val displayMetrics = mContext.resources.displayMetrics
Matrix.setIdentityM(mProjectionMatrix, 0)
Matrix.perspectiveM(mProjectionMatrix,0,45f, displayMetrics.widthPixels * 1.0f / displayMetrics.heightPixels,0.1f,100f)
val modelLoc = GLES30.glGetUniformLocation(mProgram, "model")
GLES30.glUniformMatrix4fv(modelLoc, 1, false, mModelMatrix, 0)
val viewLoc = GLES30.glGetUniformLocation(mProgram, "view")
GLES30.glUniformMatrix4fv(viewLoc, 1, false, mViewMatrix, 0)
val projectionLoc = GLES30.glGetUniformLocation(mProgram, "projection")
GLES30.glUniformMatrix4fv(projectionLoc, 1, false, mProjectionMatrix, 0)
...
}
這樣操作過后頂點坐標已經使用了模型胀茵、觀察和投影矩陣進行變換社露,最終效果如下圖:
4.2 三維效果
目前為止,盡管已經甚至是在3D空間里琼娘,但還是對2D平面進行操作呵哨。這一章節(jié)將討論實現3D效果,來渲染一個立方體轨奄,首先一共需要36個頂點(6個面 x 每個面有2個三角形組成 x 每個三角形有3個頂點),這36個頂點的位置如下:
var vertices3D = floatArrayOf(
// --- 位置 --- --- 紋理坐標 ---
-0.5f, -0.5f, -0.5f, 0f, 0f,
0.5f, -0.5f, -0.5f, 1f, 0f,
0.5f, 0.5f, -0.5f, 1f, 1f,
0.5f, 0.5f, -0.5f, 1f, 1f,
-0.5f, 0.5f, -0.5f, 0f, 1f,
-0.5f, -0.5f, -0.5f, 0f, 0f,
-0.5f, -0.5f, 0.5f, 0f, 1f,
0.5f, -0.5f, 0.5f, 1f, 1f,
0.5f, 0.5f, 0.5f, 1f, 0f,
0.5f, 0.5f, 0.5f, 1f, 0f,
-0.5f, 0.5f, 0.5f, 0f, 0f,
-0.5f, -0.5f, 0.5f, 0f, 1f,
-0.5f, 0.5f, 0.5f, 1f, 00f,
-0.5f, 0.5f, -0.5f, 1f, 1f,
-0.5f, -0.5f, -0.5f, 0f, 1f,
-0.5f, -0.5f, -0.5f, 0f, 1f,
-0.5f, -0.5f, 0.5f, 0f, 0f,
-0.5f, 0.5f, 0.5f, 1f, 0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f)
這里做一個立方體隨時間旋轉的效果,同時繪制的頂點要改成36個:
fun draw(){
...
Matrix.setIdentityM(mModelMatrix,0)
mAngle =5.0f * ((System.currentTimeMillis() /200) %60)
Matrix.rotateM(mModelMatrix,0,mAngle,0.5f,1.0f,0f)
...
GLES30.glDrawArrays(GLES30.GL_TRIANGLES,0,36)
...
}
此時會得到如下效果:
上面出現的效果的確有些像立方體拒炎,不過明顯感覺有問題挪拟。立方體的某些本應被遮擋住的面被繪制在了這個立方體其他面之上。之所以這樣是因為OpenGL ES是一個三角形一個三角形地來繪制立方體的击你,所以即便之前那里有東西它也會覆蓋之前的像素玉组。因為這個原因谎柄,有些三角形會被繪制在其它三角形上面,雖然它們本不應該是被覆蓋的惯雳。OpenGL ES存儲深度信息在一個叫做Z緩沖(Z-buffer)的緩沖中朝巫,它允許OpenGL ES決定何時覆蓋一個像素而何時不覆蓋。通過使用Z緩沖石景,可以配置OpenGL來進行深度測試劈猿。
OpenGL ES存儲它的所有深度信息于一個Z緩沖中,也被稱為深度緩沖潮孽。OpenGL ES的窗口管理系統會自動生成這樣一個緩沖(就像它也有一個顏色緩沖來存儲輸出圖像的顏色)揪荣。深度值存儲在每個片段里面,作為片段的z值往史,當片段想要輸出它的顏色時仗颈,OpenGL ES會將它的深度值和z緩沖進行比較,如果當前的片段在其它片段之后椎例,它將會被丟棄挨决,否則將會覆蓋。這個過程稱為深度測試订歪,它是由OpenGL ES自動完成的脖祈。
如果想要確定OpenGL ES真的執(zhí)行了深度測試,首先要告訴OpenGL ES想要啟用深度測試陌粹;它默認是關閉的撒犀。可以通過GLES30.glEnable()函數來開啟深度測試掏秩。GLES30.glEnable()和GLES30glDisable()允許開發(fā)者啟用或禁用某個OpenGL ES功能或舞。這個功能會一直保持啟用/禁用狀態(tài),直到另一個調用來禁用/啟用它∶苫茫現在啟用深度測試映凳,需要開啟GLES30.GL_DEPTH_TEST
//Triangle.kt
init {
...
GLES30.glEnable(GLES30.GL_DEPTH_TEST)
...
}
因為使用了深度測試,同時也想要在每次渲染迭代之前清除深度緩沖邮破,否則前一幀的深度信息仍然保存在緩沖中诈豌。就像清除顏色緩沖一樣,可以通過在glClear()中指定DEPTH_BUFFER_BIT位來清除深度緩沖抒和,同時別忘記前面文章提到的自動動態(tài)刷新要注釋掉GLSurfaceView 的 RENDERMODE_WHEN_DIRTY:
//Triangle.kt
fun draw() {
...
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)
...
}
// MyGLSurfaceView.kt
init {
...
// renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
...
}
其效果如下:
4.3 渲染多個三維圖像
現在想在屏幕上顯示4個立方體矫渔。每個立方體看起來都是一樣的,區(qū)別在于它們在世界的位置及旋轉角度不同摧莽。立方體的圖形布局已經定義好了庙洼,所以當渲染更多物體的時候不需要改變緩沖數組和屬性數組,唯一需要做的只是改變每個對象的模型矩陣來將立方體變換到世界坐標系中。
首先油够,讓為每個立方體定義一個位移向量來指定它在世界空間的位置蚁袭。
//Triangle.kt
var cubePosition =floatArrayOf(
0.0f,0.0f,0.0f,
0.9f,1.3f,0.4f,
-0.5f, -1.2f, -1.5f,
-1.8f, -1.0f, -2.3f)
現在,在循環(huán)中石咬,調用glDrawArrays() 4次揩悄,但這次在渲染之前每次傳入一個不同的模型矩陣到頂點著色器中。將會在游戲循環(huán)中創(chuàng)建一個小的循環(huán)用不同的模型矩陣渲染的物體4次鬼悠。注意也對每個箱子加了一點旋轉:
//Triangle.kt
fun draw() {
...
for (i in 0..3) {
...
Matrix.setIdentityM(mModelMatrix, 0)
mAngle = 5.0f * ((System.currentTimeMillis() / 200) % 60)
Matrix.rotateM(mModelMatrix, 0, mAngle,
cubePosition[i * 3] + 0.5f,
cubePosition[i * 3 + 1] + 1.0f,
cubePosition[i * 3 + 2])
Matrix.setIdentityM(mViewMatrix, 0)
Matrix.translateM(mViewMatrix, 0,
cubePosition[i * 3],
cubePosition[i * 3 + 1],
cubePosition[i * 3 + 2] - 4f)
val displayMetrics = mContext.resources.displayMetrics
Matrix.setIdentityM(mProjectionMatrix, 0)
Matrix.perspectiveM(mProjectionMatrix,
0,
45f,
displayMetrics.widthPixels * 1.0f / displayMetrics.heightPixels,
0.1f,
100f)
...
GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 36)
}
...
}
其最終效果如下: