OpenGL ES-案例實現(xiàn)灰度濾鏡和馬賽克濾鏡

上一篇文章中主要講了分屏濾鏡,這篇我們了解一下灰度濾鏡和馬賽克濾鏡,其中馬賽克濾鏡分別用正方形、六邊形和三角形實現(xiàn)。
因為我們只是修改濾鏡效果,所以UI邏輯和GLKit可以直接使用上一個案例的代碼。并且因為頂點著色器代碼不需要修改,我們只需要修改片元著色器文件即可。

原理及著色器代碼

1、灰度濾鏡

我們都知道,一般圖片的每一個點顏色都是由RGB,即紅綠藍三種顏色混合的到的,每一種顏色即稱為一個顏色通道,所以一般的圖片也可以叫做三通道圖。那么灰度濾鏡其實就是只有一個通道有值,也就是只要得到圖片的亮度即可,其實這也就是單通道圖。

單通道圖:俗稱灰度圖,每個像素點只能有有一個值表示顏色,它的像素值在0到255之間,0是黑色,255是白色,中間值是一些不同等級的灰色。(也有3通道的灰度圖,3通道灰度圖只有一個通道有值,其他兩個通道的值都是零)。一般都取綠色通道單通道,因為人的眼睛對綠色的反應是最強烈的,這是一種生理反應。
灰度濾鏡的常用算法一共有5種,大致分為3類:

  • 權(quán)值法:處理后的圖片比較逼真
    浮點算法:Gray = R*0.3 + G*0.59 + B*0.11 (RGB的權(quán)重總和為1)
    整數(shù)方法:Gray = (R*30 + G*59 + B*11)/100(RGB的權(quán)重總和為100)
    移位方法:Gray = (R*76 + G*151 + B*28)>>8
  • 平均值法:Gray = (R+G+B)/3,處理后的圖片比較柔和
  • 僅取綠色:Gray = G,這種方式方便簡單,且易用

接下來我們分別使用浮點算法僅取綠色實現(xiàn)灰度濾鏡算法

浮點算法代碼:

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
//RGB的變換因子,即權(quán)重值
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);

void main(){
    //獲取對應紋理坐標系下色顏色值
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    //將顏色mask 與 變換因子相乘得到灰度值
    float luminance = dot(mask.rgb, W);
    //將灰度值轉(zhuǎn)換為(luminance,luminance,luminance,mask.a)并填充到像素中
    gl_FragColor = vec4(vec3(luminance), 1.0);
}

僅取綠色值:

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

void main(){
    //獲取對應紋理坐標系下色顏色值
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    
    //將RGB全部設置為G,即GRB全部取綠色值
    gl_FragColor = vec4(mask.g, mask.g, mask.g, 1.0);
}

注意:此處增加注釋只是為了方便閱讀和理解,實際開發(fā)的時候盡量不要在著色器代碼中添加注釋,防止出現(xiàn)編譯錯誤。

我們來看下兩者的結(jié)果:

灰度濾鏡對比.png

左邊是浮點算法,右邊是取綠色值,可以看出右邊會更偏綠一點。

2、顛倒濾鏡

看過我之前關于紋理翻轉(zhuǎn)的文章的同學應該知道怎么實現(xiàn)了吧,超級簡單,只需要將片源著色器中紋理的方向根據(jù)你想要顛倒的方向取反。我們演示的是上下顛倒,所以對紋理的Y坐標取反即可。我們看下代碼:

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

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

效果圖:


顛倒.png

3、馬賽克濾鏡

?賽克效果就是把圖片的?個相當?小的區(qū)域?同一個點的顏色來表示.可以認為是大規(guī)模的降低圖像的分辨率,而讓圖像的一些細節(jié)隱藏起來。
所以我們只要根據(jù)馬賽克單元格的寬高計算出圖像總的馬賽克行數(shù)和列數(shù),然后將每個馬賽克單元格遍歷2次,第一次計算該單元格RGB的平均值,第二次遍歷賦顏色值。


image.png

代碼實現(xiàn):

precision mediump float;
//紋理坐標
varying vec2 TextureCoordsVarying;
//紋理采樣器
uniform sampler2D Texture;
//紋理圖片size
const vec2 TexSize = vec2(500.0, 500.0);
//馬賽克size
const vec2 mosaicSize = vec2(16.0, 16.0);

void main()
{
    //計算圖像的實際位置
    vec2 intXY = vec2(TextureCoordsVarying.x*TexSize.x, TextureCoordsVarying.y*TexSize.y);
    // floor (x) 內(nèi)建函數(shù),返回小于/等于X的最大整數(shù)值.
    // floor (intXY.x / mosaicSize.x) * mosaicSize.x 計算出一個?小?賽克的坐標.
    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. 
    gl_FragColor = color;
}

