美文网首页
OpenGL ES 加载图片

OpenGL ES 加载图片

作者: 尤先森 | 来源:发表于2019-06-11 22:32 被阅读0次

    上一篇文章我们介绍了如何用GLKit加载图片,为从OpenGL到OpenGL ES做一个过渡,并且介绍了EGL 和 EAGL。
    今天这篇文章,就真正的进入到OpenGL ES ,也是以加载一张图片作为案例,后续会慢慢更新更多的内容。

    OpenGL ES 跟GLKit 加载图片的区别

    1. 加载纹理
    GLKit中的GLKTextureLoader的作用是加载纹理图片,为方便开发者开发,于是对GLSL做了很大的优化,只需要一句代码就可以加载出纹理图片并生成GLKTextureInfo供开发者使用。
    在OpenGL ES中,需要开发者自己从读取图片到利用CoreGraphics绘制图片再到设置线性过滤、环绕方式等等,需要一个比较长的流程。

    2.顶点着色器/片元着色器
    GLKBaseEffect的作用是执行顶点着色器和片元着色器的工作,便于开发者使用,用起来也非常方便。简单的几句代码便可以完成顶点着色器、片元着色器的工作。
    在OpenGL ES中,需要开发者自己创建缓冲区、着色器、编译着色器等等繁杂的工作。

    3.绘制
    在GLKit当中,GLKViewDelegate提供了代理方法,只需要很简单的代码便可以完成图形的绘制。
    而在OpenGL ES当中却需要开发者主动去将内容呈现在显示器上。

    思维导图

    OpenGL ES加载图片.png

    接下来,我们跟随思维导图一步一步做。

    创建顶点着色器

    tips:着色器中的注释在使用时尽量去掉,免得出现不必要的错误

    1. 创建empty文件并命名为shaderv.vsh,命名规则无所谓,目的是为了让开发者自己能分辨清楚哪个文件的作用是什么。也可以命名为vertexSahder.a,vertexSahder.b等等等等,都可以。只要自己分得清即可。这里vsh也是vertexSahder的缩写。
      image.png
    2. 声明变量
    //四维向量 顶点坐标
    attribute vec4 position;
    //二维向量 纹理坐标
    attribute vec2 textCoordinate;
    //低精度二维向量 纹理坐标 
    /*
    此处用varying 修饰,表示要通过这个变量,将纹理坐标传递给片元着色器
    lowp表示低精度 
    精度可分为highp/mediump/lowp 分别对应高/中/低
    
    ****************************************
    此处声明变量的方式,以及变量名。
    在片元着色器中,要同样声明一个一模一样的,才能完成纹理坐标的传递。
    ****************************************
    */
    varying lowp vec2 varyTextCoord;
    
    1. main方法
    void main(){
        //varying 修饰,将纹理坐标传递到片元着色器
        varyTextCoord = textCoordinate;
        //给内建变量赋值
        gl_Position = position;
    }
    

    创建片元着色器

    tips:着色器中的注释在使用时尽量去掉,免得出现不必要的错误

    1. 同样创建一个empty文件,命名为shaderf.fsh。这里的fshfragmentShader的缩写。
    2. 变量声明
    //纹理坐标
    varying lowp vec2 varyTextCoord;
    //纹理采样器
    uniform sampler2D colorMap;
    
    1. main方法
    void main(){
        /*
        texture2D(纹理采样器,纹理坐标)
        这个方法可以获取坐标对应的纹素
        
        gl_FragColor 是GLSL的内建变量,用来将纹理颜色添加到对应的像素点上
        */
    
        gl_FragColor = texture2D(colorMap, varyTextCoord);
    }
    
    

    初始化

    1. 创建view


      image.png
    2. import
      #import <OpenGLES/ES3/gl.h>
    3. 变量声明
    @property(strong,nonatomic)CAEAGLLayer *eaglLayer;
    
    @property(strong,nonatomic)EAGLContext *context;
    
    @property(assign,nonatomic)GLuint program;
    
    @property(assign,nonatomic)GLuint frameBuffer;
    
    @property(assign,nonatomic)GLuint renderBuffer;
    

    创建CAEAGLLayer

    -(void)setupLayer{
        //创建特殊图层
        /*
         重写layerClass,将当前View返回的图层从CALayer替换成CAEAGLLayer
         */
        self.eaglLayer = (CAEAGLLayer *)self.layer;
        //设置缩放
        [self setContentScaleFactor:[UIScreen mainScreen].scale];
        /*
         kEAGLDrawablePropertyRetainedBacking :NO (告诉CoreAnimation不要试图保留任何以前绘制的图像留作以后重用)
         kEAGLDrawablePropertyColorFormat :kEAGLColorFormatRGBA8 (告诉CoreAnimation用8位来保存RGBA的值)
         也可以不设置。默认值就是这两个
         链接:https://www.jianshu.com/p/b3852409edbc
         */
        NSDictionary *options = @{kEAGLDrawablePropertyRetainedBacking:@(false),
                                 kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8};
        
        self.eaglLayer.drawableProperties = options;
    }
    //重写layer
    +(Class)layerClass{
        return [CAEAGLLayer class];
    }
    

    设置EAGLContext上下文

    -(void)setupContext{
         //创建context
        self.context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES3];
        if (!_context) {
            NSLog(@"context创建失败");
            return;
        }
        //设置当期那context并判断是否设置成功
        if ([EAGLContext setCurrentContext:self.context]==false) {
            NSLog(@"设置当前context失败!");
            return;
        }  
    }
    

    清除缓冲区

    这个其实也可以不用写

    -(void)deleteBuffers{
        glDeleteBuffers(1, &_frameBuffer);
        _frameBuffer = 0;
        glDeleteBuffers(1, &_renderBuffer);
        _renderBuffer = 0 ;
    }
    

    创建RenderBuffer

    -(void)setupRenderBuffer{
        //定义标识符ID
        GLuint bufferID;
        //glGenRenderbuffers申请标识符
        glGenRenderbuffers(1, &bufferID);
        
        self.renderBuffer = bufferID;
        //绑定缓冲区,注意此处为glBindRenderbuffer,不是glBindBuffer
        glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
        //将可绘制对象的存储绑定到OpenGL ES renderbuffer对象。
        [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eaglLayer];
        
    }
    

    创建FrameBuffer

    -(void)setupFrameBuffer{
        //定义标识符ID
        GLuint bufferID;
        //glGenFramebuffers申请标识符
        glGenFramebuffers(1, &bufferID);
        self.frameBuffer = bufferID;
        //绑定缓冲区glBindFramebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer);
        //生成帧缓冲区,把RenderBuffer跟FrameBuffer绑定到一起
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.renderBuffer);
    }
    

    绘制

    -(void)draw{
        //设置背景色
        glClearColor(0.8, 0.8, 0.8, 1);
        //清除颜色缓冲
        glClear(GL_COLOR_BUFFER_BIT);
        //获取缩放值
        CGFloat scale = [UIScreen mainScreen].scale;
        //设置视口
        glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height *scale);
        //获取vsh/fsh路径
        NSString *vertexShaderPath = [[NSBundle mainBundle]pathForResource:@"shaderv" ofType:@"vsh"];
        NSString *fragmentShaderPath = [[NSBundle mainBundle]pathForResource:@"shaderf" ofType:@"fsh"];
        
        NSLog(@"%@  ---  %@",vertexShaderPath,fragmentShaderPath);
        
        //创建shader
        GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
        GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
        //创建program
        GLuint program = glCreateProgram();
        //读取vsh/fsh内容
        NSString *vertexContent = [NSString stringWithContentsOfFile:vertexShaderPath encoding:NSUTF8StringEncoding error:nil];
        NSString *fragmentContent = [NSString stringWithContentsOfFile:fragmentShaderPath encoding:NSUTF8StringEncoding error:nil];
        //NSString转C字符串
        const char *vertexSource = (GLchar *)[vertexContent UTF8String];
        const char *fragmentSource = (GLchar *)[fragmentContent UTF8String];
        //替换shader源码内容
        glShaderSource(vertexShader, 1, &vertexSource, NULL);
        glShaderSource(fragmentShader, 1, &fragmentSource, NULL);
        //编译shader
        glCompileShader(vertexShader);
        glCompileShader(fragmentShader);
        //附着shader到program
        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        //删除shader
        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);
        
        self.program = program;
        
        //连接program
        glLinkProgram(self.program);
        //声明变量存储连接状态
        GLint linkStatus;
        //获取program连接状态
        glGetProgramiv(self.program, GL_LINK_STATUS, &linkStatus);
        //如果连接失败
        if (linkStatus == false) {
            NSLog(@"连接失败");
            char msg[1024];
            //获取programInfo 信息
            glGetProgramInfoLog(self.program, sizeof(msg), 0, &msg[0]);
            //char 转 NSString
            NSString *message = [NSString stringWithCString:msg encoding:NSUTF8StringEncoding];
            NSLog(@"%@",message);
            return;
        }
        NSLog(@"program 连接成功!");
        //使用program
        glUseProgram(self.program);
        
        //编辑顶点坐标数组
        GLfloat vertexData[] = {
            
            0.5, -0.25, 0.0f,    1.0f, 0.0f, //右下
            0.5, 0.25, -0.0f,    1.0f, 1.0f, //右上
            -0.5, 0.25, 0.0f,    0.0f, 1.0f, //左上
            
            0.5, -0.25, 0.0f,    1.0f, 0.0f, //右下
            -0.5, 0.25, 0.0f,    0.0f, 1.0f, //左上
            -0.5, -0.25, 0.0f,   0.0f, 0.0f, //左下
        };
        
        //定义标识符
        GLuint bufferID;
        //申请标识符
        glGenBuffers(1, &bufferID);
        //绑定缓冲区
        glBindBuffer(GL_ARRAY_BUFFER, bufferID);
        //将顶点数组的数据copy到顶点缓冲区中
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW);
        
        
        //从program中获取position 顶点属性
        GLuint position = glGetAttribLocation(self.program, "position");
        //开启顶点属性通道
        glEnableVertexAttribArray(position);
        //设置顶点读取方式
        glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *) NULL + 0);
        
        //从program中获取textCoordinate 纹理属性
        GLuint textCoordinate = glGetAttribLocation(self.program, "textCoordinate");
        //开启纹理属性通道
        glEnableVertexAttribArray(textCoordinate);
        //设置纹理读取方式
        glVertexAttribPointer(textCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *) NULL + 3);
        
        //获取纹理图片
        CGImageRef cgImgRef = [UIImage imageNamed:@"test"].CGImage;
        if (!cgImgRef) {
            NSLog(@"纹理获取失败");
        }
        //获取图片长、宽
        size_t width = CGImageGetWidth(cgImgRef);
        size_t height = CGImageGetHeight(cgImgRef);
        
        //计算图片所占字节数 长 * 宽 * RGBA占4个字节
        GLubyte *byte = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
        
        //
        //创建CGContextRef画布
        /*
         参数1:data,指向要渲染的绘制图像的内存地址
         参数2:width,bitmap的宽度,单位为像素
         参数3:height,bitmap的高度,单位为像素
         参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
         参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
         参数6:colorSpace,bitmap上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA
         */
        CGContextRef contextRef = CGBitmapContextCreate(byte, width, height, 8, width * 4, CGImageGetColorSpace(cgImgRef), kCGImageAlphaPremultipliedLast);
        //长宽转成float 方便下面方法使用
        float w = width;
        float h = height;
        
        //绘制图片的位置
        CGRect rect = CGRectMake(0, 0, w, h);
        //在CGContextRef上--> 将图片绘制出来
        /*
         CGContextDrawImage 使用的是Core Graphics框架,坐标系与UIKit 不一样。UIKit框架的原点在屏幕的左上角,CoreGraphics框架的原点在屏幕的左下角。
         参数1:绘图上下文
         参数2:rect坐标
         参数3:绘制的图片
         */
        CGContextDrawImage(contextRef, rect, cgImgRef);
        //图片绘制完成后,contextRef就没用了,释放
        CGContextRelease(contextRef);
        
        //0 代表第0个纹理 对应采样器的0
        glBindTexture(GL_TEXTURE_2D, 0);
        
        //设置线性过滤、环绕方式
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_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);
        
        //载入纹理2D数据
        /*
         参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
         参数2:加载的层次,一般设置为0
         参数3:纹理的颜色值GL_RGBA
         参数4:宽
         参数5:高
         参数6:border,边界宽度
         参数7:format
         参数8:type
         参数9:纹理数据
         */
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, byte);
        
        //释放byte
        free(byte);
        
        //设置纹理采样器,这里的 0 对应 glBindTexture的 0
        glUniform1i(glGetUniformLocation(self.program, "colorMap"), 0);
        //绘图
        glDrawArrays(GL_TRIANGLES, 0, 6);
        //将渲染缓冲区 呈现到 屏幕上
        [self.context presentRenderbuffer:GL_RENDERBUFFER];
    }
    

    调用上述方法

    -(void)layoutSubviews{
        [super layoutSubviews];
        //设置layer
        [self setupLayer];
        //设置context
        [self setupContext];
        //清除缓存区
        [self deleteBuffers];
        //设置渲染缓冲区
        [self setupRenderBuffer];
        //设置帧缓冲区
        [self setupFrameBuffer];
        //绘制
        [self draw];
    }
    

    最后在ViewController中创建OpenGLESView

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.glesView = [[OpenGLESView alloc]initWithFrame:self.view.bounds];
        self.view = self.glesView;
    }
    

    效果图

    image.png

    看了效果图之后,聪明的你肯定发现了,这是一张macOS mojave的壁纸截图,而且图片倒过来了,图片本身是正的。


    image.png

    于是这就引出了另外一个问题 ——OpenGL 纹理翻转

    以下内容摘取自CC

    关于纹理翻转

    纹理翻转的原因是因为OpenGL要求纹理坐标原点(0,0)在图片左下角。
    而图片信息中的原点(0,0)一般都在左上角,一行行绘制出来,就导致了图片的上下翻转。


    iOS纹理翻转解决策略

    第1种: 旋转矩阵翻转图形,不翻转纹理

    让图形顶点坐标旋转180°, 而纹理保持原状。

        GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
        float radians = 180 * 3.14159f / 180.0f;
        float s = sin(radians);
        float c = cos(radians);
        
      
        GLfloat zRotation[16] = {
            c, -s, 0, 0,
            s, c, 0, 0,
            0, 0, 1.0, 0,
            0.0, 0, 0, 1.0
        };
        
       glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);
    

    第2种: 解压图片时,将图片源文件翻转

    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
    GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
    
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
      
    CGRect rect = CGRectMake(0, 0, width, height);
    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
    
    CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
    CGContextTranslateCTM(spriteContext, 0, rect.size.height);
    CGContextScaleCTM(spriteContext, 1.0, -1.0);
    CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
    CGContextDrawImage(spriteContext, rect, spriteImage); 
    
    CGContextRelease(spriteContext);
    glBindTexture(GL_TEXTURE_2D, 0);
    

    第3种: 修改片元着色器,纹理坐标

    varying lowp vec2 varyTextCoord;
    uniform sampler2D colorMap;
    void main()
    {
        gl_FragColor = texture2D(colorMap, vec2(varyTextCoord.x,1.0-varyTextCoord.y));
    }
    

    第4种: 修改顶点着色器,纹理坐标

    attribute vec4 position;
    attribute vec2 textCoordinate;
    varying lowp vec2 varyTextCoord;
    
    void main()
    {
        varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
        gl_Position = position;
    }
    

    第5种:直接从源纹理坐标数据修改

         GLfloat attrArr[] =
         {
         0.5f, -0.5f, 0.0f,        1.0f, 1.0f, //右下
         -0.5f, 0.5f, 0.0f,        0.0f, 0.0f, // 左上
         -0.5f, -0.5f, 0.0f,       0.0f, 1.0f, // 左下
         0.5f, 0.5f, 0.0f,         1.0f, 0.0f, // 右上
         -0.5f, 0.5f, 0.0f,        0.0f, 0.0f, // 左上
         0.5f, -0.5f, 0.0f,        1.0f, 1.0f, // 右下
         };
    

    翻转过后的效果图,我用了第三种方法

    image.png

    那么这篇文章就到此为止了,感谢阅读。 ^ _ ^

    相关文章

      网友评论

          本文标题:OpenGL ES 加载图片

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