OpenGL ES 實現多種「馬賽克濾鏡」效果

「⻢賽克效果」就是把圖⽚的⼀個至關⼤⼩的區域⽤同⼀個點的顏⾊來表示,能夠認爲是⼤規模的下降圖像的分辨率,⽽讓圖像的⼀些細節隱藏起來。web


無馬賽克濾鏡

  • 「無濾鏡」效果的實現準備工做的代碼與「無分屏濾鏡」中的實現邏輯和流程一致,只須要修改相應的底部item數組及對應的着色器名稱等,這裏再也不說明這部份內容,頂點着色器也沒有任何變化,主要是針對片元着色器中GLSL代碼的實現濾鏡算法作具體的說明和實現;算法

  • 具體流程請參考: OpenGL ES 之GLSL 實現「分屏濾鏡」效果數組


方形馬賽克濾鏡

1、效果展現

2、實現原理

濾鏡算法
  • 根據紋理座標計算實際圖像中的位置,至關於將紋理顏色區放大;微信

  • 計算出一個小馬賽克的座標,即找到馬賽克提取顏色值的像素點;app

  • 將馬賽克座標換算回紋理座標,即將紋理顏色區縮小;編輯器


計算過程
  • 計算圖像的實際位置
    假設紋理座標爲(0,0)、(1,0)、(0,1)、(1,1), 紋理圖片的大小爲(400,400), 那麼實際圖片的位置爲(0,0)、(0,400)、(400,0)、(400,400);
    函數

  • 計算馬賽克實際位置
    假設當前圖片的位置爲(400,400), 單個馬賽克的大小爲(100,100), 那麼當前馬賽克的座標爲:(floor(400/100100), floor(400/100100)) = (400, 400);
    學習

  • 計算馬賽克紋理座標
    將馬賽座標換算成紋理座標,即(400/400, 400/400) = (1, 1), 根據texture2D函數獲得紋理座標的顏色值, 即當前馬賽克的顏色值;
    flex

  • 一個正方形馬賽克是由多個小的正方形圖像色塊組成, 馬賽克的顏色值是取其中一個小正方形的顏色值, 在floor(x)函數向下取整時, 就獲取了提取顏色的小方塊的像素點;google


3、GLSL實現


precision highp float;
// 紋理座標
uniform sampler2D Texture;
// 紋理採樣器
varying vec2 TextureCoordsVarying;
// 紋理圖片size
const vec2 TexSize = vec2(400.0400.0);
// 馬賽克size
const vec2 MosaicSize = vec2(16.016.0);

void main(){
    // 計算實際圖像位置
    vec2 intXY = vec2(TextureCoordsVarying.x * TexSize.x, TextureCoordsVarying.y * TexSize.y);

    // floor(x) 內建函數,返回小於/等於x最大的整數,即向下取整
    // 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、效果展現

2、實現原理

  • 六邊形馬賽克原理:將一張圖片,分割成由六邊形組成,再取每一個六邊形的重點畫出一個個的矩形,根據矩形的奇偶排列狀況求出對應的2箇中心點,並計算紋理座標與兩個中心點的距離,根據距離判斷,採起就近原則,當前的六邊形就採用近的中心點的顏色值。


  • 將圖片分割成六邊形,六邊形中心點畫出矩形後的呈現以下所示:

濾鏡算法

設置矩形的長寬比例值TR、TB(TB:TR 符合比例 3:3*sqrt(3),其中長寬比爲3:3*sqrt(3),計算過程以下:


  • 獲取紋理座標的x,y;

  • 根據紋理座標計算對應的矩形座標wx、wy。假設矩陣的比例爲3*len:sqrt(3),那麼紋理座標(x,y)對應的矩陣座標爲


  • 根據行列的奇偶狀況,求對應的中心點紋理座標v一、v2
    偶行偶列:(0,0)(1,1),即左上、右下
    偶行奇列:(0,1)(1,0),即左下、右上
    奇行偶列:(0,1)(1,0),即左下、右上
    奇行奇列:(0,0)(1,1),即左上、右下
    最終只有兩種狀況,(0,0)(1,1) 和 (0,1)(1,0),以下圖所示



  • 其中單個矩陣中,4個點的座標計算公式以下:
    對於計算中的wx+1,拿(1,0)點來講,wx+1等同於(1,0)與(0,0)之間相差一個矩形的長,這個長度爲1,爲了獲得(1,0)點的座標,要在(0,0)點座標的基礎上,將wx增長一個長;
    對於計算中的wy+1,拿(0,1)點來講,wy+1等同於(0,0)與(0,1)之間相差一個矩形的高,這個長度爲1,爲了獲得(0,1)點的座標,要在(0,0)點座標的基礎上,將wy增長一個高;



  • 根據距離公式求像素點距離兩個中心點的距離s一、s2



  • 根據求出的距離,判斷離哪一個中心點近,就取哪一個六邊形的中心點顏色值爲六邊形的顏色值;


3、GLSL實現

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;

    // 根據紋理座標計算出對應的矩陣座標 
    // 即矩陣座標 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是否爲偶數,等價於 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、效果展現

2、實現原理

  • 原理:三角形馬賽克是由六邊形馬賽克演變而來,獲得三角形的前提,就是先有六邊形,而後將正六邊形6等分,每一個三角形都是正三角形,而後求出紋理座標與中心點的夾角,同時求出三角形的中心點,根據夾角判斷,夾角屬於哪一個三角形,就將該三角形的中心點顏色做爲整個三角形的紋素;


三角形濾鏡算法是在六邊形濾鏡算法的步驟上再增長如下步驟:


  • 求出當前像素點與紋理中心點的夾角:以下圖所示,紋理座標爲(x,y),中心點爲vn,求夾角

  • 計算6個三角形的中心點

  • 判斷夾角屬於哪一個三角形,則獲取哪一個三角形的中心點座標,其中不一樣三角形的夾角範圍如圖所示

3、GLSL實現

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

float mosaicSize = 0.03;

void main (void) {
    const float TR = 0.866025;
    const float PI6 = 0.523599;

    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;

    int wx = int(x/(1.5 * mosaicSize));
    int wy = int(y/(TR * mosaicSize));

    vec2 v1, v2, vn;

    if (wx / 2 * 2 == wx) {
        if (wy/2 * 2 == wy) {
            v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy));
            v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy + 1));
        } else {
            v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy + 1));
            v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy));
        }
    } else {
        if (wy/2 * 2 == wy) {
            v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy + 1));
            v2 = vec2(mosaicSize * 1.5 * float(wx+1), mosaicSize * TR * float(wy));
        } else {
            v1 = vec2(mosaicSize * 1.5 * float(wx), mosaicSize * TR * float(wy));
            v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * 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 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);
    // 將顏色值填充到片元着色器內置變量gl_FragColor
    gl_FragColor = color;
}

注意: atan 是 GLSL 中的內建函數,有兩種計算方式

atan(y,x) 值域是[0,π],
atan(y/x),值域是[-π/2, π/2] 。


原文連接:https://blog.csdn.net/forever_wj/article/details/107923371



-- END --


進技術交流羣,掃碼添加個人微信:Byte-Flow



獲取視頻教程和源碼



推薦:

字節流動 OpenGL ES 技術交流羣來啦

Android OpenGL 渲染圖像讀取哪家強?

FFmpeg + OpenGL ES 實現 3D 全景播放器

一文掌握 YUV 圖像的基本處理

Android OpenGL ES 從入門到精通系統性學習教程

OpenGL ES 實現動態(水波紋)漣漪效果



以爲不錯,點個在看唄~

本文分享自微信公衆號 - 字節流動(google_developer)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索