OpenGL ES入門09-GLSL實現常見特效

前言

本文是關于OpenGL ES的系統性學習過程,記錄了自己在學習OpenGL ES時的收獲。
這篇文章的目標是用OpenGL ES 2.0實現常見的特效。
環境是Xcode8.1+OpenGL ES 2.0
目前代碼已經放到github上面,OpenGL ES入門09-GLSL實現常見特效

歡迎關注我的 OpenGL ES入門專題

概述

這篇文章主要是使用OpenGL ES2.0實現一些常見的濾鏡特效:1、灰度圖;2、圖像顛倒;3、圖像漩渦;4、馬賽克效果;5、浮雕效果。這些效果不是很難,但是對于學習GLSL是很有幫助的。

灰度圖

任何顏色都有紅、綠、藍三原色組成,假如原來某點的顏色為RGB(R,G,B),那么,我們可以通過下面幾種方法,將其轉換為灰度:
1.浮點算法:Gray=R*0.3+G*0.59+B*0.11
2.整數方法:Gray=(R*30+G*59+B*11)/100
3.移位方法:Gray =(R*76+G*151+B*28)>>8;
4.平均值法:Gray=(R+G+B)/3;
5.僅取綠色:Gray=G;
通過上述任一種方法求得Gray后,將原來的RGB(R,G,B)中的R,G,B統一用Gray替換,形成新的顏色RGB(Gray,Gray,Gray),用它替換原來的RGB(R,G,B)就是灰度圖了。在這里我們使用GPUImage中使用的變換因子:

const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);
灰度圖.png
//灰度圖
precision highp float;
uniform sampler2D image;
varying vec2 vTexcoord;
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);

void main()
{
    lowp vec4 textureColor = texture2D(image, vTexcoord);
    float luminance = dot(textureColor.rgb, W);
    
    gl_FragColor = vec4(vec3(luminance), textureColor.a);
}

圖像顛倒

圖像顛倒實現比較簡單,在文理采樣的時候我們只需要反轉UV坐標,便可以實現圖像顛倒的效果。

圖像顛倒.png
precision mediump float;

varying vec2 vTexcoord;
uniform sampler2D image;

void main()
{
    vec4 color = texture2D(image, vec2(vTexcoord.x, 1.0 - vTexcoord.y));
    gl_FragColor = color;
}

圖像漩渦

圖像漩渦主要是在某個半徑范圍里,把當前采樣點旋轉一定角度,旋轉以后當前點的顏色就被旋轉后的點的顏色代替,因此整個半徑范圍里會有旋轉的效果。如果旋轉的時候旋轉角度隨著當前點離半徑的距離遞減,整個圖像就會出現漩渦效果。這里使用的了拋物線遞減因子:(1.0-(r/Radius)*(r/Radius) )。

漩渦.png
precision mediump float;

const float PI = 3.14159265;
uniform sampler2D image;

const float uD = 80.0; //旋轉角度
const float uR = 0.5; //旋轉半徑

varying vec2 vTexcoord;

void main()
{
    ivec2 ires = ivec2(512, 512);
    float Res = float(ires.s);
    
    vec2 st = vTexcoord;
    float Radius = Res * uR;

    vec2 xy = Res * st;
    
    vec2 dxy = xy - vec2(Res/2., Res/2.);
    float r = length(dxy);
    
    float beta = atan(dxy.y, dxy.x) + radians(uD) * 2.0 * (-(r/Radius)*(r/Radius) + 1.0);//(1.0 - r/Radius);
    
    vec2 xy1 = xy;
    if(r<=Radius)
    {
        xy1 = Res/2. + r*vec2(cos(beta), sin(beta));
    }
    
    st = xy1/Res;
    
    vec3 irgb = texture2D(image, st).rgb;
    
    gl_FragColor = vec4( irgb, 1.0 );
}  

馬賽克

馬賽克效果就是把圖片的一個相當大小的區域用同一個點的顏色來表示.可以認為是大規模的降低圖像的分辨率,而讓圖像的一些細節隱藏起來。

馬賽克.png
precision mediump float;

