阅读 253

OpenGL ES 图片的加载以及纹理翻转

EGL

OpenGL ES 命令需要渲染上下文和绘制表面才能完成图形图形的绘制
渲染上下文:存储相关 OpenGL ES 状态
绘制表面:用于绘制图元的表面,它指定渲染所需要的缓存区类型。例如颜色缓存区、深度缓存区、模版缓存区。
OpenGL ES API 并没有提供如何创建渲染上下文或者上下文如何连接到原声窗口系统。EGL 是 Khronos 渲染 API[如 OpenGL ES]和原生窗口系统之间的接口。唯一支持 OpenGL ES 却不支持 EGL 的平台是 iOS。

EGL 的主要功能如下:

  1. 和本地窗口系统通讯
  2. 查询可用配置
  3. 创建 OpenGL ES 可用的绘制表面
  4. 同步不同类别的 API 之间的渲染,比如在 OpenGL ES 和 OpenVG 之间同步,或者在 OpenGL 和 本地窗口的绘图命令之间
  5. 管理渲染资源,比如纹理映射

iOS 中提供了 QuartzCore/CAEAGLLayer 作为绘制表面

着色器与程序

需要创建两个基本对象才能改用着色进行渲染:着色器对象和程序对象

获取链接后着色器对象的一般过程包括6个步骤:

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

API 介绍

着色器 API

GLuint glCreateShader(GLenum type)

type: 创建着色器的类型,GL_VERTEX_SHADER 或 GL_FRAGMENT_SHADER
return: 返回值是指向新着色器对象的句柄,可以调用

void glDeleteShader(GLuint shader)

shader: 要删除的着色器对象句柄

void glShaderSource(GLuint shader, GLsizei count, const GLchar* const *string, const GLint* length)

shader: 指向着色器对象的句柄
count: 着色器源字符串的数量,着色器可以由多个源字符串组成,但是每个着色器只有一个 main 函数
string: 指向保存数量为 count 的着色器源字符串的数组指针
length: 指向保存每个着色器字符串大小 且元素数量为 count 的整数数组指针

void glCompileShader(GLuint shader)

shader: 需要编译的着色器对象句柄

void glGetShaderiv(GLuint shader, GLenum pname, GLint* params)

shader: 需要编译的着色器对象句柄
pname: 获取的信息参数名字
params: 指向查询结果的整数存储位置的指针

pname 可选名字
GL_COMPILE_STATUS
GL_DELETE_STATUS
GL_INFO_LOG_LENGTH
GL_SHADER_SOURCE_LENGTH
GL_SHADER_TYPE

void glGetShaderInfoLog(GLuint shader, GLsizei bufsize, GLsizei* length, GLchar* infolog)

shader: 指向着色器对象的句柄
bufsize: 保存信息日志的缓存区大小
length: 写入信息的日志的长度(减去 null 终止符);如果不需要知道长度,可以传 Null
infolog: 指向保存信息日志的字符串缓存区的指针

程序 API

GLuint glCreateProgram(void)

创建程序
return: 返回一个新程序对象的句柄

void glDeleteProgram(GLuint program)

删除程序
program: 指向程序对象的句柄

void glAttachShader(GLuint program, GLuint shader)

着色器与程序链接/附着
program: 指向程序对象的句柄
shader: 指向程序链接的着色器对象句柄

void glDetachShader(GLuint program, GLuint shader)

断开着色器与程序的链接
program: 指向程序对象的句柄
shader: 指向程序链接的着色器对象句柄

void glLinkProgram(GLuint program)

链接程序 program: 指向程序对象的句柄

void glGetProgramiv(GLuint program, GLenum pname, GLint* params)

判断链接状态
program: 指向程序对象的句柄 pname: 获取信息的参数名称 params: 指向查询结果整数存储位置的指针

pname 可选名字
GL_ACTIVE_ATTRIBUTES
GL_ACTIVE_ATTRIBUTE_MAX_LENGTH
GL_ACTIVE_UNIFORM_BLOCKS
GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH
GL_ACTIVE_UNIFORMS
GL_ACTIVE_UNIFORM_MAX_LENGTH
GL_ATTACHED_SHADERS
GL_DELETE_STATUS
GL_INFO_LOG_LENGTH
GL_LINK_STATUS
GL_PROGRAM_BINARY_RETRIEVABLE_HINT
GL_TRANSFORM_FEEDBACK_BUFFER_MODE
GL_TRANSFORM_FEEDBACK_VARYINGS
GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH
GL_VALIDATE_STATUS

void glGetProgramInfoLog(GLuint program, GLsizei bufsize, GLsizei* length, GLchar* infolog)

获取日志信息 program: 指向程序对象的句柄
bufsize: 保存信息日志的缓存区大小
length: 写入信息的日志的长度(减去 null 终止符);如果不需要知道长度,可以传 Null
infolog: 指向保存信息日志的字符串缓存区的指针

void glUseProgram(GLuint program)

设置为活动程序
program: 指向程序对象的句柄

FrmeBuffer 和 RenderBuffer 关系

参考文档

FrameBuffer 帧缓冲区

RenderBuffer 渲染缓冲区,作为帧缓冲区的附件之一

把渲染操作的结果数据存储在 RenderBuffer,着色器可以很方便的操作 RenderBuffer 中的内容,传递到帧缓冲区进行显示速度也快。

图片加载

一、设置着色器文件

1.顶点着色器

shaderv.vsh

// 设置图片顶点
attribute vec4 position;
// 设置纹理坐标
attribute vec2 textCoordinate;
// 从顶点着色器传递到片元着色器的变量(传递纹理坐标)
varying lowp vec2 varyTextCoord;

void main() {
    varyTextCoord = textCoordinate;
    gl_Position = position;
}
复制代码

2.片元着色器

shaderf.fsh

// 设置 float 类型为高精度
precision highp float;
// 从顶点着色器传递过来的纹理坐标
varying lowp vec2 varyTextCoord;
// 纹理
uniform sampler2D colorMap;

void main() {
    // 传入纹理和坐标,返回纹素的四维向量信息(RGBA)
    gl_FragColor = texture2D(colorMap, varyTextCoord);
}
复制代码

纹理加载完整代码


#import "CustomView.h"
#import <OpenGLES/ES2/gl.h>

/**
 绘制准备工作
 1. 创建图层
 2. 创建上下文
 3. 清空缓冲区
 4. 设置 RenderBuffer
 5. 设置 FrameBuffer
 6. 开始绘制
 */

@interface CustomView ()

// 绘制载体
@property (nonatomic, strong) CAEAGLLayer *myEAGLLayer;
// 上下文
@property (nonatomic, strong) EAGLContext *myContext;

// 渲染缓冲区
@property (nonatomic, assign) GLuint myColorRenderBuffer;
// 帧缓冲区: 深度/颜色/模版
@property (nonatomic, assign) GLuint myColorFrameBuffer;

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

@end

@implementation CustomView

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

- (void)layoutSubviews {
    [self setupLayer];
    [self setupContext];
    [self deleteBuffer];
    [self setupRenderBuffer];
    [self setupFrameBuffer];
    [self renderLayer];
}

/// 绘制图层
- (void)setupLayer {
    // 1.创建图层
    self.myEAGLLayer = (CAEAGLLayer *)self.layer;
    // 2.设置scale
    [self setContentScaleFactor:[UIScreen mainScreen].scale];
    // 3.设置缓冲区
    // kEAGLDrawablePropertyRetainedBacking 绘制结束是否要保留
    // kEAGLDrawablePropertyColorFormat 颜色格式
    self.myEAGLLayer.drawableProperties = @{
        kEAGLDrawablePropertyRetainedBacking: @(NO),
        kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
    };
}

/// 设置上下文
- (void)setupContext {
    // 1.创建
    self.myContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    // 2.判断
    if (!self.myContext) {
        NSLog(@"create context failure");
        return;
    }
    // 3.设置当前 context
    if (![EAGLContext setCurrentContext:self.myContext]) {
        NSLog(@"set current context failure");
        return;
    }
}

/// 删除缓冲区
- (void)deleteBuffer {
    glDeleteBuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;
    glDeleteBuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0;
}

/// 设置 RenderBuffer
- (void)setupRenderBuffer {
    // 1.定义
    GLuint buffer;
    glGenRenderbuffers(1, &buffer);
    self.myColorRenderBuffer = buffer;
    // 2.绑定
//    glBindBuffer(GL_RENDERBUFFER, buffer);
    glBindRenderbuffer(GL_RENDERBUFFER, buffer);
    
    // 3.设置渲染上下文
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEAGLLayer];
}

/// 设置 FrameBuffer
- (void)setupFrameBuffer {
    // 1.定义
    GLuint buffer;
    glGenFramebuffers(1, &buffer);
    self.myColorFrameBuffer = buffer;
    // 2.绑定
//    glBindBuffer(GL_FRAMEBUFFER, buffer);
    glBindFramebuffer(GL_FRAMEBUFFER, buffer);
    // 3.绑定渲染缓冲区和帧缓冲区
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
}

/// 开始绘制
- (void)renderLayer {
    // 1.清理缓冲区
    glClearColor(0.3, 0.45, 0.5, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    
    // 2.设置视口
    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);
    
    // 3.读取着色器文件地址
    NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"vsh"];
    NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"fsh"];
    
    // 4.获取程序(绑定了顶点着色器和片元着色器数据)
    self.myPrograme = [self loaderShaders:vertFile withFrag:fragFile];
    
    // 5.链接程序
    glLinkProgram(self.myPrograme);
    
    GLint linkState;
    // 获取链接状态
    glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkState);
    if (linkState == GL_FALSE) {
        // link 失败
        GLchar message[512];
        glGetProgramInfoLog(self.myPrograme, sizeof(message), 0, &message[0]);
        NSString *messageString = [NSString stringWithUTF8String:message];
        NSLog(@"Programe link failure: %@", messageString);
        return;
    }
    
    // 6.link success,使用程序
    glUseProgram(self.myPrograme);
    
    // 7.顶点数据,两个三角形
    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,
    };
    
    // 8.顶点数据-> 顶点缓冲区
    GLuint attrBuffer;
    glGenBuffers(1, &attrBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
    
    // 9.打开通道
    // a.获取并打开顶点数据通道,设置数据
    GLuint position = glGetAttribLocation(self.myPrograme, "position");
    glEnableVertexAttribArray(position);
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
    
    // b.获取并打开纹理数据通道,设置数据
    GLuint textCoordinate = glGetAttribLocation(self.myPrograme, "textCoordinate");
    glEnableVertexAttribArray(textCoordinate);
    glVertexAttribPointer(textCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
    
    // 10.加载纹理
    [self setupTexture:@"Image"];
    
    // 11.设置纹理采样器
    glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
    
    // 12.绘制
    glDrawArrays(GL_TRIANGLES, 0, 6);
    
    // 13.从渲染缓冲区显示到屏幕上
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER];
}

// 获取纹理
- (GLuint)setupTexture:(NSString *)fileName {
    // 1.纹理解压缩
    CGImageRef spriteImage = [UIImage imageNamed:@"Image"].CGImage;
    if (!spriteImage) {
        NSLog(@"load image failure");
        exit(1);
    }
    // 2.宽高
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
    
    // 3.获取图片字节数 宽*高*4(RGBA)
    GLubyte *spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
    
    // 4.设置上下文
    /*
     参数1:data,指向要渲染的绘制图像的内存地址
     参数2:width,bitmap的宽度,单位为像素
     参数3:height,bitmap的高度,单位为像素
     参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
     参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
     参数6:colorSpace,bitmap上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA
     */
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    // 5.在 CGContextRef 上,将图片绘制出来
    CGRect rect = CGRectMake(0, 0, width, height);
    CGContextDrawImage(spriteContext, rect, spriteImage);
    // 6.绘制结束,释放上下文
    CGContextRelease(spriteContext);
    // 7.绑定纹理到默认纹理 ID。只有一个纹理时,第二个参数 texture,可以设置为0,且0号纹理默认激活
    glBindTexture(GL_TEXTURE_2D, 0);
    // 8.设置纹理属性
    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);
    
    // 9.载入纹理
    float fw = width, fh = height;
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    
    // 10.释放
    free(spriteData);
    return 0;
}


