OpenGL ES-加载一张本地图片

1,414 阅读4分钟

一:OpenGL ES是什么

OpenGL ES是以手持和嵌入式为目标的高级3D图形应用程序API,是目前智能手机主流图形API,是OpenGL的简化版本,我们IOS的底层图形渲染就是使用了OpenGL ES和Metal

二:我们使用OpenGL ES可以实现哪些功能

我们可以使用OpenGL ES来自定义着色器实现滤镜效果(比如市面上常见相机的图片滤镜,抖音的视频动态滤镜),地图的渲染等

三:我们如何使用OpenGL ES来加载一张本地图片

项目地址:github.com/visual-ios/…

3.1 导入库 #import <OpenGLES/ES2/gl.h>

3.2 创建上下文,并设置为当前上下文

self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:**self**.context];

3.3 创建显示的CAEAGLLayer

CAEAGLLayer *layer = [[CAEAGLLayer alloc] init];

self.myLayer = layer;

layer.frame = CGRectMake(0, (self.view.frame.size.height - self.view.frame.size.width) / 2, self.view.frame.size.width, self.view.frame.size.width);

[self.view.layer addSublayer:layer];

3.4 创建缓存区RenderBuffer(渲染缓存)和FrameBuffer(帧缓存)

GLuint renderBuffer;

GLuint frameBuffer;

glGenRenderbuffers(1, &renderBuffer);

glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);

[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myLayer];

    
glGenFramebuffers(1, &frameBuffer);

glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);

3.5 设置视口,视口需要依赖缓存区的创建,所以放在创建缓存区后面

GLint backingWidth;

glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);

GLint backingHeight;

glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);

glViewport(0, 0, backingWidth, backingHeight);

3.6 获取纹理图片

self.textureID = [self createTextureWithImage:@"tupian"];

- (GLuint)createTextureWithImage:(NSString *)imageName

{

    UIImage *image = [UIImage imageNamed:imageName];

    CGImageRef cgImageRef = [image CGImage];

    **if** (!cgImageRef) {

        NSLog(@"Failed to load image");
    }

    //读取图片的大小宽高

    GLuint width = (GLuint)CGImageGetWidth(cgImageRef);

    GLuint height = (GLuint)CGImageGetHeight(cgImageRef);

    //获取图片的rect

    CGRect rect = CGRectMake(0, 0, width, height);

    

    //获取图片的颜色空间

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

    //3.获取图片字节数 宽*高*4(RGBA)

    **void** *imageData = malloc(width * height * 4);

    //4.创建上下文

    /*

     参数1:data,指向要渲染的绘制图像的内存地址

     参数2:width,bitmap的宽度,单位为像素

     参数3:height,bitmap的高度,单位为像素

     参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8

     参数5:bytesPerRow,bitmap的没一行的内存所占的比特数

     参数6:colorSpace,bitmap上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA

     */

    CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

    //对图片进行重新绘制,得到一张新的解压缩后的位图

    CGContextDrawImage(context, rect, cgImageRef);

    
    //设置纹理

    GLuint textureID;

    glGenTextures(1, &textureID);

    glBindTexture(GL_TEXTURE_2D, textureID);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);

    
    //设置纹理属性

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    
    //绑定纹理

    glBindTexture(GL_TEXTURE_2D, 0);

    CGContextRelease(context);

    free(imageData);

    return textureID;

}

3.7 创建顶点和纹理坐标,此处为相对坐标,前面三个为顶点坐标(X,Y,Z),后面两个为纹理坐标(U,V)

[self createVertices];

- (void)createVertices
{
    GLfloat vertex[] =
    {
        1.f, -1.f, 0.f,     1.0f, 1.0f,
        -1.f, 1.f, 0.f,     0.0f, 0.0f,
        -1.f, -1.f, 0.f,    0.0f, 1.0f,
        1.f, 1.f, 0.f,      1.0f, 0.0f,
        -1.f, 1.f, 0.f,     0.0f, 0.0f,
        1.f, -1.f, 0.f,     1.0f, 1.0f,
    };

    GLuint vertexBuffer;

    glGenBuffers(1, &vertexBuffer);

    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);

    glBufferData(GL_ARRAY_BUFFER, **sizeof**(vertex), vertex, GL_DYNAMIC_DRAW);

    self.vertexBuffer = vertexBuffer;

}

3.8 创建顶点着色器Normal.vsh和片元着色器Normal.fsh,着色器代码会被编译为字符串

Normal.vsh

attribute vec4 Position;

attribute vec2 TextureCoords;

varying vec2 TextureCoordsVarying;


void main(void) {

    gl_Position = Position;

    TextureCoordsVarying = TextureCoords;

}

Normal.fsh

precision highp float;

uniform sampler2D Texture;

varying vec2 TextureCoordsVarying;

void main (void) {

    gl_FragColor = texture2D(Texture, TextureCoordsVarying);

}

3.9 编译我们刚刚写的着色器代码

GLuint vertexShader = [self compileShaderWithName:name type:GL_VERTEX_SHADER];

GLuint fragmentShader = [self compileShaderWithName:name type:GL_FRAGMENT_SHADER];

- (GLuint)compileShaderWithName:(NSString *)name type:(GLenum)shaderType

{
    NSString *shaderPath = [[NSBundle mainBundle] pathForResource:name ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"];

    NSError *error;

    NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];

    if (!shaderString) {

        NSAssert(NO, @"读取shader失败");
    }

    

    //2. 创建shader->根据shaderType

    GLuint shader = glCreateShader(shaderType);

    
    //3.获取shader source

    const char *shaderStringUTF8 = [shaderString UTF8String];

    int shaderStringLength = (int)[shaderString length];

    glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);

    
    //4.编译shader

    glCompileShader(shader);


    //5.查看编译是否成功

    GLint compileSuccess;

    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileSuccess);

    if (compileSuccess == GL_FALSE) {

        GLchar messages[256];

        glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);

        NSString *messageString = [NSString stringWithUTF8String:messages];

        NSAssert(NO, @"shader编译失败:%@", messageString);

        exit(1);

    }

    //6.返回shader

    return shader;

}

3.10 创建程序,把着色器代码附着在程序上,链接程序,此处传入的name为着色器的name(Normal)

GLuint program = [self programWithShaderName:name];

- (GLuint)programWithShaderName:(NSString *)name

{

    GLuint vertexShader = [self compileShaderWithName:name type:GL_VERTEX_SHADER];

    GLuint fragmentShader = [self compileShaderWithName:name type:GL_FRAGMENT_SHADER];

    GLuint program = glCreateProgram();

    self.program = program;

    glAttachShader(program, vertexShader);

    glAttachShader(program, fragmentShader);

    
    glLinkProgram(program);

    
    GLint linkSuccess;

    glGetProgramiv(program, GL_LINK_STATUS, &linkSuccess);

    if (linkSuccess == GL_FALSE) {

        GLchar messages[256];

        glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);

        NSString *messageString = [NSString stringWithUTF8String:messages];

        NSAssert(NO, @"program链接失败:%@", messageString);

        exit(1);

    }

    //5.返回program

    return program;

}

3.11 把对应的顶点坐标和纹理坐标传入着色器,此处传入的program为刚刚链接完成的program

//3. 获取Position,Texture,TextureCoords 的索引位置

    GLuint positionSlot = glGetAttribLocation(program, "Position");

    GLuint textureSlot = glGetUniformLocation(program, "Texture");

    GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords");

    
    //激活纹理

    glActiveTexture(GL_TEXTURE0);

    glBindTexture(GL_TEXTURE_2D, self.textureID);

    glUniform1i(textureSlot, 0);

    //打开顶点坐标通道

    glEnableVertexAttribArray(positionSlot);

    //传入顶点坐标

    glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float*)NULL);


    //打开纹理坐标通道

    glEnableVertexAttribArray(textureCoordsSlot);

    //传入纹理坐标

    glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (float*)NULL + 3);

3.12 开始绘制,绘制之前最好再次执行绑定及清除的操作,避免别人的代码会产生影响

//使用program

glUseProgram(self.program);

//绑定buffer

glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer);

// 清除画布

glClear(GL_COLOR_BUFFER_BIT);

glClearColor(1, 1, 1, 1);

// 重绘

glDrawArrays(GL_TRIANGLES, 0, 6);

//渲染到屏幕上

[self.context presentRenderbuffer:GL_RENDERBUFFER];