關(guān)于OpenGL ES實(shí)現(xiàn)馬賽克濾鏡的思路與實(shí)現(xiàn)

倡導(dǎo)文明和諧,往往需要給圖片打上萬(wàn)惡的馬賽克,對(duì)于iOS開發(fā)者來(lái)說(shuō),給圖片打碼需要使用OpenGL ES,編寫GLSL文件給圖片打碼。
馬賽克的原理其實(shí)就是將圖片的某個(gè)區(qū)域用同一個(gè)色塊填充,從而達(dá)到降低圖片分辨率的效果,色塊的顏色要根據(jù)該區(qū)域某個(gè)點(diǎn)來(lái)確定。本文將介紹正方形、正六邊形、正三角形3種馬賽克的實(shí)現(xiàn)算法。

正方形馬賽克

正方形馬賽克

正方形馬賽克是將圖片分成n*n個(gè)小的正方形色塊,每個(gè)色塊取一個(gè)當(dāng)前色塊某個(gè)點(diǎn)的顏色值填充。
比如一張 w*w 圖片,如果我們要使用s*s的正方形馬賽克濾鏡,那么這個(gè)圖片就要分割成 n*n 個(gè)正方形色塊(n=floor(w/s), floor()是向下取整公式),每個(gè)正方形的色塊顏色取這個(gè)這個(gè)正方形起始點(diǎn)的顏色值。比如如果當(dāng)前紋理坐標(biāo)為(x, y),我們可以通過(guò)公式

(floor(x/s)*s, floor(y/s)*s)

得到這個(gè)紋理坐標(biāo)所處色塊的起始點(diǎn)坐標(biāo),并取該起始點(diǎn)坐標(biāo)的顏色值填充。

正方形馬賽克原理.png

算法的具體實(shí)現(xiàn)在片元著色代碼中。


precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
// 假定紋理的大小
const vec2 TextureSize = vec2(400.0, 400.0);
// 馬賽克的大小
const vec2 MosaicSize = vec2(8.0, 8.0);

void main (void) {
    // 紋理坐標(biāo)是0~1, 先將紋理坐標(biāo)擴(kuò)大假定紋理大小
    vec2 TextureXY = vec2(TextureCoordsVarying.x * TextureSize.x, TextureCoordsVarying.y * TextureSize.y);
    // 計(jì)算得到假定紋理大小下當(dāng)前紋理坐標(biāo)所處色塊的起始點(diǎn)位置
    vec2 MosaicXY = vec2(floor(TextureXY.x/MosaicSize.x)*MosaicSize.x, floor(TextureXY.y/MosaicSize.y)*MosaicSize.y);
    // 在將起始點(diǎn)位置換算成標(biāo)準(zhǔn)0~1的范圍
    vec2 MosaicCoord = vec2(MosaicXY.x/TextureSize.x, MosaicXY.y/TextureSize.y);
    
    vec4 mask = texture2D(Texture, MosaicCoord);
    gl_FragColor = vec4(mask.rgb, 1.0);
}

另外附上頂點(diǎn)著色器代碼

attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;

void main() {
    TextureCoordsVarying = TextureCoords;
    gl_Position = Position;
}

頂點(diǎn)著色器不需要做任何與馬賽克濾鏡有關(guān)的操作,較為簡(jiǎn)單,后續(xù)的六邊形馬賽克和三角形馬賽克均采用相同頂點(diǎn)著色器代碼。

六邊形馬賽克

六邊形馬賽克

六邊形馬賽克濾鏡是用交錯(cuò)的六邊形將圖片分割成一個(gè)個(gè)色塊,與正方形不同,我們無(wú)法直接知道當(dāng)前紋理坐標(biāo)位于哪個(gè)六邊形,但我們可以將兩個(gè)相鄰的六邊形組成一個(gè)矩形,如下圖所示。

六邊形

我們可以將六邊形問(wèn)題轉(zhuǎn)換為矩形問(wèn)題,和正方形一樣,我們可以算出當(dāng)前坐標(biāo)位于哪個(gè)矩形內(nèi),然后再判斷當(dāng)前坐標(biāo)是離v1和v2距離,決定采用哪個(gè)v1還是v2的色值,達(dá)到我們的目標(biāo)效果。

