說在開頭:
PhotoShop和特效相機中有許多特效的濾鏡。片元着色器時基於片元爲單位執行的,完全可以實現特殊的濾鏡效果。要想實現這些濾鏡效果還需要簡單的瞭解《數字圖像處理》中的圖像卷積與濾波的一些知識。
作者:憨豆酒(YinDou),聯繫我[email protected],熟悉圖形學,圖像處理領域,本章的源代碼可在此倉庫中找到: https://github.com/douysu/person-summary 如果大家發現錯誤以及不合理之處,還希望多多指出。
參考內容:
- 《OpenGL ES 3.X 遊戲開發 下卷》
- zouxy09的博客:http://blog.csdn.net/zouxy09/article/details/49080029
我將兩者的精華內容進行了結合,同時結合了我自己的一些知識和着色器的相關知識。
圖像卷積的原理
zouxy09的博客中的內容很詳細,已下爲他的博客中的內容
大家主要看三部分,源像素值,卷積內核的值,最終的像素值,看如何進行乘積的。(圖片右上角有計算過程)
濾波器的規則要求:
1)濾波器的大小應該是奇數,這樣它纔有一箇中心,例如3x3,5x5或者7x7。有中心了,也有了半徑的稱呼,例如5x5大小的核的半徑就是2。
2)濾波器矩陣所有的元素之和應該要等於1,這是爲了保證濾波前後圖像的亮度保持不變。當然了,這不是硬性要求了。
3)如果濾波器矩陣所有元素之和大於1,那麼濾波後的圖像就會比原圖像更亮,反之,如果小於1,那麼得到的圖像就會變暗。如果和爲0,圖像不會變黑,但也會非常暗。
4)對於濾波後的結構,可能會出現負數或者大於255的數值。對這種情況,我們將他們直接截斷到0和255之間即可。對於負數,也可以取絕對值。
運行效果及代碼
然後看看使用不同的卷積內核對圖片的影響吧。
平滑過濾
片元着色器代碼(SmoothFilter.frag)
#version 400
#extension GL_ARB_separate_shader_objects : enable
#extension GL_ARB_shading_language_420pack : enable
layout (binding = 1) uniform sampler2D sTexture;//紋理採樣器
layout (location = 0) in vec2 vTextureCoord;//紋理座標
layout (location = 0) out vec4 outColor;//輸出片元顏色
void main() {
//紋理偏移量單位步進
const float stStep = 512.0;
const float scaleFactor = 1.0/9.0;//給出最終求和時的加權因子(爲調整亮度)
//給出卷積內核中各個元素對應像素相對於待處理像素的紋理座標偏移量
vec2 offsets[9]=vec2[9](
vec2(-1.0,-1.0),vec2(0.0,-1.0),vec2(1.0,-1.0),
vec2(-1.0,0.0),vec2(0.0,0.0),vec2(1.0,0.0),
vec2(-1.0,1.0),vec2(0.0,1.0),vec2(1.0,1.0)
);
//卷積內核中各個位置的值
float kernelValues[9]=float[9] (
1.0,1.0,1.0,
1.0,1.0,1.0,
1.0,1.0,1.0
);
//最終的顏色值
vec4 sum=vec4(0,0,0,0);
//顏色求和
for(int i=0;i<9;i++){
sum=sum+kernelValues[i]*scaleFactor*texture(sTexture, vTextureCoord+offsets[i]/stStep);
}
outColor=sum;
}
顏色求和部分的代碼稍微不好理解。
1、首先kernelValues[i]*scaleFactor,scaleFactor爲卷積內核中數的和的倒數,也就是1/9,這麼做的目的是爲了保持圖片的亮度與原來的亮度不變,這個數大於1時,處理後的圖片變亮,小於1處理後的圖片變暗。
2、然後是texture(sTexture, vTextureCoord+offsets[i]/stStep),這裏讓紋理座標的偏移量除以stStep,stStep爲紋理座標偏移量的步進,在着色器中控制着濾波器處理區域的大小。
邊緣檢測
片元着色器代碼(EdgeDetection.frag)只給出卷積內核的值
//卷積內核中各個位置的值
float kernelValues[9]=float[9] (
0.0,1.0,0.0,
1.0,-4.0,1.0,
0.0,1.0,0.0
);
銳化效果
片元着色器代碼(Sharpen.frag)只給出卷積內核的值
float kernelValues[9]=float[9] (
0.0,-1.0,0.0,
-1.0,5.0,-1.0,
0.0,-1.0,0.0
);
浮雕效果
片元着色器代碼(SmoothFilter.frag)只給出卷積內核的值
float kernelValues[9]=float[9] (
2.0,0.0,2.0,
0.0,0.0,0.0,
3.0,0.0,-6.0
);
均值模糊
片元着色器代碼(MeanBlur.frag)只給出卷積內核的值
可以多次模糊也是可以的
float kernelValues[9]=float[9] (
0.0,1.0,0.0,
1.0,1.0,1.0,
0.0,1.0,0.0
);
高斯模糊
片元着色器代碼(GaussianBlur.frag)只給出卷積內核的值
const float scaleFactor = 1.0/209.0;//給出最終求和時的加權因子(爲調整亮度)
//卷積內核中各個位置的值
float kernelValues[9]=float[9] (
16.0,26.0,16.0,
26.0,41.0,26.0,
16.0,26.0,16.0
);
這裏的加權因子應該爲卷積內核所有值的和的倒數。也就是1/209。
數字圖像處理中卷積邊界的處理
他的博客中總結的很好。
片元着色器中的邊界處理
對於片元着色器中的邊界處理我還沒有理解清楚,希望清楚的人可以解答。
最後
這裏只是簡單的介紹了幾種卷積內核,還有更多的卷積內核,不同的卷積內核實現的效果也不同,我只是把這些知識使用片元着色器實現了,有什麼問題可以互相討論,我的知識水平也有限,有什麼問題希望大家指出。