OpenGL ES GLSL加载纹理

使用GLSL语言加载纹理,需要自定义顶点着色器和片源着色器。

GLSL编写的顶点着色器和片元着色器其实是一段代码,也是一段字符串,所以文件名和后缀可以自定义。

通过创建Empty文件,修改文件名为shaderv.vshshaderf.fsh,分别代表顶点着色器和片元着色器。在GLSL语言编写的文件中,建议不要写中文注释,否则可能发生一些奇怪的错误。

顶点着色器 shaderv.vsh代码

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

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

解释:
attribute vec4 position; 指的是attribute属性的 4维向量 的顶点坐标。
attribute vec2 textCoordinate; 指的是attribute属性的 2维向量 的纹理坐标。
varying lowp vec2 varyTextCoord; 指的是通过顶点着色器透传到片元着色器中的 低精度 的 2维向量 的纹理坐标。
varying修饰符代表可以通过顶点着色器传参到片元着色器,在片元着色器中的接收变量 声明 需要和顶点着色器中 一模一样
每一个着色器文件都有一个main函数。
varyTextCoord = textCoordinate; 将纹理坐标通过varyTextCoord变量传递到片元着色器。
gl_Position = position; 不对顶点坐标做任何处理,直接传入给OpenGL ES中的内建变量gl_Position

片元着色器 shaderf.fsh代码

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

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

解释:
precision highp float; 在本文件中使用高精度的 float
varying lowp vec2 varyTextCoord; 声明和顶点着色器中一致,用来接收纹理坐标。
uniform sampler2D colorMap; 纹理采样器
texture2D(colorMap, varyTextCoord); 通过纹理采样器,获取纹理每个像素点对应的颜色值,赋值给OpenGL ES中的内建变量gl_FragColor

使用GLSL编写自定义着色器加载纹理需要以下步骤:

  • 创建图层
  • 创建上下文
  • 清空缓冲区
  • 设置renderBuffer
  • 设置frameBuffer
  • 开始绘制

以下代码在自定义CustomView继承自UIView中编写。

0.定义属性

@interface CustomView()

@property (nonatomic, strong) CAEAGLLayer *eaglLayer;//自定义图层

@property (nonatomic, strong) EAGLContext *context;//上下文

@property (nonatomic, assign) GLuint renderBuffer;//渲染缓冲区ID
@property (nonatomic, assign) GLuint frameBuffer;//帧缓冲区ID

@property (nonatomic, assign) GLuint program;//程序ID

@end

1.创建图层

//1.设置图层
- (void)setupLayer {
    self.eaglLayer = (CAEAGLLayer *)self.layer;
    
    [self setContentScaleFactor:[UIScreen mainScreen].scale];
    
    /**
     kEAGLDrawablePropertyColorFormat:颜色缓冲区格式
     kEAGLDrawablePropertyRetainedBacking:绘制后是否保留其内容
     */
    self.eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@false, kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}

需要重写+ (Class)layerClass;方法才能强转生效。

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

2.创建上下文

//2.设置上下文
- (void)setupContext {
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (!self.context) {
        NSLog(@"create context failed");
        return;
    }
    BOOL ret = [EAGLContext setCurrentContext:self.context];
    if (!ret) {
        NSLog(@"setCurrentContext failed");
        return;
    }
}

3.清空缓冲区

//3.清空缓冲区
- (void)clearRenderAndFrameBuffer {
    //Frame Buffer Object FBO
    //Render Buffer 三类:颜色缓冲区、深度缓冲区、模版缓冲区
    
    glDeleteRenderbuffers(1, &_renderBuffer);
    self.renderBuffer = 0;
    
    glDeleteFramebuffers(1, &_frameBuffer);
    self.frameBuffer = 0;
}

4.设置renderBuffer

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

5.设置frameBuffer

//5.设置frameBuffer
- (void)setupFrameBuffer {
    GLuint frameBuffer;
    
    glGenFramebuffers(1, &frameBuffer);
    
    self.frameBuffer = frameBuffer;
    
    glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer);
    
    /*生成帧缓存区之后,则需要将renderbuffer跟framebuffer进行绑定,
     调用glFramebufferRenderbuffer函数进行绑定到对应的附着点上,后面的绘制才能起作用
     */
    //将渲染缓存区frameBuffer 通过glFramebufferRenderbuffer函数绑定到 GL_COLOR_ATTACHMENT0上。
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.renderBuffer);
}

6.开始绘制

