獲取示例代碼
上一篇文章中說到了透視和正交兩種投影矩陣,文末提到了三個基本矩陣MVP。本文就以介紹MVP為開頭,然后再詳細講解攝像機的概念。
MVP表示的是模型矩陣(Model),觀察矩陣(View),投影矩陣(Projection)。投影矩陣介紹過了。模型矩陣針對的是單個3D模型,渲染每一個3D模型前,需要將各自的模型矩陣傳遞給Vertex Shader。觀察矩陣針對的是場景中的所有物體,當觀察矩陣改變時,所有頂點的位置都會受到影響,就好像你移動現實世界的攝像機,拍攝到的場景就會變化一樣。所以觀察矩陣可以理解為OpenGL 3D世界中的攝像機。我們有了攝像機這個變換矩陣之后,就可以很方便的在3D世界中游覽,就像第一人稱視角游戲中一樣。
大概了解MVP之后,我們開始使用代碼實現它們。首先要修改一下Vertex Shader。
attribute vec4 position;
attribute vec4 color;
uniform float elapsedTime;
uniform mat4 projectionMatrix;
uniform mat4 cameraMatrix;
uniform mat4 modelMatrix;
varying vec4 fragColor;
void main(void) {
fragColor = color;
mat4 mvp = projectionMatrix * cameraMatrix * modelMatrix;
gl_Position = mvp * position;
}
我把之前的uniform transform
換成了三個變換矩陣projectionMatrix
,cameraMatrix
,modelMatrix
,它們分別是投影矩陣,觀察矩陣,模型矩陣。將它們相乘projectionMatrix * cameraMatrix * modelMatrix
,結果乘以position
賦值給gl_Position
。注意相乘的順序,這個順序的結果是先進行模型矩陣變換,再是觀察矩陣,最后是投影矩陣變換。這樣Vertex Shader中的MVP就實現完了,很簡單是不是。
回到OC代碼,我將之前的屬性transform換成了4個變換矩陣,分別是兩個M和VP。本文的例子將繪制兩個矩形,所以我為它們分別定義了模型矩陣modelMatrix1
和modelMatrix2
。
@property (assign, nonatomic) GLKMatrix4 projectionMatrix; // 投影矩陣
@property (assign, nonatomic) GLKMatrix4 cameraMatrix; // 觀察矩陣
@property (assign, nonatomic) GLKMatrix4 modelMatrix1; // 第一個矩形的模型變換
@property (assign, nonatomic) GLKMatrix4 modelMatrix2; // 第二個矩形的模型變換
接下來初始化這些屬性。
// 使用透視投影矩陣
float aspect = self.view.frame.size.width / self.view.frame.size.height;
self.projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(90), aspect, 0.1, 100.0);
// 設置攝像機在 0,0,2 坐標,看向 0,0,0點。Y軸正向為攝像機頂部指向的方向
self.cameraMatrix = GLKMatrix4MakeLookAt(0, 0, 2, 0, 0, 0, 0, 1, 0);
// 先初始化矩形1的模型矩陣為單位矩陣
self.modelMatrix1 = GLKMatrix4Identity;
// 先初始化矩形2的模型矩陣為單位矩陣
self.modelMatrix2 = GLKMatrix4Identity;
投影矩陣使用了透視投影進行初始化。兩個模型矩陣初始化為單位矩陣。本文的主角觀察矩陣初始化為攝像機在 0,0,2 坐標,看向 0,0,0點,向上朝向0,1,0。GLKMatrix4MakeLookAt
提供了快捷創建觀察矩陣的方法,需要傳遞9個參數,攝像機的位置eyeX,eyeY,eyeZ,攝像機看向的點centerX,centerY,centerZ,攝像機向上的朝向upX, upY, upZ。改變這幾個參數就能控制攝像機在3D世界中通過不同角度拍攝物體。
我把上一篇的剖面示意圖做了一下修改。
圖中的lookAt就是center。
我們可以這么理解觀察矩陣。在觀察矩陣的作用下,透視矩陣的原點變成了攝像機的位置eye。up決定了攝像機圍繞eye和lookAt形成的軸(本例中就是Z軸)的旋轉角度,讀者可以修改本例的中的up值看看效果。lookAt決定了攝像機能看到的區域,可以看做是控制攝像機在Y軸和X軸上的旋轉角度。
在第一人稱的游戲中,只要控制lookAt的位置就可以實現360度查看周邊景物的效果,后面介紹到渲染3D場景的時候會深入講解。
初始化完后在update中為這些矩陣賦新的值。
- (void)update {
[super update];
float varyingFactor = (sin(self.elapsedTime) + 1) / 2.0; // 0 ~ 1
self.cameraMatrix = GLKMatrix4MakeLookAt(0, 0, 2 * (varyingFactor + 1), 0, 0, 0, 0, 1, 0);
GLKMatrix4 translateMatrix1 = GLKMatrix4MakeTranslation(-0.7, 0, 0);
GLKMatrix4 rotateMatrix1 = GLKMatrix4MakeRotation(varyingFactor * M_PI * 2, 0, 1, 0);
self.modelMatrix1 = GLKMatrix4Multiply(translateMatrix1, rotateMatrix1);
GLKMatrix4 translateMatrix2 = GLKMatrix4MakeTranslation(0.7, 0, 0);
GLKMatrix4 rotateMatrix2 = GLKMatrix4MakeRotation(varyingFactor * M_PI, 0, 0, 1);
self.modelMatrix2 = GLKMatrix4Multiply(translateMatrix2, rotateMatrix2);
}
float varyingFactor = (sin(self.elapsedTime) + 1) / 2.0;
的值從0到1。
攝像機的Z軸坐標為2 * (varyingFactor + 1)
,從2到4。
第一個矩形向左偏移0.7,繞Y軸旋轉varyingFactor * M_PI * 2
,從0到360度。
第二個矩形向右偏移0.7,繞Z軸旋轉varyingFactor * M_PI * 2
,從0到360度。
最后給uniform賦值。
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
[super glkView:view drawInRect:rect];
GLuint projectionMatrixUniformLocation = glGetUniformLocation(self.shaderProgram, "projectionMatrix");
glUniformMatrix4fv(projectionMatrixUniformLocation, 1, 0, self.projectionMatrix.m);
GLuint cameraMatrixUniformLocation = glGetUniformLocation(self.shaderProgram, "cameraMatrix");
glUniformMatrix4fv(cameraMatrixUniformLocation, 1, 0, self.cameraMatrix.m);
GLuint modelMatrixUniformLocation = glGetUniformLocation(self.shaderProgram, "modelMatrix");
// 繪制第一個矩形
glUniformMatrix4fv(modelMatrixUniformLocation, 1, 0, self.modelMatrix1.m);
[self drawRectangle];
// 繪制第二個矩形
glUniformMatrix4fv(modelMatrixUniformLocation, 1, 0, self.modelMatrix2.m);
[self drawRectangle];
}
先給uniform projectionMatrix
和uniform cameraMatrix
賦值。每個矩形繪制之前,再將各自的modelMatrix賦值給uniform modelMatrix
,就像開頭說的那樣,每個3D模型有自己的模型變換。
最終效果如下。
本篇主要介紹了攝像機(觀察矩陣),三大基本矩陣MVP的概念。下一篇小試牛刀,開始渲染真正的3D物體-正方體。