iOS開(kāi)發(fā)學(xué)習(xí)OpenGL ES系列 -- 變換矩陣

從數(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ō)了,盡量控制一下篇幅,就在下一篇講吧!

本例代碼:LearningOpenGL ES GitHub

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 前言 OpenGL渲染3D模型離不開(kāi)空間幾何的數(shù)學(xué)理論知識(shí),而本篇文章的目的就是對(duì)空間幾何進(jìn)行簡(jiǎn)單的介紹,并對(duì)...
    RichardJieChen閱讀 7,555評(píng)論 1 11
  • 深度渲染緩存 深度緩存是一個(gè)可選的輸出緩存,并且與像素顏色渲染緩存相似,幾乎所有的OpenGL ES都使用深度緩存...
    McDan閱讀 1,767評(píng)論 0 0
  • 目錄 一、分析拉伸的原因 二、準(zhǔn)備知識(shí),三維變換 三、OpenGL 下的三維變換 四、修復(fù)拉伸問(wèn)題 一、分析拉伸的...
    半紙淵閱讀 2,428評(píng)論 3 0
  • 1. 深度渲染緩存(Depth Render Buffer) 三角形、線段和點(diǎn)是按它們被GPU處理的順序被渲染的。...
    碧玉小瑕閱讀 475評(píng)論 0 0
  • >*很不幸,沒(méi)人能告訴你母體是什么,你只能自己體會(huì)* --駭客帝國(guó) 在第四章“可視效果”中,我們研究了一些增強(qiáng)圖層...
    夜空下最亮的亮點(diǎn)閱讀 1,732評(píng)論 0 2

友情鏈接更多精彩內(nèi)容