前言
本文是關(guān)于OpenGL ES的系統(tǒng)性學(xué)習(xí)過程如筛,記錄了自己在學(xué)習(xí)OpenGL ES時的收獲。
這篇文章的目標(biāo)是通過OpenGL ES 3.0的VAO赃承,VBO等緩存技術(shù)進(jìn)行貝塞爾曲線的繪制妙黍。如果不明白貝塞爾曲線原理,請參考之前的博客 理解與運用貝塞爾曲線瞧剖。
環(huán)境是Xcode8.1+OpenGL ES 3.0
目前代碼已經(jīng)放到github上面拭嫁,OpenGL ES入門04-OpenGL ES VAO VBO FBO緩存
歡迎關(guān)注我的 OpenGL ES入門專題
實現(xiàn)效果
頂點緩存VBO
普通的頂點數(shù)組的傳輸,需要在繪制的時候頻繁地從CPU到GPU傳輸頂點數(shù)據(jù)抓于,這種做法效率低下做粤,為了加快顯示速度,顯卡增加了一個擴展 VBO (Vertex Buffer object)捉撮,即頂點緩存怕品。它直接在 GPU 中開辟一個緩存區(qū)域來存儲頂點數(shù)據(jù),因為它是用來緩存儲頂點數(shù)據(jù)巾遭,因此被稱之為頂點緩存肉康。使用頂點緩存能夠大大較少了CPU到GPU 之間的數(shù)據(jù)拷貝開銷闯估,因此顯著地提升了程序運行的效率。
- 創(chuàng)建頂點緩存對象
void glGenBuffers (GLsizei n, GLuint* buffers);
參數(shù) n : 表示需要創(chuàng)建頂點緩存對象的個數(shù)
參數(shù) buffers :用于存儲創(chuàng)建好的頂點緩存對象句柄
- 將頂點緩存對象設(shè)置為當(dāng)前數(shù)組緩存對象
void glBindBuffer (GLenum target, GLuint buffer);
參數(shù) target :指定綁定的目標(biāo)吼和,取值為 GL_ARRAY_BUFFER(用于頂點數(shù)據(jù)) 或 GL_ELEMENT_ARRAY_BUFFER(用于索引數(shù)據(jù))
參數(shù) buffer :頂點緩存對象句柄
- 為頂點緩存對象分配空間
void glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
參數(shù) target:與 glBindBuffer 中的參數(shù) target 相同涨薪;
參數(shù) size :指定頂點緩存區(qū)的大小,以字節(jié)為單位計數(shù)炫乓;
參數(shù) data :用于初始化頂點緩存區(qū)的數(shù)據(jù)刚夺,可以為 NULL,表示只分配空間末捣,之后再由 glBufferSubData 進(jìn)行初始化览闰;
參數(shù) usage :表示該緩存區(qū)域?qū)蝗绾问褂么览猓闹饕康氖怯糜趯υ摼彺鎱^(qū)域做何種程度的優(yōu)化,比如經(jīng)常修改的數(shù)據(jù)可能就會放在GPU緩存中達(dá)到快速操作的目的。
|參數(shù)|解釋|
|:---:|:---:|
|GL_STATIC_DRAW|表示該緩存區(qū)不會被修改|
|GL_DYNAMIC_DRAW|表示該緩存區(qū)會被周期性更改|
|GL_STREAM_DRAW|表示該緩存區(qū)會被頻繁更改|
- 更新頂點緩沖區(qū)數(shù)據(jù)
void glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
參數(shù) offset: 表示需要更新的數(shù)據(jù)的起始偏移量歌豺;
參數(shù) size: 表示需要更新的數(shù)據(jù)的個數(shù)分瘦,也是以字節(jié)為計數(shù)單位银锻;
參數(shù) data: 用于更新的數(shù)據(jù)酥筝;
- 釋放頂點緩存
void glDeleteBuffers (GLsizei n, const GLuint* buffers);
參數(shù) n : 表示頂點緩存對象的個數(shù)
參數(shù) buffers :頂點緩存對象句柄
頂點數(shù)組對象VAO
VAO的全名是Vertex ArrayObject。它不用作存儲數(shù)據(jù)圃酵,但它與頂點繪制相關(guān)柳畔。
它的定位是狀態(tài)對象,記錄存儲狀態(tài)信息郭赐。VAO記錄的是一次繪制中做需要的信息薪韩,這包括數(shù)據(jù)在哪里、數(shù)據(jù)格式是什么等信息捌锭。VAO其實可以看成一個容器俘陷,可以包括多個VBO。 由于它進(jìn)一步將VBO容于其中观谦,所以繪制效率將在VBO的基礎(chǔ)上更進(jìn)一步拉盾。目前OpenGL ES3.0及以上才支持頂點數(shù)組對象。
- 創(chuàng)建頂點數(shù)組對象
glGenVertexArrays (GLsizei n, GLuint* arrays) ;
參數(shù) n : 表示頂點數(shù)組對象的個數(shù)
參數(shù) arrays :頂點數(shù)組對象句柄
- 將頂點數(shù)組對象設(shè)置為當(dāng)前頂點數(shù)組對象
glBindVertexArray (GLuint array) ;
參數(shù) arrays :頂點數(shù)組對象句柄
- 釋放頂點數(shù)組對象
glDeleteVertexArrays (GLsizei n, const GLuint* arrays);
參數(shù) n : 表示頂點數(shù)組對象的個數(shù)
參數(shù) arrays :頂點數(shù)組對象句柄
注意:如果需要在OpenGL ES2.0上使用VAO豁状,可以使用蘋果擴展的相關(guān)的API捉偏,具體擴展如下:
GLvoid glGenVertexArraysOES(GLsizei n, GLuint *arrays)
GLvoid glBindVertexArrayOES(GLuint array);
GLvoid glDeleteVertexArraysOES(GLsizei n, const GLuint *arrays);;
GLboolean glIsVertexArrayOES(GLuint array);
實現(xiàn)貝塞爾曲線
1、創(chuàng)建OpenGL ES3.0上下文對象泻红,由于Opengl ES3.0不是所有的設(shè)備和iOS版本都支持夭禽,因此查看支持情況請參考 OpenGL ES入門01-OpenGL ES概述 或者參見蘋果官網(wǎng) 設(shè)備特性
// 引入OpenGL ES3.0頭文件
#import <OpenGLES/ES3/gl.h>
- (void)setupContext
{
// 設(shè)置OpenGLES的版本為3.0
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!_context) {
NSLog(@"Failed to initialize OpenGLES 3.0 context");
exit(1);
}
// 將當(dāng)前上下文設(shè)置為我們創(chuàng)建的上下文
if (![EAGLContext setCurrentContext:_context]) {
NSLog(@"Failed to set current OpenGL context");
exit(1);
}
}
2、創(chuàng)建OpenGL ES3.0相關(guān)的著色器谊路。要想使用OpenGL ES3.0的新特性讹躯,必須要指定著色器的版本 #version 300 es 。如果不指定,則會按照2.0的版本處理潮梯,因此3.0的新特性比如in骗灶、out、layout等關(guān)鍵字會報錯秉馏。我們通過layout(location = i)指定屬性的位置矿卑,這樣我們就可以不用調(diào)用glGetAttribLocation獲取屬性的位置了。至于編譯沃饶、鏈接、創(chuàng)建著色器程序和以前2.0一樣轻黑,所以不再特別指明糊肤。
#version 300 es
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
out vec3 outColor;
void main()
{
gl_Position = vec4(position, 1.0);
outColor = color;
}
#version 300 es
precision mediump float;
in vec3 outColor;
out vec4 v_color;
void main()
{
v_color = vec4(outColor, 1.0);
}
3、創(chuàng)建貝塞爾曲線定點氓鄙。我們通過指定一個控制點和另外兩個頂點便可以通過曲線方程創(chuàng)建貝塞爾曲線(曲線方程見之前的文章 理解與運用貝塞爾曲線 )馆揉。我們再次將t分為100份,就可以創(chuàng)建100個頂點數(shù)據(jù)抖拦,然后通過線段代表曲線的近似方式便可以畫出貝塞爾曲線升酣。
- (void)setupVertexData
{
CGPoint p1 = CGPointMake(-0.8, 0);
CGPoint p2 = CGPointMake(0.8, 0.2);
CGPoint control = CGPointMake(0, -0.9);
CGFloat deltaT = 0.01;
_vertCount = 1.0/deltaT;
_vertext = (Vertex *)malloc(sizeof(Vertex) * _vertCount);
// t的范圍[0,1]
for (int i = 0; i < _vertCount; i++) {
float t = i * deltaT;
// 二次方計算公式
float cx = (1-t)*(1-t)*p1.x + 2*t*(1-t)*control.x + t*t*p2.x;
float cy = (1-t)*(1-t)*p1.y + 2*t*(1-t)*control.y + t*t*p2.y;
_vertext[i] = (Vertex){cx, cy, 0.0, 1.0, 0.0, 0.0};
printf("%f, %f\n",cx, cy);
}
}
4、創(chuàng)建頂點緩存對象VBO态罪,在此通過GLUtil封裝了一下VBO的創(chuàng)建噩茄。
GLuint createVBO(GLenum target, int usage, int datSize, void *data)
{
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(target, vbo);
glBufferData(target, datSize, data, usage);
return vbo;
}
5、創(chuàng)建頂點數(shù)組對象VAO复颈。通過VAO容易我們包裹了VBO和數(shù)組傳遞等操作绩聘,使用的時候只要激活當(dāng)前的VAO便可以完成繪制,加快了繪制效率耗啦。
- (void)setupVAO
{
glGenVertexArrays(1, &_vao);
glBindVertexArray(_vao);
// VBO
GLuint vbo = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(Vertex) * (_vertCount + 1), _vertext);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), NULL);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), NULL+sizeof(GLfloat)*3);
glBindVertexArray(0);
}
6凿菩、繪制。只需要通過 glBindVertexArray 啟用創(chuàng)建VAO便可以完成繪制帜讲。
- (void)render
{
glClearColor(1.0, 1.0, 0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(2.0);
glViewport(0, 0, self.frame.size.width, self.frame.size.height);
// VAO
glBindVertexArray(_vao);
glDrawArrays(GL_LINE_STRIP, 0, _vertCount);
//將指定 renderbuffer 呈現(xiàn)在屏幕上衅谷,在這里我們指定的是前面已經(jīng)綁定為當(dāng)前 renderbuffer 的那個,在 renderbuffer 可以被呈現(xiàn)之前似将,必須調(diào)用renderbufferStorage:fromDrawable: 為之分配存儲空間获黔。
[_context presentRenderbuffer:GL_RENDERBUFFER];
}