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度,這會讓桌子處於一個很好的角度,就像我們站在它前面一樣。

 

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


版權聲明:本文為博主原創文章,未經博主允許不得轉載。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM