OpenGL ES 自定义着色器-绘制图片

2020-08-04  本文已影响0人  lkm_0bdc

上一篇我们提到固定着色器同时继承GLKViewController
绘制图片,那么现在,将讲述如何自定义着色器绘制图片。

由于苹果没有提供·相应的文件,因此我们需要自己创建文件使用(相当于一个字符串),一般后缀使用\color{red}{vsh}代表顶点着色器,\color{red}{fsh}表示片元着色器,现在看看两个文件的内容。

在顶点着色器中position定义了图片的顶点位置,textCoordinate是纹理坐标,varyTextCoodi其实也是一个纹理坐标,但是它的作用是一个\color{red}{侨接}的角色,把顶点着色器的纹理传递到片元着色器中,为了达到这个目地,我们需要注意,\color{red}{传递的坐标名字必须一模一样才能完成},如果不需要传递,可以不用保持一致。
那么在\color{red}{main}函数中,我们进行纹理赋值,由于案例中的图片不需要做形变,因此直接将坐标传出去。

//顶点坐标
attribute vec4 position;
//纹理坐标
attribute vec2 textCoordinate;
varying lowp vec2 varyTextCoord;
void main(){
    varyTextCoord = textCoordinate;
    gl_Position = position;
}

下面,让我们看看片元着色器需要做的事情,首先定义了一片元着色器的精度,然后varyTextCoord获得顶点着色器的纹理坐标,并且定义了纹理。这是两个必须要获取的数据。
main函数中我们获取当前像素下面的纹素。
\color{red}{Textture2d}函数是一个常用的函数,当我们调用这个内建函数之后,会返回一个\color{red}{vec4四维向量}(指的颜色),最后要给到最终结果的内建变量\color{red}{gl_FragColor}

precision highp float;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
    //lowp vec4 temp = texture2D(colorMap, varyTextCoord);
//gl_FragColor = temp;
//参数1:纹理
//参数2:纹理坐标
    gl_FragColor = texture2D(colorMap, varyTextCoord);
}

顶点着色器/片元着色器的手动编译连接使用

在开始绘制之前,我们需要加载编译shader

/加载shader
-(GLuint)loadShaders:(NSString *)vert Withfrag:(NSString *)frag
{
    //1.定义2个零时着色器对象
    GLuint verShader, fragShader;
    //创建program
    GLint program = glCreateProgram();
    
    //2.编译顶点着色程序、片元着色器程序
    //参数1:编译完存储的底层地址
    //参数2:编译的类型,GL_VERTEX_SHADER(顶点)、GL_FRAGMENT_SHADER(片元)
    //参数3:文件路径
    [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.释放不需要的shader
    glDeleteShader(verShader);
    glDeleteShader(fragShader);

    return program;
}
//编译shader
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{
    
    //1.读取文件路径字符串
    NSString* content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    const GLchar* source = (GLchar *)[content UTF8String];
    
    //2.创建一个shader(根据type类型)
    *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.创建上下文
3.清空缓存区
4.设置RenderBuffer
5.设置FrameBuffer
6.开始绘制

创建图层

//1.设置图层
-(void)setupLayer
{
    //1.创建特殊图层
    /*
     重写layerClass,将CCView返回的图层从CALayer替换成CAEAGLLayer
     */
    self.myEagLayer = (CAEAGLLayer *)self.layer;
    
    //2.设置scale
    [self setContentScaleFactor:[[UIScreen mainScreen]scale]];
    NSLog(@"%f",[[UIScreen mainScreen]scale]);
    //3.设置描述属性,这里设置不维持渲染内容以及颜色格式为RGBA8
    /*
     kEAGLDrawablePropertyRetainedBacking  表示绘图表面显示后,是否保留其内容。
     kEAGLDrawablePropertyColorFormat
         可绘制表面的内部颜色缓存区格式,这个key对应的值是一个NSString指定特定颜色缓存区对象。默认是kEAGLColorFormatRGBA8;  
         kEAGLColorFormatRGBA8:32位RGBA的颜色,4*8=32位
         kEAGLColorFormatRGB565:16位RGB的颜色,
         kEAGLColorFormatSRGBA8:sRGB代表了标准的红、绿、蓝,即CRT显示器、LCD显示器、投影机、打印机以及其他设备中色彩再现所使用的三个基本色素。sRGB的色彩空间基于独立的色彩坐标,可以使色彩在不同的设备使用传输中对应于同一个色彩坐标体系,而不受这些设备各自具有的不同色彩坐标的影响。
     */
    self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false,kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,nil]; 
}
+(Class)layerClass
{
    return [CAEAGLLayer class];
}

设置上下文

我们依然使用EAGLContext创建上下文,这个必须要做事情。

//2.设置上下文
-(void)setupContext
{
    //1.指定OpenGL ES 渲染API版本,我们使用2.0
    EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
    //2.创建图形上下文
    EAGLContext *context = [[EAGLContext alloc]initWithAPI:api];
    //3.判断是否创建成功
    if (!context) {
        NSLog(@"Create context failed!");
        return;
    }
    //4.设置图形上下文
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"setCurrentContext failed!");
        return;
    }
//5.将局部context,变成全局的
self.myContext = context;

清空缓存区

缓存区使用完之后,是需要清空的,不然会照成不必要的内存浪费。

//3.清空缓存区
-(void)deleteRenderAndFrameBuffer
{
    /*
     buffer分为frame buffer 和 render buffer2个大类。
     其中frame buffer 相当于render buffer的管理者。
     frame buffer object即称FBO。
     render buffer则又可分为3类。colorBuffer、depthBuffer、stencilBuffer。
     */
    glDeleteBuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;

    glDeleteBuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0; 
}

设置RenderBuffer

RenderBuffer是由FrameBuffer管理的,所以先设置RenderBuffer

//4.设置RenderBuffer
-(void)setupRenderBuffer
{
    //1.定义一个缓存区ID
    GLuint buffer;
    
    //2.申请一个缓存区标志
    glGenRenderbuffers(1, &buffer);
    
    //3.
    self.myColorRenderBuffer = buffer;
    
    //4.将标识符绑定到GL_RENDERBUFFER
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    
    //5.将可绘制对象drawable object's  CAEAGLLayer的存储绑定到OpenGL ES renderBuffer对象
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];  
}

设置FrameBuffer

//5.设置FrameBuffer
-(void)setupFrameBuffer
{
    //1.定义一个缓存区ID
    GLuint buffer;
    
    //2.申请一个缓存区标志
    //glGenRenderbuffers(1, &buffer);
    //glGenFramebuffers(1, &buffer);
    glGenBuffers(1, &buffer);
    
    //3.
    self.myColorFrameBuffer = buffer;
    
    //4.
    glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    
    /*生成帧缓存区之后,则需要将renderbuffer跟framebuffer进行绑定,
     调用glFramebufferRenderbuffer函数进行绑定到对应的附着点上,后面的绘制才能起作用
     */
    
    //5.将渲染缓存区myColorRenderBuffer 通过glFramebufferRenderbuffer函数绑定到 GL_COLOR_ATTACHMENT0上。
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);  
}

纹理加载流程

纹理加载.png

开始绘制

绘制流程图

这就是我们开始绘制图片的流程,我们最开始加载的shader也是在这里调用

上一篇下一篇

猜你喜欢

热点阅读