OpenGL ES GLSL 初体验

663 阅读4分钟

注:本文旨在记录笔者的学习过程,仅代表笔者个人的理解,如果有表述不准确的地方,欢迎各位指正!因为涉及到的概念来源自网络,所以如有侵权,也望告知!

前言

本文主要是通过OpenGL ES、GLSL来实现图片的加载。

正文

一、知识了解

1.什么是OpenGL ES?

**OpenGL ES** (OpenGL for Embedded Systems) 是以⼿持和嵌⼊式为⽬标的⾼级3D图形应
⽤程序编程接⼝(API). OpenGL ES 是⽬前智能⼿机中占据统治地位的图形API.⽀持的平
台: iOS, Andriod , BlackBerry ,bada ,Linux ,Windows。

2.什么是GLSL?

GLSL(OpenGL Shading Language)是一个以C语言为基础的高阶着色语言。它是由 OpenGL ARB 所建立,提供开发者对绘图管线更多的直接控制,而无需使用汇编语言或硬件规格语言。

3.什么是EGL?

OpenGL ES 命令需要渲染上下⽂和绘制表⾯才能完成图形图像的绘制。渲染上下⽂: 存储相关OpenGL ES 状态。绘制表⾯: 是⽤于绘制图元的表⾯,它指定渲染所需要的缓存区类型,例如颜⾊缓存区,深度缓冲区和模板缓存区。OpenGL ES API 并没有提供如何创建渲染上下⽂或者上下⽂如何连接到原⽣窗⼝系统. EGL (Embedded Graphics Library )是Khronos 渲染API(如OpenGL ES) 和原⽣窗⼝系统之间的接⼝。但是唯⼀⽀持OpenGL ES 却不⽀持EGL 的平台是iOS,因为iOS自身实现了一套与EGL相同的机制EAGL。

二、图片绘制流程

1.⽤EAGL创建屏幕上的渲染表⾯

//视图
self.eaglLayer = (CAEAGLLayer *)self.layer;
self.contentScaleFactor = [UIScreen mainScreen].scale;
self.eaglLayer.drawableProperties = @{
    kEAGLDrawablePropertyRetainedBacking : @(false),
    kEAGLDrawablePropertyColorFormat : kEAGLColorFormatRGBA8
};
//上下文
EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!context) {    
    NSLog(@"create context failed");    
    return;
}    
if (![EAGLContext setCurrentContext:context]) {    
    NSLog(@"setCurrentContext failed");    
    return;
}    
self.eaglContext = context;

2.设置渲染缓冲区、帧缓冲区

//清理缓存区(渲染、帧)
glDeleteBuffers(1, &_myRenderBuffer);self.myRenderBuffer = 0;
glDeleteBuffers(1, &_myFrameBuffer);self.myFrameBuffer = 0;
//初始化缓冲区(渲染、帧)
glGenRenderbuffers(1, &_myRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, self.myRenderBuffer);
[self.eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.eaglLayer];
glGenFramebuffers(1, &_myFrameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, self.myFrameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myRenderBuffer);

3.清除颜色缓存区、设置视口

//清屏
glClearColor(1, 1, 1, 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);

4.顶点/片元着色器GLSL实现

a.顶点着色器实现shaderv.vsh

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

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

b.片元着色器实现shaderf.fsh

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

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

5.加载编译顶点/片元着色器,创建⼀个程序对象,链接顶点/⽚元着⾊器,链接程序对象

//加载着色器程序
self.program = [self loadShaders:vertexFile withFrag:fragmentFile];
//program链接
glLinkProgram(self.program);
GLint linkStatus;
glGetProgramiv(self.program, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
    GLchar message[512];
    glGetProgramInfoLog(self.program, sizeof(message), 0, &message[0]);
    NSString *messageString = [NSString stringWithUTF8String:message];
    NSLog(@"Program Link Error:%@",messageString);
    return;
}
//使用programglUseProgram(self.program);

- (GLuint)loadShaders:(NSString *)vertexFile withFrag:(NSString *)fragmentFile {
    //1.定义临时着色器对象(顶点、片元)
    GLuint vertexShader, fragmentShader;
    //2.编译着色器程序(顶点、片元)
    [self compileShader:&vertexShader type:GL_VERTEX_SHADER file:vertexFile];
    [self compileShader:&fragmentShader type:GL_FRAGMENT_SHADER file:fragmentFile];
    //3.创建program
    GLuint program = glCreateProgram();
    //4.附着着色器对象
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    //5.释放临时着色器对象(顶点、片元)
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    return program;
}

- (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
    *shader = glCreateShader(type);
    //3.附着着色器代码
    glShaderSource(*shader, 1, &source, NULL);
    //4.编译着色器代码
    glCompileShader(*shader);
}

6.绘制图形、加载纹理

//顶点、纹理坐标数组
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,
};
//设置顶点缓冲区
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
//传参到着色器
GLuint position = glGetAttribLocation(self.program, "position");
glEnableVertexAttribArray(position);
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
GLuint textCoord = glGetAttribLocation(self.program, "textCoordinate");
glEnableVertexAttribArray(textCoord);
glVertexAttribPointer(textCoord, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
//加载纹理
[self setUpTexture:@"demoPic.jpg"];
GLuint colorMap = glGetUniformLocation(self.program, "colorMap");
glUniform1i(colorMap, 0);
//绘制
glDrawArrays(GL_TRIANGLES, 0, 6);
//从渲染缓存区显示到屏幕上
[self.eaglContext presentRenderbuffer:GL_RENDERBUFFER];

- (void)setUpTexture:(NSString *)fileName {
    //1.将 UIImage转换为CGImageRef
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    //判断图片是否获取成功
    if (!spriteImage) {
        NSLog(@"Failed to load image %@", fileName);
        return;
    }
    //2.图片解码成位图
    //读取图片的大小(宽和高)
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
    //获取图片字节数 宽*高*4(RGBA)
    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, rect, spriteImage);
    //释放上下文
    CGContextRelease(spriteContext);
    //3.绑定纹理到默认的纹理ID
    glBindTexture(GL_TEXTURE_2D, 0);
    //4.设置纹理属性
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    //5.载入纹理
    float fw = width, fh = height;
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    free(spriteData);
}

三、纹理翻转问题

当我们完成以上的绘制流程后,我们就可以看到如图的效果:

不难发现,我们的图片中人物是颠倒过来的,因此我们需要将纹理进行翻转。纹理翻转的方式有很多种,大家也可以自己去探究一下,这边笔者也就不累赘了,给出其中一种方式:通过矩阵变化,使纹理绕z轴旋转180度。

-(void)rotateTextureImage {
    //1\. rotate等于shaderv.vsh中的uniform属性,rotateMatrix
    GLuint rotate = glGetUniformLocation(self.myPrograme, "rotateMatrix");
    //2.获取渲旋转的弧度
    float radians = 180 * 3.14159f / 180.0f;
    //3.求得弧度对于的sin\cos值
    float s = sin(radians);
    float c = cos(radians);
    //4.Z轴旋转矩阵
    GLfloat zRotation[16] = {
        c,-s,0,0,
        s,c,0,0,
        0,0,1,0,
        0,0,0,1
    };
    //5.设置旋转矩阵
    glUniformMatrix4fv(rotate, 1, GL_FALSE, zRotation);
}

shaderv.vsh文件对应修改

void main() {
    varyTextCoord = textCoordinate;
    vec4 vPos = position; 
    vPos = vPos * rotateMatrix;
    gl_Position = vPos;
}

最终翻转后的效果如下: