美文网首页
iOS OpenGLES渲染学习

iOS OpenGLES渲染学习

作者: iOS_tree | 来源:发表于2019-03-16 11:54 被阅读0次

    在许多视频开发的项目中,需要我们自己对视频数据进行渲染,我们可以使用系统的框架进行渲染,我们也可以使用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

    相关文章

      网友评论

          本文标题:iOS OpenGLES渲染学习

          本文链接:https://www.haomeiwen.com/subject/ucrcmqtx.html