iOS OpenGLES渲染学习

2019-03-16  本文已影响0人  iOS_tree

在许多视频开发的项目中,需要我们自己对视频数据进行渲染,我们可以使用系统的框架进行渲染,我们也可以使用OpenGL进行渲染。我们在项目中使用的是OpenGL进行的渲染,总结一下渲染过程,后附demo代码。

1、创建UIView子类

使用opengles时,我们使用一个UIView作为载体,对opengles进行控制和加载数据的媒介。
我们自定义一个UIView的子类。
在iOS中使用opengles时,需要和CAEAGLLayer进行绑定使用,我们可以自定义我们的View的Layer的类型,代码如下:

+ (Class)layerClass {
    return [CAEAGLLayer class];
}

我们在创建UIView的时候,UIView的layer则为CAEAGLLayer类型。

2、设置layer属性

我们需要设置layer的颜色属性,只有两个属性可以设置,代码如下:

//设置layer属性
    CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
    NSDictionary *dict = @{kEAGLDrawablePropertyRetainedBacking:@(NO),
                           kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8
                           };

    [eaglLayer setDrawableProperties:dict];

3、设置OpenGL上下文

在iOS中,我们需要创建一个渲染的上下文环境,并绑定到当前线程。代码如下:

self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (self.context == nil) {
        NSLog(@"create context failed!");
        return;
    }
    BOOL result = [EAGLContext setCurrentContext:self.context];
    if (result == NO) {
        NSLog(@"set context failed!");
    }

4、设置GPU着色器程序

我们以常见的YUV数据GPU着色器程序为例,当我们需要渲染YUV数据到屏幕上时,需要经过显卡的处理,也就是GPU程序的处理,然后提交到显示的缓冲中去进行显示。我们需要把数据转换成可以显示的数据。我们的GPU着色器程序需要我们自己编写。GPU分为顶点着色器程序和纹理着色器程序。我们创建两个文件分别写入着色器代码,顶点着色器文件名命名为:vertexshader_YUV.vtsd,纹理着色器文件名为:fragmentshader_YUV.fmsd。可自定义,与程序中的文件名相对应即可。
顶点着色器程序文件代码如下:

attribute vec4 position;
attribute vec2 vTexCords;
varying vec2 yuvTexCoord;

void main() {
gl_Position=position;
yuvTexCoord=vTexCords;
}

纹理着色器程序代码如下:

precision highp float;
varying highp vec2 yuvTexCoord;
uniform sampler2D s_texture_y;
uniform sampler2D s_texture_u;
uniform sampler2D s_texture_v;

void main() {
highp float y = texture2D(s_texture_y,yuvTexCoord).r;
highp float u = texture2D(s_texture_u,yuvTexCoord).r - 0.5;
highp float v = texture2D(s_texture_v,yuvTexCoord).r - 0.5;

highp float r = y + 1.402 * v;
highp float g = y - 0.344 * u - 0.714 * v;
highp float b = y + 1.772 * u;
gl_FragColor=vec4(r,g,b,1.0);
}

GPU程序的使用流程:编译程序->链接程序->使用程序->绑定变量;
代码如下:

- (void)setupYUVGPUProgram {
    //编译顶点着色器、纹理着色器
    GLuint vertexShader = [self compileShader:@"vertexshader_YUV.vtsd" withType:GL_VERTEX_SHADER];
    GLuint fragmentShader = [self compileShader:@"fragmentshader_YUV.fmsd" withType:GL_FRAGMENT_SHADER];
    //绑定链接程序
    GLuint programHandle = glCreateProgram();
    glAttachShader(programHandle, vertexShader);
    glAttachShader(programHandle, fragmentShader);
    glLinkProgram(programHandle);
    
    GLint linkSuccess;
    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
    if (linkSuccess == GL_FALSE) {
        GLchar message[256];
        glGetProgramInfoLog(programHandle, sizeof(message), 0, &message[0]);
        NSString *messageStr = [NSString stringWithUTF8String:message];
        NSLog(@"%@", messageStr);
        return;
    }
    //使用程序
    glUseProgram(programHandle);
    self.mYUVGLProgId = programHandle;
    //绑定变量
    _mYUVGLPosition = glGetAttribLocation(programHandle, "position");
    glEnableVertexAttribArray(_mYUVGLPosition);
    
    _mYUVGLTextureCoords = glGetAttribLocation(programHandle, "vTexCords");
    glEnableVertexAttribArray(_mYUVGLTextureCoords);
    
    
    _s_texture_y = glGetUniformLocation(programHandle, "s_texture_y");
    _s_texture_u = glGetUniformLocation(programHandle, "s_texture_u");
    _s_texture_v = glGetUniformLocation(programHandle, "s_texture_v");
    
    glUniform1i(_s_texture_y, 0);
    glUniform1i(_s_texture_u, 1);
    glUniform1i(_s_texture_v, 2);

}
- (GLuint)compileShader:(NSString *)shaderName withType:(GLenum)shaderType {
    NSString *shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:nil];
    NSError *error;
    NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
    if (!shaderString)
    {
        NSLog(@"Error loading shader: %@", error.localizedDescription);
        return 0;
    }
    
    // create ID for shader
    GLuint shaderHandle = glCreateShader(shaderType);
    
    // define shader text
    const char * shaderStringUTF8 = [shaderString UTF8String];
    int shaderStringLength = (int)[shaderString length];
    glShaderSource(shaderHandle, 1, &shaderStringUTF8, &shaderStringLength);
    
    // compile shader
    glCompileShader(shaderHandle);
    
    // verify the compiling
    GLint compileSucess;
    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSucess);
    if (compileSucess == GL_FALSE)
    {
        GLchar message[256];
        glGetShaderInfoLog(shaderHandle, sizeof(message), 0, &message[0]);
        NSString *messageStr = [NSString stringWithUTF8String:message];
        NSLog(@"----%@", messageStr);
        return 0;
    }
    
    return shaderHandle;
}

5、创建绑定framebuffer和renderbuffer缓冲区

我们需要预先创建framebuffer和renderbuffer缓冲区,准备渲染数据,代码如下:

- (void)setupBuffers {
    //创建帧缓冲区
    glGenFramebuffers(1, &_frameBuffer);
    //绑定缓冲区
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    
    //创建绘制缓冲区
    glGenRenderbuffers(1, &_renderBuffer);
    //绑定缓冲区
    glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
    
    //为绘制缓冲区分配内存
    [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
    
    //获取绘制缓冲区像素高度/宽度
    GLint width;
    GLint height;
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);
    
    self.viewWidth = width;
    self.viewHeight = height;
    //将绘制缓冲区绑定到帧缓冲区
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
    
    //检查状态
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"failed to make complete frame buffer object!");
        return;
    }
    GLenum glError = glGetError();
    if (GL_NO_ERROR != glError) {
        NSLog(@"failed to setup GL %x", glError);
    }
}

6、渲染YUV数据

渲染YUV数据流程:设置当前OpenGL上下文到当前线程->删除已经存在的纹理数据->清屏->创建绑定YUV纹理对象->设置渲染的范围->绑定YUV纹理的坐标数据->绘制当前YUV纹理数据->提交当前缓冲进行渲染->删除已使用的纹理,解绑纹理。
每个线程的OpenGL上下文是需要绑定到当前渲染线程的,不然会崩溃。如果已经不在使用的纹理不进行删除会导致内存泄漏。具体代码如下:

