前言
- 因为在书上和网上看到说写Blog各种好, 所以就来尝试一下,虽然不会有人看,但是没关系毕竟可以巩固和更加深入的了解.
- 我公司所处的行业是安防监控, 所以刚好有机会接触音视频相关的内容.虽然我是个菜鸟, 但是菜鸟也会有春天的O(∩_∩)O哈哈~.
- 在这里跟大家一起学习OpenGLES,一起进步.
- 本系列不会讲太多的理论知识, 会讲解尽量多的用法, 毕竟了解更多的用法在实际使用中可以产生更多想法.
最终效果
实现流程
-
1.设置上下文
-
2.设置Layer
-
3.设置渲染缓冲区
-
4.设置帧缓冲区
-
5.创建顶点着色器和片段着色器
-
6.编译着色器
-
7.链接着色器
-
8.声明矩形四个角对应的数据
-
9.开始渲染
-
10.释放资源
自定义的OpenGLView有点小复杂, 实现过程比系统提供的GLKView复杂了一丢丢.但是可以更深入的了解OpenGLES实现过程
实现过程
1.设置上下文
/**
* 设置上下文
*/
- (void)setupContext {
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!_context) {
_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
}
if (![EAGLContext setCurrentContext:_context]) {
NSLog(@"Failed to set current OpenGL context");
exit(1);
}
}
2.设置Layer
+(Class)layerClass {
return [CAEAGLLayer class];
}
- (void)setupLayer {
_eaglLayer = (CAEAGLLayer*)self.layer;
_eaglLayer.opaque = YES;
}
3.设置渲染缓冲区
/**
* 创建一个渲染缓冲
*/
- (void)setupRenderBuffer {
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
[_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
}
4.设置帧缓冲区
/**
* 设置帧缓冲区
*/
- (void)setupFrameBuffer {
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
}
5.创建顶点着色器和片段着色器
(1)创建顶点着色器RectangleVertex.glsl
attribute vec4 Position;
attribute vec4 InColor;
varying vec4 OutColor;
void main(void){
OutColor = InColor;
gl_Position = Position;
}
/*
vec2.vec3.vec4.表示对应的234阶矩阵
attribute修饰符表示从"应用程序"传过来的数据(也就是下面这些数据传过来)
//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,
};
varying修饰符是用于和片段着色器通讯的接口
*/
(2)创建片段着色器RectangleFragment.glsl
varying lowp vec4 OutColor;
void main(void){
gl_FragColor = OutColor;
}
上面的这些修饰符可以在这里看.
6.编译着色器
- (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType {
// 获取资源路径
NSString *shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"glsl"];
NSError *error;
NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath
encoding:NSUTF8StringEncoding
error:&error];
if (!shaderString) {
NSLog(@"Error loading shader: %@", error.localizedDescription);
exit(1);
}
/*
* GL_FRAGMENT_SHADER 创建一个片段着色器
* GL_VERTEX_SHADER 创建一个顶点着色器
*/
GLuint shaderHandle = glCreateShader(shaderType);
// 转换成char*类型(给予OpenGL着色器)
const char *shaderStringUTF8 = [shaderString UTF8String];
int shaderStringLength = (int)[shaderString length];
/**
* @param shader 所产生的着色器名称
* @param count 表示多少资源传递一次(如果只上传一个着色代码,这里必须填1)
* @param string C语言的资源路径
* @param length C语言资源路径的字符长度
*/
glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
// 调用运行时编译的着色器
glCompileShader(shaderHandle);
// 查看是否有错误,有的话获取错误信息
GLint compileSuccess;
glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
if (compileSuccess == GL_FALSE) {
GLchar messages[256];
glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"%@", messageString);
glDeleteShader(shaderHandle);
exit(1);
}
return shaderHandle;
}
7.链接着色器
/**
* 编译着色器
*/
- (void)compileShaders {
//编译顶点着色器和片段着色器
GLuint vertexShader = [self compileShader:@"RectangleVertex" withType:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShader:@"RectangleFragment" withType:GL_FRAGMENT_SHADER];
//把顶点和片段着色器链接到一个完整的程序
_program = glCreateProgram();
glAttachShader(_program, vertexShader);
glAttachShader(_program, fragmentShader);
//连接程序
glLinkProgram(_program);
//检查是否有错误, 有的话获取错误信息
if(![self validateProgram:_program]) {
glDeleteProgram(_program);
exit(1);
}
//告诉OpenGL使用该程序
glUseProgram(_program);
//这里是获取刚才着色器里面的变量并使用
_positionSlot = glGetAttribLocation(_program, "Position");
_colorSlot = glGetAttribLocation(_program, "InColor");
glEnableVertexAttribArray(_positionSlot);
glEnableVertexAttribArray(_colorSlot);
}
//验证链接程序是否有效
- (BOOL)validateProgram:(GLuint)prog {
GLint linkSuccess;
glGetProgramiv(prog, GL_LINK_STATUS, &linkSuccess);
if(linkSuccess == GL_FALSE) {
GLchar messages[256];
glGetProgramInfoLog(prog, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSLog(@"%@", messageString);
return NO;
}
return YES;
}
8.声明矩形四个角对应的数据
//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,
};
9.开始渲染
- (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];
}
glDrawArrays的第一个属性有如下图三种绘制方式.
我们例子中用到的是GL_TRIANGLE_STRIP
顺序是(左下右下左上右上).
当然我们也可以用GL_TRIANGLE_FAN
,但是顺序需要修改为(左下右下右上左上)这种方式,
第一种GL_TRIANGLES
表示没玩过~(> _ <)~. 原文链接
10.释放资源
- (void)dealloc {
//删除绑定的渲染缓冲区
if(_renderBuffer) {
glDeleteRenderbuffers(GL_RENDERBUFFER, &_renderBuffer);
}
//删除绑定的帧缓冲区
if(_frameBuffer) {
glDeleteFramebuffers(GL_FRAMEBUFFER, &_frameBuffer);
}
//释放着色器
if(_vertexShader) {
//删除顶点着色器连接
glDetachShader(_program, _vertexShader);
//删除顶点着色器
glDeleteShader(_vertexShader);
}
if(_fragmentShader) {
//删除片段着色器连接
glDetachShader(_program, _fragmentShader);
//删除片段着色器
glDeleteShader(_fragmentShader);
}
if(_program) {
glDeleteProgram(_program);
}
}
网友评论