效果圖:


正方形馬賽克1.png

如果覺得太粗糙了也可以將馬賽克size改小一點:

const vec2 mosaicSize = vec2(8.0, 8.0);

效果圖:


正方形馬賽克2.png

4、六邊形馬賽克

和正方形類似,我們是將馬賽克顯示的圖形從正方形換到了六邊形,我們要提取的馬賽克顏色實際上就從每一個六邊形中提取,默認我們是選擇六邊形的中心點顏色作為最終馬賽克的顯示顏色。如下圖:


分割成六邊形.png

我們提取出其中一部分放大看,就會發(fā)現(xiàn)實際上每一個六邊形都可以由四個矩形組成。


4個矩形.png

由基本的數(shù)據(jù)知識,我們可以知道這個矩陣的長寬比為3:√3,假如我們的屏幕的左上點為上圖的(0,0)點,則屏幕上的任一點我 們找到它所對應的那個矩形。
假定我們設定的矩陣比例為3*length : √3*length ,那么屏幕上的任意 點(x, y)所對應的矩陣坐標為(int(x/(3*length)), int(y/ (√3*length)))。那么(wx, wy) 表示紋理坐標在所對應的矩陣坐標為:
wx = int(x/(1.5 * length)); wy = int(y/(TR * length))

我們再觀察下4個矩陣,實際上他們只有以下兩種類型:


兩種類型的矩形.png

所以我們也可以計算出每個矩形的4個頂點的坐標:


坐標.png

每一種類型中,也只包含兩個六邊形的中心點,所以我們在計算選擇哪個點顏色時候,只需要判斷這個點離哪個六邊形的中心點近,然后選后近的那個中心點并取其對應的顏色即可:
根據(jù)距離公式求像素點距離兩個中心點的距離D1、D2
D1 = √((v1.x-x)2 + (v1.y-y)2)
D2 = √((v2.x-x)2 + (v2.y-y)2)

那我們怎么判斷是哪兩個中心點呢?再來看一下上面的分割成六邊形.png,在劃分六邊形的時候已經(jīng)對分割的區(qū)域進行了行列標號,

  • 偶數(shù)行偶數(shù)列計算左上中心點和右下中心點即包含六邊形的邊為/
  • 偶數(shù)行奇數(shù)列計算左下中心點和右上中心點即包含六邊形的邊為\
  • 奇數(shù)行偶數(shù)列計算左下中心點和右上中心點即包含六邊形的邊為\
  • 奇數(shù)行奇數(shù)列計算左上中心點和右下中心點即包含六邊形的邊為/

接下來看下代碼:

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
//六邊形的邊長
const float mosaicSize = 0.03;

