學習之路系列
今天我們實現一個三維空間中物體的變換操作
本篇主要內容
三維坐標變換
效果圖
實現過程
首先我們跟之前一樣給出以下頂點數據
//4個頂點(分別表示xyz軸)
static const float Vertices[] = {
-0.5, -0.5, 0, //左下
0.5, -0.5, 0, //右下
-0.5, 0.5, 0, //左上
0.5, 0.5, 0, //右上
};
//4個點的顏色(分別表示RGBA值)
static const float Colors[] = {
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
0, 0, 0, 1,
};
然后進行渲染, 中間的渲染緩沖區、幀緩沖區、編譯著色器
我們暫時先省略,有不明白的小伙伴可以參考這篇文章
- (void)render:(CADisplayLink *)displayLink {
//用指定的顏色清除,清除顏色被設置為(0.5f, 0.5f, 0.5f, 1.0f), 所以為黑色
glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//設定窗口的范圍(如果不是很明白, 可以自己動手修改下試試)
//他這個是左下角為(0,0) 右上角為(width,height)
glViewport(0, 0, _width, _height);
//指定了渲染時索引值為 index 的頂點屬性數組的數據格式和位置。
/*
* indx:指定要修改的頂點屬性的索引值
* size:指定每個頂點屬性的組件數量。(必須坐標xyz軸就是3, 顏色rgba就是4)
* type:指定數組中每個組件的數據類型。(一般為GL_FLOAT)
* normalized:一般為GL_FALSE
* stride:指定連續頂點屬性之間的偏移量。如果為0,那么頂點屬性會被理解為:它們是緊密排列在一起的。初始值為0。
* ptr:指向數據的指針
*/
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, Vertices);
glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, 0, Colors);
glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(Vertices) / (sizeof(int) * 3));
//把緩沖區的數據呈現到UIView上
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
渲染結果
我們接下來需要把他弄成正方形的,由于還沒有學習投影,我們先改動渲染的窗口來達成這一目的
將render:
方法中的
glViewport(0, 0, _width, _height);
改成
glViewport(0, 0, _width, _width);
看下效果
這樣子就實現了正方形了
三維變換
給大家介紹一個網址LearnOpenGL, 上面對三維變換的講解很詳細, 下面就來實現下這三種操作(縮放、位移、旋轉)
系統已經提供了矩陣操作的框架, 網上也有一些大神提供的矩陣操作的第三方庫,我們可以直接拿到使用,不會矩陣的小伙伴也可以玩轉三維操作O(∩_∩)O。這里我們直接使用系統的
我們先修改下頂點著色器
attribute vec4 Position;
attribute vec4 InColor;
varying vec4 OutColor;
uniform mat4 Model; //new
void main(void){
OutColor = InColor;
// gl_Position = Position;
gl_Position = Model * Position;
}
并在編譯著色器(compileShaders:)
的地方獲取著色器中的ModelView
變量
_modelView = glGetUniformLocation(_program, "ModelView");
下面的的代碼直接在
-
縮放
GLKMatrix4 modelView = GLKMatrix4MakeScale(_transformScale[0],
_transformScale[1],
_transformScale[2]);
//給著色器變量賦值
/*
location: 變量的位置
count : 要更改變量的個數
transpose: 是否需要轉置
value : 給出對應count個元素的指針
*/
glUniformMatrix4fv(_modelView, 1, GL_FALSE, modelView.m);
GLKMatrix4MakeScale
是系統初始化縮放矩陣的函數
看下效果:
-
位移
GLKMatrix4 modelView = GLKMatrix4MakeTranslation(_transformTranslation[0],
_transformTranslation[1],
_transformTranslation[2]);
//給著色器變量賦值
/*
location: 變量的位置
count : 要更改變量的個數
transpose: 是否需要轉置
value : 給出對應count個元素的指針
*/
glUniformMatrix4fv(_modelView, 1, GL_FALSE, modelView.m);
GLKMatrix4MakeTranslation
是系統初始化平移矩陣的函數
看下效果:
-
旋轉
GLKMatrix4 modelView = GLKMatrix4MakeRotation(_transformRotation[0],1,0,0);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[1], 0, 1, 0);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[2], 0, 0, 1);
//給著色器變量賦值
/*
location: 變量的位置
count : 要更改變量的個數
transpose: 是否需要轉置
value : 給出對應count個元素的指針
*/
glUniformMatrix4fv(_modelView, 1, GL_FALSE, modelView.m);
旋轉操作需要將3個軸(x、y、z)
分開單獨操作
上面的旋轉函數需要用弧度。
角度 = 弧度 * (180.0f / PI)
弧度 = 角度 * (PI / 180.0f)
看下效果:
-
矩陣組合
學過矩陣的童鞋都知道,矩陣相乘不遵守交換律
縮放、位移、旋轉
三種矩陣按照不同的順序相乘會產生不一樣的效果
舉個例子: 先縮放
在位移
那么當你縮放完以后,你的位移向量一樣會被縮放,比如你要向右移動兩個單位,這時候縮小兩倍,那么的平移的向量一樣會縮小兩倍,就變成了一個單位
順序的話一般都沒有硬性規定, 看需求, 本文順序位移->旋轉->縮放
下面我們把三種效果組合試一下
GLKMatrix4 modelView = GLKMatrix4MakeTranslation(_transformTranslation[0],
_transformTranslation[1],
_transformTranslation[2]);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[0], 1, 0, 0);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[1], 0, 1, 0);
modelView = GLKMatrix4Rotate(modelView, _transformRotation[2], 0, 0, 1);
modelView = GLKMatrix4Scale(modelView,
_transformScale[0],
_transformScale[1],
_transformScale[2]);
//給著色器變量賦值
/*
location: 變量的位置
count : 要更改變量的個數
transpose: 是否需要轉置
value : 給出對應count個元素的指針
*/
glUniformMatrix4fv(_modelView, 1, GL_FALSE, modelView.m);
看下效果:
本來想著這篇文章到這樣就結束了,但是回頭一看發現內容有點少,就順便在實現多一個效果吧
先看下下面這段頂點數據
//4個頂點(分別表示xyz軸)
static const float Vertices[] = {
-0.5, -0.5, 0, //左下
0.5, -0.5, 0, //右下
-0.5, 0.5, 0, //左上
0.5, 0.5, 0, //右上
};
這里有個Z軸還沒用到的,上面是其中一個面
我們還需要把后面的4個坐標點加上,才能湊夠一個立方體(8個點)
//4個頂點(分別表示xyz軸)
static const float Vertices[] = {
//前面4個坐標
-0.5, -0.5, 0.5,
0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5,
//后面4個坐標
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
-0.5, 0.5, -0.5,
0.5, 0.5, -0.5,
};
//4個點的顏色(分別表示RGBA值)
static const float Colors[] = {
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
0, 0, 0, 1,
1, 0, 0, 1,
0, 1, 0, 1,
0, 0, 1, 1,
0, 0, 0, 1,
};
由于我們只給了8個點的坐標, 所以我們用原來的這個方法有點不合適
glDrawArrays(GL_TRIANGLE_STRIP, 0, sizeof(Vertices) / (sizeof(float) * 3));
我們來介紹另一個方法glDrawElements
, 這個方法跟glDrawArrays
一樣都是渲染
這兩種方法的介紹,可以看下這篇文章
創建索引數組
static const GLubyte Indices[] = {
0, 1, 2,
2, 3, 1,
4, 5, 6,
6, 7, 5,
2, 3, 6,
6, 7, 3,
0, 1, 4,
4, 5, 1,
0, 4, 2,
2, 6, 4,
1, 5, 3,
3, 7, 5
};
渲染
/*
* mode : 渲染的方式
* count : 索引數組元素的數量
* type : 索引值的類型
* indices: 索引數組的指針
*/
glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, Indices);
很明顯的看到,層次有點不對
我們需要新增一個深度緩沖區
深度緩沖區簡單來說就是讓物體的渲染有層次感, 詳解看這篇文章
下面這段代碼需要在渲染緩沖區前調用
/**
* 設置深度緩沖區
*/
- (void)setupDepthBuffer {
glGenRenderbuffers(1, &_depthRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _width, _height);
}
在幀緩沖區中新增一段代碼
/**
* 設置幀緩沖區
*/
- (void)setupFrameBuffer {
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer); //new
}
將render:
中的glClearColor
修改下
glClearColor(0.8f, 0.8f, 0.8f, 1.0f);
// glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_DEPTH_TEST); //new
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //new
效果:
結語
自從把這個三維的立方體實現以后,突然感覺OpenGLES很好玩,雖然在這過程中不斷的踩坑調試,但是實現以后又有了滿滿的信心.(??????)??加油
后面的例子會越來越好玩的,一起一步一步的學下去吧
Demo
1.可以嘗試著把
縮放、位移、旋轉
三種方式的順序換一下,看會有怎么樣子的效果
2.感興趣的小伙伴可以把紋理加上
3.下篇文章我們來實現一個更好玩的坐標系統