OpenGL学习- 15.分屏滤镜

260 阅读4分钟

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

15940369059357.jpg

把着色器文件的命名规则统一,可以简化着色器选择时值的传递。之所以分开每个着色器对应自己的顶点和片元着色器,是为了以后如果针对不同着色器有不同需求时方便修改。尽量不要写通用型的着色器,哪怕多写几个,因为着色器里每多一次处理过程,程序会多执行非常多次的

#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

着色器传值时,命名最好能够统一,这样外部向着色器中进行传值的时候就会非常方便

15940381226441.jpg 需要注意的是,告诉着色器如何从数据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);
}

15940843584404.jpg

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);
}

15940843813342.jpg

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);
}

15940843992221.jpg

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);
}

15940844167533.jpg

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);
}

15940844367803.jpg

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);
}

15940844545413.jpg

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);
}

15940844686127.jpg

代码示例见:Github