基于iOS的OpenGL ES入门

2021-07-31  本文已影响0人  Faner_NG

学习动机:


一、OpenGL是什么

全名是open graphics library , 用于渲染2d,3d图像的跨平台,跨语言的应用程序编程接口

二、OpenGL 能做什么?

可以对图像进行各种美颜,滤镜,裁剪,贴纸等处理,源图像数据可以是来自相机,文件,图片等。GPUImage框架底层就是用opengl实现的

三、利用OpenGL渲染帧数据并显示

01自定义图层类型

//CAEAGLLayer是OpenGL专门用来渲染的图层,
//使用OpenGL必须使用这个图层
+ (Class)layerClass {
    return [CAEAGLLayer class];
}

02初始化CAEAGLLayer图层属性

 //设置图层
    CAEAGLLayer *eaglLayer       = (CAEAGLLayer *)self.layer;
    eaglLayer.opaque = NO; //这个一定要设置  不然无法透明
    eaglLayer.backgroundColor = [UIColor clearColor].CGColor;
    /*kEAGLDrawablePropertyRetainedBacking  是否需要保留已经绘制到图层上面的内容
     kEAGLDrawablePropertyColorFormat 绘制对象内部的颜色缓冲区的格式 kEAGLColorFormatRGBA8 4*8 = 32*/
    eaglLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking : [NSNumber numberWithBool:NO],
                                     kEAGLDrawablePropertyColorFormat     : kEAGLColorFormatRGBA8};

03-创建EAGLContext

//设置上下文
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    [EAGLContext setCurrentContext:context];
    

04-创建渲染缓冲区

 //创建渲染缓存
    glGenRenderbuffers(1, colorBufferHandle);
    glBindRenderbuffer(GL_RENDERBUFFER, *colorBufferHandle);
    
    // 把渲染缓存绑定到渲染图层上CAEAGLLayer,并为它分配一个共享内存。
    // 并且会设置渲染缓存的格式,和宽度
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];

05-创建帧缓冲区

//创建帧缓存
    glGenFramebuffers(1, frameBufferHandle);
    glBindFramebuffer(GL_FRAMEBUFFER, *frameBufferHandle);
    // 把颜色渲染缓存 添加到 帧缓存的GL_COLOR_ATTACHMENT0上,就会自动把渲染缓存的内容填充到帧缓存,在由帧缓存渲染到屏幕
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, *colorBufferHandle);

06-创建着色器

//顶点着色器代码
attribute vec4 Position;
attribute vec4 TextureCoords;
varying vec2 TextureCoordsVarying;

void main (void) {
    gl_Position = Position;
    TextureCoordsVarying = TextureCoords.xy;
}


//片段着色器代码
precision mediump float;

uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    vec4 alpha = texture2D(Texture, TextureCoordsVarying + vec2(-0.5, 0.0));
    gl_FragColor = vec4(mask.rgb, alpha.r);
}

 GLint status;

//sourceString 是顶点or片段着色器 的代码文本
    const GLchar *source;
    source = (GLchar *)[sourceString UTF8String];
    
//type 顶点or片段着色器
    *shader = glCreateShader(type); // 创建着色器
    glShaderSource(*shader, 1, &source, NULL);//加载着色器源代码
    glCompileShader(*shader); //编译着色器
    
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);//获取完成状态
    if (status == 0) {
        // 没有完成就直接删除着色器
        glDeleteShader(*shader);
        return NO;
    }

07-创建着色器程序

 //创建一个着色器程序对象
    GLuint program = glCreateProgram();
    _rgbProgram = program;
    
    //关联着色器对象到着色器程序对象
    //绑定顶点着色器
    glAttachShader(program, vertShader);
    //绑定片元着色器
    glAttachShader(program, fragShader);
    
    // 绑定着色器属性,方便以后获取,以后根据角标获取
    // 一定要在链接程序之前绑定属性,否则拿不到
    glBindAttribLocation(program, ATTRIB_VERTEX  , "Position");
    glBindAttribLocation(program, ATTRIB_TEXCOORD, "TextureCoords");
    
    //链接程序
    if (![self linkProgram:program]) {
        //链接失败释放vertShader\fragShader\program
        if (vertShader) {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (program) {
            glDeleteProgram(program);
            program = 0;
        }
        return;
    }
    
    /// 获取全局参数,注意 一定要在连接完成后才行,否则拿不到
    _displayInputTextureUniform = glGetUniformLocation(program, "Texture");
    
    //释放已经使用完毕的verShader\fragShader
    if (vertShader) {
        glDetachShader(program, vertShader);
        glDeleteShader(vertShader);
    }
    if (fragShader) {
        glDetachShader(program, fragShader);
        glDeleteShader(fragShader);
    }

//启动程序
    glUseProgram(rgbProgram);

08-创建纹理对象

通过获取的一张一张的图片(CVPixelBufferRef pixelBuffer),可以把图片转换为OpenGL中的纹理, 然后再把纹理画到OpenGL的上下文中

比如绘制一面砖墙,就可以用一幅真实的砖墙图像或照片作为纹理贴到一个矩形上,这样,一面逼真的砖墙就画好了。如果不用纹理映射的方法,则墙上的每一块砖都必须作为一个独立的多边形来画。另外,纹理映射能够保证在变换多边形时,多边形上的纹理图案也随之变化。

纹理映射是一个相当复杂的过程,基本步骤如下:

1)激活纹理单元、2)创建纹理 、3)绑定纹理 、4)设置滤波
注意:纹理映射只能在RGBA方式下执行

