OpenGL ES入門:濾鏡篇 - 漩渦、馬賽克

系列推薦文章(基礎篇):
OpenGL/OpenGL ES入門:圖形API以及專業名詞解析
OpenGL/OpenGL ES入門:渲染流程以及固定存儲着色器
OpenGL/OpenGL ES入門:圖像渲染實現以及渲染問題
OpenGL/OpenGL ES入門:基礎變換 - 初識向量/矩陣
OpenGL/OpenGL ES入門:紋理初探 - 經常使用API解析
OpenGL/OpenGL ES入門: 紋理應用 - 紋理座標及案例解析(金字塔)
OpenGL/OpenGL ES入門: 頂點着色器與片元着色器(OpenGL過渡OpenGL ES)
OpenGL/OpenGL ES入門: GLKit以及API簡介
OpenGL/OpenGL ES入門: GLKit使用以及案例
OpenGL/OpenGL ES入門: 使用OpenGL ES 渲染圖片
OpenGL/OpenGL ES入門:iOS紋理翻轉策略解析
OpenGL ES入門: 渲染金字塔 - 顏色、紋理、紋理與顏色混合填充以及GLKit實現
OpenGL ES入門: 濾鏡篇 - 分屏濾鏡git

和上一篇文章OpenGL ES入門: 濾鏡篇 - 分屏濾鏡同樣,使用GLSL實現濾鏡的前提條件是可以用GLSL顯示普通圖片github

思路:算法

詳細內容請參考OpenGL/OpenGL ES入門: 使用OpenGL ES 渲染圖片bash

灰度濾鏡GLSL 算法解析

圖片的顯示由三個顏色通道(rgb)顯示的,而灰度濾鏡只有一個值,也就是說只要獲得亮度即可。下面提供5種方式實現灰度濾鏡(前三種是利用權重來實現)函數

原理:post

  • 浮點算法: Gray = R * 0.3 + G * 0.59 + B * 0.11
  • 整數算法: Gray = (R * 30 + G * 59 + B * 11) / 100
  • 移位算法: Gray = (R * 76 + G * 151 + B * 28) >> 8
  • 平均值法: Gray = (R + G + B) / 3;
  • 僅取綠色: Gray = G

片元着色器代碼實現:ui

precision highp float;

uniform sampler2D Texture;
varying highp vec2 varyTextureCoord;
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721); // 借用GPUImage的值

void main() {
    vec4 mask = texture2D(Texture, varyTextureCoord);
    float temp = dot(mask.rgb, W);
    gl_FragColor = vec4(vec3(temp), 1.0);
}
複製代碼

漩渦濾鏡GLSL 算法解析

把我最喜歡的女明星旋轉成這樣,真是罪過。。。

原理:
圖形漩渦主要是在某個半徑範圍內,把當前採樣點旋轉必定角度,旋轉之後當前點的顏色就被旋轉後的點的顏色代替,所以整個半徑範圍裏會有旋轉的效果。 若是旋轉的時候旋轉角度隨着當前點距離半徑的距離遞減,整個圖像就會出現漩渦效果,如上圖同樣。 這裏會使用拋物線遞減因子: (1.0 - (r / Radius) * (r / Radius))spa

片元着色器代碼3d

precision mediump float; 

uniform sampler2D Texture; 
//旋轉⻆角度
const float uD = 80.0; 
//旋渦半徑
const float uR = 0.5;
//紋理座標
varying vec2 TextureCoordsVarying;

void main() {
    //獲取旋轉的直徑
    float Res = float(512); 
    //紋理座標[0,0],[1,0],[0,1],[1,1]...
    vec2 st = TextureCoordsVarying; 
    //半徑 = 直徑 * 0.5;
    float Radius = Res * uR;
    //準備旋轉處理的紋理座標 = 紋理座標 * 直徑 
    vec2 xy = Res * st;
   
    vec2 dxy = xy - vec2(Res/2.0, Res/2.0);
    //r
    float r = length(dxy);
    //拋物線遞減因⼦子:(1.0-(r/Radius)*(r/Radius) )
    float beta = atan(dxy.y, dxy.x) + radians(uD) * 2.0 * (1.0-(r/Radius)*(r/Radius));
    if(r <= Radius)
    {
        //獲取的紋理座標旋轉beta度.
        xy = vec2(Res/2.0, Res/2.0) + r*vec2(cos(beta), sin(beta));
    }
    //st = 旋轉後的紋理座標/旋轉範圍 
    st = xy/Res;
    //將旋轉的紋理座標替換原始紋理座標TextureCoordsVarying 獲取對應像素點的顏⾊. 
    vec3 irgb = texture2D(Texture, st).rgb;
    //將計算後的顏⾊填充到像素點中 gl_FragColor
    gl_FragColor = vec4( irgb, 1.0 );
}
複製代碼

解析:
相比前面所說的濾鏡,這個算法比較複雜,上面代碼中,每一行也都有註釋,可是,我的感受看起來仍是挺難懂的(可能筆者本身學的太差),因此這裏把本身理解的方式敘述一下,若是有錯誤,歡迎你們指正。
上述代碼中的Res,相似把一張圖片分紅了512個像素點同樣來進行分析,實際上最後幾步也經過xy/Res把它轉換回來,因此下面就針對一個像素點或者說一個紋理來理解。code

請先熟悉上面代碼,至少知道每一個變量的所表明的意義。

對於一個紋理來講:
vec2 xy = 1 * st 表示着紋理座標,
vec2 dxy = xy - vec2(1.0/2.0, 1.0/2.0),經過下圖來理解一下