- (void)loadYUV420PDataWithYData:(NSData *)yData uData:(NSData *)uData vData:(NSData *)vData width:(NSInteger)width height:(NSInteger)height {
    dispatch_sync(self.openglesQueue, ^{
        
        BOOL result = [EAGLContext setCurrentContext:self.context];
        if (result == NO) {
            NSLog(@"set context failed!");
        }
        if (self.ytexture) {
            glDeleteTextures(1, &_ytexture);
        }
        if (self.utexture) {
            glDeleteTextures(1, &_utexture);
        }
        if (self.vtexture) {
            glDeleteTextures(1, &_vtexture);
        }
        
        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        
        //创建纹理
        [self createTexWithYUVDataWithYData:yData uData:uData vData:vData width:(int)width height:(int)height];
        
        //调整画面宽度
        CGFloat x = 0;
        CGFloat y = 0;
        CGFloat w = 0;
        CGFloat h = 0;
        
        //获取控件宽高比,与视频宽高比
        if (self.viewWidth / self.viewHeight * 1.0 > width / height) {
            h = self.viewHeight;
            w = width * h / height;
            x = (self.viewWidth - w) / 2;
            glViewport(x, y, w, h);
        }else {
            w = self.viewWidth;
            h = height * w / width;
            y = (self.viewHeight - h) / 2;
            glViewport(x, y, w, h);
        }
        

        //设置物体坐标
        GLfloat vertices[] = {
            -1.0,-1.0,
            1.0,-1.0,
            -1.0,1.0,
            1.0,1.0
        };
//        glEnableVertexAttribArray(_mYUVGLPosition);
        glVertexAttribPointer(_mYUVGLPosition, 2, GL_FLOAT, 0, 0, vertices);
        //设置纹理坐标
        GLfloat texCoords2[] = {
            0,1,
            1,1,
            0,0,
            1,0
        };
//        glEnableVertexAttribArray(_mYUVGLTextureCoords);
        glVertexAttribPointer(_mYUVGLTextureCoords, 2, GL_FLOAT, 0, 0, texCoords2);
        //执行绘制操作
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        
        [self.context presentRenderbuffer:GL_RENDERBUFFER];
        
        //删除不使用纹理
        glDeleteTextures(1, &_ytexture);
        glDeleteTextures(1, &_utexture);
        glDeleteTextures(1, &_vtexture);
        //解绑纹理
        glBindTexture(GL_TEXTURE_2D, 0);
        
    });
}
- (void)createTexWithYUVDataWithYData:(NSData *)YData uData:(NSData *)uData vData:(NSData *)vData width:(int)width height:(int)height {
    
    void *ydata = (void *)[YData bytes];
    
    //传递纹理对象
    //创建纹理
    glActiveTexture(GL_TEXTURE0);
    glGenTextures(1, &_ytexture);
    //绑定纹理
    glBindTexture(GL_TEXTURE_2D, _ytexture);
    [self createYUVTextureWithData:ydata width:width height:height texture:&_ytexture];
    
    void *udata = (void *)[uData bytes];
    //创建纹理
    glActiveTexture(GL_TEXTURE1);
    glGenTextures(1, &_utexture);
    //绑定纹理
    glBindTexture(GL_TEXTURE_2D, _utexture);
    [self createYUVTextureWithData:udata width:width / 2 height:height / 2 texture:&_utexture];
    
    void *vdata = (void *)[vData bytes];
    
    //创建纹理
    glActiveTexture(GL_TEXTURE2);
    glGenTextures(1, &_vtexture);
    //绑定纹理
    glBindTexture(GL_TEXTURE_2D, _vtexture);
    [self createYUVTextureWithData:vdata width:width / 2 height:height / 2 texture:&_vtexture];
    if (!_ytexture || !_ytexture || !_vtexture)
    {
        NSLog(@"glGenTextures faild.");
        return;
    }
}

- (void)createYUVTextureWithData:(void *)data  width:(int)width height:(int)height  texture:(GLuint *)texture {
    
    
    //设置过滤参数
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
    //设置映射规则
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, data);
    
}

7、使用完毕内存释放

在我们不需要使用OpenGL渲染时,我们需要释放OpenGL相关的内存,我们可以在View的dealloc里面进行释放,也可以自定义其他方法在有需要的时候进行释放,需要解除OpenGLES上下文、删除GPU程序、删除framebuffers和renderbuffers、删除绑定的纹理内存。代码如下:

- (void)dealloc {
    NSLog(@"%@====%s",self,__FUNCTION__);
    
    if(self.context) {
        [EAGLContext setCurrentContext:nil];
    }
    if (self.mYUVGLProgId) {
        glDeleteProgram(self.mYUVGLProgId);
    }
    if (self.mGLProgId) {
        glDeleteProgram(self.mGLProgId);
    }
    if (_frameBuffer) {
        glDeleteFramebuffers(1, &_frameBuffer);
    }
    if (_renderBuffer) {
        glDeleteRenderbuffers(1, &_renderBuffer);
    }
    if (_texture) {
        glDeleteTextures(1, &_texture);
    }
    if (_ytexture) {
        glDeleteTextures(1, &_ytexture);
    }
    if (_utexture) {
        glDeleteTextures(1, &_utexture);
    }
    if (_vtexture) {
        glDeleteTextures(1, &_vtexture);
    }
}

至此,我们使用OpenGLES渲染的过程已经完成。其中OpenGL部分不是非常详细,如果需要详细了解,可留意讨论,如果需要深入理解,建议学习一下OpenGL相关方面的专业知识,那样就容易理解。
OpenGLES渲染YUV数据的demo地址:https://github.com/XMSECODE/ESCOpenGLESShowYUVDataDemo
OpenGLES渲染RGB图片的demo地址:https://github.com/XMSECODE/ESCOpenGLESShowImageDemo

上一篇 下一篇

猜你喜欢

热点阅读