這次要做的是一個(gè)立方體,在學(xué)完紋理之后其實(shí)大家應(yīng)該能自己實(shí)現(xiàn)一個(gè)立方體,也就是把Z軸利用上就可以實(shí)現(xiàn)了。唯一的難點(diǎn)是這里涉及一個(gè)深度測(cè)試,如果不加深度測(cè)試渲染出來(lái)的立方體會(huì)是一個(gè)有缺失面的立方體。現(xiàn)在我們進(jìn)入正題,和以前一樣我們用兩套代碼來(lái)實(shí)現(xiàn)。
學(xué)習(xí)的代碼都在我的github倉(cāng)庫(kù)歡迎大家學(xué)習(xí)指教!
不使用GLKit的方式
一、設(shè)置頂點(diǎn)和紋理數(shù)據(jù)
為了避免重復(fù)同樣的話,前面講過(guò)的知識(shí)點(diǎn)將不再說(shuō)明,我們是在上一篇代碼的基礎(chǔ)之上進(jìn)行修改的。前面我們已經(jīng)做出了一個(gè)紋理,只要用六個(gè)面就可以拼一個(gè)立方體,和單個(gè)紋理面的區(qū)別是空間坐標(biāo)不一樣而已。現(xiàn)將頂點(diǎn)坐標(biāo)數(shù)組寫成以下方式:
// 頂點(diǎn)結(jié)構(gòu)體
typedef struct{
GLfloat position[3];
GLfloat texturePosion[2];
} Vertex;
const Vertex vertexes[] = {
// 頂點(diǎn) 紋理
// 前面
{{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f}}, // 前左上 0
{{-0.5f, -0.5f, 0.5f}, {0.0f, 1.0f}}, // 前左下 1
{{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f}}, // 前右下 2
{{0.5f, 0.5f, 0.5f}, {1.0f, 0.0f}}, // 前右上 3
// 后面
{{-0.5f, 0.5f, -0.5f}, {1.0f, 0.0f}}, // 后左上 4
{{-0.5f, -0.5f, -0.5f}, {1.0f, 1.0f}}, // 后左下 5
{{0.5f, -0.5f, -0.5f}, {0.0f, 1.0f}}, // 后右下 6
{{0.5f, 0.5f, -0.5f}, {0.0f, 0.0f}}, // 后右上 7
// 左面
{{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f}}, // 后左上 8
{{-0.5f, -0.5f, -0.5f}, {0.0f, 1.0f}}, // 后左下 9
{{-0.5f, 0.5f, 0.5f}, {1.0f, 0.0f}}, // 前左上 10
{{-0.5f, -0.5f, 0.5f}, {1.0f, 1.0f}}, // 前左下 11
// 右面
{{0.5f, 0.5f, 0.5f}, {0.0f, 0.0f}}, // 前右上 12
{{0.5f, -0.5f, 0.5f}, {0.0f, 1.0f}}, // 前右下 13
{{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f}}, // 后右下 14
{{0.5f, 0.5f, -0.5f}, {1.0f, 0.0f}}, // 后右上 15
// 上面
{{-0.5f, 0.5f, -0.5f}, {0.0f, 0.0f}}, // 后左上 16
{{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f}}, // 前左上 17
{{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f}}, // 前右上 18
{{0.5f, 0.5f, -0.5f}, {1.0f, 0.0f}}, // 后右上 19
// 下面
{{-0.5f, -0.5f, 0.5f}, {0.0f, 0.0f}}, // 前左下 20
{{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f}}, // 前右下 21
{{-0.5f, -0.5f, -0.5f}, {0.0f, 1.0f}}, // 后左下 22
{{0.5f, -0.5f, -0.5f}, {1.0f, 1.0f}}, // 后右下 23
};
// 頂點(diǎn)索引
const GLbyte indexes[] = {
// 前面
0, 1, 2,
0, 2, 3,
// 后面
4, 5, 6,
4, 6, 7,
// 左面
8, 9, 11,
8, 11, 10,
// 右面
12, 13, 14,
12, 14, 15,
// 上面
16, 17, 18,
16, 18, 19,
// 下面
20, 22, 23,
20, 23, 21,
};
你們可能已經(jīng)看到了,這次的頂點(diǎn)坐標(biāo)和上一篇筆記的不一樣,這里是把頂點(diǎn)對(duì)應(yīng)的紋理坐標(biāo)也寫在一起了。這樣寫我們也可以學(xué)習(xí)另一種方式設(shè)置頂點(diǎn)和紋理坐標(biāo),這里我們還用到了索引,這樣有可以實(shí)現(xiàn)頂點(diǎn)數(shù)據(jù)的重復(fù)利用。
二、設(shè)置VBO(頂點(diǎn)緩存)
這次繪制立方體的時(shí)候有沒(méi)有會(huì)發(fā)現(xiàn)頂點(diǎn)數(shù)據(jù)比原來(lái)多很多!其實(shí)這還算是少的。簡(jiǎn)單的圖形我們可以通過(guò)手寫頂點(diǎn)數(shù)據(jù)來(lái)實(shí)現(xiàn),但是如果是一個(gè)復(fù)雜的圖形就不行了,我們?cè)谕娲笮陀螒虻臅r(shí)候都看過(guò)人物的結(jié)構(gòu)很復(fù)雜,這個(gè)用我們手寫頂點(diǎn)數(shù)據(jù)是不可能實(shí)現(xiàn)的!一般是由設(shè)計(jì)師用建模工具來(lái)實(shí)現(xiàn)的,最終的文件還是頂點(diǎn)數(shù)據(jù)。這些數(shù)據(jù)是在內(nèi)存中的,只有在繪制的時(shí)候才會(huì)從CPU傳給GPU,這種數(shù)據(jù)傳遞本身的性能也不是很高,當(dāng)頂點(diǎn)數(shù)據(jù)很大的時(shí)候?qū)π阅艿挠绊懜黠@。一般的優(yōu)化方案就是將頂點(diǎn)數(shù)據(jù)緩存的GPU的顯存里,這樣不用每次從CPU傳遞頂點(diǎn)數(shù)據(jù)到GPU從而提高圖形的性能。下面我們就用代碼實(shí)現(xiàn)VBO:
/**
設(shè)置頂點(diǎn)緩存
*/
- (void)setupVBO{
// 設(shè)置VBO
// 設(shè)置頂點(diǎn)緩存
GLuint bufferVBO;
glGenBuffers(1, &bufferVBO);
glBindBuffer(GL_ARRAY_BUFFER, bufferVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
// 設(shè)置索引緩存
GLuint bufferIndex;
glGenBuffers(1, &bufferIndex);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferIndex);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);
// 設(shè)置頂點(diǎn)
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
glEnableVertexAttribArray(GLKVertexAttribPosition);
// 設(shè)置紋理坐標(biāo)
glVertexAttribPointer(GLKVertexAttribTexCoord0 , 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
}
三、引入矩陣變換的庫(kù)
在圖像的世界里圖像的放大、縮小、位移都是通過(guò)圖片乘以一個(gè)矩陣來(lái)實(shí)現(xiàn)的(通過(guò)矩陣相乘效率更高)你在iOS開(kāi)發(fā)中是否遇到過(guò)圖形的transform屬性,你打印一下看看里面的結(jié)構(gòu)你就會(huì)明白,這也是一個(gè)矩陣數(shù)據(jù)。這個(gè)涉及線性代數(shù)的東西,你大學(xué)里學(xué)的線性代數(shù)的東西是不是已經(jīng)忘記了?不過(guò)不用怕網(wǎng)上已經(jīng)有很多矩陣計(jì)算相關(guān)的庫(kù),我們直接用就可以了。其實(shí)GLKit里也有矩陣計(jì)算的庫(kù),因?yàn)槲覀冞@里不打算用GLKit所以我們用一個(gè)別人寫好的庫(kù)。可以從這里下載這個(gè)庫(kù)。下載圖片中的這幾個(gè)文件。
四、實(shí)現(xiàn)觸摸來(lái)滾動(dòng)立方體
如果我們不添加讓立方體滾動(dòng)的功能就只能看到一個(gè)面,看不出立方體的效果,而且深度測(cè)試的效果也看不出來(lái)。滾動(dòng)的實(shí)現(xiàn)方式也很簡(jiǎn)單讓其乘以一個(gè)矩陣即可,我們用兩個(gè)變量來(lái)記錄手指在屏幕上劃動(dòng)的量變。代碼如下:
// 限制上下轉(zhuǎn)動(dòng)的角度
#define kLimitDegreeUpDown 40.0
@interface ViewController ()
// 記錄x軸方向上的變量
@property(nonatomic,assign)GLfloat degreeX;
// 記錄y軸方向上的變量
@property(nonatomic,assign)GLfloat degreeY;
@end
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch * touch = touches.anyObject;
CGPoint currentPoint = [touch locationInView:self.view];
CGPoint previousPoint = [touch previousLocationInView:self.view];
self.degreeX += previousPoint.y - currentPoint.y;
// 限制上下轉(zhuǎn)動(dòng)的角度
if (self.degreeX > kLimitDegreeUpDown) {
self.degreeX = kLimitDegreeUpDown;
}
if (self.degreeX < -kLimitDegreeUpDown) {
self.degreeX = -kLimitDegreeUpDown;
}
self.degreeY += previousPoint.x - currentPoint.x;
}
五、設(shè)置視角
可能你會(huì)在上一個(gè)筆記中看到我們創(chuàng)建的紋理是一個(gè)長(zhǎng)方形的,但我們?cè)O(shè)置的坐標(biāo)應(yīng)該出現(xiàn)的是一個(gè)正方形才對(duì)。其實(shí)我以前也糾結(jié)這個(gè)問(wèn)題,查了很多資料都沒(méi)找到相關(guān)說(shuō)明,后來(lái)在我無(wú)意間設(shè)置完視角發(fā)現(xiàn)一切都正常了,所以我們要對(duì)代碼進(jìn)行以下更改:
- 將頂點(diǎn)著色器文件修改如下
attribute vec4 myPosition;
// 添加物體運(yùn)動(dòng)的變換矩陣
uniform mat4 modelView;
// 添加視角的變換矩陣
uniform mat4 projection;
attribute vec2 textureCoordsIn;
varying vec2 textureCoordsOut;
void main()
{
// 將原來(lái)的頂點(diǎn)乘以矩陣數(shù)據(jù),注意乘的前后順序
gl_Position = projection * modelView * myPosition;
textureCoordsOut = textureCoordsIn;
}
- 添加相應(yīng)的槽
// 添加以下成員變量
GLuint _modelViewSlot; // 物體變換的槽
GLuint _projectionSlot; // 攝像機(jī)的槽
// 在原來(lái)設(shè)置槽的位置添加以下代碼
_modelViewSlot = glGetUniformLocation(_program, "modelView");
_projectionSlot = glGetUniformLocation(_program, "projection");
- 用一個(gè)方法來(lái)傳遞視角數(shù)據(jù)和用一個(gè)方法來(lái)傳遞圖形的變換矩陣,該方法在render方法里調(diào)用
- (void)setupPerspactive{
GLfloat aspect = self.view.frame.size.width / self.view.frame.size.height;
ksMatrix4 tempMatrix;
ksMatrixLoadIdentity(&tempMatrix);
// 設(shè)置視角矩陣
ksPerspective(&tempMatrix, 60, aspect, 0.1f, 10.0f);
// 傳遞視角矩陣數(shù)據(jù)
glUniformMatrix4fv(_projectionSlot, 1 , GL_FALSE, (GLfloat *)&tempMatrix.m[0][0]);
}
/**
設(shè)置圖形的變換矩陣
*/
- (void)setupModelViewMatrix{
// 設(shè)置物體的變換
ksMatrixLoadIdentity(&_matrix4);
// 遠(yuǎn)離視野,不然是在視角會(huì)在立方體的中心
ksMatrixTranslate(&_matrix4, 0, 0, -3);
// x方向旋轉(zhuǎn)
ksMatrixRotate(&_matrix4, self.degreeX, 1, 0, 0);
// y方向旋轉(zhuǎn)
ksMatrixRotate(&_matrix4, self.degreeY, 0, 1, 0);
glUniformMatrix4fv(_modelViewSlot, 1, GL_FALSE, (GLfloat *)&_matrix4.m[0][0]);
}
六、實(shí)現(xiàn)視圖的刷新
前面我們的代碼都是只渲染一次,在修改了數(shù)據(jù)后視圖是不會(huì)改變的。為了能及時(shí)渲染每次修改的數(shù)據(jù)我們要用CADisplayLink來(lái)不停渲染數(shù)據(jù)。實(shí)現(xiàn)代碼正如下:
@interface ViewController ()
@property (nonatomic,strong)CADisplayLink * link;
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 設(shè)置視圖刷新
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
修改渲染方法,因?yàn)槲覀兏某闪耸褂盟饕齺?lái)繪制圖形,所以繪制方法也需要修改,修改后的渲染方法如下:
/**
渲染
*/
- (void)render {
[self setupPerspactive];
[self setupModelViewMatrix];
glViewport(0, 0, self.view.frame.size.width, self.view.frame.size.height);
glClearColor(1.0, 1.0, 1.0, 1.0);
glEnable(GL_DEPTH_TEST);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 用索引要設(shè)置要繪制的方法
glDrawElements(GL_TRIANGLES, sizeof(indexes)/sizeof(indexes[0]), GL_UNSIGNED_BYTE, 0);
[_context presentRenderbuffer:GL_RENDERBUFFER];
}
七、實(shí)現(xiàn)深度測(cè)試
通過(guò)以上步驟我們已經(jīng)可實(shí)現(xiàn)一個(gè)半成品了,我們先運(yùn)行看一下結(jié)果。
你可能已經(jīng)看到了問(wèn)題:有的面有缺失。對(duì)于該問(wèn)題是因?yàn)楹箐秩镜拿娓采w了先前渲染的面所造成的,解決方法是設(shè)置深度測(cè)試即可。這樣在渲染的時(shí)候會(huì)檢測(cè)各個(gè)面的前后關(guān)系,在后面被擋住的面將不會(huì)渲染,從而來(lái)解決此問(wèn)題。深度測(cè)試的代碼如下:
- 在原來(lái)設(shè)置渲染緩存的方法里添加深度測(cè)試緩存代碼
- (void)setupFrameAndRenderBuffer {
// 申請(qǐng)渲染緩存
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
// 該方法最好在綁定渲染后立即設(shè)置,不然后面會(huì)被綁定為深度渲染緩存
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_glLayer];
// 設(shè)置深度調(diào)試
GLint width;
GLint height;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
// 申請(qǐng)深度渲染緩存
glGenRenderbuffers(1, &_depthRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _depthRenderBuffer);
// 設(shè)置深度測(cè)試的存儲(chǔ)信息
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
// 申請(qǐng)幀緩存
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
// 將渲染緩存掛載到GL_DEPTH_ATTACHMENT這個(gè)掛載點(diǎn)上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthRenderBuffer);
// GL_RENDERBUFFER綁定的是深度測(cè)試渲染緩存,所以要綁定回色彩渲染緩存
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
// 檢查幀緩存狀態(tài)
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
NSLog(@"Error: Frame buffer is not completed.");
exit(1);
}
}
- 在渲染方法里將原來(lái)的清空緩存區(qū)數(shù)據(jù)的方法改成以下方法
glEnable(GL_DEPTH_TEST);
// 清空色彩數(shù)據(jù)的同時(shí)清空深度數(shù)據(jù)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
運(yùn)行結(jié)果如下:
實(shí)現(xiàn)只有線連成的方法體
我在評(píng)論區(qū)看到有人想問(wèn)的問(wèn)題可能是在問(wèn)怎么實(shí)現(xiàn)用線繪制立方體,就在這里補(bǔ)充一下評(píng)論區(qū)里的問(wèn)題的答案。我們目前都是通過(guò)將頂點(diǎn)繪制成三角形的方式來(lái)實(shí)現(xiàn)一些圖形效果,除此之外我們還可以將頂點(diǎn)數(shù)據(jù)繪制成點(diǎn)(也可以說(shuō)成點(diǎn)精靈)或者直線。這次我們用直線實(shí)現(xiàn),首先將頂點(diǎn)和索引數(shù)據(jù)要修改如下:
const GLfloat vertexes[] = {
-0.5f, 0.5f, 0.5f, // 前左上 0
-0.5f, -0.5f, 0.5f, // 前左下 1
0.5f, -0.5f, 0.5f, // 前右下 2
0.5f, 0.5f, 0.5f, // 前右上 3
// 后面
-0.5f, 0.5f, -0.5f, // 后左上 4
-0.5f, -0.5f, -0.5f, // 后左下 5
0.5f, -0.5f, -0.5f, // 后右下 6
0.5f, 0.5f, -0.5f // 后右上 7
};
const GLbyte indexes[] = {
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7
};
由于我們的頂點(diǎn)數(shù)據(jù)沒(méi)有了紋理坐標(biāo),指針的跨度由5變成了3所以我們要將設(shè)置頂點(diǎn)參數(shù)的代碼修改如下:
glVertexAttribPointer(_vertexSlot, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, NULL);
我們的片元著色器也得修改,因?yàn)樵瓉?lái)是從紋理中獲取顏色數(shù)據(jù)而現(xiàn)在我們直接設(shè)置顏色數(shù)據(jù),將著色器修改如下:
precision mediump float;
void main()
{
// 我們?cè)谶@里將顏色設(shè)成了紅色,下面4個(gè)數(shù)分別對(duì)應(yīng)RGBA
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
上面說(shuō)過(guò)我們這次要繪制成直線,所以在繪制的方法里將GL_TRIANGLES修改成GL_LINES,表示用線繪制。
glDrawElements(GL_LINES, sizeof(indexes) / sizeof(indexes[0]), GL_UNSIGNED_BYTE, 0);
好了,現(xiàn)在運(yùn)行一下代碼看看效果吧。代碼也同步更新了,可以在我的github下載。
知識(shí)擴(kuò)展
后面會(huì)講到天空盒子,其實(shí)天空盒子就是一個(gè)立方體只是視角在立方體的中心而已,還記得我們?cè)O(shè)置圖形變換矩陣的時(shí)候有一行這樣的代碼:
// 遠(yuǎn)離視野,不然是在視角會(huì)在立方體的中心
ksMatrixTranslate(&_matrix4, 0, 0, -3);
這是為了看到圖形的全貌才設(shè)置的讓其在Z軸上遠(yuǎn)離我們,如果將這行代碼注釋了你就會(huì)看到我們的視角在立方體的中心,平面是雙面顯示的所以在立方體中心也能看到美女的圖片(后面我們會(huì)講到面剔除讓其只在一面顯示從而提高性能),你可以設(shè)置每個(gè)面都不同就可以實(shí)現(xiàn)天空盒子了,這個(gè)先留給大家嘗試一下吧!
使用GLKit的方式
使用GLKit的方法要簡(jiǎn)單很多,簡(jiǎn)單到不用再寫shader了。上面的原理能講的都已經(jīng)講完了,這里就只提供相關(guān)代碼和注釋。需要說(shuō)明的會(huì)在代碼注釋里。
// 限制上下轉(zhuǎn)動(dòng)的角度
#define kLimitDegreeUpDown 40.0
// 頂點(diǎn)結(jié)構(gòu)體
typedef struct{
GLfloat position[3];
GLfloat texturePosion[2];
} Vertex;
const Vertex vertexes[] = {
// 頂點(diǎn) 紋理
// 前面
{{-0.5f, 0.5f, 0.5f}, {0.0f, 1.0f}}, // 前左上 0
{{-0.5f, -0.5f, 0.5f}, {0.0f, 0.0f}}, // 前左下 1
{{0.5f, -0.5f, 0.5f}, {1.0f, 0.0f}}, // 前右下 2
{{0.5f, 0.5f, 0.5f}, {1.0f, 1.0f}}, // 前右上 3
// 后面
{{-0.5f, 0.5f, -0.5f}, {1.0f, 1.0f}}, // 后左上 4
{{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f}}, // 后左下 5
{{0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}}, // 后右下 6
{{0.5f, 0.5f, -0.5f}, {0.0f, 1.0f}}, // 后右上 7
// 左面
{{-0.5f, 0.5f, -0.5f}, {0.0f, 1.0f}}, // 后左上 8
{{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}}, // 后左下 9
{{-0.5f, 0.5f, 0.5f}, {1.0f, 1.0f}}, // 前左上 10
{{-0.5f, -0.5f, 0.5f}, {1.0f, 0.0f}}, // 前左下 11
// 右面
{{0.5f, 0.5f, 0.5f}, {0.0f, 1.0f}}, // 前右上 12
{{0.5f, -0.5f, 0.5f}, {0.0f, 0.0f}}, // 前右下 13
{{0.5f, -0.5f, -0.5f}, {1.0f, 0.0f}}, // 后右下 14
{{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f}}, // 后右上 15
// 上面
{{-0.5f, 0.5f, -0.5f}, {0.0f, 1.0f}}, // 后左上 16
{{-0.5f, 0.5f, 0.5f}, {0.0f, 0.0f}}, // 前左上 17
{{0.5f, 0.5f, 0.5f}, {1.0f, 0.0f}}, // 前右上 18
{{0.5f, 0.5f, -0.5f}, {1.0f, 1.0f}}, // 后右上 19
// 下面
{{-0.5f, -0.5f, 0.5f}, {0.0f, 1.0f}}, // 前左下 20
{{0.5f, -0.5f, 0.5f}, {1.0f, 1.0f}}, // 前右下 21
{{-0.5f, -0.5f, -0.5f}, {0.0f, 0.0f}}, // 后左下 22
{{0.5f, -0.5f, -0.5f}, {1.0f, 0.0f}}, // 后右下 23
};
const GLbyte indexes[] = {
// 前面
0, 1, 2,
0, 2, 3,
// 后面
4, 5, 6,
4, 6, 7,
// 左面
8, 9, 11,
8, 11, 10,
// 右面
12, 13, 14,
12, 14, 15,
// 上面
16, 17, 18,
16, 18, 19,
// 下面
20, 22, 23,
20, 23, 21,
};
@interface ViewController ()
@property (nonatomic,strong)GLKBaseEffect * effect;
@property(nonatomic,assign)GLfloat degreeX;
@property(nonatomic,assign)GLfloat degreeY;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
GLKView * glView = (GLKView *)self.view;
EAGLContext * contex = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!contex) {
NSLog(@"context創(chuàng)建失敗");
}
if (![EAGLContext setCurrentContext:contex]) {
NSLog(@"設(shè)置當(dāng)前context失敗");
}
glView.context = contex;
glView.drawableDepthFormat = GLKViewDrawableDepthFormat16;
self.effect = [[GLKBaseEffect alloc] init];
// 加載紋理圖片
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@(1), GLKTextureLoaderOriginBottomLeft, nil];
NSError * error;
CGImageRef image = [UIImage imageNamed:@"girl"].CGImage;
GLKTextureInfo * textureInfo = [GLKTextureLoader textureWithCGImage:image options:options error:&error];
if (error) {
NSLog(@"%@", error);
}
// 設(shè)置紋理可用
self.effect.texture2d0.enabled = GL_TRUE;
// 傳遞紋理信息
self.effect.texture2d0.name = textureInfo.name;
[self setupVBO];
[self setupPerspective];
glClearColor(1.0, 1.0, 1.0, 1.0);
glEnable(GL_DEPTH_TEST);
}
/**
設(shè)置頂點(diǎn)緩存
*/
- (void)setupVBO{
// 設(shè)置VBO
GLuint bufferVBO;
glGenBuffers(1, &bufferVBO);
glBindBuffer(GL_ARRAY_BUFFER, bufferVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW);
GLuint bufferIndex;
glGenBuffers(1, &bufferIndex);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufferIndex);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexes), indexes, GL_STATIC_DRAW);
// 設(shè)置頂點(diǎn)
glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
glEnableVertexAttribArray(GLKVertexAttribPosition);
// 設(shè)置紋理坐標(biāo)
glVertexAttribPointer(GLKVertexAttribTexCoord0 , 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
}
- (void)setupPerspective{
GLfloat aspect = self.view.frame.size.width / self.view.frame.size.height;
GLKMatrix4 perspective = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(60), aspect, 0.1, 200);
self.effect.transform.projectionMatrix = perspective;
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch * touch = touches.anyObject;
CGPoint currentPoint = [touch locationInView:self.view];
CGPoint previousPoint = [touch previousLocationInView:self.view];
self.degreeX += currentPoint.y - previousPoint.y;
// 限制上下轉(zhuǎn)動(dòng)的角度
if (self.degreeX > kLimitDegreeUpDown) {
self.degreeX = kLimitDegreeUpDown;
}
if (self.degreeX < -kLimitDegreeUpDown) {
self.degreeX = -kLimitDegreeUpDown;
}
self.degreeY += currentPoint.x - previousPoint.x;
}
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 遠(yuǎn)離視野,不然是在正方體內(nèi)部
GLKMatrix4 modelMat = GLKMatrix4Translate(GLKMatrix4Identity, 0, 0, -3);
modelMat = GLKMatrix4RotateX(modelMat, GLKMathDegreesToRadians(self.degreeX));
modelMat = GLKMatrix4RotateY(modelMat, GLKMathDegreesToRadians(self.degreeY));
self.effect.transform.modelviewMatrix = modelMat;
[self.effect prepareToDraw];
glDrawElements(GL_TRIANGLES, sizeof(indexes)/sizeof(indexes[0]), GL_UNSIGNED_BYTE, 0);
}