持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天
OpenglES在iOS上的实践
OpenGLES主要可分为顶点着色器和片元着色器,其主要的加载方式是以字符串的形式被加载到GPU上。
下面我把创建一个自定义的使用OpenGLES图层的步骤进行描述,总结一下整个过程。
1. 设置图层和初始化上下文
在iOS上为Opengles 提供的上下文叫做EGLContext,在构建画面之前需要给opengl 提供对应的图层,并指定上下文对象,操作如下:
构建一个可供opengl渲染的View需要将该View 的layer 指定为CAEAGLLayer 的形式,而该layer 默认是透明的,这个在进行渲染的时候对性能会存在影响,所以需要设置他的透明度,具体操作代码如下
// 重写view 图层的layer
+ (Class)layerClass {
return [CAEAGLLayer class];
}
- (EAGLContext *)createEAGLContextWithFrame:(CGRect)frame {
// 创建layer图层,用来加载opengl图层
CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
eaglLayer.opaque = YES;
eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : [NSNumber numberWithBool:NO],
kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8};
// iOS提供给openglES 的接口
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:context];
[self creatFBOWithContext:context width:frame.size.width height:frame.size.height];
[self loadShader];
CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, context, NULL, &_glesCacheRef);
if (err != noErr) {
NSLog(@"创建纹理缓存区失败");
return nil;
}
return context;
}
上面的步骤只是iOS上层为opengl所做的准备工作,接下来就需要去构建真正的opengles 元素了。
1.1 创建程序,编译和加载着色器
在iOS 中,VSH一般指的是顶点着色器,fsh是片段着色器,着色器对于GPU 而言都是通过字符串的形式被加载到内部,所以在编程过程中,在上述的两个sh 内部进行编程是不会有报错的,那么如何去控制fsh、vsh 内部的代码就是需要注意的了。下面是在iOS平台上加载和编辑着色器的代码演示:
过程是
- 创建顶点和片段着色器
- 编译和加载着色器
- 创建一个程序对象并链接着色器
- (void)loadShader {
GLuint vertShader, fragShader;
NSURL *vertShaderURL, *fragShaderURL;
NSString *shaderName;
GLuint program;
// 创建程序
program = glCreateProgram();
shaderName = @"nv12";
_nv12Program = program;
// 加载编译顶点着色器
vertShaderURL = [[NSBundle mainBundle] URLForResource:shaderName withExtension:@"vsh"];
if (![self compileShader:&vertShader type:GL_VERTEX_SHADER URL:vertShaderURL]) {
log4cplus_error(kModuleName, "Failed to compile vertex shader");
return;
}
// 加载编译片段着色器
fragShaderURL = [[NSBundle mainBundle] URLForResource:shaderName withExtension:@"fsh"];
if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER URL:fragShaderURL]) {
log4cplus_error(kModuleName, "Failed to compile fragment shader");
return;
}
// 连接着色器和程序
glAttachShader(program, vertShader);
glAttachShader(program, fragShader);
// 将通用顶点属性索引与命名属性变量相关联,对应vsh 内部的顶点,用来在渲染时给图像找确认点
glBindAttribLocation(program, ATTRIB_VERTEX , "position");
glBindAttribLocation(program, ATTRIB_TEXCOORD, "inputTextureCoordinate");
// 链接程序
if (![self linkProgram:program]) {
if (vertShader) {
glDeleteShader(vertShader);
vertShader = 0;
}
if (fragShader) {
glDeleteShader(fragShader);
fragShader = 0;
}
if (program) {
glDeleteProgram(program);
program = 0;
}
return;
}
uniforms[UNIFORM_Y] = glGetUniformLocation(program , "luminanceTexture");
uniforms[UNIFORM_UV] = glGetUniformLocation(program, "chrominanceTexture");
uniforms[UNIFORM_COLOR_CONVERSION_MATRIX] = glGetUniformLocation(program, "colorConversionMatrix");
if (vertShader) {
glDetachShader(program, vertShader);
glDeleteShader(vertShader);
}
if (fragShader) {
glDetachShader(program, fragShader);
glDeleteShader(fragShader);
}
}
- (BOOL)loadAndCompileShader:(GLuint *)shader url:(NSURL *)url type:(GLenum)type {
NSString *sourceString = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
GLint status;
const GLchar *source =(GLchar *) [sourceString UTF8String];
// 创建
*shader = glCreateShader(type);
// 加载资源
glShaderSource(*shader, 1, &source, NULL);
// 编译
glCompileShader(*shader);
// 获取编译状态
glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
if (status == 0) {
glDeleteShader(*shader);
return NO;
}
return YES;
}
1.2 配置FBO/VBO
初始化的代码如下:
- (void)creatFBOWithContext:(EAGLContext *)context width:(CGFloat)width height:(CGFloat)height {
// glVertexAttribPointer 和 glEnableVertexAttribArray 没有顺序,需要在draw之前完成,glEnableVertexAttribArray的作用是让GPU 显示 对应的顶点数据、片段数据
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
glEnableVertexAttribArray(0);
// 设置framebuffer,帧缓冲区
glGenFramebuffers(1, &_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
// 设置renderbuffer,渲染缓冲区
glGenRenderbuffers(1, &_renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderbuffer);
// 将可绘制对象的存储绑定到opengles 的renderbuffer上
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH , &_backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_backingHeight);
// 将渲染缓冲区挂载到当前帧缓冲区上
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderbuffer);
}
### 2. 显示已解码的pixelBuffer