從數(shù)學(xué)上說(shuō),變換就是在兩個(gè)坐標(biāo)系之間轉(zhuǎn)換頂點(diǎn)坐標(biāo)。每個(gè)坐標(biāo)系都是相對(duì)于其他的參照坐標(biāo)系定義的。對(duì)于OpenGL ES來(lái)說(shuō),最終的參照坐標(biāo)系是在一個(gè)像素顏色渲染緩存中的像素位置的2D數(shù)組。
OpenGL ES中的四種基本變換:平移(translation)、旋轉(zhuǎn)(rotation)?;咀儞Q的聯(lián)合決定了在一個(gè)新的坐標(biāo)系中的每一個(gè)頂點(diǎn)位置是怎么轉(zhuǎn)換為參考坐標(biāo)系中的一個(gè)位置的。四個(gè)基本變換足以產(chǎn)生無(wú)窮的坐標(biāo)系。
每個(gè)基本變換對(duì)應(yīng)于矩陣的一個(gè)簡(jiǎn)單變化。定義一個(gè)與參考坐標(biāo)系相同的坐標(biāo)系的矩陣叫做單位矩陣。任意兩個(gè)矩陣可以在一個(gè)聯(lián)級(jí)(又叫矩陣乘法)操作中結(jié)合起來(lái)以產(chǎn)生一個(gè)新矩陣,這個(gè)新矩陣包含了兩個(gè)矩陣的所有變換。實(shí)際上,每個(gè)基本變換會(huì)產(chǎn)生一個(gè)簡(jiǎn)單的矩陣,然后把這個(gè)簡(jiǎn)單的矩陣與當(dāng)前變換矩陣連接起來(lái)以產(chǎn)生一個(gè)新的當(dāng)前矩陣。
定義全局
平移
通過(guò)相對(duì)于參考坐標(biāo)系的原點(diǎn)移動(dòng)新坐標(biāo)系的原點(diǎn)、平移定義了一個(gè)新的坐標(biāo)系。平移不會(huì)影響坐標(biāo)軸的單位長(zhǎng)度,平移不會(huì)改變坐標(biāo)軸相對(duì)于參考坐標(biāo)系的方向。
GLKit提供了GLKMatrix4MakeTranslation(float x,float y,float z)函數(shù),這個(gè)函數(shù)通過(guò)平移一個(gè)單位矩陣來(lái)返回一個(gè)定義了坐標(biāo)系的新矩陣。x、y、z參數(shù)指定了新坐標(biāo)原點(diǎn)沿著當(dāng)前參考坐標(biāo)系的每個(gè)軸移動(dòng)的單位數(shù)。
修改代碼,看一下GLKit提供的GLKMatrix4MakeTranslation函數(shù)實(shí)現(xiàn)平移效果。
在頂點(diǎn)著色器中新增了一個(gè)uniform mat4 transform。 mat4這個(gè)類型前文有提到過(guò),4X4的矩陣。它是Shader內(nèi)置的類型,支持直接加減乘等操作。使用矩陣會(huì)產(chǎn)生更少的運(yùn)算指令,GPU可以更好的優(yōu)化運(yùn)算過(guò)程。
attribute vec4 position;
attribute vec4 color;
varying vec4 fColor;
uniform mat4 transform;
void main(void) {
fColor = color;
gl_Position = transform * position;
}
因?yàn)槲覀兊臄?shù)據(jù)更新在update里,而賦值繪制在glkView:(GLKView *)view drawInRect:(CGRect)rect中,所以需要定義為全局:
GLKMatrix4 transformMatrix;
// 初始化為單位矩陣,不對(duì)圖形產(chǎn)生任何變換
transformMatrix = GLKMatrix4Identity;
這里的GLKMatrix4Identity相當(dāng)于下面的矩陣:
接下來(lái)在應(yīng)用中創(chuàng)建一個(gè)平移變換矩陣:
// 定義一個(gè)在值域-1和1之間的局部變量
GLfloat elValue = sinf(changeValue);
// 創(chuàng)建平移變換矩陣,這里只改變了X坐標(biāo)
GLKMatrix4 translateMatrix =
GLKMatrix4MakeTranslation(elValue, 0.0, 0.0);
把這個(gè)變換矩陣賦值到uniform mat4 transform上:
GLuint transformUniformLocation = glGetUniformLocation(program, "transform");
glUniformMatrix4fv(transformUniformLocation, 1, 0, transformMatrix.m);
繪制之后看下效果:
因?yàn)槲覀冎皇瞧揭屏俗鴺?biāo)X值,Y和Z值沒(méi)有變,所以三角形只是沿著X軸平行移動(dòng)。
上面的elValue值域?yàn)閇-1,1],先看一下為-1的時(shí)候,我們?nèi)∑渲幸粋€(gè)頂點(diǎn)(0,0.5,0)經(jīng)過(guò)(-1,0,0)的平移之后為(-1,0.5,0),我們看一下矩陣計(jì)算:
如果平移經(jīng)過(guò)(1,0,0)的平移之后就為:(1 0.5 0 1),這就是我們上面看到的三角形的上頂點(diǎn)在屏幕之間移動(dòng)了。
通過(guò)上面的矩陣計(jì)算可以發(fā)現(xiàn),平移矩陣就是在一個(gè)4x4的變換矩陣中填充第4行前面三個(gè)位置:
縮放
縮放是通過(guò)相對(duì)于參考坐標(biāo)系的坐標(biāo)軸的單位長(zhǎng)度改變新坐標(biāo)系的坐標(biāo)軸的單位長(zhǎng)度來(lái)定義一個(gè)新坐標(biāo)系。縮放坐標(biāo)系與參考坐標(biāo)系使用同一個(gè)原點(diǎn)。坐標(biāo)軸的方向通常不會(huì)改變。不過(guò),通過(guò)一個(gè)負(fù)值所做的縮放就會(huì)翻轉(zhuǎn)坐標(biāo)軸的方向。
GLKit提供了GLKMatrix4MakeScale(float x,float y,float z)函數(shù),這個(gè)函數(shù)會(huì)通過(guò)擴(kuò)大或者縮小一個(gè)單位矩陣的任意坐標(biāo)軸的單位長(zhǎng)度來(lái)返回一個(gè)定義了坐標(biāo)系的矩陣。x、y和z參數(shù)指定了用來(lái)擴(kuò)大或者縮小每個(gè)軸的單位長(zhǎng)度的因素。GLKMatrix4Scale(float x,float y,float z)函數(shù)通過(guò)按指定的因數(shù)縮放作為參數(shù)傳入矩陣來(lái)返回一個(gè)定義了坐標(biāo)系的新矩陣。
GLKMatrix4 scaleMatrix = GLKMatrix4MakeScale(elValue, elValue, 1.0);
// 很明顯這個(gè)矩陣會(huì)改變頂點(diǎn)坐標(biāo)的x、y值
看一下最終效果:
簡(jiǎn)單分析:elValue為正弦函數(shù)的值,所以在-1和1之間,在elValue為1是,不會(huì)對(duì)原數(shù)據(jù)有影響,當(dāng)為-1時(shí),就會(huì)翻轉(zhuǎn)坐標(biāo)軸的方向。所以最終效果就是上圖那樣,從原圖縮小到0,然后負(fù)數(shù)增大,就是倒過(guò)來(lái)了。
假定還是點(diǎn)(0.0,0.5,0.0)的x和y值在縮小0.5之后為(0.0,0.25,0.0),看下矩陣計(jì)算:
旋轉(zhuǎn)
旋轉(zhuǎn)是通過(guò)相對(duì)于參考坐標(biāo)系坐標(biāo)軸的方向旋轉(zhuǎn)新坐標(biāo)系的坐標(biāo)軸來(lái)定義一個(gè)新坐標(biāo)系。旋轉(zhuǎn)的坐標(biāo)系會(huì)與參考坐標(biāo)系使用同一個(gè)原點(diǎn)。旋轉(zhuǎn)不會(huì)影響坐標(biāo)軸的單位長(zhǎng)度,只有坐標(biāo)軸的方向會(huì)發(fā)生變化。
GLKit提供了GLKMatrix4MakeRotation(float angleRedians, float x, float y, float z)函數(shù),這個(gè)函數(shù)通過(guò)旋轉(zhuǎn)一個(gè)單位矩陣來(lái)返回一個(gè)定義了坐標(biāo)系的新矩陣。angleRedians參數(shù)指定了要旋轉(zhuǎn)的弧度數(shù)。使用GLKMathDegreesToRadians(float degrees)函數(shù)可以把角度轉(zhuǎn)換成弧度。x、y和z參數(shù)用于指定當(dāng)前坐標(biāo)系的哪一個(gè)軸作為旋轉(zhuǎn)的輪轂。
例如:GLKMatrix4MakeRotation(GLKMathDegreesToRadians(60.0) , 1.0, 0.0, 0.0)會(huì)沿著一個(gè)特定的坐標(biāo)系的X軸旋轉(zhuǎn)60度來(lái)產(chǎn)生一個(gè)新的坐標(biāo)系。
相對(duì)于平移和縮放,旋轉(zhuǎn)要復(fù)雜一些。旋轉(zhuǎn)包含兩個(gè)重要元素,旋轉(zhuǎn)的角度,繞什么軸旋轉(zhuǎn)。
GLKMatrix4 rotateMatrix = GLKMatrix4MakeRotation(elValue , 0.0, 0.0, 1.0);
// 繞Z軸旋轉(zhuǎn)
看下效果圖:
除了利用GLKit提供的GLKMatrix4MakeRotation函數(shù)直接生成,我們還可以自己定義4x4的旋轉(zhuǎn)矩陣:
// 繞x軸
transformMatrix = GLKMatrix4Make(1.0, 0.0, 0.0, 0.0,
0.0, cos(elValue), -sin(elValue), 0.0,
0.0, sin(elValue), cos(elValue), 0.0,
0.0, 0.0, 0.0, 1.0);
// 繞y軸
transformMatrix = GLKMatrix4Make(cos(elValue),0.0, sin(elValue), 0.0,
0.0, 1.0, 0.0, 0.0,
-sin(elValue),0.0, cos(elValue), 0.0,
0.0, 0.0, 0.0, 1.0);
// 繞z軸
transformMatrix = GLKMatrix4Make(cos(elValue),-sin(elValue), 0.0, 0.0,
sin(elValue), cos(elValue), 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0);
關(guān)于旋轉(zhuǎn)矩陣可以參考這篇三維空間中的旋轉(zhuǎn):旋轉(zhuǎn)矩陣、歐拉角
下面結(jié)合三種變換,直接相乘就可以,但是要注意順序:平移旋轉(zhuǎn)縮放。這樣可以保證先縮放,再旋轉(zhuǎn),最后平移。
// 縮放
GLKMatrix4 scaleMatrix = GLKMatrix4MakeScale(elValue, elValue, 1.0);
// 旋轉(zhuǎn)
GLKMatrix4 rotateMatrix = GLKMatrix4MakeRotation(elValue , 0.0, 0.0, 1.0);
// 平移
GLKMatrix4 translateMatrix = GLKMatrix4MakeTranslation(elValue, elValue, 0.0);
/*
transformMatrix = translateMatrix * rotateMatrix * scaleMatrix
矩陣會(huì)按照從右到左的順序應(yīng)用到position上。也就是先縮放(scale),再旋轉(zhuǎn)(rotate),最后平移(translate)
如果這個(gè)順序反過(guò)來(lái),就完全不同了。從線性代數(shù)角度來(lái)講,就是矩陣A乘以矩陣B不等于矩陣B乘以矩陣A。
*/
transformMatrix = GLKMatrix4Multiply(translateMatrix, rotateMatrix);
transformMatrix = GLKMatrix4Multiply(transformMatrix, scaleMatrix);
看下最終包含了平移、縮放、旋轉(zhuǎn)的效果:
關(guān)于OpenGL ES的三種基本變換就說(shuō)到這里,其實(shí)還有兩個(gè)重要的投影變換 -- 透視投影和正交投影。就不在本篇繼續(xù)說(shuō)了,盡量控制一下篇幅,就在下一篇講吧!