在上一篇教程中咱們主要講的是關於創建一個多邊形。這一篇教程全都是關於變換的——如何移動一個多邊形到任意位置。我會接着上一篇教程而繼續講下去,因此你能夠繼續使用上一篇的源碼或者它的副本。java
在這一篇裏你可能會嫌我叨叨一大堆數學知識,可是我認爲這些對於瞭解opengl ES渲染網格過程當中對全部的頂點乘以一個矩陣是重要的。你作的全部變換實際上都是經過不一樣的方式去修改頂點的矩陣。你能夠把矩陣看做一張紙,在開始繪製以前,你沒有移動過筆,因此你會始終繪製在紙的中央位置。可是經過變換,你能夠移動這張紙和中心點。旋轉操做就好像把這張紙繞着中心點旋轉。縮放操做有點難以理解,姑且理解爲你繪製網格的單位大小改變了。一般咱們所說的變換是基於網格而不是基於世界座標,這是很重要的一點。
android
opengl使用所謂的「右手座標系」。一個座標系,若是你沿座標軸,從正方向向着原點望去,逆時針的旋轉被認爲是正向旋轉,那麼這個座標系就被稱爲右手座標系。當你初始化了你的視圖,而且沒有作任何變換的時候,座標軸處於這樣的狀態:X軸從左向右,Y軸從下向上,Z軸從屏幕裏向外。
編程
public abstract void glTranslatef(float x, float y, float z)
對矩陣的平移表現爲矩陣的網格被移動。平移是沿着座標軸,沒有任何旋轉操做,座標軸處於默認的狀態。平移對一個多邊形的全部頂點在同一座標軸方向上偏移一樣的距離,它是對當前值的簡單加減操做。下圖展現了一個二維的平移操做。緩存
起點是{x:-2,y:1},咱們想要移動到{x:1,y:3},因此咱們增長{x:3,y:2},即一個簡單的加法操做:
框架
{x:-2, y:1} + {x:3, y:2} = {x:-2 + 3, y:1 + 2} = {x:1, y:3}google
一樣,在三維中,咱們若是定位在{x:1, y:1, z:0},咱們想要向屏幕裏移動3個單位,因而咱們加上{x:0, y:0, z:-3}獲得{x:1, y:1, z:-3}。spa
在上篇教程中,爲了看到繪製的四邊形,咱們把它向屏幕裏移動了4個單位,咱們的作法是在當前位置上加上{x:0, y:0, z:-4} ,這是咱們曾經用過的代碼:.net
// 向屏幕裏移動4個單位 gl.glTranslatef(0, 0, -4);
若是你沿着x,y,z軸作一系列的平移操做,順序並非很重要,可是當咱們作旋轉操做的時候,順序就很是重要了。
code
記住座標軸的放置方式有點困難,好在咱們有個便於記憶的方法。向下圖同樣伸出你的左手。每一個手指的方向表明着座標軸的正方向。在我一開始接觸3D編程的時候,我曾經在個人這幾個手指上寫上X,Y,Z。
orm
public abstract void glRotatef(float angle, float x, float y, float z)
旋轉顧名思義,你對一個矩陣作旋轉操做,會表現爲網格被旋轉了。在旋轉以前若是沒有任何平移操做的話,那麼就是繞着原點旋轉。x,y,z三個值定義了旋轉操做的中心點,angle表明旋轉的角度值。
若是你記住下面三點,你將會很容易的進行旋轉操做:
好多框架和數學方法使用弧度制,可是opengl使用角度制
若是你要還原一個旋轉操做,只須要把角度值或者三個座標值取負就能夠,例如glRotatef(angle, x, y, z) 能夠被 glRotatef(angle, -x, -y, -z)或者glRotatef(-angle, x, y, z)復位。
可是若是你像下面這樣作了一系列的旋轉操做:
gl.glRotatef(90f, 1.0f, 0.0f, 0.0f); gl.glRotatef(90f, 0.0f, 1.0f, 0.0f); gl.glRotatef(90f, 0.0f, 0.0f, 1.0f);
這時候想要復位到原始位置,你不能向下面這樣僅僅對座標值(原文中是的「對角度」,我的認爲做者這裏有筆誤——譯者注)取負:
gl.glRotatef(90f, -1.0f, 0.0f, 0.0f); gl.glRotatef(90f, 0.0f, -1.0f, 0.0f); gl.glRotatef(90f, 0.0f, 0.0f, -1.0f);
你還須要逆序執行,以下:
gl.glRotatef(90f, 0.0f, 0.0f, -1.0f); gl.glRotatef(90f, 0.0f, -1.0f, 0.0f); gl.glRotatef(90f, -1.0f, 0.0f, 0.0f);
一系列旋轉操做的順序是很是重要的。
若是你握着一支鉛筆,筆頭和你的大拇指方向相同,以下圖,將鉛筆放在x軸上,讓筆頭指向座標軸的正方向,你的其他四指握起來的方向就是沿着這個座標軸旋轉的正方向。
因爲平移和旋轉操做都是基於網格本身的座標系統,因此記住平移和旋轉操做的順序是很重要的事情。
若是你先對網格進行了平移操做,而後又進行旋轉,那麼平移是基於當前的網格座標狀態,而旋轉則是基於新的座標。
若是你先進行了旋轉操做,而後進行平移,那麼平移操做將會在旋轉以後的座標基礎上進行。
public abstract void glScalef(float x, float y, float z)
縮放,見文之一,不解釋。它可沿任意座標軸方向上獨立進行。縮放和全部的頂點都乘以一個縮放因子的效果是同樣的。在下圖中,咱們用gl.glScalef(2f, 2f, 2f)進行縮放,這也就至關於全部的頂點座標都乘以2.
縮放和平移的相互順序也很重要。若是你在縮放以前進行了平移,那麼平移的結果不受影響。以下面的例子,咱們先平移了兩個單位,而後縮放0.5個單位。
gl.glTranslatef(2, 0, 0); gl.glScalef(0.5f, 0.5f, 0.5f);
可是若是你先進行了縮放,而後平移,你會獲得一個徹底不一樣的結果。由於你先縮放了網格的座標系統,而後才平移,因此你平移操做不會和以前那樣移動一樣的尺度。因此若是你先縮放了0.5個單位,而後平移兩個單位,結果將會表現爲像平移了一個單位同樣。
gl.glScalef(0.5f, 0.5f, 0.5f); gl.glTranslatef(2, 0, 0);
當你進行平移,旋轉,縮放操做的時候,這些操做並非在同一座標條件下進行的,每次變換都會基於前一次變換,因此你可能須要復位位置。
public abstract void glLoadIdentity()
glLoadIdentity這個方法是替換當前矩陣爲初始矩陣。這和經過glLoadMatrix來加載下面這個矩陣是同樣的效果:
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
然而有些場景下,你可能不想要初始化整個模型矩陣,你可能須要回到上一次變換以前的狀態。
public abstract void glPushMatrix()
glPushMatrix是備份當前的矩陣,放入棧中。這也就是說你在執行glPushMatrix以後,進行的任何變換都是基於這個副本的。
public abstract void glPopMatrix()
這個方法是把你以前經過glPushMatrix放到棧裏的矩陣拿回來。
一個好的實踐就是在每一幀的開始調用glLoadIdentity,而在以後使用glPushMatrix和glPopMatrix。
如今讓咱們基於這個新知識點作個例子。先作三個四邊形A,B,C。先對他們進行縮放,使得B比A小一半,C比B小一半。而後讓A在屏幕中心繞逆時針方向旋轉。B逆時針繞着A旋轉,C順時針繞着B旋轉,同時逆時針高速自轉。
public void onDrawFrame(GL10 gl) { // 清空屏幕和深度緩存 gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 替換當前矩陣爲初始矩陣 gl.glLoadIdentity(); // 像屏幕裏移動10個單位 gl.glTranslatef(0, 0, -10); // 四邊形 A // 保留當前矩陣 gl.glPushMatrix(); // 逆時針旋轉A gl.glRotatef(angle, 0, 0, 1); // 繪製A square.draw(gl); // 回覆到變換前的狀態 gl.glPopMatrix(); // 四邊形 B // 保留當前矩陣 gl.glPushMatrix(); //旋轉B,而後平移,使它繞着A運動 gl.glRotatef(-angle, 0, 0, 1); // 平移 B. gl.glTranslatef(2, 0, 0); // 縮小到A的50% gl.glScalef(.5f, .5f, .5f); // 繪製B square.draw(gl); // 四邊形 C //保留當前矩陣 gl.glPushMatrix(); // 使其繞着B旋轉 gl.glRotatef(-angle, 0, 0, 1); gl.glTranslatef(2, 0, 0); // 縮小到B的50% gl.glScalef(.5f, .5f, .5f); // 自轉 gl.glRotatef(angle*10, 0, 0, 1); // 繪製C square.draw(gl); // 回覆到C以前的狀態 gl.glPopMatrix(); //回覆到B以前的狀態 gl.glPopMatrix(); // 角度自增 angle++; }
最後不要忘了增長角度這個變量,謝謝提醒。
public class OpenGLRenderer implements Renderer { private Square square; private float angle; // 不要忘了添加這個變量 ...
這篇教程引用以下文獻:
你能夠下載教程的源碼:Tutorial_Part_III
你也能夠檢出代碼:code.google.com
前一篇教程:安卓opengl ES教程之二——建立多邊形
後一篇教程:安卓opengl ES教程之四——添加顏色