OpenGL ES 纹理绘制

2020-08-03  本文已影响0人  Maji1

本篇文章主要讲解如何使用GLSL语言来进行纹理的绘制。
首先我们需要了解如何编写GLSL程序,经过之前的学习我们知道,我们能操作的只有 顶点着色器片元着色器。所以我们需要创建两个空文件分别命名为shaderv.vshshaderf.fsh:


当然文件名字看自己的习惯,可以修改。shaderv.vshshaderf.fsh分别表示 顶点着色器片元着色器 的程序文件。

GLSL代码

先来看下 shaderv.vsh 文件的代码:

attribute vec4 position;
attribute vec2 textureCoordinate;
varying lowp vec2 varyTextCoord

void main()
{
    varyTextCoord = textureCoordinate;
    gl_Position = position;
}

position:顶点坐标
textureCoordinate:纹理坐标
varyTextCoord:用来传递纹理坐标到片元着色器。
gl_Position:内建变量,用来接收顶点坐标。

shaderf.fsh 文件的代码:

precision highp float;
varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;

void main()
{
    gl_FragColor = texture2D(colorMap, varyTextCoord);
}

precision highp float;:设置默认精度。
varying lowp vec2 varyTextCoord;:用来接收顶点着色器传过来的纹理坐标。需要跟shaderv.vsh 文件的名字保持一致,完全一样。
colorMap;:颜色贴图(也就是纹理)
gl_FragColor:内建变量

绘制流程

简单了解了GLSL程序书写位置,下面我们介绍下绘制流程。
首先我们需要将viewlayer替换为CAEAGLLayer类型的layer:

+ (Class)layerClass {
    return [CAEAGLLayer class];
}

然后在layoutSubviews方法里进行操作绘制:

 - (void)layoutSubviews {
    //1.设置图层
    [self setupLayer];
    //2.设置上下文
    [self setupContext];
    //3.清空缓存区
    [self clearColorBuffer];
    //4.设置RenderBuffer
    [self setupRenderBuffer];
    //5.设置FrameBuffer
    [self setupFrameBuffer];
    //6.渲染图层
    [self renderLayer];
}

该段代码大概分为6步:1、设置图层 2、设置上下文 3、清空缓存区 4、设置RenderBuffer 5、设置FrameBuffer 6、渲染图层。这些都是自定义的方法。

1、设置图层

- (void)setupLayer {
    self.eaglLayer = (CAEAGLLayer *)self.layer;
    [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
    self.eaglLayer.drawableProperties = @{
        kEAGLDrawablePropertyRetainedBacking:@(0), 
        kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8
    };
}

drawableProperties绘制属性:
kEAGLDrawablePropertyRetainedBacking 表示绘图表面显示后,是否保留其内容, true保留, false不保留。
kEAGLDrawablePropertyColorFormat 可绘制表面的内部颜色缓存区格式,这个key对应的值是一个NSString指定特定颜色缓存区对象。默认是kEAGLColorFormatRGBA8
2、设置上下文

    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"context set failed");
        return;
    }
    self.currentContext = context;

3、清空缓存区

    glDeleteFramebuffers(1, &_colorFrameBuffer);
    self.colorFrameBuffer = 0;
    glDeleteRenderbuffers(0, &_colorRenderBuffer);
    self.colorRenderBuffer = 0;

4、设置RenderBuffer

    GLuint renderBufferID;
    glGenRenderbuffers(1, &renderBufferID);
    self.colorRenderBuffer = renderBufferID;
    //绑定(将标识符绑定到GL_RENDERBUFFER)
    glBindRenderbuffer(GL_RENDERBUFFER, renderBufferID);
    //绑定layer到GL_RENDERBUFFER
    [self.currentContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eaglLayer];

5、设置FrameBuffer

    GLuint frameBufferID;
    glGenFramebuffers(1, &frameBufferID);
    self.colorFrameBuffer = frameBufferID;
    glBindFramebuffer(GL_FRAMEBUFFER, frameBufferID);
    
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.colorRenderBuffer);

生成帧缓存区之后,则需要将renderbufferframebuffer进行绑定,调用glFramebufferRenderbuffer函数进行绑定到对应的附着点上,后面的绘制才能起作用。
6、渲染图层
6.1 基本操作

    glClearColor(0.2, 0.6, 0.4, 1.0);
    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);

这段代码大家应该很熟悉了,都是些常规的操作。

