在上一篇,其实我们什么都没做,就只是设置OpenGLES 的基本配置,这一篇我们回来绘制一个三角形,三角形是OpenGLES 中的基本图元。
前面说到,OpenGLES 是一个状态机器,这意味着在一个程序中设置了一个配置值后,这个值会一直保持,直到程序修改了这个值。上下文中的信息可能会被保存在CPU所控制的内存中,也可能会被保存在GPU所控制的内存中。OpenGLES会按需在两个内存区域之间复制信息,知道何时复制有助于程序的优化。
OpenGLEs 上下文会跟踪用于渲染的帧缓存。上下文还会跟踪用于集合数据、颜色等的缓存,上下文会决定是否使用某些功能,比如文理和灯光。
创建颜色缓存
- (void)setupRenderBuffer {
glGenRenderbuffers(1, &_colorRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
// 为 color renderbuffer 分配存储空间
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}
每个OpenGL 程序至少有一个缓冲区(color buffer,depth buffer,stencil buffer)
帧缓存
GPU需要知道应该在内存中的哪个位置存储渲染出来的2D图像像素数据。就像为GPU提供数据的缓存一样,接收渲染结果的缓冲区就叫做帧缓存(frame buffer)。程序会想任何其他种类的缓存一样生成、绑定、删除帧缓存。但是帧缓存不需要初始化,因为渲染指令会在适当的时候替换缓存的内容。帧缓存会在被绑定的时候隐式开启,同时OpenGLES会自动的根据特定平台的硬件配置和功能来设置数据的类型和偏移。
可以同时存在很多帧缓存,并且可以通过OpenGL ES 让GPU把渲染结果存储到任意数量的帧缓存中。但是屏幕显示像素要受到保存在前帧缓存(front frame buffer)的特定帧缓存中的像素颜色元素的控制。程序和操作系统很少会直接渲染到前帧缓存中。因为那样会让用户看到正在渲染的还没有渲染完成的图像。相反,程序和操作系统会把渲染结果保存到后帧缓存在内的其他帧缓存中。当渲染后的后帧缓存包含一个完成的图像时,前帧缓存与后帧缓存几乎会瞬间切换。后帧缓存会变成新的前帧缓存,同时旧的前帧缓存会变成新的后帧缓存。
创建帧缓存
- (void)setupFrameBuffer {
glGenRenderbuffers(1, &_framebuffer);
// 设置为当前 framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
// 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);
}
设置shader 程序
- (void)setupProgram {
NSString *VertexFile = [[NSBundle mainBundle] pathForResource:@"VertexFile.glsl" ofType:nil];
NSString *FragmentFile = [[NSBundle mainBundle] pathForResource:@"FragmentFile.glsl" ofType:nil];
_programHandle0 = [OpenGLUtils programWithVertexFile:VertexFile fragmentFile:FragmentFile];
glUseProgram(_programHandle0);//激活shader 程序
_positionSlot = glGetAttribLocation(_programHandle0, "vPosition");//在ES3.0 中有另一种方式
_vertexColorSlot = glGetAttribLocation(_programHandle0, "color");
glEnableVertexAttribArray(_positionSlot);
glEnableVertexAttribArray(_vertexColorSlot);
}
其中,这个program 可以理解为shader 程序的一个句柄,拿到这个句柄后,我们就可以获取program 中的一些变量值,就可以往shader程序中传输数据。
Vertex Shader
attribute vec3 vPosition;
attribute vec3 color;
varying vec3 outColor;
void main()
{
gl_Position = vec4(vPosition,1.0);
outColor = color;
}
在这里我们什么都没有做,仅仅只是把顶点坐标和顶点颜色值传输给OpenGL, 其中:Vertex shader在ES3.0 中
#version 300 es
layout (location = 0) in vec3 vPosition;
layout (location = 1) in vec3 inColor;
out vec3 color;
void main()
{
gl_Position = vec4(vPosition,1.0);
color = inColor;
}
Fragment Shader
precision mediump float;
varying vec3 color;
void main()
{
gl_FragColor = vec4(color, 1.0);
}
在ES3.0 中
//指定版本号和精度
#version 300 es
precision mediump float;
out vec4 fragColor;
//从顶点着色器传来的输入变量(名称相同、类型相同)
in vec3 color;
void main()
{
//在通过out字段将颜色输出
fragColor = vec4(color,1.0);
}
设置顶点数据
GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, 0, vertices );//填充数据
glEnableVertexAttribArray(_positionSlot);
设置顶点颜色
//颜色数据
static GLfloat colors[] = {
0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f
};
glVertexAttribPointer(_vertexColorSlot, 3, GL_FLOAT, GL_FALSE, 0, colors);
glEnableVertexAttribArray(_vertexColorSlot);
而在OpenGLES3.0 中填充数据也就变成了:
GLfloat vertices[] = {
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
};
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices );//填充数据
glEnableVertexAttribArray(0);
static GLfloat colors[] = {
0.0f, 1.0f, 1.0f,
1.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f
};
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, colors);
glEnableVertexAttribArray(1);
不再需要使用
glGetAttribLocation(_programHandle0, "vPosition");
_vertexColorSlot = glGetAttribLocation(_programHandle0, "color");
这里只是提一下,有空后面再讲。
看效果图:
也可以自己尝试着绘制其他图形,如果是立体图,就需要用到3D变换,平移、旋转,缩放。马上就会讲到。
最后,附上我自己在学习过程中的源码:
参考:
[1]:OpenGL ES 应用开发实践指南iOS卷