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

 

posted @ 2020-08-13 22:33  张张_z  阅读(720)  评论(0编辑  收藏  举报