前言
本文是关于OpenGL ES的系统性学习过程,记录了自己在学习OpenGL ES时的收获。
这篇文章的目标是通过OpenGL ES 3.0的VAO,VBO等缓存技术进行贝塞尔曲线的绘制。如果不明白贝塞尔曲线原理,请参考之前的博客 理解与运用贝塞尔曲线。
环境是Xcode8.1+OpenGL ES 3.0
目前代码已经放到github上面,OpenGL ES入门04-OpenGL ES VAO VBO FBO缓存
欢迎关注我的 OpenGL ES入门专题
实现效果
顶点缓存VBO
普通的顶点数组的传输,需要在绘制的时候频繁地从CPU到GPU传输顶点数据,这种做法效率低下,为了加快显示速度,显卡增加了一个扩展 VBO (Vertex Buffer object),即顶点缓存。它直接在 GPU 中开辟一个缓存区域来存储顶点数据,因为它是用来缓存储顶点数据,因此被称之为顶点缓存。使用顶点缓存能够大大较少了CPU到GPU 之间的数据拷贝开销,因此显著地提升了程序运行的效率。
- 创建顶点缓存对象
void glGenBuffers (GLsizei n, GLuint* buffers);
参数 n : 表示需要创建顶点缓存对象的个数
参数 buffers :用于存储创建好的顶点缓存对象句柄
- 将顶点缓存对象设置为当前数组缓存对象
void glBindBuffer (GLenum target, GLuint buffer);
参数 target :指定绑定的目标,取值为 GL_ARRAY_BUFFER(用于顶点数据) 或 GL_ELEMENT_ARRAY_BUFFER(用于索引数据)
参数 buffer :顶点缓存对象句柄
- 为顶点缓存对象分配空间
void glBufferData (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
参数 target:与 glBindBuffer 中的参数 target 相同;
参数 size :指定顶点缓存区的大小,以字节为单位计数;
参数 data :用于初始化顶点缓存区的数据,可以为 NULL,表示只分配空间,之后再由 glBufferSubData 进行初始化;
参数 usage :表示该缓存区域将会被如何使用,它的主要目的是用于对该缓存区域做何种程度的优化,比如经常修改的数据可能就会放在GPU缓存中达到快速操作的目的。
|参数|解释|
|:---:|:---:|
|GL_STATIC_DRAW|表示该缓存区不会被修改|
|GL_DYNAMIC_DRAW|表示该缓存区会被周期性更改|
|GL_STREAM_DRAW|表示该缓存区会被频繁更改|
- 更新顶点缓冲区数据
void glBufferSubData (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
参数 offset: 表示需要更新的数据的起始偏移量;
参数 size: 表示需要更新的数据的个数,也是以字节为计数单位;
参数 data: 用于更新的数据;
- 释放顶点缓存
void glDeleteBuffers (GLsizei n, const GLuint* buffers);
参数 n : 表示顶点缓存对象的个数
参数 buffers :顶点缓存对象句柄
顶点数组对象VAO
VAO的全名是Vertex ArrayObject。它不用作存储数据,但它与顶点绘制相关。
它的定位是状态对象,记录存储状态信息。VAO记录的是一次绘制中做需要的信息,这包括数据在哪里、数据格式是什么等信息。VAO其实可以看成一个容器,可以包括多个VBO。 由于它进一步将VBO容于其中,所以绘制效率将在VBO的基础上更进一步。目前OpenGL ES3.0及以上才支持顶点数组对象。
- 创建顶点数组对象
glGenVertexArrays (GLsizei n, GLuint* arrays) ;
参数 n : 表示顶点数组对象的个数
参数 arrays :顶点数组对象句柄
- 将顶点数组对象设置为当前顶点数组对象
glBindVertexArray (GLuint array) ;
参数 arrays :顶点数组对象句柄
- 释放顶点数组对象
glDeleteVertexArrays (GLsizei n, const GLuint* arrays);
参数 n : 表示顶点数组对象的个数
参数 arrays :顶点数组对象句柄
注意:如果需要在OpenGL ES2.0上使用VAO,可以使用苹果扩展的相关的API,具体扩展如下:
GLvoid glGenVertexArraysOES(GLsizei n, GLuint *arrays)
GLvoid glBindVertexArrayOES(GLuint array);
GLvoid glDeleteVertexArraysOES(GLsizei n, const GLuint *arrays);;
GLboolean glIsVertexArrayOES(GLuint array);
实现贝塞尔曲线
1、创建OpenGL ES3.0上下文对象,由于Opengl ES3.0不是所有的设备和iOS版本都支持,因此查看支持情况请参考 OpenGL ES入门01-OpenGL ES概述 或者参见苹果官网 设备特性
// 引入OpenGL ES3.0头文件
#import <OpenGLES/ES3/gl.h>
- (void)setupContext
{
// 设置OpenGLES的版本为3.0
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!_context) {
NSLog(@"Failed to initialize OpenGLES 3.0 context");
exit(1);
}
// 将当前上下文设置为我们创建的上下文
if (![EAGLContext setCurrentContext:_context]) {
NSLog(@"Failed to set current OpenGL context");
exit(1);
}
}
2、创建OpenGL ES3.0相关的着色器。要想使用OpenGL ES3.0的新特性,必须要指定着色器的版本 #version 300 es 。如果不指定,则会按照2.0的版本处理,因此3.0的新特性比如in、out、layout等关键字会报错。我们通过layout(location = i)指定属性的位置,这样我们就可以不用调用glGetAttribLocation获取属性的位置了。至于编译、链接、创建着色器程序和以前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、创建贝塞尔曲线定点。我们通过指定一个控制点和另外两个顶点便可以通过曲线方程创建贝塞尔曲线(曲线方程见之前的文章 理解与运用贝塞尔曲线 )。我们再次将t分为100份,就可以创建100个顶点数据,然后通过线段代表曲线的近似方式便可以画出贝塞尔曲线。
- (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、创建顶点缓存对象VBO,在此通过GLUtil封装了一下VBO的创建。
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、创建顶点数组对象VAO。通过VAO容易我们包裹了VBO和数组传递等操作,使用的时候只要激活当前的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 启用创建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 呈现在屏幕上,在这里我们指定的是前面已经绑定为当前 renderbuffer 的那个,在 renderbuffer 可以被呈现之前,必须调用renderbufferStorage:fromDrawable: 为之分配存储空间。
[_context presentRenderbuffer:GL_RENDERBUFFER];
}