// 创建亮度纹理
    // 激活纹理单元0, 不激活,创建纹理会失败
    glActiveTexture(GL_TEXTURE0);
    // 创建纹理对象
    CVReturn error;
    error = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
                                                         videoTextureCache,
                                                         pixelBuffer,
                                                         NULL,
                                                         GL_TEXTURE_2D,
                                                         GL_RGBA,
                                                         frameWidth,
                                                         frameHeight,
                                                         GL_BGRA,
                                                         GL_UNSIGNED_BYTE,
                                                         0,
                                                         &renderTexture);
    if (error) {
        NSLog(@"Error at CVOpenGLESTextureCacheCreateTextureFromImage %d", error);
    }else {
        _renderTexture = renderTexture;
    }
    //获取纹理对象  CVOpenGLESTextureGetName(renderTexture)
    //绑定纹理
    glBindTexture(CVOpenGLESTextureGetTarget(renderTexture), CVOpenGLESTextureGetName(renderTexture));
    
    //设置纹理滤波
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

09-绘制纹理

如果源视频色彩空间是yuv格式则需要转为grb格式,一般直播推流时使用的是yuv格式,此处我是播放的静态mp4文件,所以略过格式转换

// 在创建纹理之前,有激活过纹理单元,glActiveTexture(GL_TEXTURE0)
    // 指定着色器中亮度纹理对应哪一层纹理单元
    // 这样就会把亮度纹理,往着色器上贴
    glUniform1i(displayInputTextureUniform, 0);
    
    if (self.pixelbufferWidth != frameWidth || self.pixelbufferHeight != frameHeight) {
        CGSize normalizedSamplingSize = CGSizeMake(1.0, 1.0);
        self.pixelbufferWidth = frameWidth;
        self.pixelbufferHeight = frameHeight;
 /*       
//纹理坐标
GLfloat quadTextureData[] = {
    0.5f, 1.0f,
    0.5f, 0.0f,
    1.0f, 1.0f,
    1.0f, 0.0f,
};

//顶点坐标
GLfloat quadVertexData[] = {
    -1.0f, 1.0f,
    -1.0f, -1.0f,
    1.0f, 1.0f,
    1.0f, -1.0f,
};
*/
        // 左下角
        quadVertexData[0] = -1 * normalizedSamplingSize.width;
        quadVertexData[1] = -1 * normalizedSamplingSize.height;
        // 左上角
        quadVertexData[2] = -1 * normalizedSamplingSize.width;
        quadVertexData[3] = normalizedSamplingSize.height;
        // 右下角
        quadVertexData[4] = normalizedSamplingSize.width;
        quadVertexData[5] = -1 * normalizedSamplingSize.height;
        // 右上角
        quadVertexData[6] = normalizedSamplingSize.width;
        quadVertexData[7] = normalizedSamplingSize.height;
    }
    
    //激活ATTRIB_VERTEX顶点数组
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    //给ATTRIB_VERTEX顶点数组赋值
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, quadVertexData);
    
    //激活ATTRIB_TEXCOORD顶点数组
    glEnableVertexAttribArray(ATTRIB_TEXCOORD);
    //给ATTRIB_TEXCOORD顶点数组赋值
    glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, 0, 0, quadTextureData);
    
    //渲染纹理数据
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

10-渲染缓冲区到屏幕

- (void)displayFramebuffer:(CMSampleBufferRef)sampleBuffer{

// 因为是多线程,每一个线程都有一个上下文,只要在一个上下文绘制就好,设置线程的上下文为我们自己的上下文,就能绘制在一起了,否则会黑屏.
    if ([EAGLContext currentContext] != _context) {
        [EAGLContext setCurrentContext:_context];
    }
    
    // 清空之前的纹理,要不然每次都创建新的纹理,耗费资源,造成界面卡顿
    [self cleanUpTextures];

    //执行第8步 创建纹理对象

//设置视口大小
    glViewport(0, 0, backingWidth, backingHeight);
    //设置一个RGB颜色和透明度,接下来会用这个颜色涂满全屏
    glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
    //清除颜色缓冲区
    glClear(GL_COLOR_BUFFER_BIT);

    //执行第9步 绘制纹理,也就是着色器提取并处理纹理

    

 /// 把上下文的东西渲染到屏幕上
    if ([EAGLContext currentContext] == context) {
        [context presentRenderbuffer:GL_RENDERBUFFER];
    }

}


清理内存

- (void)cleanUpTextures {
    if (_renderTexture) {
        CFRelease(_renderTexture);
        _renderTexture = NULL;
    }
    // 清空纹理缓存
    CVOpenGLESTextureCacheFlush(_videoTextureCache, 0);
}
- (void)dealloc {
    [self cleanUpTextures];
    
    if(_videoTextureCache) {
        CFRelease(_videoTextureCache);
    }
}
上一篇下一篇

猜你喜欢

热点阅读