varying vec2 vTexcoord;
uniform sampler2D image;
const vec2 TexSize = vec2(400.0, 400.0);
const vec2 mosaicSize = vec2(8.0, 8.0);

void main()
{
    vec2 intXY = vec2(vTexcoord.x*TexSize.x, vTexcoord.y*TexSize.y);
    vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x, floor(intXY.y/mosaicSize.y)*mosaicSize.y);
    vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x, XYMosaic.y/TexSize.y);
    vec4 color = texture2D(image, UVMosaic);
    gl_FragColor = color;
}

浮雕效果

浮雕效果是指圖像的前景前向凸出背景。實現思路:把圖象的一個象素和左上方的象素進行求差運算,并加上一個灰度。這個灰度就是表示背景顏色。這里我們設置這個插值為128 (圖象RGB的值是0-255)。同時,我們還應該把這兩個顏色的差值轉換為亮度信息,避免浮雕圖像出現彩色像素。

浮雕.png
//浮雕效果
precision mediump float;
varying vec2 vTexcoord;
uniform sampler2D image;
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);
const vec2 TexSize = vec2(100.0, 100.0);
const vec4 bkColor = vec4(0.5, 0.5, 0.5, 1.0);

void main()
{
    vec2 tex = vTexcoord;
    vec2 upLeftUV = vec2(tex.x-1.0/TexSize.x, tex.y-1.0/TexSize.y);
    vec4 curColor = texture2D(image, vTexcoord);
    vec4 upLeftColor = texture2D(image, upLeftUV);
    vec4 delColor = curColor - upLeftColor;
    float luminance = dot(delColor.rgb, W);
    gl_FragColor = vec4(vec3(luminance), 0.0) + bkColor;
}

完整代碼

//
//  OpenGLESView.m
//  OpenGLES01-環境搭建
//
//  Created by qinmin on 2017/2/9.
//  Copyright ? 2017年 qinmin. All rights reserved.
//

#import "OpenGLESView.h"
#import <OpenGLES/ES2/gl.h>
#import "GLUtil.h"
#include "JpegUtil.h"

@interface OpenGLESView ()
{
    CAEAGLLayer     *_eaglLayer;
    EAGLContext     *_context;
    GLuint          _colorRenderBuffer;
    GLuint          _frameBuffer;

    GLuint          _program;
    GLuint          _vbo;
    GLuint          _texture;
    GLuint          _texture1;
    int             _vertCount;
}
@end

@implementation OpenGLESView

+ (Class)layerClass
{
    // 只有 [CAEAGLLayer class] 類型的 layer 才支持在其上描繪 OpenGL 內容。
    return [CAEAGLLayer class];
}

- (void)dealloc
{
    glDeleteBuffers(1, &_vbo);
    glDeleteTextures(1, &_texture);
    glDeleteProgram(_program);
}

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        [self setupLayer];
        [self setupContext];
        [self setupGLProgram];
        [self setupVBO];
        [self setupTexure];
        [self setupTexure1];
    }
    return self;
}

- (void)layoutSubviews
{
    [EAGLContext setCurrentContext:_context];
    
    [self destoryRenderAndFrameBuffer];
    
    [self setupFrameAndRenderBuffer];
    
    [self render];
}


#pragma mark - Setup
- (void)setupLayer
{
    _eaglLayer = (CAEAGLLayer*) self.layer;
    
    // CALayer 默認是透明的,必須將它設為不透明才能讓其可見
    _eaglLayer.opaque = YES;
    
    // 設置描繪屬性,在這里設置不維持渲染內容以及顏色格式為 RGBA8
    _eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}

- (void)setupContext
{
    // 設置OpenGLES的版本為2.0 當然還可以選擇1.0和最新的3.0的版本,以后我們會講到2.0與3.0的差異,目前為了兼容性選擇2.0的版本
    _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (!_context) {
        NSLog(@"Failed to initialize OpenGLES 2.0 context");
        exit(1);
    }
    
    // 將當前上下文設置為我們創建的上下文
    if (![EAGLContext setCurrentContext:_context]) {
        NSLog(@"Failed to set current OpenGL context");
        exit(1);
    }
}

- (void)setupFrameAndRenderBuffer
{
    glGenRenderbuffers(1, &_colorRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    // 為 color renderbuffer 分配存儲空間
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
    
    glGenFramebuffers(1, &_frameBuffer);
    // 設置為當前 framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    // 將 _colorRenderBuffer 裝配到 GL_COLOR_ATTACHMENT0 這個裝配點上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
                              GL_RENDERBUFFER, _colorRenderBuffer);
}

- (void)setupGLProgram
{
    NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"vert" ofType:@"glsl"];
    NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"cameo" ofType:@"glsl"];
    
    _program = createGLProgramFromFile(vertFile.UTF8String, fragFile.UTF8String);
    glUseProgram(_program);
}

- (void)setupVBO
{
    _vertCount = 6;
    
    GLfloat vertices[] = {
        0.8f,  0.6f, 0.0f, 1.0f, 0.0f,   // 右上
        0.8f, -0.6f, 0.0f, 1.0f, 1.0f,   // 右下
        -0.8f, -0.6f, 0.0f, 0.0f, 1.0f,  // 左下
        -0.8f, -0.6f, 0.0f, 0.0f, 1.0f,  // 左下
        -0.8f,  0.6f, 0.0f, 0.0f, 0.0f,  // 左上
        0.8f,  0.6f, 0.0f, 1.0f, 0.0f,   // 右上
    };
    
    // 創建VBO
    _vbo = createVBO(GL_ARRAY_BUFFER, GL_STATIC_DRAW, sizeof(vertices), vertices);
    
    glEnableVertexAttribArray(glGetAttribLocation(_program, "position"));
    glVertexAttribPointer(glGetAttribLocation(_program, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);
    
    glEnableVertexAttribArray(glGetAttribLocation(_program, "texcoord"));
    glVertexAttribPointer(glGetAttribLocation(_program, "texcoord"), 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL+sizeof(GL_FLOAT)*3);
}

- (void)setupTexure
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"1" ofType:@"jpg"];
    
    unsigned char *data;
    int size;
    int width;
    int height;
    
    // 加載紋理
    if (read_jpeg_file(path.UTF8String, &data, &size, &width, &height) < 0) {
        printf("%s\n", "decode fail");
    }
    
    // 創建紋理
    _texture = createTexture2D(GL_RGB, width, height, data);
    
    if (data) {
        free(data);
        data = NULL;
    }
}

- (void)setupTexure1
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"noise" ofType:@"jpg"];
    
    unsigned char *data;
    int size;
    int width;
    int height;
    
    // 加載紋理
    if (read_jpeg_file(path.UTF8String, &data, &size, &width, &height) < 0) {
        printf("%s\n", "decode fail");
    }
    
    // 創建紋理
    _texture1 = createTexture2D(GL_RGB, width, height, data);
    
    if (data) {
        free(data);
        data = NULL;
    }
}

#pragma mark - Clean
- (void)destoryRenderAndFrameBuffer
{
    glDeleteFramebuffers(1, &_frameBuffer);
    _frameBuffer = 0;
    glDeleteRenderbuffers(1, &_colorRenderBuffer);
    _colorRenderBuffer = 0;
}

#pragma mark - Render
- (void)render
{
    glClearColor(1.0, 1.0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glLineWidth(2.0);
    
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
    
    // 激活紋理
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, _texture);
    glUniform1i(glGetUniformLocation(_program, "image"), 0);
    
    // 激活紋理
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, _texture1);
    glUniform1i(glGetUniformLocation(_program, "image1"), 1);
    
    glDrawArrays(GL_TRIANGLES, 0, _vertCount);
    
    //將指定 renderbuffer 呈現在屏幕上,在這里我們指定的是前面已經綁定為當前 renderbuffer 的那個,在 renderbuffer 可以被呈現之前,必須調用renderbufferStorage:fromDrawable: 為之分配存儲空間。
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}

@end

參考資料

GPUImage

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容