1. 程式人生 > 實用技巧 >OpenGL 12 - 案例:分屏濾鏡

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