1. 程式人生 > >Android OpenGL ES(六)----進入三維在程式碼中建立投影矩陣和旋轉矩陣

Android OpenGL ES(六)----進入三維在程式碼中建立投影矩陣和旋轉矩陣

我們現在準備好在程式碼中新增透視投影了。Android的Matrix類為它準備了兩個方法------frustumM()和perspectiveM()。不幸的是,frustumM()的個缺陷,它會影響某些型別的投影,而perspectiveM()只是從Android的ICS版本開始才被引入,在早期的Android版本里並沒有這個方法。我們可以簡單地支援ICS及其以上的版本,但是這樣會丟掉很大一部分市場,一些使用者依然執行早期的Android版本。

作為替代,我們可以建立我們自己的方法來實現投影矩陣。

1.建立自己的perspectiveM

在工具包中建立新的類MatrixHelper,在開始處加入如下方法簽名:

public static void perspectiveM(float[] m,float yFovInDegress,float aspect,float n,float f){

計算焦距

我們要做的第一件事就是計算焦距,這將基於在Y軸上的視野。就在方法簽名之後加入如下程式碼:

final float angleInRadians=(float)(yFovInDegress*Math.PI/180.0);
final float a=(float)(1.0/Math.tan(angleInRadians/2.0));

我們使用Java的Math類計算那個正切函式,因為它需要弧度角,所以我們把視野從度轉換為弧度。接著計算焦距。

輸出矩陣

我們現在可以更具上一節的數學證明直接寫出矩陣,增加如下程式碼:

m[0]=a/aspect;
m[1]=0f;
m[2]=0f;
m[3]=0f;


m[4]=0f;
m[5]=a;
m[6]=0f;
m[7]=0f;


m[8]=0f;
m[9]=0f;
m[10]=-((f+n)/(f-n));
m[11]=-1f;


m[12]=0f;
m[13]=0f;
m[14]=-((2f*f*n)/(f-n));
m[15]=0f;

這就是把矩陣資料存到了引數m定義的浮點陣列中,這個陣列需要至少16個元素。OpenGL把矩陣資料按照以列為主的順序儲存,這就意味著我們一次寫一列資料,而不是一次寫一行。前四個值是第一列,下一組四個數是第二列,以此類推。

2.開始使用投影矩陣

我們現在將轉而使用那個透視投影矩陣了。開啟你的渲染類,並從onSurfaceChanged()去掉所有的程式碼,只保留glViewPort()呼叫,加入如下程式碼:

MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / (float) height, 1f, 10f);

這會用45度的視野建立一個透視投影。 這個視椎體從Z值為-1的位置開始,在Z值為-10的位置結束。

加入MatrixHelper的匯入後,繼續前面的程式執行,你可以會發現桌面不見了,因為我們沒有給桌子指定Z的位置,預設情況下Z在為0的位置。因為這個視椎體是從Z值為-1的位置開始的,除非把它移動到那個距離內,否則我們無法看到桌子。

不要硬編碼Z的值,在使用投影矩陣進行投影之前,讓我們使用一個平移矩陣把桌子移出來。依照慣例,我們把這個矩陣稱為模型矩陣。

利用模型矩陣移動物體

在類的頂部加入如下矩陣的定義:

private final float[] modelMatrix=new float[16];

我們要使用這個矩陣把桌面移動到那個距離內。在onSurfaceChanged()結尾處,加入如下程式碼:

Matrix.setIdentityM(modelMatrix, 0);
Matrix.translateM(modelMatrix,0,0f,0f,-2f);

這就是把模型矩陣設為單位矩陣,在沿著Z軸平移-2。當我們把桌面的座標與這個矩陣相乘的時候,那些座標最終會沿著Z軸負方向移動2個單位。

相乘一次還是相乘兩次

我們現在要做一個選擇:我們依然需要把這個矩陣應用於每個頂點,因此第一個選項是給頂點著色器新增一個額外的矩陣。我們把每個頂點都與這個模型矩陣相乘,讓它們沿著Z軸負方向移動2個單位,接下來把每個頂點與投影矩陣相乘。這樣,OpenGL就可以做透視除法,並把這些頂點變換到歸一化裝置座標了。

如果不想這麼麻煩,還有一個更好的方式:我們可以把模型矩陣與投影矩陣相乘,得到一個矩陣,然後把這個矩陣傳遞給頂點著色器。通過這種方式我們就可以在著色器中僅保留一個矩陣。

選擇適當的順序

