OpenGL 12 - 案例:分屏濾鏡
阿新 • • 發佈:2020-08-13
案例:利用 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