OpenglES在iOS上的实践

163 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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平台上加载和编辑着色器的代码演示: 过程是

  1. 创建顶点和片段着色器
  2. 编译和加载着色器
  3. 创建一个程序对象并链接着色器
- (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