//6.开始绘制
- (void)renderLayer {
    //设置背景色
    glClearColor(0.45, 0.5, 0, 1);
    //清空颜色缓冲区
    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);
    
    //顶点着色器和片元着色器文件路径
    NSString *vertFilePath = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
    NSString *fragFilePath = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
    
    //加载顶点着色器和纹理着色器 创建program
    self.program = [self loaderShader:vertFilePath withFrag:fragFilePath];
    
    //链接program
    glLinkProgram(self.program);
    
    //获取program链接状态
    GLint linkStatus;
    glGetProgramiv(self.program, GL_LINK_STATUS, &linkStatus);
    if (linkStatus == GL_FALSE) {
        GLchar loginfo[512];
        glGetProgramInfoLog(self.program, sizeof(loginfo), 0, &loginfo[0]);
        NSString *message = [NSString stringWithUTF8String:loginfo];
        NSLog(@"program link error:%@", message);
        return;
    }
    
    //使用program
    glUseProgram(self.program);
    
    //准备顶点数据/纹理坐标
    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,
     };
    
    //将顶点坐标和纹理坐标拷贝到GPU中
    GLuint attrBuffer;
    glGenBuffers(1, &attrBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
    
    //获取顶点数据通道ID v.sh positon 打开顶点通道 并设置数据读取方式
    GLuint position = glGetAttribLocation(self.program, "position");
    glEnableVertexAttribArray(position);
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
    
    //打开纹理通道 并设置数据读取方式
    GLuint textCoordinate = glGetAttribLocation(self.program, "textCoordinate");
    glEnableVertexAttribArray(textCoordinate);
    glVertexAttribPointer(textCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);

    //加载纹理
    [self setupTexture:@"mew_progressive.jpg"];
    
    //设置纹理采样器 第二个参数为0、1、2...,第几个纹理就传几
    glUniform1i(glGetUniformLocation(self.program, "colorMap"), 0);
    
    //开始绘制
    glDrawArrays(GL_TRIANGLES, 0, 6);
    
    //从渲染缓冲区显示到屏幕上
    [self.context presentRenderbuffer:GL_RENDERBUFFER];
}

加载纹理

//加载纹理
- (GLuint)setupTexture:(NSString *)filePath {
    //获取图像CGImage
    CGImageRef cgImage = [UIImage imageNamed:filePath].CGImage;
    if (!cgImage) {
        NSLog(@"faile to load image");
        return -1;
    }
    
    //获取图片宽高
    size_t width = CGImageGetWidth(cgImage);
    size_t height = CGImageGetHeight(cgImage);
    
    //获取图片字节数 宽*高*4(RGBA)
    GLubyte *data = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
    
    //创建上下文
    /*
     参数1:data,指向要渲染的绘制图像的内存地址
     参数2:width,bitmap的宽度,单位为像素
     参数3:height,bitmap的高度,单位为像素
     参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
     参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
     参数6:colorSpace,bitmap上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA
     */
    CGContextRef cgContext = CGBitmapContextCreate(
                                                   data,
                                                   width,
                                                   height,
                                                   8,
                                                   width * 4,
                                                   CGImageGetColorSpace(cgImage),
                                                   kCGImageAlphaPremultipliedLast);
    //使用CGContextRef 将图片绘制出来 也是一个解码的过程
    /*
     CGContextDrawImage 使用的是Core Graphics框架,坐标系与UIKit 不一样。UIKit框架的原点在屏幕的左上角,Core Graphics框架的原点在屏幕的左下角。
     CGContextDrawImage
     参数1:绘图上下文
     参数2:rect坐标
     参数3:绘制的图片
     */
    CGRect rect = CGRectMake(0, 0, width, height);
    
    CGContextDrawImage(cgContext, rect, cgImage);
    
    CGContextRelease(cgContext);
    
    //纹理 当只有一个纹理时 纹理ID为0, 多个纹理需要激活
    GLuint textureID;
    glGenTextures(1, &textureID);
    
    glBindTexture(GL_TEXTURE_2D, textureID);
    
    //设置纹理属性
    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数据
    /*
     参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
     参数2:加载的层次,一般设置为0
     参数3:纹理的颜色值GL_RGBA
     参数4:宽
     参数5:高
     参数6:border,边界宽度
     参数7:format
     参数8:type
     参数9:纹理数据
     */
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    
    free(data);
    return 0;
}

加载着色器

//加载着色器
- (GLuint)loaderShader:(NSString *)vert withFrag:(NSString *)frag {
    //顶点着色器对象 片元着色器对象/句柄
    GLuint verShader, fragShader;
    
    //创建空的program
    GLuint program = glCreateProgram();
    
    //编译
    [self compileShader:&verShader type:GL_VERTEX_SHADER filePath:vert];
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER filePath:frag];
    
    //把shader附着 到编译好的程序
    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);
    
    //附着之后 就可以删除
    glDeleteShader(verShader);
    glDeleteShader(fragShader);
    
    return program;
}

编译program

//编译
- (void)compileShader:(GLuint *)shader type:(GLenum)type filePath:(NSString *)filePath {
    //读取路径
    NSString *content = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    const GLchar *source = [content UTF8String];
    
    //创建对应类型的shader
    *shader = glCreateShader(type);
    
    //将着色器附着到 着色器对象上
    glShaderSource(*shader, 1, &source, NULL);
    
    //编译
    glCompileShader(*shader);
}

demo地址:GLSL加载图片

glVertexAttribPointer函数最后一个参数,从哪里开始访问数据的 NULL, 如果不加(GLfloat *),将会导致纹理加载不上去!!!

由于CoreGraphics坐标系与UIKit不一致,导致图片显示为倒的,之后介绍五种旋转图片的方法。

你可能感兴趣的:(OpenGL ES GLSL加载纹理)