為了弄清楚我們應該使用那種順序,讓我們看一下只使用投影矩陣的數學運算:

VerteXeye代表場景中的頂點與投影矩陣相乘之前的位置。我們一旦加入模型矩陣來移動那個桌子,這個數學運算看起來就像這樣。

VerteXmodel代表頂點在模型矩陣放進場景中之前的位置。把這兩個表示式合併在一起,最後得到的公式如下:

了使用一個矩陣替換這兩個矩陣,我們不得不把投影矩陣乘以模型矩陣,就是把投影矩陣放在左邊,把模型矩陣放在右邊。

更新程式碼使用一個矩陣

讓我們把這個新的矩陣程式碼封裝一下,把下面的程式碼加到onSurfaceChanged()中的translate()呼叫後面:

final float[] temp=new float[16];
Matrix.multiplyMM(temp,0,projectionMatrix,0,modelMatrix,0);
System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);

不論什麼時候把兩個矩陣相乘,都需要一個臨時變數來儲存其結果。如果嘗試直接寫入這個結果,這個結果將是未定義的。

我們首先建立了一個臨時的浮點陣列用來儲存其臨時結果;然後呼叫multiplyMM()把投影矩陣和模型矩陣相乘,其結果存進這個臨時陣列。下一步,我們呼叫System.arraycopy()把結果存回projectMatrix,它現在包含模型矩陣與投影矩陣的組合效應。

如果我們現在執行這個程式,會發現這依然是個平面的桌面。

3.增加旋轉

既然我們已經有一個配置好的投影矩陣和一個可以移動的桌子的模型矩陣,那麼我們需要做的就是旋轉這張桌子,以便可以從某個角度觀察它。如果使用旋轉矩陣,我們只需要一行程式碼就可以做到。我們還從來沒有用過旋轉,現在花一些時間瞭解一下這些旋轉是怎麼工作的。

需要弄清楚一件時是我們需要圍繞哪個軸旋轉以及旋轉多少度。讓我們再看下圖:

搞清楚一個物體是怎麼圍繞一個給定的軸旋轉,我們將使用右手座標規則:伸出你的右手,握拳,讓大拇指指向正軸方向。倘若是一個正角度的旋轉,捲曲的手指會告訴你一個物體是怎麼圍繞那個軸旋轉的。觀察上圖,當你把大拇指指向X軸正方向時,看看旋轉的方向是怎麼跟隨手指的繞軸線捲曲的。

分別用X軸,Y軸和Z軸試一下這個旋轉。如果繞Y軸旋轉,桌子會繞著它的頂端和低端水平旋轉。如果繞著Z軸旋轉,桌子會在一個圓圈內旋轉。我們想要做的是讓桌子繞著X軸向後旋轉,因為這會讓桌子看起來更有層次。

旋轉矩陣

我們將使用一個旋轉矩陣去做實際的旋轉。旋轉矩陣使用正弦和餘弦三角函式把旋轉轉換成縮放因子。下面就是繞X軸旋轉所用矩陣定義:

然後是繞Y軸旋轉所用的矩陣:

最後,還有一個繞Z軸旋轉所用的矩陣:

所有矩陣合併為一個通用的旋轉矩陣,使其可以基於任意一個角度和向量旋轉,這也是可能的。

作為一個測試,讓我們試試繞著X軸旋轉。我們從一個點開始,它在原點上面一個單位,也就是Y值是1。把它繞X軸旋轉90度。首先,讓我們準備這個旋轉矩陣:

把這個矩陣與這個點的位置向量相乘,看看得到了什麼:

個點從(0,1,0)被移動到了(0,0,1)。如果我們回過頭來看一下上面那個旋轉座標軸,並對X軸使用右手規則,我們可以看到正向旋轉是如何把一個點沿著一個繞X軸的圈移動的。

4.在程式碼中加入旋轉

我們現在準備好把這個旋轉加入程式碼了。回到onSurfaceChanged(),調整那個平移矩陣,並加入一個旋轉矩陣,如下:

Matrix.translateM(modelMatrix,0,0f,0f,-2.5f);
Matrix.rotateM(modelMatrix,0,-60f,1f,0f,0f);

我們把這張桌子放得更遠了一點,因為我們一旦把它旋轉了,它的底部會距離我們更近。我們接著把它繞X軸旋轉-60度,這會讓桌子處於一個很好的角度,就像我們站在它前面一樣。

這張桌子現在看起來應該如下圖所示: