OpenGL ES-加载一张本地图片
一: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];
作者:Visual
链接:https://juejin.cn/post/7012907114599284749