zoukankan      html  css  js  c++  java
  • OpenGL 十

    案例:使用编译链接自定义的着色器(shader),用简单的 glsl 语言来实现顶点、片元着色器,绘制图形并进行简单的变换。

    思路:

     1.创建图层

     2.创建上下文

     3.清空缓存区

     4.设置 RenderBuffer

     5.设置 FrameBuffer

     6.开始绘制

    Demo 

    一、准备工作

    步骤 1~5

     1 - (void)layoutSubviews {
     2     
     3     // 1. 创建设置图层
     4     // 设置 layer
     5     self.myEGLLayer = (CAEAGLLayer *)self.layer;
     6     
     7     // 设置 scale
     8     [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
     9     
    10     // 设置属性
    11     /*
    12      kEAGLDrawablePropertyRetainedBacking:绘图表面显示后,是否保留其内容。
    13      kEAGLDrawablePropertyColorFormat:可绘制表面的内部颜色缓存区格式,这个key对应的值是一个NSString指定特定颜色缓存区对象。默认是kEAGLColorFormatRGBA8;
    14      
    15      kEAGLColorFormatRGBA8:32位RGBA的颜色,4*8=32位
    16      kEAGLColorFormatRGB565:16位RGB的颜色,
    17      kEAGLColorFormatSRGBA8:sRGB代表了标准的红、绿、蓝,即CRT显示器、LCD显示器、投影机、打印机以及其他设备中色彩再现所使用的三个基本色素,sRGB的色彩空间基于独立的色彩坐标,可以使色彩在不同的设备使用传输中对应于同一个色彩坐标体系,而不受这些设备各自具有的不同色彩坐标的影响。
    18      */
    19 //    self.myEGLLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@(NO),kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8};
    20     self.myEGLLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil];
    21 
    22     
    23     // 2. 设置上下文
    24     self.myContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    25     if (!self.myContext) {
    26         NSLog(@"create context failed!");
    27         return;
    28     }
    29     BOOL isSetSuccess = [EAGLContext setCurrentContext:self.myContext];
    30     if (!isSetSuccess) {
    31         return;
    32     }
    33     
    34     
    35     // 3. 清空缓冲区
    36     glDeleteBuffers(1, &_myColorRenderBuffer);
    37     self.myColorRenderBuffer = 0;
    38     glDeleteBuffers(1, &_myColorFrameBuffer);
    39     self.myColorFrameBuffer = 0;
    40     
    41     
    42     // 4. 设置渲染缓冲区 renderBuffer
    43     // 生成缓冲区 ID
    44     GLuint rb;
    45     glGenRenderbuffers(1, &rb);
    46     self.myColorRenderBuffer = rb;
    47     // 绑定缓冲区
    48     glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    49     
    50     // 绑到 context: contect 与 eagllayer绑定在一起
    51     [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEGLLayer];
    52     
    53     
    54     // 5. 设置帧缓冲区 FrameBuffer
    55     glGenBuffers(1, &_myColorFrameBuffer);
    56     glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    57     
    58     // 渲染缓冲区 与 帧缓冲区绑在一起
    59     /*
    60      target:
    61      attachment:将 renderBuffer 附着到frameBuffer的哪个附着点上
    62      renderbuffertarget
    63      renderbuffer
    64      */
    65     //    glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
    66     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
    67     
    68     // 开始绘制
    69     [self renderLayer];
    70     
    71 }

    二、开始绘制

    1、首先获取并使用 链接后着⾊器对象,过程:

      a1、创建⼀个顶点着⾊器对象和⼀个⽚段着⾊器对象

      b1、将源代码链接到每个着⾊器对象

      c1、编译着⾊器对象

      d1、创建⼀个程序对象

      e1、将编译后的着⾊器对象连接到程序对象

      f1、链接程序对象

    a1、着色器:

    顶点着色器代码:

    attribute vec4 position;
    attribute vec2 textCoordinate;
    varying lowp vec2 varyTextCoord;
    
    void main() {
        
        varyTextCoord = textCoordinate;
        gl_Position = position;
    }

    片元着色器代码:

    precision highp float;
    varying lowp vec2 varyTextCoord;
    uniform sampler2D colorMap;
    
    void main() {
        
        gl_FragColor = texture2D(colorMap, varyTextCoord);
    }

    注意:使用 .vsh / .fsh 区分顶点、片元着色器 --> .vsh --> 顶点着色器 / .fsh --> 片元着色器 --> .vsh / .fsh 文件,只是用来给开发者区分着色器代码。其本质是一串字符串。

     

    b1 ~ f1 过程:

     1 - (void)renderLayer {
     2     
     3     glClearColor(0.7, 0.7, 0.7, 1);
     4     glClear(GL_COLOR_BUFFER_BIT);// 清空颜色缓冲区
     5     
     6     /// 1. 设置视口
     7     CGFloat mainScale = [UIScreen mainScreen].scale;
     8     glViewport(self.frame.origin.x * mainScale, self.frame.origin.y * mainScale, self.frame.size.width * mainScale, self.frame.size.height * mainScale);
     9     
    10     /// 2. 读取着色器代码
    11     // 路径
    12     NSString *verPath = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
    13     NSString *fragPath = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
    14     
    15     /// 3. 加载着色器
    16     self.myProgram = [self loadShadersWithVertex:verPath Withfrag:fragPath];
    17     
    18     /// 4. 链接 program
    19     glLinkProgram(self.myProgram);
    20     // 获取连接状态
    21     GLint linkStatus;
    22     glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkStatus);
    23     if (linkStatus == GL_FALSE) {// 链接出错
    24         // 获取错误信息 log
    25         GLchar message[512];
    26         glGetProgramInfoLog(self.myProgram, sizeof(message), 0, &message[0]);
    27         NSString *messageString = [NSString stringWithUTF8String:message];
    28         NSLog(@"Program Link Error:%@",messageString);
    29         return;
    30     }
    31     
    32     /// 5. 使用 program
    33     glUseProgram(self.myProgram);
    34  }

    加载编译着色器:

     1 // 加载着色器
     2 // 顶点着色器 和 片元着色器 的代码传进来(.vsh  .fsh)
     3 -(GLuint)loadShadersWithVertex:(NSString *)vert Withfrag:(NSString *)frag {
     4     
     5     // 1.定义 着色器
     6     GLuint verShader, fragShader;
     7     
     8     // 2.创建程序 program
     9     GLint program = glCreateProgram();// 创建一个空的程序对象
    10     
    11     // 3.编译着色器 --> 封装一个方法 compileShaderWithShader:
    12     [self compileShaderWithShader:&verShader shaderType:GL_VERTEX_SHADER filePath:vert];
    13     [self compileShaderWithShader:&fragShader shaderType:GL_FRAGMENT_SHADER filePath:frag];
    14     
    15     // 4.attach shader, 将shader附着到 程序
    16     glAttachShader(program, verShader);
    17     glAttachShader(program, fragShader);
    18     
    19     //5.已附着好的 shader 删掉,避免不必要的内存占用
    20     glDeleteShader(verShader);
    21     glDeleteShader(fragShader);
    22     
    23     return program;// 返回编译好的程序
    24 }
    25 // 编译着色器
    26 /*
    27  shader: 着色器 ID
    28  type: 着色器类型
    29  path: 着色器代码文件路径
    30  */
    31 - (void)compileShaderWithShader:(GLuint *)shader shaderType:(GLenum)type filePath:(NSString *)path {
    32     
    33     // 1.读取文件路径
    34     NSString *file = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    35     // NSString 转 C 的 char
    36     const GLchar *source = (GLchar *)[file UTF8String];
    37     
    38     // 2.创建对应类型的shader
    39     *shader = glCreateShader(type);
    40     
    41     // 3.读取着色器源码 将其附着到着色器对象上面
    42     /* params:
    43      shader: 要编译的着色器对象 *shader
    44      numOfStrings: 传递的源码字符串数量 1个
    45      参数3:strings: 着色器程序的源码(真正的着色器程序源码)
    46      参数4:lenOfStrings: 长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
    47      */
    48     //    glShaderSource(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length)
    49     glShaderSource(*shader, 1, &source,NULL);
    50     
    51     // 4. 编译
    52     glCompileShader(*shader);
    53 }

    2、绘制型关信息的设置,过程:

      a2、设置坐标

      b2、加载纹理

      c2、draw 绘制

    a2、设置坐标信息:

        /// 6. 设置顶点、纹理坐标
        // 3个顶点坐标,2个纹理坐标
        GLfloat attrArr[] = {
            0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
            -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
            -0.5f, -0.5f, -1.0f,    0.0f, 0.0f,
            
            0.5f, 0.5f, -1.0f,      1.0f, 1.0f,
            -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
            0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
        };
        
        /// 7. copy 到顶点缓冲区
        GLuint buffer;
        glGenBuffers(1, &buffer);
        glBindBuffer(GL_ARRAY_BUFFER, buffer);
        // 顶点数据 copy 到缓冲区
        glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
    
        /// 8. 打开通道 传递数据
        // 8.1 顶点数据
        // 获取通道 ID
        /*
         glGetAttribLocation(GLuint program, const GLchar *name)
         program:
         name: 给谁传 --> .vsh 的 position
         */
        GLuint position = glGetAttribLocation(self.myProgram, "position");
        // 打开通道
        glEnableVertexAttribArray(position);
        // 读数据
        glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
    
        // 8.2 纹理数据 /*传给谁 --> .vsh 的 textCoordinate */
        GLuint texture = glGetAttribLocation(self.myProgram, "textCoordinate");
        glEnableVertexAttribArray(texture);
        glVertexAttribPointer(texture, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float *)NULL + 3);
        
        
        /// 9. 加载纹理
        [self loadTexture];

    b2、加载纹理

    // 加载纹理
    - (void)loadTexture {
        
        // 9.0 image 转为 CGImageRef
        CGImageRef spriteImage = [UIImage imageNamed:@"0002"].CGImage;
        // 图片是否获取成功
        if (!spriteImage) {
            NSLog(@"Failed to load image ");
            exit(1);// 退出程序
        }
        // 获取图片宽高
        size_t width = CGImageGetWidth(spriteImage);
        size_t height = CGImageGetHeight(spriteImage);
        // 获取图片字节数 宽*高*4(RGBA)
        GLubyte *spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
        
        // 创建上下文
        /*
         data:指向要渲染的绘制图像的内存地址
         width:bitmap 的宽度,单位为像素
         height:bitmap 的高度,单位为像素
         bitPerComponent:内存中像素的每个组件的位数,比如 32 位 RGBA,就设置为 8
         bytesPerRow:bitmap 的没一行的内存所占的比特数
         colorSpace:bitmap 上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA
         */
        CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    
        // 在 CGContextRef 上 --> 将图片绘制出来
        /*
         CGContextDrawImage 使用的 Core Graphics 框架,坐标系与 UIKit 不一样。UIKit 框架的原点在屏幕的左上角,Core Graphics 框架的原点在屏幕的左下角。
         CGContextDrawImage(CGContextRef  _Nullable c, CGRect rect, CGImageRef  _Nullable image)
         c:绘图上下文
         rect:rect坐标
         image:绘制的图片
         */
        CGRect rect = CGRectMake(0, 0, width, height);
        CGContextDrawImage(spriteContext, rect, spriteImage);
    
        // 绘完 释放上下文
        CGContextRelease(spriteContext);
        
        // 9.1. 绑定纹理到默认的纹理ID
        glBindTexture(GL_TEXTURE_2D, 0);
        
        // 9.2. 设置纹理属性
        /*
         glTexParameteri(GLenum target, GLenum pname, GLint param)
         target:纹理维度
         pname:线性过滤; 为s,t坐标设置模式
         param:wrapMode; 环绕模式
         */
        // 过滤方式
        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);
            
        // 9.3 载入纹理
        /* 载入纹理 glTexImage2D
        参数1:纹理维度,GL_TEXTURE_2D
        参数2:mip贴图层次
        参数3:纹理单元存储的颜色成分(从读取像素图中获得)
        参数4:加载纹理宽度
        参数5:加载纹理的高度
        参数6:为纹理贴图指定一个边界宽度 0
        参数7、8:像素数据的数据类型, GL_UNSIGNED_BYTE无符号整型
        参数9:指向纹理图像数据的指针
        */
        float fw = width, fh = height;
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    
        // 9.4 释放 sprite
        free(spriteData);
    }

    c2、绘制

        /// 10. 设置纹理采样器
        glUniform1i(glGetUniformLocation(self.myProgram, "colorMap"), 0);
        
        /// 11.  绘制
        glDrawArrays(GL_TRIANGLES, 0, 6);
        
        // 12. 从渲染缓冲区显示到屏幕
        [self.myContext presentRenderbuffer:GL_RENDERBUFFER];

    运行结果如下图:

    图片是倒立的?如何解决呢?

    UIKit 框架的原点是屏幕的左上角,Core Graphics 框架的原点是屏幕的左下角。

    三、解决图片倒立问题

    1、加载纹理的绘制图片过程中,将图片通过转换矩阵旋转

       // 矩阵转换 - 翻转图片
        // x、y 轴平移
        CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
        // y 平移
        CGContextTranslateCTM(spriteContext, 0, rect.size.height);
        // Y 轴方向 Scale -1 翻转
        CGContextScaleCTM(spriteContext, 1.0, -1.0);
        // 平移回原点位置处
        CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
        // 重绘
        CGContextDrawImage(spriteContext, rect, spriteImage);

    2、顶点坐标对应纹理时 反向对应

    //    GLfloat attrArr[] = {
    //        0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
    //        -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
    //        -0.5f, -0.5f, -1.0f,    0.0f, 0.0f,
    //
    //        0.5f, 0.5f, -1.0f,      1.0f, 1.0f,
    //        -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
    //        0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
    //    };
    
    // 纹理坐标反向对应
    GLfloat attrArr[] = {
    
            0.5f, -0.5f, -1.0f,     1.0f, 1.0f,
            -0.5f, 0.5f, -1.0f,     0.0f, 0.0f,
            -0.5f, -0.5f, -1.0f,    0.0f, 1.0f,
            
            0.5f, 0.5f, -1.0f,      1.0f, 0.0f,
            -0.5f, 0.5f, -1.0f,     0.0f, 0.0f,
            0.5f, -0.5f, -1.0f,     1.0f, 1.0f,
        };

    3、修改着色器代码进行翻转

    3.1、纹理着色器中 对纹理 y 坐标进行翻转

     

    纹理着色器代码:

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

    3.2、顶点着色器中 对纹理坐标转换

    原理同 3.1

    代码:

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

    3.3、顶点着色器 传入旋转矩阵对顶点进行旋转

    顶点着色器代码:

    attribute vec4 position;
    attribute vec2 textCoordinate;
    uniform mat4 rotateMatrix; varying lowp vec2 varyTextCoord;
    void main() { varyTextCoord = textCoordinate; vec4 vPos = position; vPos = vPos * rotateMatrix; gl_Position = vPos; }

    绘制前  旋转矩阵 --> 矩阵使用 uniform 修饰传递

        // 1. rotate等于shaderv.vsh中的 uniform 属性,rotateMatrix
        GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
        
        // 2.获取渲旋转的弧度
        float radians = 180 * 3.14159f / 180.0f;
        // 3.求得弧度对于的sincos值
        float s = sin(radians);
        float c = cos(radians);
        
        // 4.因为在3D课程中用的是横向量,在OpenGL ES用的是列向量
        // 参考Z轴旋转矩阵
        GLfloat zRotation[16] = {
            c,-s,0,0,
            s,c,0,0,
            0,0,1,0,
            0,0,0,1
        };
        
        // 5.设置旋转矩阵
        /*
         glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat* value)
         location : 对于shader 中的ID
         count : 个数
         transpose : 是否转置矩阵
         value : 指针
         */
        glUniformMatrix4fv(rotate, 1, GL_FALSE, zRotation);

    一般使用第一种方式,修改着色器代码 每一个像素都要运行一次,成本太高。

     

  • 相关阅读:
    符号修饰与函数签名、extern “C”
    WinInet单线程断点续传下载
    关掉"离开模式“,解决计算机无法进入睡眠状态
    链接库——动态链接库
    Google开源项目风格指南——类
    使用CURL读取HTTP数据到字符串或者文件中
    Wininet多线程断点续传下载
    contains
    [转]在linux下如何使用Makefile对fortran程序进行编译
    【转】一些解决变态数学公式的算法地址
  • 原文地址:https://www.cnblogs.com/zhangzhang-y/p/13414166.html
Copyright © 2011-2022 走看看