【翻譯】安卓opengl ES教程之三——變換

在上一篇教程中咱們主要講的是關於創建一個多邊形。這一篇教程全都是關於變換的——如何移動一個多邊形到任意位置。我會接着上一篇教程而繼續講下去,因此你能夠繼續使用上一篇的源碼或者它的副本。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);

初始化,push,pop矩陣

當你進行平移,旋轉,縮放操做的時候,這些操做並非在同一座標條件下進行的,每次變換都會基於前一次變換,因此你可能須要復位位置。

glLoadIdentity

public abstract void glLoadIdentity()

glLoadIdentity這個方法是替換當前矩陣爲初始矩陣。這和經過glLoadMatrix來加載下面這個矩陣是同樣的效果:

1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

然而有些場景下,你可能不想要初始化整個模型矩陣,你可能須要回到上一次變換以前的狀態。

glPushMatrix

public abstract void glPushMatrix()

glPushMatrix是備份當前的矩陣,放入棧中。這也就是說你在執行glPushMatrix以後,進行的任何變換都是基於這個副本的。

glPopMatrix

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; // 不要忘了添加這個變量
        ...

引用

這篇教程引用以下文獻:

Android Developers

OpenGL ES 1.1 Reference Pages

你能夠下載教程的源碼:Tutorial_Part_III

你也能夠檢出代碼:code.google.com

前一篇教程:安卓opengl ES教程之二——建立多邊形

後一篇教程:安卓opengl ES教程之四——添加顏色

相關文章
相關標籤/搜索