15.分屏滤镜
这次尝试简单的分屏滤镜,在片元着色器中通过调整每个像素点对应的纹理坐标,来实现分屏。
#import "ViewController.h"
#import "RenderView.h"
#import "FilterCell.h"
@interface ViewController ()<UICollectionViewDelegate, UICollectionViewDataSource>
@property (nonatomic, assign) CGFloat width;
@property (nonatomic, assign) CGFloat height;
@property (nonatomic, strong) UIButton *nextBtn;
@property (nonatomic, assign) CGRect contentRect;
@property (nonatomic, strong) RenderView *renderView;
@property (nonatomic, strong) UICollectionView *collectionView;
@property (nonatomic, strong) NSArray *dataArray;
@property (nonatomic, assign) NSInteger imageIndex;
@property (nonatomic, assign) NSInteger filterIndex;
@property (nonatomic, strong) NSArray *imageArray;
@property (nonatomic, strong) NSArray *shaderArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor blackColor];
self.width = self.view.frame.size.width;
self.height = self.view.frame.size.height;
self.contentRect = CGRectMake(0, 20, self.width, self.height - 100 - 10 - 50 - 10 - 20);
self.imageIndex = 0;
self.filterIndex = 0;
[self.view addSubview:self.collectionView];
[self.view addSubview:self.nextBtn];
[self.collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:self.filterIndex inSection:0] animated:NO scrollPosition:0];
[self reRender];
}
#pragma mark - func
- (void)nextBtnClick {
self.imageIndex ++;
if (self.imageIndex >= self.imageArray.count) {
self.imageIndex = 0;
}
[self reRender];
}
- (void)reRender {
UIImage *image = [UIImage imageNamed:self.imageArray[self.imageIndex]];
NSString *shaderName = self.shaderArray[self.filterIndex];
CGFloat x = 0.0;
CGFloat y = 0.0;
CGFloat width = 0.0;
CGFloat height = 0.0;
if (image.size.width/image.size.height >= self.contentRect.size.width/self.contentRect.size.height) {
width = self.contentRect.size.width;
height = width / image.size.width * image.size.height;
x = self.contentRect.origin.x;
y = self.contentRect.origin.y + (self.contentRect.size.height - height)/2.0;
}else {
height = self.contentRect.size.height;
width = height / image.size.height * image.size.width;
x = self.contentRect.origin.x + (self.contentRect.size.width - width)/2.0;
y = self.contentRect.origin.y;
}
if (self.renderView) {
[self.renderView freeMemory];
[self.renderView removeFromSuperview];
self.renderView = nil;
}
self.renderView = [[RenderView alloc] initWithFrame:CGRectMake(x, y, width, height)];
[self.renderView setShader:shaderName image:image];
[self.view addSubview:self.renderView];
}
#pragma mark - UICollectionViewDelegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return self.dataArray.count;
}
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
FilterCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cellID" forIndexPath:indexPath];
cell.title = self.dataArray[indexPath.item];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
if (self.filterIndex == indexPath.item) {
return;
}
self.filterIndex = indexPath.item;
[self reRender];
}
#pragma mark - lazy
- (UIButton *)nextBtn {
if (!_nextBtn) {
_nextBtn = [[UIButton alloc] initWithFrame:CGRectMake(10, self.height - 160, self.width - 20, 50)];
_nextBtn.titleLabel.font = [UIFont boldSystemFontOfSize:20];
_nextBtn.layer.cornerRadius = 5;
_nextBtn.clipsToBounds = YES;
_nextBtn.backgroundColor = [UIColor orangeColor];
[_nextBtn setTitle:@"NEXT" forState:UIControlStateNormal];
[_nextBtn setTitleColor:UIColor.redColor forState:UIControlStateNormal];
[_nextBtn addTarget:self action:@selector(nextBtnClick) forControlEvents:UIControlEventTouchUpInside];
}
return _nextBtn;
}
- (UICollectionView *)collectionView {
if (!_collectionView) {
UICollectionViewFlowLayout *layout = [[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.itemSize = CGSizeMake(80, 80);
layout.minimumLineSpacing = 10;
layout.minimumInteritemSpacing = 10;
layout.sectionInset = UIEdgeInsetsMake(0, 10, 0, 10);
_collectionView = [[UICollectionView alloc] initWithFrame:CGRectMake(0, self.height - 100, self.width, 100) collectionViewLayout:layout];
_collectionView.backgroundColor = [UIColor whiteColor];
_collectionView.delegate = self;
_collectionView.dataSource = self;
[_collectionView registerClass:[FilterCell class] forCellWithReuseIdentifier:@"cellID"];
}
return _collectionView;
}
- (NSArray *)dataArray {
if (!_dataArray) {
_dataArray = @[@"无", @"二分屏", @"三分屏", @"四分屏", @"六分屏", @"九分屏", @"十六\n分屏"];
}
return _dataArray;
}
- (NSArray *)shaderArray {
if (!_shaderArray) {
_shaderArray = @[@"shader_0",@"shader_2",@"shader_3",@"shader_4",@"shader_6",@"shader_9",@"shader_16"];
}
return _shaderArray;
}
- (NSArray *)imageArray {
if (!_imageArray) {
_imageArray = @[@"01", @"02", @"03", @"04", @"05"];
}
return _imageArray;
}
@end
把着色器文件的命名规则统一,可以简化着色器选择时值的传递。之所以分开每个着色器对应自己的顶点和片元着色器,是为了以后如果针对不同着色器有不同需求时方便修改。尽量不要写通用型的着色器,哪怕多写几个,因为着色器里每多一次处理过程,程序会多执行非常多次的
#import "RenderView.h"
#import <GLKit/GLKit.h>
@interface RenderView ()
@property (nonatomic, strong) CAEAGLLayer *glLayer;
@property (nonatomic, strong) EAGLContext *context;
@property (nonatomic, assign) GLuint program;
@property (nonatomic, assign) GLuint buffer;
@property (nonatomic, assign) GLuint renderBuffer;
@property (nonatomic, assign) GLuint frameBuffer;
@end
@implementation RenderView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.glLayer = [[CAEAGLLayer alloc] init];
self.glLayer.frame = CGRectMake(0, 0, frame.size.width, frame.size.height);
self.glLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.layer addSublayer:self.glLayer];
self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
[EAGLContext setCurrentContext:self.context];
glDeleteBuffers(1, &_renderBuffer);
glDeleteBuffers(1, &_frameBuffer);
glGenRenderbuffers(1, &_renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer];
glGenFramebuffers(1, &_frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _renderBuffer);
glViewport(0, 0, self.bounds.size.width * [[UIScreen mainScreen] scale], self.bounds.size.height * [[UIScreen mainScreen] scale]);
glClearColor(0.5, 0.5, 0.5, 1);
}
return self;
}
- (void)setShader:(NSString *)shader image:(UIImage *)image {
/// 载入纹理
if ([self loadTextureWithImage:image]) {
/// link program
if ([self linkProgramWithShaderName:shader]) {
glUseProgram(_program);
[self render];
}
}
}
- (void)render {
GLfloat vertices[20] = {
-1.0, 1.0, 0.0, 0.0, 1.0,
-1.0, -1.0, 0.0, 0.0, 0.0,
1.0, 1.0, 0.0, 1.0, 1.0,
1.0, -1.0, 0.0, 1.0, 0.0,
};
glGenBuffers(1, &_buffer);
glBindBuffer(GL_ARRAY_BUFFER, _buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);
GLuint position = glGetAttribLocation(_program, "i_position");
glEnableVertexAttribArray(position);
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
GLuint textureCoord = glGetAttribLocation(_program, "i_textureCoord");
glEnableVertexAttribArray(textureCoord);
glVertexAttribPointer(textureCoord, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL + sizeof(GLfloat)*3);
// glVertexAttribPointer(textureCoord, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (GLfloat*)NULL + 3);
GLuint sampler = glGetUniformLocation(_program, "u_sampler");
glUniform1i(sampler, 0);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[self.context presentRenderbuffer:GL_RENDERBUFFER];
}
- (BOOL)linkProgramWithShaderName:(NSString *)shaderName {
NSString *vPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"vsh"];
NSError *vError;
NSString *vString = [NSString stringWithContentsOfFile:vPath encoding:NSUTF8StringEncoding error:&vError];
GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
const char *vStringUTF8 = [vString UTF8String];
glShaderSource(vShader, 1, &vStringUTF8, NULL);
glCompileShader(vShader);
NSString *fPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:@"fsh"];
NSError *fError;
NSString *fString = [NSString stringWithContentsOfFile:fPath encoding:NSUTF8StringEncoding error:&fError];
GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);
const char *fStringUTF8 = [fString UTF8String];
glShaderSource(fShader, 1, &fStringUTF8,NULL);
glCompileShader(fShader);
_program = glCreateProgram();
glAttachShader(_program, vShader);
glAttachShader(_program, fShader);
glDeleteShader(vShader);
glDeleteShader(fShader);
glLinkProgram(_program);
return YES;
}
- (BOOL)loadTextureWithImage:(UIImage *)image {
CGImageRef imageRef = image.CGImage;
if (!imageRef) {
return NO;
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
GLubyte *imageData = calloc(width * height * 4, sizeof(GLubyte));
CGContextRef contextRef = CGBitmapContextCreate(imageData, width, height, 8, width*4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast);
/// 上下翻转
CGContextTranslateCTM(contextRef, 0, height);
CGContextScaleCTM(contextRef, 1.0f, -1.0f);
CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(contextRef, rect, imageRef);
CGContextRelease(contextRef);
glBindTexture(GL_TEXTURE_2D, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)width, (GLsizei)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
free(imageData);
return YES;
}
- (void)freeMemory {
if ([EAGLContext currentContext] == self.context) {
[EAGLContext setCurrentContext:nil];
}
if (_renderBuffer) {
glDeleteBuffers(1, &_renderBuffer);
}
if (_frameBuffer) {
glDeleteBuffers(1, &_frameBuffer);
}
if (_buffer) {
glDeleteBuffers(1, &_buffer);
}
}
@end
着色器传值时,命名最好能够统一,这样外部向着色器中进行传值的时候就会非常方便
需要注意的是,告诉着色器如何从数据buffer里取对应值的时候,指针偏移那里,一定要根据数据类型的长度来进行偏移,不然会出错的。就如上面第1种方式就是错误的,第2、3种都是正确的。
attribute vec4 i_position;
attribute vec2 i_textureCoord;
varying lowp vec2 o_textureCoord;
void main()
{
gl_Position = i_position;
o_textureCoord = i_textureCoord;
}
顶点着色器都是统一的代码,仅负责给gl_Position赋值,和传递纹理坐标。
分屏着色器:
1.正常显示
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
gl_FragColor = texture2D(u_sampler, o_textureCoord);
}
2.二分屏
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
lowp vec2 point = o_textureCoord.xy;
if (point.y >= 0.0 && point.y <= 0.5) {
point.y += 0.25;
}else {
point.y -= 0.25;
}
gl_FragColor = texture2D(u_sampler, point);
}
3.三分屏
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
lowp vec2 point = o_textureCoord.xy;
if (point.y < 1.0/3.0) {
point.y += 1.0/3.0;
}else if (point.y > 2.0/3.0) {
point.y -= 1.0/3.0;
}
gl_FragColor = texture2D(u_sampler, point);
}
4.四分屏
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
lowp vec2 point = o_textureCoord.xy;
if (point.x < 0.5) {
point.x *= 2.0;
}else {
point.x = 2.0 * (point.x - 0.5);
}
if (point.y < 0.5) {
point.y *= 2.0;
}else {
point.y = 2.0 * (point.y - 0.5);
}
gl_FragColor = texture2D(u_sampler, point);
}
5.六分屏
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
lowp vec2 point = o_textureCoord.xy;
if (point.x > 0.5) {
point.x = 2.0 * (point.x - 0.5);
}else {
point.x = 2.0 * point.x;
}
if (point.y < 1.0/3.0) {
point.y += 1.0/3.0;
}else if (point.y > 2.0/3.0) {
point.y -= 1.0/3.0;
}
gl_FragColor = texture2D(u_sampler, point);
}
6.九分屏
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
lowp vec2 point = o_textureCoord.xy;
point.x = mod(point.x, 1.0/3.0) * 3.0;
point.y = mod(point.y, 1.0/3.0) * 3.0;
gl_FragColor = texture2D(u_sampler, point);
}
7.十六分屏
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
void main()
{
lowp vec2 point = o_textureCoord.xy;
point.x = mod(point.x, 0.25) * 4.0;
point.y = mod(point.y, 0.25) * 4.0;
gl_FragColor = texture2D(u_sampler, point);
}
代码示例见:Github