上一篇文章中主要講了分屏濾鏡,這篇我們了解一下灰度濾鏡和馬賽克濾鏡,其中馬賽克濾鏡分別用正方形、六邊形和三角形實現(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é)果:
左邊是浮點算法,右邊是取綠色值,可以看出右邊會更偏綠一點。
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;
}
效果圖:
3、馬賽克濾鏡
?賽克效果就是把圖片的?個相當?小的區(qū)域?同一個點的顏色來表示.可以認為是大規(guī)模的降低圖像的分辨率,而讓圖像的一些細節(jié)隱藏起來。
所以我們只要根據(jù)馬賽克單元格的寬高計算出圖像總的馬賽克行數(shù)和列數(shù),然后將每個馬賽克單元格遍歷2次,第一次計算該單元格RGB的平均值,第二次遍歷賦顏色值。
代碼實現(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;
}
效果圖:
如果覺得太粗糙了也可以將馬賽克size改小一點:
const vec2 mosaicSize = vec2(8.0, 8.0);
效果圖:
4、六邊形馬賽克
和正方形類似,我們是將馬賽克顯示的圖形從正方形換到了六邊形,我們要提取的馬賽克顏色實際上就從每一個六邊形中提取,默認我們是選擇六邊形的中心點顏色作為最終馬賽克的顯示顏色。如下圖:
我們提取出其中一部分放大看,就會發(fā)現(xiàn)實際上每一個六邊形都可以由四個矩形組成。
由基本的數(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個矩陣,實際上他們只有以下兩種類型:
所以我們也可以計算出每個矩形的4個頂點的坐標:
每一種類型中,也只包含兩個六邊形的中心點,所以我們在計算選擇哪個點顏色時候,只需要判斷這個點離哪個六邊形的中心點近,然后選后近的那個中心點并取其對應的顏色即可:
根據(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;
}
效果如下:
同樣的可已通過修改六邊形的邊長來控制六邊形的大小。比如:
const float mosaicSize = 0.01;
效果如下:
5、三角形馬賽克
三角形馬賽克實際上就是從六邊形馬賽克基礎上演變而來的,一個六邊形可以拆分為6個三角形:
結(jié)合六邊形的邏輯,我們在取到對應的六邊形的中心點后,再獲取到該像素點和六邊形中心點的夾角:
假設點的坐標為:(x,y) 六邊形中心點為:Vn(x,y)
注意夾角的范圍是
[-180,180]
,即[-π,π]
,再根據(jù)三角形馬賽克.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;
}
效果如下:
同樣也可以修改通過修改六邊形的邊長來修改三角形的大小:
const float mosaicSize = 0.01;
效果如下:
覺得不錯記得點贊哦!聽說看完點贊的人逢考必過,逢獎必中。?( ′???` )比心