要實(shí)現(xiàn)這個(gè)算法我們首先需要計(jì)算矩形的長(zhǎng)寬,我們用高中的幾何知識(shí)來(lái)計(jì)算,目前的已知條件是六邊形的邊長(zhǎng)ab,根據(jù)正六邊形的性質(zhì),我們知道頂點(diǎn)a到原點(diǎn)v1的距離等于邊長(zhǎng),即 v1a = ab。而a、d、v2組成的三角形,很明顯是一個(gè)角度分別為30°、60°、90°的直角三角形,我們都知道直角三角形30°角所對(duì)的那條邊等于斜邊的一半,得出 ad = 0.5*v2a = 0.5*ab,另外根據(jù)勾股定理得出 v2d = ad*√3 = ab*0.5*√3 ≈ 0.866025*ab, 而矩形的長(zhǎng) v1d = v1a + ad = ab + 0.5*ab = 1.5ab。所以我們可以得出公式

width = length * 1.5
height = length * 0.866025

length為六邊形的邊長(zhǎng),width為矩形的長(zhǎng),height為矩形的寬

得到矩形的長(zhǎng)寬后,我們就可以計(jì)算當(dāng)前紋理坐標(biāo)(x, y)所處矩形的兩個(gè)原點(diǎn)v1、v2,因?yàn)榫匦蔚脑c(diǎn)的位置有兩種情況,我們需要分開討論。

矩形一

矩形1

首先需要知道當(dāng)前坐標(biāo)位于第幾行第幾列的矩形,

column = floor(x/v1d) = floor(x/(ab*1.5)) = floor(x/(1.5*length))
row = floor(y/v2d) = floor(y/(0.866025*length))

所以,

v1.x = column * width = column * length * 1.5
v1.y = row * height + height = (row+1) * length * 0.866025

v2.x = column * width + width = (column+1) * length * 1.5
v2.y = row * height = row * length * 0.866025

矩形二

矩形2

同樣的,可以計(jì)算得到

v1.x = column * width = column * length * 1.5
v1.y = row * height = row * length * 0.866025

v2.x = column * width + width = (column+1) * length * 1.5
v2.y = row * height + height = (row+1) * length * 0.866025

接下來(lái)的步驟顯然是得知當(dāng)前坐標(biāo)(x, y)所在矩形是矩形一還是矩形二,我們可以通過(guò)奇偶行列獲得。

奇偶關(guān)系

化繁為簡(jiǎn),我們可以從起始位置開始觀察,當(dāng)前坐標(biāo)位于偶數(shù)行、偶數(shù)列和奇數(shù)行、奇數(shù)列時(shí)是矩形二,奇數(shù)行、偶數(shù)列和偶數(shù)行、奇數(shù)列時(shí)是矩形一,至此,我們可以拿到v1和v2的坐標(biāo)。

得到v1,v2后,我們就可以判斷當(dāng)前坐標(biāo)(x, y)離誰(shuí)更近而決定采用哪個(gè)原點(diǎn)的顏色值。

附上著色器代碼:


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

const float mosaicSize = 0.015;

void main (void) {
    
    float length = mosaicSize;
    
    float dx = 1.5;
    float dy = 0.866025;
    
    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;
    
    // 當(dāng)前位于第幾行和第幾列矩形
    int wx = int(x/dx/length);
    int wy = int(y/dy/length);
    
    vec2 v1, v2, vn;
    
    if (wx/2 * 2 == wx) {// 偶數(shù)行
        if (wy/2 * 2 == wy) { // 偶數(shù)列
            v1 = vec2(length*dx*float(wx), length*dy*float(wy));
            v2 = vec2(length*dx*float(wx+1), length*dy*float(wy+1));
        } else { // 奇數(shù)列
            v1 = vec2(length*dx*float(wx), length*dy*float(wy+1));
            v2 = vec2(length*dx*float(wx+1), length*dy*float(wy));
        }
    } else { // 奇數(shù)行
        if (wy/2 * 2 == wy) {  //偶數(shù)列
            v1 = vec2(length*dx*float(wx), length*dy*float(wy+1));
            v2 = vec2(length*dx*float(wx+1), length*dy*float(wy));
        } else { // 奇數(shù)列
            v1 = vec2(length*dx*float(wx), length*dy*float(wy));
            v2 = vec2(length*dx*float(wx+1), length*dy*float(wy+1));
        }
    }
    
    // 當(dāng)前坐標(biāo)到v1、v2的距離
    float d1 = sqrt(pow(v1.x-x, 2.0) + pow(v1.y-y, 2.0));
    float d2 = sqrt(pow(v2.x-x, 2.0) + pow(v2.y-y, 2.0));
    
    if (d1 < d2) {
        vn = v1;
    } else {
        vn = v2;
    }

    vec4 mask = texture2D(Texture, vn);
    gl_FragColor = mask;
}