void main(){
    
    float length = mosaicSize;
    //矩形的高的比例為√3,取值 √3/2 ,也可以直接取√3
    float TR = 0.866025;
    //矩形的長的比例為3,取值 3/2 = 1.5,也可以直接取3
    float TB = 1.5;
    
    //取出紋理坐標
    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;
    
    //根據(jù)紋理坐標計算出對應的矩陣坐標 
    //即 矩陣坐標wx = int(紋理坐標x/ 矩陣長),矩陣長 = TB*len
    //即 矩陣坐標wy = int(紋理坐標y/ 矩陣寬),矩陣寬 = TR*len
    int wx = int(x / TB / length);
    int wy = int(y / TR / length);
    vec2 v1, v2, vn;
    
    //判斷wx是否為偶數(shù),等價于 wx % 2 == 0
    if (wx/2 * 2 == wx) {
        if (wy/2 * 2 == wy) {//偶行偶列
            //(0,0),(1,1)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy));
            v2 = vec2(length * TB * float(wx+1), length * TR * float(wy+1));
        }else{//偶行奇列
            //(0,1),(1,0)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy+1));
            v2 = vec2(length * TB * float(wx+1), length * TR * float(wy));
        }
    }else{
        if (wy/2 * 2 == wy) {//奇行偶列
            //(0,1),(1,0)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy+1));
            v2 = vec2(length * TB * float(wx+1), length * TR * float(wy));
        }else{//奇行奇列
            //(0,0),(1,1)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy));
            v2 = vec2(length * TB * 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));
    
    //選擇距離小的則為六邊形的中心點,且獲取它的顏色
    vn = (s1 < s2) ? v1 : v2;
    //獲取六邊形中心點的顏色值
    vec4 color = texture2D(Texture, vn);
    gl_FragColor = color;
}

效果如下:


六邊形馬賽克1.png

同樣的可已通過修改六邊形的邊長來控制六邊形的大小。比如:

const float mosaicSize = 0.01;

效果如下:


六邊形馬賽克2.png

5、三角形馬賽克

三角形馬賽克實際上就是從六邊形馬賽克基礎上演變而來的,一個六邊形可以拆分為6個三角形:

三角形馬賽克.png

結(jié)合六邊形的邏輯,我們在取到對應的六邊形的中心點后,再獲取到該像素點和六邊形中心點的夾角:
假設點的坐標為:(x,y) 六邊形中心點為:Vn(x,y)
計算夾角.png

注意夾角的范圍是[-180,180],即[-π,π],再根據(jù)三角形馬賽克.png即可獲取到所在的三角形,然后再獲取該三角形中心點的顏色,并賦值給三角形即可。
計算三角形中心點:
計算三角形中心點.png

著色器代碼:

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

float mosaicSize = 0.03;

void main (void){
    
    float length = mosaicSize;
    //矩形的高的比例為√3,取值 √3/2 ,也可以直接取√3
    float TR = 0.866025;
    //矩形的長的比例為3,取值 3/2 = 1.5,也可以直接取3
    float TB = 1.5;
    
    //取出紋理坐標
    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;
    
    //根據(jù)紋理坐標計算出對應的矩陣坐標 
    //即 矩陣坐標wx = int(紋理坐標x/ 矩陣長),矩陣長 = TB*len
    //即 矩陣坐標wy = int(紋理坐標y/ 矩陣寬),矩陣寬 = TR*len
    int wx = int(x / TB / length);
    int wy = int(y / TR / length);
    vec2 v1, v2, vn;
    
    //判斷wx是否為偶數(shù),等價于 wx % 2 == 0
    if (wx/2 * 2 == wx) {
        if (wy/2 * 2 == wy) {//偶行偶列
            //(0,0),(1,1)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy));
            v2 = vec2(length * TB * float(wx+1), length * TR * float(wy+1));
        }else{//偶行奇列
            //(0,1),(1,0)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy+1));
            v2 = vec2(length * TB * float(wx+1), length * TR * float(wy));
        }
    }else{
        if (wy/2 * 2 == wy) {//奇行偶列
            //(0,1),(1,0)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy+1));
            v2 = vec2(length * TB * float(wx+1), length * TR * float(wy));
        }else{//奇行奇列
            //(0,0),(1,1)
            v1 = vec2(length * TB * float(wx), length * TR * float(wy));
            v2 = vec2(length * TB * 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));
    
    //選擇距離小的則為六邊形的中心點,且獲取它的顏色
    vn = (s1 < s2) ? v1 : v2;
    //獲取六邊形中心點的顏色值
    vec4 mid = texture2D(Texture, vn);

    //獲取像素點與中心點的角度
   float a = atan((x-vn.x)/(y-vn.y));
    
    //判斷夾角,屬于哪個三角形,則獲取哪個三角形的中心點坐標
    vec2 area1 = vec2(vn.x, vn.y - mosaicSize * TR / 2.0);
    vec2 area2 = vec2(vn.x + mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);
    vec2 area3 = vec2(vn.x + mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
    vec2 area4 = vec2(vn.x, vn.y + mosaicSize * TR / 2.0);
    vec2 area5 = vec2(vn.x - mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
    vec2 area6 = vec2(vn.x - mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);
    
    if (a >= PI6 && a < PI6 * 3.0) {
        vn = area1;
    }else if (a >= PI6 * 3.0 && a < PI6 * 5.0){
        vn = area2;
    }else if ((a >= PI6 * 5.0 && a <= PI6 * 6.0) || (a < -PI6 * 5.0 && a > -PI6 * 6.0)){
        vn = area3;
    }else if (a < -PI6 * 3.0 && a >= -PI6 * 5.0){
        vn = area4;
    }else if (a <= -PI6 && a > -PI6 * 3.0){
        vn = area5;
    }else if (a > -PI6 && a < PI6){
        vn = area6;
    }
    //獲取對應三角形重心的顏色值
    vec4 color = texture2D(Texture, vn);
    // 將顏色值填充到片元著色器內(nèi)置變量gl_FragColor
    gl_FragColor = color;

}

效果如下:


三角形馬賽克1.png

同樣也可以修改通過修改六邊形的邊長來修改三角形的大小:

const float mosaicSize = 0.01;

效果如下:


三角形馬賽克2.png

覺得不錯記得點贊哦!聽說看完點贊的人逢考必過,逢獎必中。?( ′???` )比心

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內(nèi)容