#pragma mark - shader

/// 创建着色器
/// @param vertFilePath 顶点着色器字符串文件路径
/// @param fragFilePath 片元着色器字符串文件路径
- (GLuint)loaderShaders:(NSString *)vertFilePath withFrag:(NSString *)fragFilePath {
    // 1.定义顶点着色器和片元着色对象
    GLuint verShader, fragShader;
    // 2.创建程序
    GLuint program = glCreateProgram();
    
    // 3.创建并编译着色器
    [self compileShader:&verShader type:GL_VERTEX_SHADER filePath:vertFilePath];
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER filePath:fragFilePath];
    
    // 4.链接着色器和程序
    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);
    
    // 5.清理无用对象
    glDeleteShader(verShader);
    glDeleteShader(fragShader);
    
    return program;
}


/// 创建并编译着色器
/// @param shader 着色器指针
/// @param type 类型:顶点/片元
/// @param filePath 着色器字符串文件路径
- (void)compileShader:(GLuint *)shader type:(GLenum)type filePath:(NSString *)filePath {
    // 1.读取文件
    NSString *content = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    const GLchar *source = (GLchar *)[content UTF8String];
    
    // 2.创建一个对应类型的着色器
    *shader = glCreateShader(type);
    
    // 3.将着色器源码附着到着色器
    glShaderSource(*shader, 1, &source, NULL);
    
    // 4.编译
    glCompileShader(*shader);
}

@end
复制代码

纹理翻转

1.旋转矩阵,不翻转纹理

    GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
    float radians = 180 * 3.14159f / 180.0f;
    float s = sin(radians);
    float c = cos(radians);
    
  
    GLfloat zRotation[16] = {
        c, -s, 0, 0,
        s, c, 0, 0,
        0, 0, 1.0, 0,
        0.0, 0, 0, 1.0
    };
    
   glUniformMatrix4fv(rotate, 1, GL_FALSE, (GLfloat *)&zRotation[0]);
复制代码

2.解压图片时,将图片源文件翻转(最常用✨✨✨)

CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;

size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));

CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
  
CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);

CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
CGContextTranslateCTM(spriteContext, 0, rect.size.height);
CGContextScaleCTM(spriteContext, 1.0, -1.0);
CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
CGContextDrawImage(spriteContext, rect, spriteImage); 

CGContextRelease(spriteContext);
glBindTexture(GL_TEXTURE_2D, 0);
复制代码

3.修改片元着色器文件,纹理坐标

varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
    gl_FragColor = texture2D(colorMap, vec2(varyTextCoord.x,1.0-varyTextCoord.y));
}
复制代码

4.修改顶点着色器,比第三种执行次数少

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

void main()
{
    varyTextCoord = vec2(textCoordinate.x,1.0-textCoordinate.y);
    gl_Position = position;
}

复制代码

5.修改纹理坐标数据映射关系

     GLfloat attrArr[] =
     {
     0.5f, -0.5f, 0.0f,        1.0f, 1.0f, //右下
     -0.5f, 0.5f, 0.0f,        0.0f, 0.0f, // 左上
     -0.5f, -0.5f, 0.0f,       0.0f, 1.0f, // 左下
     0.5f, 0.5f, 0.0f,         1.0f, 0.0f, // 右上
     -0.5f, 0.5f, 0.0f,        0.0f, 0.0f, // 左上
     0.5f, -0.5f, 0.0f,        1.0f, 1.0f, // 右下
     };
     */
    
复制代码

参考文章
OpenGL 2D纹理单元&纹理翻转解决策略

文章分类
阅读
文章标签