使用自定义着色器实现一个图片的渲染

388 阅读7分钟
  • 变量和数据类型

    • 基本数据类型

      1. 布尔类型 bool
      2. 有符号整型 int
      3. 无符号整型 uint
      4. 浮点型 float
    • 向量类型

      类型描述
      vec2,vec3,vec42分量、3分量、4分量浮点向量
      ivec2,ivec3,ivec42分量、3分量、4分量整型向量
      uvec2,uvec3,uvec42分量、3分量、4分量无符号整型向量
      uvec2,uvec3,uvec42分量、3分量、4分量布尔向量
    • 矩阵类型 所有矩阵类型都只支持浮点型

      类型描述
      mat2,mat2x2两行两列
      mat3,mat3x3三行三列
      mat4,mat4x4四行四列
      mat2x3两行三列
      mat2x4两行四列
      mat3x2三行两列
      mat3x4三行四列
      mat4x2四行两列
      mat4x3四行三列
  • 存储限定符

    类型描述
    <none>只是普通的本地变量,外部不可见,外部不可访问
    const一个编译时常量,或者说是一个对函数来说为只读的参数
    in/varying一个从前面阶段传递过来的变量
    in centroid一个从前面阶段传递过来的变量,使用质心插值
    out/attribute传递到下一个处理阶段或者在一个函数中指定一个返回值
    out centroid传递到下一个处理阶段,使用质心插值
    inout一个读/写变量,只能用于局部函数参数
    uniform一个从客户端代码传递过来的变量,在顶点之间不做改变
  • 着色器的编译、绑定和链接的基本步骤

    1. 创建一个顶点着色器对象和一个片元着色器对象
    2. 将源代码连接到每个着色器对象
    3. 编译着色器对象
    4. 创建一个程序对象
    5. 将编译后的着色器对象连接到程序对象
    6. 链接程序对象
  • FrameBuffer与RenderBuffer

    首先看一下两者的关系图 官方文档着这样说的:
    A renderbuffer object is a 2D image buffer allocated by the application. The renderbuffer can be used to allocate and store color, depth, or stencil values and can be used as a color, depth, or stencil attachment in a framebuffer object. A renderbuffer is similar to an off-screen window system provided drawable surface, such as a pbuffer. A renderbuffer, however, cannot be directly used as a GL texture. ⼀个renderbuffer对象是应⽤分配的一个2D图像缓存区。renderbuffer能够被用来分配和存储颜色、深度或者模板值。也能够在一个framebuffer中被用作颜色、深度、模板的附件。⼀个renderbuffer是一个类似于屏幕窗口系统提供可绘制的表面。⽐如pBuffer。⼀个renderbuffer, 并不能直接被当做⼀个GL纹理使用。 A framebuffer object (often referred to as an FBO) is a collection of color, depth, and stencil buffer attachment points; state that describes properties such as the size and format of the color, depth, and stencil buffers attached to the FBO; and the names of the texture and renderbuffer objects attached to the FBO. Various 2D images can be attached to the color attachment point in the framebuffer object. These include a renderbuffer object that stores color values, a mip-level of a 2D texture or a cube map face, or even a mip-level of a 2D slice in a 3D texture. Similarly, various 2D images contain-ing depth values can be attached to the depth attachment point of an FBO. These can include a renderbuffer, a mip-level of a 2D texture or a cubemap face that stores depth values. The only 2D image that can be attached to the stencil attachment point of an FBO is a renderbuffer object that stores stencil values. ⼀个frameBuffer对象(通常被称为⼀个FBO)。是一个颜色、深度和模板缓存区附着点的集合。描述属性的状态,例如颜⾊、深度和模板缓存区的⼤⼩和格式,都关联到FBO(Frame Buffer Object)。并且纹理的名字和renderBuffer对象也都是关联于FBO。各种各样的2D图形能够被附着framebuffer对象的颜色附着点。它们包含了renderbuffer对象存储的颜色值、一个2D纹理或⽴⽅体贴图。或者一个mip-level的⼆维切面在3D纹理。同样,各种各样的2D图形包含了当时的深度值可以附加到⼀个FBO的深度附着点钟去。唯一的二维图像,能够附着在FBO的模板附着点,是一个renderbuffer对象存储模板值。
    还是比较难懂通俗点说framebuffer并非是个缓冲区他是一个包含一个或多个附件的聚合器对象,它们依次是实际的缓冲区。你可以理解帧缓冲器作为C结构,其中每个成员都是指向缓冲区的指针。没有任何附件,帧缓冲器对象的占用空间非常小。renderbuffer是一个实际的缓冲区(一个字节数组,或整数,或像素)。在运用的时候呢RenderBuffer需要附着于FrameBuffer, FrameBuffer管理RenderBuffer, 需要先设置RenderBuffer, 然后和FramBuffer进行绑定操作, 后面的绘制才能起到作用

  • 利用自定义着色器渲染一个图片代码

    1. 顶点着色器代码(着色器代码其实就是字符串)

      //顶点坐标
      attribute vec4 position;
      //纹理坐标(应为片元着色器无法接收到通过attribute传递的坐标信息,所以只能先传到顶点着色器然后桥接给片元着色器)
      attribute vec2 textCoordinate;
      //桥接纹理坐标参数
      varying lowp vec2 varyTextCoord;
      
      //这里有多少个顶点main函数就会执行多少次
      void main()
      {
          varyTextCoord = textCoordinate;
          //gl_Position内建变量赋值就好
          gl_Position = position;
      }
      
    1. 片元着色器代码
        
        ```
        //首先定义所有的顶点信息都是高精度的
        precision highp float;
        //用来接收顶点着色器传递的参数(切记参数要和顶点着色器声明的一样)
        varying lowp vec2 varyTextCoord;
        //接收客户端传递的纹理ID(应为纹理已经被加载进来了所以只需要传递纹理id)
        uniform sampler2D colorMap;
        
        //这里又多少个像素点就会执行所少次
        void main()
        {
        	//获取每个像素点的颜色值(纹素)
            gl_FragColor = texture2D(colorMap, varyTextCoord);
        }
        ```
    1. 着色器的编译、绑定、链接
        
        ```
        /**
         创建一个顶点着色器对象和一个片元着色器对象
         将源代码连接到每个着色器对象
         编译着色器对象
         创建一个程序对象
         将编译后的着色器对象连接到程序对象
         */
        -(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag{
            //1.创建两个临时的着色器对象
            GLuint verShader,fragShader;
            //创建一个程序program
            GLuint program = glCreateProgram();
            //2.编译两个着色器
            [self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
            [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
            
            //3.创建最终程序(将编译后的着色器对象连接到程序对象)
            glAttachShader(program, verShader);
            glAttachShader(program, fragShader);
            
            //4.此时着色器已经添加到程序上了了已经不需要了所以释放
            glDeleteShader(verShader);
            glDeleteShader(fragShader);
            
            return program;
        }
        
        //编译shader
        /**
         shader 着色器对象
         type 着色器类型
         file 着色器代码(就是一个字符串的路径)
         */
        - (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{
            //1.先读取文件路径的字符串(就是着色器代码)
            NSString * content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
            //转换一下字符串类型(OC字符串类型转成C++字符串类型)
            const GLchar *source = (GLchar *)[content UTF8String];
            
            //2.根据类型来创建着色器
            *shader = glCreateShader(type);
            
            //3.将着色的源码附着在着色器对象上面
            //参数1:shader,要编译的着色器对象 *shader
            //参数2:numOfStrings,传递的源码字符串数量 1个
            //参数3:strings,着色器程序的源码(真正的着色器程序源码)
            //参数4:lenOfStrings,长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
            glShaderSource(*shader, 1, &source, NULL);
            
            //4.将着色器源码编译成目标代码
            glCompileShader(*shader);
            
        }
        ```
    1. 获取和设置属性
    
        ```
        //(2).设置合适的格式从buffer里面读取数据
            glEnableVertexAttribArray(position);
            
            //(3).设置读取方式
            //参数1:index,顶点数据的索引
            //参数2:size,每个顶点属性的组件数量,1,2,3,或者4.默认初始值是4.
            //参数3:type,数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
            //参数4:normalized,固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE)
            //参数5:stride,连续顶点属性之间的偏移量,默认为0;
            //参数6:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0
            glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
            
            //处理纹理数据
            GLuint textCoor = glGetAttribLocation(self.myPrograme, "textCoordinate");
            glEnableVertexAttribArray(textCoor);
            glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float *)NULL + 3);
            
            //加载纹理
            [self setupTexture:@"daitu"];
            
            //设置纹理采样器sampler2D
            glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
            
            glDrawArrays(GL_TRIANGLES, 0, 6);
            
            //从渲染缓冲区显示到屏幕上
            [self.myContext presentRenderbuffer:GL_RENDERBUFFER];
        ```
        
        
    

完整代码: github.com/Bore-TuDou/…