OpenGL 12 - 案例:分屏滤镜
案例:利用 GLSL 实现分屏滤镜 --> 2、3、4、6、9分屏。
分屏滤镜简单思路:绘制原始纹理图片 -- 绘制分屏 --> 1、片元着色器 来修改设置每个像素 --> 2、重绘
实现效果:
一、绘制原图过程不变:
1、创建图层:CAEAGLLayer
2、创建上下文 EAGLContext
3、设置绑定渲染缓冲区、帧缓冲区 Renderbuffers、Framebuffers
4、纹理图片、顶点数据准备 attribute、texture
5、着色器代码准备 shader
6、着色器编译、附着链接program、program使用
7、传递顶点、纹理数据到着色器 glVertexAttribPointer、glUniform1i
8、绘制 draw
二、分屏原理
分屏滤镜过程中,纹理图片的顶点数据是没有改变的,所以顶点着色器代码相同:
1 attribute vec4 Position; 2 attribute vec2 TextureCoords; 3 varying vec2 TextureCoordsVarying; 4 5 void main (void) { 6 gl_Position = Position; 7 TextureCoordsVarying = TextureCoords; 8 }
关于分屏所取的纹理位置可以按自己需求修改,例如取中心位置、左上角等等。
1、2分屏
1.1、片元着色器代码
1 precision highp float; 2 uniform sampler2D Texture; 3 varying highp vec2 TextureCoordsVarying; 4 5 void main() { 6 vec2 uv = TextureCoordsVarying.xy; 7 float y; 8 if (uv.y >= 0.0 && uv.y <= 0.5) { 9 y = uv.y + 0.25; 10 } else { 11 y = uv.y - 0.25; 12 } 13 gl_FragColor = texture2D(Texture, vec2(uv.x, y)); 14 }
2、3分屏
2.1、片元着色器代码
1 precision highp float; 2 uniform sampler2D Texture; 3 varying highp vec2 TextureCoordsVarying; 4 5 void main() { 6 vec2 uv = TextureCoordsVarying.xy; 7 if (uv.y < 1.0/3.0) { 8 uv.y = uv.y + 1.0/3.0; 9 } else if (uv.y > 2.0/3.0){ 10 uv.y = uv.y - 1.0/3.0; 11 } 12 gl_FragColor = texture2D(Texture, uv); 13 }
3、4分屏原理
3.1、片元着色器代码
1 precision highp float; 2 uniform sampler2D Texture; 3 varying highp vec2 TextureCoordsVarying; 4 5 void main() { 6 vec2 uv = TextureCoordsVarying.xy; 7 if(uv.x <= 0.5){ 8 uv.x = uv.x * 2.0; 9 }else{ 10 uv.x = (uv.x - 0.5) * 2.0; 11 } 12 13 if (uv.y<= 0.5) { 14 uv.y = uv.y * 2.0; 15 }else{ 16 uv.y = (uv.y - 0.5) * 2.0; 17 } 18 19 gl_FragColor = texture2D(Texture, uv); 20 }
4、6分屏原理
4.1、片元着色器代码
1 precision highp float; 2 uniform sampler2D Texture; 3 varying highp vec2 TextureCoordsVarying; 4 5 void main() { 6 vec2 uv = TextureCoordsVarying.xy; 7 8 if(uv.x <= 1.0 / 3.0){ 9 uv.x = uv.x + 1.0/3.0; 10 }else if(uv.x >= 2.0/3.0){ 11 uv.x = uv.x - 1.0/3.0; 12 } 13 14 if(uv.y <= 0.5){ 15 uv.y = uv.y + 0.25; 16 }else { 17 uv.y = uv.y - 0.25; 18 } 19 20 gl_FragColor = texture2D(Texture, uv); 21 }
5、9分屏原理
5.1、片元着色器代码
1 precision highp float; 2 uniform sampler2D Texture; 3 varying highp vec2 TextureCoordsVarying; 4 5 void main() { 6 vec2 uv = TextureCoordsVarying.xy; 7 if (uv.x < 1.0 / 3.0) { 8 uv.x = uv.x * 3.0; 9 } else if (uv.x < 2.0 / 3.0) { 10 uv.x = (uv.x - 1.0 / 3.0) * 3.0; 11 } else { 12 uv.x = (uv.x - 2.0 / 3.0) * 3.0; 13 } 14 if (uv.y <= 1.0 / 3.0) { 15 uv.y = uv.y * 3.0; 16 } else if (uv.y < 2.0 / 3.0) { 17 uv.y = (uv.y - 1.0 / 3.0) * 3.0; 18 } else { 19 uv.y = (uv.y - 2.0 / 3.0) * 3.0; 20 } 21 gl_FragColor = texture2D(Texture, uv); 22 }
三、主要代码
1 #import "ViewController.h" 2 #import <GLKit/GLKit.h> 3 #import "FilterBar.h" 4 5 typedef struct { 6 GLKVector3 positionCoord; // (X, Y, Z) 7 GLKVector2 textureCoord; // (U, V) 8 } SenceVertex; 9 10 @interface ViewController ()<FilterBarDelegate> 11 @property (nonatomic, assign) SenceVertex *vertices; 12 @property (nonatomic, strong) EAGLContext *context; 13 // 用于刷新屏幕 14 @property (nonatomic, strong) CADisplayLink *displayLink; 15 // 开始的时间戳 16 @property (nonatomic, assign) NSTimeInterval startTimeInterval; 17 // 着色器程序 18 @property (nonatomic, assign) GLuint program; 19 // 顶点缓存 20 @property (nonatomic, assign) GLuint vertexBuffer; 21 // 纹理 ID 22 @property (nonatomic, assign) GLuint textureID; 23 @end 24 25 @implementation ViewController 26 27 - (void)viewWillDisappear:(BOOL)animated { 28 [super viewWillDisappear:animated]; 29 30 // 移除 displayLink 31 if (self.displayLink) { 32 [self.displayLink invalidate]; 33 self.displayLink = nil; 34 } 35 } 36 37 - (void)viewDidLoad { 38 [super viewDidLoad]; 39 // Do any additional setup after loading the view, typically from a nib. 40 // 设置背景颜色 41 self.view.backgroundColor = [UIColor blackColor]; 42 // 创建滤镜工具栏 43 [self setupFilterBar]; 44 // 滤镜处理初始化 45 [self filterInit]; 46 // 开始一个滤镜动画 47 [self startFilerAnimation]; 48 } 49 50 // 创建滤镜栏 51 - (void)setupFilterBar { 52 CGFloat filterBarWidth = [UIScreen mainScreen].bounds.size.width; 53 CGFloat filterBarHeight = 100; 54 CGFloat filterBarY = [UIScreen mainScreen].bounds.size.height - filterBarHeight; 55 FilterBar *filerBar = [[FilterBar alloc] initWithFrame:CGRectMake(0, filterBarY, filterBarWidth, filterBarHeight)]; 56 filerBar.delegate = self; 57 [self.view addSubview:filerBar]; 58 59 NSArray *dataSource = @[@"无",@"分屏_2",@"分屏_3",@"分屏_4",@"分屏_6",@"分屏_9"]; 60 filerBar.itemList = dataSource; 61 } 62 63 64 - (void)filterInit { 65 66 // 1. 初始化上下文并设置为当前上下文 67 self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; 68 [EAGLContext setCurrentContext:self.context]; 69 70 // 2. 开辟顶点数组内存空间 71 self.vertices = malloc(sizeof(SenceVertex) * 4); 72 73 // 3. 初始化顶点(0,1,2,3)的顶点坐标以及纹理坐标 74 self.vertices[0] = (SenceVertex){{-1, 1, 0}, {0, 1}}; 75 self.vertices[1] = (SenceVertex){{-1, -1, 0}, {0, 0}}; 76 self.vertices[2] = (SenceVertex){{1, 1, 0}, {1, 1}}; 77 self.vertices[3] = (SenceVertex){{1, -1, 0}, {1, 0}}; 78 79 // 4.创建图层(CAEAGLLayer) 80 CAEAGLLayer *layer = [[CAEAGLLayer alloc] init]; 81 // 设置图层frame 82 layer.frame = CGRectMake(0, 100, self.view.frame.size.width, self.view.frame.size.width); 83 // 设置图层的scale 84 layer.contentsScale = [[UIScreen mainScreen] scale]; 85 // 给View添加layer 86 [self.view.layer addSublayer:layer]; 87 88 // 5.绑定渲染缓存区 89 [self bindRenderLayer:layer]; 90 91 // 6.获取处理的图片路径 92 NSString *imagePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"cat.jpg"]; 93 // 读取图片 94 UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; 95 // 将JPG图片转换成纹理图片 96 GLuint textureID = [self createTextureWithImage:image]; 97 // 设置纹理ID 98 self.textureID = textureID; // 将纹理 ID 保存,方便后面切换滤镜的时候重用 99 100 // 7.设置视口 101 glViewport(0, 0, self.drawableWidth, self.drawableHeight); 102 103 // 8.设置顶点缓存区 104 GLuint vertexBuffer; 105 glGenBuffers(1, &vertexBuffer); 106 glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); 107 GLsizeiptr bufferSizeBytes = sizeof(SenceVertex) * 4; 108 glBufferData(GL_ARRAY_BUFFER, bufferSizeBytes, self.vertices, GL_STATIC_DRAW); 109 110 // 9.设置默认着色器 111 [self setupNormalShaderProgram]; // 一开始选用默认的着色器 112 113 // 10.将顶点缓存保存,退出时才释放 114 self.vertexBuffer = vertexBuffer; 115 } 116 117 // 绑定渲染缓存区和帧缓存区 118 - (void)bindRenderLayer:(CALayer <EAGLDrawable> *)layer { 119 120 // 1.渲染缓存区,帧缓存区对象 121 GLuint renderBuffer; 122 GLuint frameBuffer; 123 124 // 2.获取帧渲染缓存区名称,绑定渲染缓存区以及将渲染缓存区与layer建立连接 125 glGenRenderbuffers(1, &renderBuffer); 126 glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer); 127 [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer]; 128 129 // 3.获取帧缓存区名称,绑定帧缓存区以及将渲染缓存区附着到帧缓存区上 130 glGenFramebuffers(1, &frameBuffer); 131 glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer); 132 glFramebufferRenderbuffer(GL_FRAMEBUFFER, 133 GL_COLOR_ATTACHMENT0, 134 GL_RENDERBUFFER, 135 renderBuffer); 136 } 137 138 // 从图片中加载纹理 139 - (GLuint)createTextureWithImage:(UIImage *)image { 140 141 // 1、将 UIImage 转换为 CGImageRef 142 CGImageRef cgImageRef = [image CGImage]; 143 // 判断图片是否获取成功 144 if (!cgImageRef) { 145 NSLog(@"Failed to load image"); 146 exit(1); 147 } 148 // 2、读取图片的大小,宽和高 149 GLuint width = (GLuint)CGImageGetWidth(cgImageRef); 150 GLuint height = (GLuint)CGImageGetHeight(cgImageRef); 151 // 获取图片的rect 152 CGRect rect = CGRectMake(0, 0, width, height); 153 154 // 获取图片的颜色空间 155 CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 156 // 3.获取图片字节数 宽*高*4(RGBA) 157 void *imageData = malloc(width * height * 4); 158 // 4.创建上下文 159 /* 160 参数1:data,指向要渲染的绘制图像的内存地址 161 参数2:width,bitmap的宽度,单位为像素 162 参数3:height,bitmap的高度,单位为像素 163 参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8 164 参数5:bytesPerRow,bitmap的没一行的内存所占的比特数 165 参数6:colorSpace,bitmap上使用的颜色空间 kCGImageAlphaPremultipliedLast:RGBA 166 */ 167 CGContextRef context = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big); 168 169 // 将图片翻转过来(图片默认是倒置的) 170 CGContextTranslateCTM(context, 0, height); 171 CGContextScaleCTM(context, 1.0f, -1.0f); 172 CGColorSpaceRelease(colorSpace); 173 CGContextClearRect(context, rect); 174 175 // 对图片进行重新绘制,得到一张新的解压缩后的位图 176 CGContextDrawImage(context, rect, cgImageRef); 177 178 // 设置图片纹理属性 179 // 5. 获取纹理ID 180 GLuint textureID; 181 glGenTextures(1, &textureID); 182 glBindTexture(GL_TEXTURE_2D, textureID); 183 184 // 6.载入纹理2D数据 185 /* 186 参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 187 参数2:加载的层次,一般设置为0 188 参数3:纹理的颜色值GL_RGBA 189 参数4:宽 190 参数5:高 191 参数6:border,边界宽度 192 参数7:format 193 参数8:type 194 参数9:纹理数据 195 */ 196 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData); 197 198 // 7.设置纹理属性 199 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); 200 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); 201 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 202 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 203 204 // 8.绑定纹理 205 /* 206 参数1:纹理维度 207 参数2:纹理ID,因为只有一个纹理,给0就可以了。 208 */ 209 glBindTexture(GL_TEXTURE_2D, 0); 210 211 // 9.释放context,imageData 212 CGContextRelease(context); 213 free(imageData); 214 215 // 10.返回纹理ID 216 return textureID; 217 } 218 219 // 开始一个滤镜动画 220 - (void)startFilerAnimation { 221 // 1.判断displayLink 是否为空 222 // CADisplayLink 定时器 223 if (self.displayLink) { 224 [self.displayLink invalidate]; 225 self.displayLink = nil; 226 } 227 // 2. 设置displayLink 的方法 228 self.startTimeInterval = 0; 229 self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeAction)]; 230 231 // 3.将displayLink 添加到runloop 运行循环 232 [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] 233 forMode:NSRunLoopCommonModes]; 234 } 235 236 // 2. 动画 237 - (void)timeAction { 238 // DisplayLink 的当前时间撮 239 if (self.startTimeInterval == 0) { 240 self.startTimeInterval = self.displayLink.timestamp; 241 } 242 // 使用program 243 glUseProgram(self.program); 244 // 绑定buffer 245 glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer); 246 247 // 传入时间 248 CGFloat currentTime = self.displayLink.timestamp - self.startTimeInterval; 249 GLuint time = glGetUniformLocation(self.program, "Time"); 250 glUniform1f(time, currentTime); 251 252 // 清除画布 253 glClear(GL_COLOR_BUFFER_BIT); 254 glClearColor(1, 1, 1, 1); 255 256 // 重绘 257 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 258 // 渲染到屏幕上 259 [self.context presentRenderbuffer:GL_RENDERBUFFER]; 260 } 261 262 #pragma mark - FilterBarDelegate 263 - (void)filterBar:(FilterBar *)filterBar didScrollToIndex:(NSUInteger)index { 264 // 1. 选择默认shader 265 if (index == 0) { 266 [self setupNormalShaderProgram]; 267 }else if(index == 1) { 268 [self setupSplitScreen_2ShaderProgram]; 269 }else if(index == 2) { 270 [self setupSplitScreen_3ShaderProgram]; 271 }else if(index == 3) { 272 [self setupSplitScreen_4ShaderProgram]; 273 }else if(index == 4) { 274 [self setupSplitScreen_6ShaderProgram]; 275 }else if(index == 5) { 276 [self setupSplitScreen_9ShaderProgram]; 277 } 278 // 重新开始滤镜动画 279 [self startFilerAnimation]; 280 } 281 282 #pragma mark - Shader 283 // 默认着色器程序 284 - (void)setupNormalShaderProgram { 285 [self setupShaderProgramWithName:@"Normal"]; 286 } 287 288 // 分屏(2屏) 289 - (void)setupSplitScreen_2ShaderProgram { 290 [self setupShaderProgramWithName:@"SplitScreen_2"]; 291 } 292 293 // 分屏(3屏) 294 - (void)setupSplitScreen_3ShaderProgram { 295 [self setupShaderProgramWithName:@"SplitScreen_3"]; 296 } 297 298 // 分屏(4屏) 299 - (void)setupSplitScreen_4ShaderProgram { 300 [self setupShaderProgramWithName:@"SplitScreen_4"]; 301 } 302 303 // 分屏(6屏) 304 - (void)setupSplitScreen_6ShaderProgram { 305 [self setupShaderProgramWithName:@"SplitScreen_6"]; 306 } 307 308 // 分屏(9屏) 309 - (void)setupSplitScreen_9ShaderProgram { 310 [self setupShaderProgramWithName:@"SplitScreen_9"]; 311 } 312 313 // 初始化着色器程序 314 - (void)setupShaderProgramWithName:(NSString *)name { 315 // 1. 获取着色器program 316 GLuint program = [self programWithShaderName:name]; 317 318 // 2. use Program 319 glUseProgram(program); 320 321 // 3. 获取Position,Texture,TextureCoords 的索引位置 322 GLuint positionSlot = glGetAttribLocation(program, "Position"); 323 GLuint textureSlot = glGetUniformLocation(program, "Texture"); 324 GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords"); 325 326 // 4.激活纹理,绑定纹理ID 327 glActiveTexture(GL_TEXTURE0); 328 glBindTexture(GL_TEXTURE_2D, self.textureID); 329 330 // 5.纹理sample 331 glUniform1i(textureSlot, 0); 332 333 // 6.打开positionSlot 属性并且传递数据到positionSlot中(顶点坐标) 334 glEnableVertexAttribArray(positionSlot); 335 glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord)); 336 337 // 7.打开textureCoordsSlot 属性并传递数据到textureCoordsSlot(纹理坐标) 338 glEnableVertexAttribArray(textureCoordsSlot); 339 glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord)); 340 341 // 8.保存program,界面销毁则释放 342 self.program = program; 343 } 344 345 #pragma mark -shader compile and link 346 // link Program 347 - (GLuint)programWithShaderName:(NSString *)shaderName { 348 // 1. 编译顶点着色器/片元着色器 349 GLuint vertexShader = [self compileShaderWithName:shaderName type:GL_VERTEX_SHADER]; 350 GLuint fragmentShader = [self compileShaderWithName:shaderName type:GL_FRAGMENT_SHADER]; 351 352 // 2. 将顶点/片元附着到program 353 GLuint program = glCreateProgram(); 354 glAttachShader(program, vertexShader); 355 glAttachShader(program, fragmentShader); 356 357 // 3.linkProgram 358 glLinkProgram(program); 359 360 // 4.检查是否link成功 361 GLint linkSuccess; 362 glGetProgramiv(program, GL_LINK_STATUS, &linkSuccess); 363 if (linkSuccess == GL_FALSE) { 364 GLchar messages[256]; 365 glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]); 366 NSString *messageString = [NSString stringWithUTF8String:messages]; 367 NSAssert(NO, @"program链接失败:%@", messageString); 368 exit(1); 369 } 370 // 5.返回program 371 return program; 372 } 373 374 // 编译shader代码 375 - (GLuint)compileShaderWithName:(NSString *)name type:(GLenum)shaderType { 376 377 // 1.获取shader 路径 378 NSString *shaderPath = [[NSBundle mainBundle] pathForResource:name ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"]; 379 NSError *error; 380 NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error]; 381 if (!shaderString) { 382 NSAssert(NO, @"读取shader失败"); 383 exit(1); 384 } 385 386 // 2. 创建shader->根据shaderType 387 GLuint shader = glCreateShader(shaderType); 388 389 // 3.获取shader source 390 const char *shaderStringUTF8 = [shaderString UTF8String]; 391 int shaderStringLength = (int)[shaderString length]; 392 glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength); 393 394 // 4.编译shader 395 glCompileShader(shader); 396 397 // 5.查看编译是否成功 398 GLint compileSuccess; 399 glGetShaderiv(shader, GL_COMPILE_STATUS, &compileSuccess); 400 if (compileSuccess == GL_FALSE) { 401 GLchar messages[256]; 402 glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]); 403 NSString *messageString = [NSString stringWithUTF8String:messages]; 404 NSAssert(NO, @"shader编译失败:%@", messageString); 405 exit(1); 406 } 407 // 6.返回shader 408 return shader; 409 } 410 411 // 获取渲染缓存区的宽 412 - (GLint)drawableWidth { 413 GLint backingWidth; 414 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth); 415 return backingWidth; 416 } 417 // 获取渲染缓存区的高 418 - (GLint)drawableHeight { 419 GLint backingHeight; 420 glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight); 421 return backingHeight; 422 } 423 424 // 释放 425 - (void)dealloc { 426 // 1.上下文释放 427 if ([EAGLContext currentContext] == self.context) { 428 [EAGLContext setCurrentContext:nil]; 429 } 430 // 顶点缓存区释放 431 if (_vertexBuffer) { 432 glDeleteBuffers(1, &_vertexBuffer); 433 _vertexBuffer = 0; 434 } 435 // 顶点数组释放 436 if (_vertices) { 437 free(_vertices); 438 _vertices = nil; 439 } 440 } 441 442 @end