經過上面圖片來理解vec2 dxy = xy - vec2(1.0/2.0, 1.0/2.0)
而後經過length(),來取模,獲得r

先忽略beta這一行,看if(r <= Radius),經過這個判斷獲取到的紋理像素,就是上圖中深藍色圓的紋素,因此最終造成的圓形漩渦就是經過這種方式來實現。
從這裏也能夠看出,若是你想控制圓形漩渦的半徑或者圓形漩渦的位置都是可控的,即uR控制着圓的半徑,向量vec2(Res/2.0, Res/2.0)控制着圓心位置,和Res無關,取決於分母2,有興趣的小夥伴能夠嘗試一下。

下面來看角度beta的獲取
當前角度:
atan(dxy.y, dxy.x)
加重漩渦角度:
atan(dxy.y, dxy.x) + radians(uD) * 2.0
加重漩渦衰減角度:
atan(dxy.y, dxy.x) + radians(uD) * 2.0 * (1.0-(r/Radius)*(r/Radius))

上圖理解當前角度的計算。

獲取的紋理座標旋轉beta度,看下圖,爲了方便,
假設(x0,y0)爲未旋轉以前的點,(x1,y1)爲旋轉以後的點,旋轉角度爲beta,長度便是模r不變.
那麼r * vec2(cos(beta), sin(beta))即可理解爲向量dx1y1,若是這裏不清晰,能夠嘗試畫個三角形,分別求出cos、sin的函數表達式,而後乘以模r,我相信應該很好理解,
因此vec2(Res/2.0, Res/2.0) + r*vec2(cos(beta), sin(beta))也就是最終的向量(x,y),以下圖所示:

後面的代碼也就是簡單的獲取紋理像素,而後填充到內建函數gl_FragColor中。

OK,有關漩渦濾鏡實現原理的解析就到這裏,若是你們發現描述有誤,請及時提醒筆者改正,謝謝!!!

馬賽克濾鏡 GLSL 算法解析

原理:
馬賽克效果就是把圖片的一個至關大小的區域用同一個點的顏色來表示,能夠認爲是大規模的下降圖像的分辨率,而讓圖片的一些細節隱藏起來。

矩形馬賽克

片元着色器代碼:

precision highp float;
// 紋理座標
varying vec2 varyTextureCoord;
// 紋理採樣器
uniform sampler2D Texture;
// 紋理圖片Size
const vec2 TexSize = vec2(400.0, 400.0);
// 馬賽克Size
const vec2 mosaicSize = vec2(10.0, 10.0);

void main () {
    vec2 intXY = vec2(varyTextureCoord.x * TexSize.x, varyTextureCoord.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(Texture, UVMosaic);
    gl_FragColor = color;
}
複製代碼

上面代碼中,首先計算出intXY,表示實際圖像位置; floor(x)內建函數,表示返回小於/等於x的最大整數值。 因此XYMosaic表示小馬賽克的座標,而後換算出馬賽克的紋理座標便可

六邊形馬賽克

片元着色器代碼

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

const float mosaicSize = 0.03;

void main () {
    float length = mosaicSize;
    float TR = 0.866025;
    
    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;
    
    int wx = int(x / 1.5 / length);
    int wy = int(y / TR / length);
    vec2 v1, v2, vn;
    
    if (wx/2 * 2 == wx) {
        if (wy/2 * 2 == wy) {
            //(0,0),(1,1)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        } else {
            //(0,1),(1,0)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        }
    }else {
        if (wy/2 * 2 == wy) {
            //(0,1),(1,0)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        } else {
            //(0,0),(1,1)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        }
    }
    
    float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
    float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
    if (s1 < s2) {
        vn = v1;
    } else {
        vn = v2;
    }
    vec4 color = texture2D(Texture, vn);
    
    gl_FragColor = color;
}
複製代碼

解析:
咱們須要作的效果就是讓一張圖片,分割成由六邊形組成,讓每一個六邊形中的顏色相同,下面採用直接取六邊形中心點像素來實現

如上圖,畫出不少長和寬比例爲3 :√3的矩形排列,而後對每個點進行編號。 假如咱們的屏幕的左上點爲上圖的(0,0)點,則屏幕上的任一點咱們找到它所對應的那個矩形了了。

假定咱們設定的矩陣比例爲 3*LEN : √3*LEN ,那麼屏幕上的任意 點(x, y)所對應的矩陣座標爲(int(x/(3*LEN)), int(y/ (√3*LEN)))

(wx, wy) 表示紋理座標在所對應的矩陣座標爲:

wx = int(x/(1.5 * length))
wy = int(y/(TR * length))
複製代碼

觀察上圖,標出了四個矩形,經過觀察能夠了解到,屏幕上的任意點(x, y),對應哪個六邊形,只要找到點,對應上面哪一個矩形的關鍵點越近即可,而矩形座標爲(wx, wy),那麼要判斷(x, y)在那個矩形的關鍵點近取決於什麼呢?

對於任何一個矩形,他們的四個頂點分別爲:

  • 左上:vec2(length * 1.5 * float(wx), length * TR * float(wy));
  • 左下:vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
  • 右上:vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
  • 右下:vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));

上面片元着色器中的if判斷,則是找到對應的那個矩形,而後找到關鍵的兩個點v1,v2。 最後根據兩點間距離公式,算出分別距離v1,v2的長度,距離那一個短,便屬於那一個六邊形。

文章寫之不易,奈何數學功底有限,若有錯誤,但願你們指正,謝謝!!!

案例傳送門:
github.com/SXDgit/Open…

相關文章
相關標籤/搜索