6.2 加载shader程序

    NSString *vPath = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
    NSString *fPath = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
    NSLog(@"vPath:%@ \n fPath:%@",vPath, fPath); 
    self.program = [self loadShaderWithVPath:vPath fPath:fPath];

我们重点看下loadShaderWithVPath:fPath:代码:

- (GLuint)loadShaderWithVPath:(NSString *)vPath fPath:(NSString *)fPath {
    //创建program
    GLuint program = glCreateProgram();
    //编译顶点着色程序、片元着色器程序
    GLuint vShader, fShader;
    [self compileShader:&vShader type:GL_VERTEX_SHADER file:vPath];
    [self compileShader:&fShader type:GL_FRAGMENT_SHADER file:fPath];
    //将顶点着色程序、片元着色器程序附着在program上
    glAttachShader(program, vShader);
    glAttachShader(program, fShader);
    //使用完之后删除
    glDeleteShader(vShader);
    glDeleteShader(fShader);
    return program;
}

- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)path {
    *shader = glCreateShader(type);
    NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    const GLchar *source = (GLchar *)[content UTF8String];

    glShaderSource(*shader, 1, &source, NULL);

    //着色器源代码编译成目标代码
    glCompileShader(*shader);
}

6.3 链接

    glLinkProgram(self.program);
    GLint linkStatus;// 链接的状态
    glGetProgramiv(self.program, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE) {
        GLchar errorMessage[512];
        glGetProgramInfoLog(self.program, sizeof(errorMessage), 0, &errorMessage[0]);
        NSString *errorString = [NSString stringWithUTF8String:errorMessage];
        NSLog(@"Program Link Error:%@", errorString);
        return;;
    }
    NSLog(@"Program Link Success!");

6.4 使用program

glUseProgram(self.program);

6.5 处理顶点数据

    GLfloat attrArr[] =
    {
        -0.5f, -0.5f, -1.0f,    0.0f, 0.0f,
        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,      1.0f, 1.0f,
        -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
        0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
    };
    GLuint vBuffer;
    glGenBuffers(1, &vBuffer);
    //将attrBuffer绑定到GL_ARRAY_BUFFER标识符上
    glBindBuffer(GL_ARRAY_BUFFER, vBuffer);
    //把顶点数据从CPU内存复制到GPU上
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);

6.6 设置读取方式

    //顶点数据
    GLuint position = glGetAttribLocation(self.program, "position");
    glEnableVertexAttribArray(position);//打开顶点数据读取通道
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
 
    //纹理数据
    GLuint textureCoordinate = glGetAttribLocation(self.program, "textureCoordinate");
    glEnableVertexAttribArray(textureCoordinate);//打开纹理数据读取通道
    glVertexAttribPointer(textureCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);

6.7 加载纹理图片

- (GLuint)setupTexture:(NSString *)imageName {
    CGImageRef imageRef = [UIImage imageNamed:imageName].CGImage;
    if (!imageRef) {
        NSLog(@"failed image!");
        exit(1);
    } 
    size_t width = CGImageGetWidth(imageRef);
    size_t height = CGImageGetHeight(imageRef);
    //获取图片字节数
    GLubyte *imageData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
    //创建上下文
    CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast);
    CGRect rect = CGRectMake(0, 0, width, height);
    //绘制
    CGContextDrawImage(context, rect, imageRef);
    CGContextRelease(context); 

    glBindBuffer(GL_TEXTURE_2D, 0);

    //设置纹理属性
    /*
     参数1:纹理维度
     参数2:线性过滤、为s,t坐标设置模式
     参数3: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);
    
    
    float fw = width, fh = height;
    
    //载入纹理2D数据
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); 

    free(imageData);
    return 0;
}

6.8 设置纹理采样器 sampler2D

    GLint colorMap = glGetUniformLocation(self.program, "colorMap");
    glUniform1i(colorMap, 0);

"colorMap"需要跟shaderf.fsh文件中保持一致。

6.9 绘制

    glDrawArrays(GL_TRIANGLES, 0, 6);

6.10 从渲染缓存区显示到屏幕上

    [self.currentContext presentRenderbuffer:GL_RENDERBUFFER];

最后我们看下绘制的效果:



我们发现绘制的效果是倒过来的,如何让这张图正过来,相信大家都有自己的想法,这篇文章就不做介绍了。

上一篇 下一篇

猜你喜欢

热点阅读