OpenGL ES入門(mén)09-GLSL實(shí)現(xiàn)常見(jiàn)特效

前言

本文是關(guān)于OpenGL ES的系統(tǒng)性學(xué)習(xí)過(guò)程,記錄了自己在學(xué)習(xí)OpenGL ES時(shí)的收獲。
這篇文章的目標(biāo)是用OpenGL ES 2.0實(shí)現(xiàn)常見(jiàn)的特效。
環(huán)境是Xcode8.1+OpenGL ES 2.0
目前代碼已經(jīng)放到github上面,OpenGL ES入門(mén)09-GLSL實(shí)現(xiàn)常見(jiàn)特效

歡迎關(guān)注我的 OpenGL ES入門(mén)專題

概述

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

灰度圖

任何顏色都有紅、綠、藍(lán)三原色組成,假如原來(lái)某點(diǎn)的顏色為RGB(R,G,B),那么,我們可以通過(guò)下面幾種方法,將其轉(zhuǎn)換為灰度:
1.浮點(diǎn)算法:Gray=R*0.3+G*0.59+B*0.11
2.整數(shù)方法: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;
通過(guò)上述任一種方法求得Gray后,將原來(lái)的RGB(R,G,B)中的R,G,B統(tǒng)一用Gray替換,形成新的顏色RGB(Gray,Gray,Gray),用它替換原來(lái)的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);
}

圖像顛倒

圖像顛倒實(shí)現(xiàn)比較簡(jiǎn)單,在文理采樣的時(shí)候我們只需要反轉(zhuǎn)UV坐標(biāo),便可以實(shí)現(xiàn)圖像顛倒的效果。

圖像顛倒.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;
}

圖像漩渦

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

漩渦.png
precision mediump float;

const float PI = 3.14159265;
uniform sampler2D image;

const float uD = 80.0; //旋轉(zhuǎn)角度
const float uR = 0.5; //旋轉(zhuǎn)半徑

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

馬賽克

馬賽克效果就是把圖片的一個(gè)相當(dāng)大小的區(qū)域用同一個(gè)點(diǎn)的顏色來(lái)表示.可以認(rèn)為是大規(guī)模的降低圖像的分辨率,而讓圖像的一些細(xì)節(jié)隱藏起來(lái)。

馬賽克.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;
}

浮雕效果

浮雕效果是指圖像的前景前向凸出背景。實(shí)現(xiàn)思路:把圖象的一個(gè)象素和左上方的象素進(jìn)行求差運(yùn)算,并加上一個(gè)灰度。這個(gè)灰度就是表示背景顏色。這里我們?cè)O(shè)置這個(gè)插值為128 (圖象RGB的值是0-255)。同時(shí),我們還應(yīng)該把這兩個(gè)顏色的差值轉(zhuǎn)換為亮度信息,避免浮雕圖像出現(xiàn)彩色像素。

浮雕.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-環(huán)境搭建
//
//  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 內(nèi)容。
    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 默認(rèn)是透明的,必須將它設(shè)為不透明才能讓其可見(jiàn)
    _eaglLayer.opaque = YES;
    
    // 設(shè)置描繪屬性,在這里設(shè)置不維持渲染內(nèi)容以及顏色格式為 RGBA8
    _eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
}

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

- (void)setupFrameAndRenderBuffer
{
    glGenRenderbuffers(1, &_colorRenderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
    // 為 color renderbuffer 分配存儲(chǔ)空間
    [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer];
    
    glGenFramebuffers(1, &_frameBuffer);
    // 設(shè)置為當(dāng)前 framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer);
    // 將 _colorRenderBuffer 裝配到 GL_COLOR_ATTACHMENT0 這個(gè)裝配點(diǎn)上
    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,   // 右上
    };
    
    // 創(chuàng)建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");
    }
    
    // 創(chuàng)建紋理
    _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");
    }
    
    // 創(chuàng)建紋理
    _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 呈現(xiàn)在屏幕上,在這里我們指定的是前面已經(jīng)綁定為當(dāng)前 renderbuffer 的那個(gè),在 renderbuffer 可以被呈現(xiàn)之前,必須調(diào)用renderbufferStorage:fromDrawable: 為之分配存儲(chǔ)空間。
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}

@end

參考資料

GPUImage

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容