美文网首页
OpenGL ES 纹理绘制

OpenGL ES 纹理绘制

作者: Maji1 | 来源:发表于2020-08-03 22:18 被阅读0次

本篇文章主要讲解如何使用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);
}
  • glShaderSource(*shader, 1, &source, NULL);将着色器源码附加到着色器对象上:
  • shader:要编译的着色器对*shader
  • numOfStrings:传递的源码字符串数量 1个
  • strings:着色器程序的源码(真正的着色器程序源码&sourc
  • lenOfStrings:长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的.

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);
  • glGetAttribLocation(self.program, "position");
    program读取顶点数据position,注意这里的"position"一定要跟shaderv.vsh文件中的position保持一致。
  • glEnableVertexAttribArray(position);
    打开顶点数据读取通道,默认是关闭的。
  • glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);设置读取方式:
    index:顶点数据的索引 position
    size:每个顶点属性的组件数量,1、2、3或者4。默认初始值是4。我们这里是3
    type:数据中的每个组件的类型,常用的有GL_FLOAT,GL_BYTE,GL_SHORT。默认初始值为GL_FLOAT
    normalized:固定点数据值是否应该归一化,或者直接转换为固定值。(GL_FALSE
    stride:连续顶点属性之间的偏移量,默认为0。我们这里一组数据有5个,所以需要乘以5。
    NULL:指定一个指针,指向数组中的第一个顶点属性的第一个组件。默认为0。

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;
}
  • calloc(width * height * 4, sizeof(GLubyte));
    获取图片字节数 宽4,因为我们用的颜色是 RGBA 4分量。
  • CGBitmapContextCreate();创建上下文:
    参数1:data,指向要渲染的绘制图像的内存地址
    参数2:width,bitmap的宽度,单位为像素
    参数3:height,bitmap的高度,单位为像素
    参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
    参数5:bytesPerRow,bitmap的每一行的内存所占的比特数
    参数6:colorSpace,bitmap上使用的颜色空间
    参数7:kCGImageAlphaPremultipliedLast:RGBA
  • CGContextDrawImage()绘制:
    使用的是Core Graphics框架,坐标系与UIKit 不一样。UIKit框架的原点在屏幕的左上角,Core Graphics框架的原点在屏幕的左下角。
  • glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);载入纹理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:纹理数据

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];

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



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

相关文章

  • Android音视频开发 - 用OpenGL ES绘制图片

    1. 纹理介绍 使用 OpenGL ES 绘制简单的几何形状还不够,OpenGL 更多地是用来显示而纹理图像,比如...

  • OpenGL ES 分屏滤镜

    想要绘制滤镜首先我们需要清楚如何绘制纹理,如果不了解的可以参考 OpenGL ES 纹理绘制。这篇文章中绘制的图片...

  • OpenGL ES 纹理绘制

    纹理绘制就是纹理映射,这里有个术语 纹素 ,不是用像素来表示纹理对象中的显示元素,主要是为了强调纹理对象的应用方式...

  • OpenGL ES 纹理绘制

    本篇文章主要讲解如何使用GLSL语言来进行纹理的绘制。首先我们需要了解如何编写GLSL程序,经过之前的学习我们知道...

  • OpenGL ES on iOS --- 2D纹理

    OpenGL ES on iOS --- 2D纹理 简介 纹理是用来丰富我们绘制物体细节的,它可以是一张2D图片(...

  • OpenGL-ES 学习示例

    OpenGL-ES 学习示例 描述:1、GLKit 绘制图片和正方体2、GLSL 绘制金字塔、颜色纹理混合3、GL...

  • OpenGL ES 3.0纹理映射-绘制一张图片

    本篇博客了解一下2D纹理,并完成一个绘制显示一张图片的Renderer。 2D纹理 2D纹理是OpenGL ES中...

  • OpenGL坐标概念

    openGL 顶点,坐标系,纹理坐标Android OpenGL es 纹理坐标设定与贴图规则对Android o...

  • OpenGL之纹理及应用案例

    纹理介绍 OpenGL使用的图片数据(纹理)都是tga格式的,而iOS/OpenGL ES使用PNG/JPEG格式...

  • OpenGL ES基础知识回顾

    OpenGL ES基础知识回顾 GLSL着色器语言 语言基础 绘制方式 纹理映射 一、GLSL着色器语言 渲染管线...

网友评论

      本文标题:OpenGL ES 纹理绘制

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