三角形馬賽克

三角形馬賽克

三角形馬賽克是在六邊形馬賽克基礎(chǔ)上變化得來(lái)的,從下圖可以看到,一個(gè)正六邊形可以分割成6個(gè)正三角形,而我們已經(jīng)知道當(dāng)前坐標(biāo)(x, y)所在的六邊形,只需要通過(guò)計(jì)算當(dāng)前坐標(biāo)(x, y)和原點(diǎn)的連線與起始邊的夾角判斷該點(diǎn)位于哪個(gè)3角形區(qū)域內(nèi),再取該三角形區(qū)域中心點(diǎn)的顏色值填充,就可以得到最終的結(jié)果。

三角形馬賽克原理

夾角的計(jì)算可以使用用公式
a = atan((y-v0.y), (x-v0.x))

著色器具體代碼如下:


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

const float mosaicSize = 0.03;

void main (void) {
    
    float length = mosaicSize;
    
    float dx = 1.5;
    float dy = 0.866025;
    
    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;
    
    int wx = int(x/dx/length);
    int wy = int(y/dy/length);
    
    vec2 v1, v2, vn;
    
    if (wx/2 * 2 == wx) {
        if (wy/2 * 2 == wy) {
            v1 = vec2(length*dx*float(wx), length*dy*float(wy));
            v2 = vec2(length*dx*float(wx+1), length*dy*float(wy+1));
        } else {
            v1 = vec2(length*dx*float(wx), length*dy*float(wy+1));
            v2 = vec2(length*dx*float(wx+1), length*dy*float(wy));
        }
    } else {
        if (wy/2 * 2 == wy) {
            v1 = vec2(length*dx*float(wx), length*dy*float(wy+1));
            v2 = vec2(length*dx*float(wx+1), length*dy*float(wy));
        } else {
            v1 = vec2(length*dx*float(wx), length*dy*float(wy));
            v2 = vec2(length*dx*float(wx+1), length*dy*float(wy+1));
        }
    }
    
    float d1 = sqrt(pow(v1.x-x, 2.0) + pow(v1.y-y, 2.0));
    float d2 = sqrt(pow(v2.x-x, 2.0) + pow(v2.y-y, 2.0));
    
    if (d1 < d2) {
        vn = v1;
    } else {
        vn = v2;
    }
    
    // 將π分為3等分
    float PI3 = 3.14159/3.0;
    // 獲得弧度值
    float a = atan((y-vn.y), (x-vn.x));
    // 每個(gè)中心點(diǎn)與原點(diǎn)的xy偏移值 
    float xoffset = length*0.5;
    float yoffset = xoffset*dy;
    
    // 對(duì)象圖中6個(gè)三角形區(qū)域的中心點(diǎn)
    vec2 area1 = vec2(vn.x+xoffset, vn.y+yoffset);
    vec2 area2 = vec2(vn.x, vn.y+yoffset);
    vec2 area3 = vec2(vn.x-xoffset, vn.y+yoffset);
    vec2 area4 = vec2(vn.x-xoffset, vn.y-yoffset);
    vec2 area5 = vec2(vn.x, vn.y-yoffset);
    vec2 area6 = vec2(vn.x+xoffset, vn.y-yoffset);
    
    // 判斷當(dāng)前坐標(biāo)位于哪個(gè)區(qū)域
    if (a >= 0.0 && a < PI3) {
        vn = area1;
    } else if (a >= PI3 && a < PI3*2.0) {
        vn = area2;
    } else if (a >= PI3*2.0 && a <= PI3*3.0) {
        vn = area3;
    } else if (a <= -PI3*2.0 && a >= -PI3*3.0) {
        vn = area4;
    } else if (a <= -PI3 && a > -PI3*2.0) {
        vn = area5;
    } else if (a <= 0.0 && a < -PI3) {
        vn = area6;
    }

    vec4 mask = texture2D(Texture, vn);
    gl_FragColor = mask;
}

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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