獲取示例代碼
在介紹本文的代碼之前,先要了解一個概念:矩陣。學過線性代數的朋友應該都知道矩陣相當于是一個二維數組,有自己的運算規則。下面就通過幾個例子簡單了解一下矩陣的特性。
3X3矩陣的加法
從圖中可以看出3X3矩陣就像是一個3X3的表格,每個單元格中填寫一個數。它的加法就是把兩個矩陣對應位置的元素加起來放在結果矩陣對應的位置上。那么如果相加的兩個矩陣尺寸不一樣怎么辦?答案是無法運算。矩陣的加減要求兩邊的矩陣必須尺寸相等。
下面是減法的運算,和加法相似,很好理解。
接下來是乘法。
乘法稍微有些復雜,所以我用符號來代替數值,方便觀察規律。我們看結果的第一行第一列aj + bm + cp
,它就是左邊的矩陣第一行和右邊的矩陣第一列逐個相乘再相加的結果。
再看結果的第二行第一列
aj + bm + cp
,它就是左邊的矩陣第二行和右邊的矩陣第一列逐個相乘再相加的結果。
差不多已經可以總結出規律了,結果矩陣的第n行第m列的結果就是左邊的矩陣第n行和右邊的矩陣第m列逐個相乘再相加的結果。讀者可以看看其他的值是不是這樣計算出來的。
最后說一下除法,矩陣的除法有些特殊,比如說B/A,可以換算成B*inv(A)。inv(A)是A的逆矩陣,因為不是所有矩陣都有逆矩陣,所以除法在矩陣計算中并不總是可用。逆矩陣的求解比較復雜,這里暫時就不解釋了。本文目前也沒有用到求逆矩陣的地方。如果讀者感興趣的話,可以自行百度或者翻一翻以前的線性代數課本。
變換矩陣
說完了矩陣,那么什么是變換矩陣呢?在圖形繪制過程中,有三種變換,分別是平移,縮放,旋轉。如果我們想要用代碼表示一個3D環境中的變換需要幾個變量呢,首先要有平移tx, ty, tz
,然后是縮放sx, sy, sz
,最后是旋轉rx, ry, rz
。在渲染的時候把這些變量附加到原始的位置數據上就可以實現變換了。這種方式雖然可行但不夠好,尤其是在GPU上這種方式產生的運算負擔遠大于使用矩陣。
attribute vec4 position;
attribute vec4 color;
uniform float elapsedTime;
uniform mat4 transform;
varying vec4 fragColor;
void main(void) {
fragColor = color;
gl_Position = transform * position;
}
這是本文代碼例子中的Vertex Shader,新增了一個uniform
uniform mat4 transform;
,mat4
這個類型前文有提到過,4X4的矩陣。它是Shader內置的類型,支持直接加減乘等操作。使用矩陣會產生更少的運算指令,GPU可以更好的優化運算過程。那么應該怎么使用呢?接下來我就一一介紹每一種變換矩陣。
平移矩陣
假設有一個點(1, 2, 3)
,經過大小為(1, 2, 3)
的平移,最終必定會平移到(1+1, 2+2, 3+3)
的位置。使用矩陣計算如下。
這里補充一點,如果左邊的矩陣的列數等于右邊的矩陣的行數,它們就可以相乘,結果矩陣的行數等于左邊矩陣的行數,列數等于右邊矩陣的列數。
平移矩陣就是一個4X4的單位矩陣的第4行的前三個元素用tx,ty,tz填充之后的矩陣。下面就是一個單位矩陣。
使用
GLKMatrix4 translateMatrix = GLKMatrix4MakeTranslation(tx, ty, tz);
可以得到一個平移矩陣。GLKMatrix4MakeTranslation
位于GLKit
中。
縮放矩陣
縮放矩陣的三個縮放元素sx,sy,sz,分布在從左到右的對角線上,矩陣相乘后位置的x,y,z分別乘以了sx,sy,sz,從而實現了縮放。
代碼實現如下。
GLKMatrix4 scaleMatrix = GLKMatrix4MakeScale(sx, sy, sz);
旋轉矩陣
旋轉矩陣相比于上面兩個略微有些復雜,旋轉包含兩個重要元素,旋轉的角度,繞什么軸旋轉。具體原理可以參考三維空間中的旋轉:旋轉矩陣、歐拉角。代碼實現如下。
GLKMatrix4 rotateMatrix = GLKMatrix4MakeRotation(M_PI/2 , 0.0, 0.0, 1.0);
M_PI/2是弧度,0.0,0.0,1.0是旋轉軸的向量。
綜合三個矩陣
現在我們得到了三個矩陣,接下來就是把它們相乘。
self.transformMatrix = GLKMatrix4Multiply(translateMatrix, rotateMatrix);
self.transformMatrix = GLKMatrix4Multiply(self.transformMatrix, scaleMatrix);
注意相乘的順序translateMatrix * rotateMatrix * scaleMatrix
,這樣可以保證先縮放再旋轉,最后再平移。如果先平移再縮放,點的位置已經改變,縮放出來的結果自然就不對了。
代碼實現
最后回到本文的代碼實現中來,我把chapter4的代碼整理了一下,公用的東西移到了基類GLBaseViewController
里,這樣可以更加專注于要重點介紹的知識點。目前ViewController.m
中代碼如下。
//
// ViewController.m
// OpenGLESDemo
//
// Created by wangyang on 15/8/28.
// Copyright (c) 2015年 wangyang. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@property (assign, nonatomic) GLKMatrix4 transformMatrix;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.transformMatrix = GLKMatrix4Identity;
}
#pragma mark - Update Delegate
- (void)update {
[super update];
float varyingFactor = sin(self.elapsedTime);
GLKMatrix4 scaleMatrix = GLKMatrix4MakeScale(varyingFactor, varyingFactor, 1.0);
GLKMatrix4 rotateMatrix = GLKMatrix4MakeRotation(varyingFactor , 0.0, 0.0, 1.0);
GLKMatrix4 translateMatrix = GLKMatrix4MakeTranslation(varyingFactor, 0.0, 0.0);
// transformMatrix = translateMatrix * rotateMatrix * scaleMatrix
// 矩陣會按照從右到左的順序應用到position上。也就是先縮放(scale),再旋轉(rotate),最后平移(translate)
// 如果這個順序反過來,就完全不同了。從線性代數角度來講,就是矩陣A乘以矩陣B不等于矩陣B乘以矩陣A。
self.transformMatrix = GLKMatrix4Multiply(translateMatrix, rotateMatrix);
self.transformMatrix = GLKMatrix4Multiply(self.transformMatrix, scaleMatrix);
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
[super glkView:view drawInRect:rect];
GLuint transformUniformLocation = glGetUniformLocation(self.shaderProgram, "transform");
glUniformMatrix4fv(transformUniformLocation, 1, 0, self.transformMatrix.m);
[self drawTriangle];
}
#pragma mark - Draw Many Things
- (void)drawTriangle {
static GLfloat triangleData[36] = {
0, 0.5f, 0, 1, 0, 0, // x, y, z, r, g, b,每一行存儲一個點的信息,位置和顏色
-0.5f, 0.0f, 0, 0, 1, 0,
0.5f, 0.0f, 0, 0, 0, 1,
0, -0.5f, 0, 1, 0, 0,
-0.5f, 0.0f, 0, 0, 1, 0,
0.5f, 0.0f, 0, 0, 0, 1,
};
[self bindAttribs:triangleData];
glDrawArrays(GL_TRIANGLES, 0, 6);
}
@end
Vertex Shader
attribute vec4 position;
attribute vec4 color;
uniform float elapsedTime;
uniform mat4 transform;
varying vec4 fragColor;
void main(void) {
fragColor = color;
gl_Position = transform * position;
}
代碼中每一次update計算新的變換矩陣,渲染時把值傳遞給Vertex Shader的uniform mat4 transform
,Vertex Shader把原始位置和transform相乘,得出新的位置。注意,因為transform是mat4,所以給uniform賦值時使用的是glUniformMatrix4fv
。代碼效果如下。
本篇主要介紹了什么是變換矩陣,如何使用變換矩陣以及怎樣和Vertex Shader配合。下一篇就要開始介紹3D渲染最基本的技術,透視投影矩陣。