Unreal Engine 4 手绘风滤镜(Paint Filter)即 桑原滤镜(Kuwahara Filter)教程(下)
本文是《Unreal Engine 4 手繪風濾鏡(Paint Filter)即 桑原濾鏡(Kuwahara Filter)教程》的下半部分,上半部分請見《Unreal Engine 4 手繪風濾鏡(Paint Filter)即 桑原濾鏡(Kuwahara Filter)教程(上)》
作者|Tommy Tran May 1 2018 | 翻譯 開發游戲的老王
文章目錄
- 選擇方差最低的核
- 方向性桑原濾鏡(Directional Kuwahara filter)
- 索貝爾是如何工作的
- 獲取局部走向
- 什么是矩陣
- 旋轉核
- 構造旋轉矩陣
選擇方差最低的核
添加如下代碼獲得方差最低的核
// 1 float3 FinalColor = MeanAndVariance[0].rgb; float MinimumVariance = MeanAndVariance[0].a;// 2 for (int i = 1; i < 4; i++) {if (MeanAndVariance[i].a < MinimumVariance){FinalColor = MeanAndVariance[i].rgb;MinimumVariance = MeanAndVariance[i].a;} }return FinalColor;解釋一下上述代碼:
然后找到 Materials\PostProcess文件夾,打開PP_Kuwahara,點擊應用,再回到主編輯界面就可以看到效果了。
效果看起來不錯,但是如果你貼近一看,會發現一些奇怪的“塊狀斑”。下圖中我把它們高亮標注了一下:
這是使用軸向平行核(axis-aligned kernel)產生的副作用。一種除去這種塊狀斑的方法就是使用改進版的濾鏡,我們稱之為方向性桑原濾鏡(Directional Kuwahara filter)。
方向性桑原濾鏡(Directional Kuwahara filter)
這種濾鏡和之前的很類似,只不過它平行于像素的局部走向。下面是個核大小為3×5的方向性桑原濾鏡:
注:因為我們把一個核視為一個矩陣(matrix),所以以Height x Width的形式書寫它的維度,而不是像往常一樣寫成Width x Height。下文中我們會繼續介紹矩陣的知識。
核首先要計算出像素邊緣的走向,然后將整個核旋轉使其平行。
我們使用索貝爾算子(Sobel)進行卷積運算來獲得局部走向。如果索貝爾這個詞你聽起來很熟悉,八成因為它是一非常經典的邊緣檢測技術。既然它是一種邊緣檢測技術,我們能用它來獲取局部走向么?我們先來了解一下索貝爾的工作原理,你就明白了。
索貝爾是如何工作的
索貝爾使用2個核
Gx用于獲取水平方向的梯度;Gy用于獲取垂直方向的梯度。我們以下面3×3的灰度圖為例:
首先,使用中間的像素核每個核進行卷積。
譯者注:對應單元格中的值相乘再相加
如果把每對值都標記到一個2D平面上,那么我們就可以將這個向量所指的方向視為像素的邊緣走向
然后我們可以對這個斜度值求反正切(arc tangent 或 atan),然后就可以用這個角度對核進行旋轉了。
這就是我們利用索貝爾來計算像素局部走向的方法。
獲取局部走向
打開Global.usf將下列代碼添加到 GetPixelAngle():
float GradientX = 0; float GradientY = 0; float SobelX[9] = {-1, -2, -1, 0, 0, 0, 1, 2, 1}; float SobelY[9] = {-1, 0, 1, -2, 0, 2, -1, 0, 1}; int i = 0;注意:GetPixelAngle()函數的}一定不要寫!!!前面文章講過的知識點!
解釋一下每個變量的意義:
- GradientX: 保存水平方向的斜度
- GradientY: 保存垂直方向的斜度
- SobelX: 將水平索貝爾核存儲到一個數組中
- SobelY: 將垂直索貝爾核存儲到一個數組中
- i: 用于訪問SobelX和SobelY數組中的元素
接下來使用SobelX 和 SobelY 實施卷積,代碼如下:
for (int x = -1; x <= 1; x++) {for (int y = -1; y <= 1; y++){// 1float2 Offset = float2(x, y) * TexelSize;float3 PixelColor = SceneTextureLookup(UV + Offset, 14, false).rgb;float PixelValue = dot(PixelColor, float3(0.3,0.59,0.11));// 2GradientX += PixelValue * SobelX[i];GradientY += PixelValue * SobelY[i];i++;} }解釋一下代碼:
直接把斜度值代入atan()函數。將下面的代碼添加到for循環的下面:
return atan(GradientY / GradientX);現在我們擁有了獲取像素角度的函數,還需要研究一下如何旋轉核。一種方法就是使用矩陣(matrix)。
注:實際上你也可以使用基本的三角方法來旋轉,只不過我覺得這里是一個學習矩陣的好機會,因為它們實在是好用。
什么是矩陣
矩陣就是一個數字組成的二維數組。例如,下面是一個2×3的矩陣(2行 3列):
矩陣本身看起來沒什么意思。但當你用一個向量和矩陣相乘的時候,就會體會到它的強大威力了。它可以讓你很方便地進行旋轉縮放等操作。如何使用矩陣來實現旋轉呢?
在坐標系統中,每個維度都可以用向量表示。我們稱之為基向量,它們定義了坐標軸的正方向。
下面是幾個不同基向量的例子。紅色箭頭表示X方向,綠箭頭表示Y方向。
我們可以使用基向量構建一個旋轉矩陣,來旋轉一個向量。簡單地說,就是一個包含著旋轉后基向量位置的矩陣。舉個例子:你有一個向量(橙色箭頭所示)(1, 1)。
假設我們想順時針將它旋轉90度。首先,我們要把基向量先旋轉該角度。
然后,以新的基向量位置構造一個2×2的矩陣。第一列是紅色箭頭的位置,第二列是綠色箭頭的位置
最后,使用橙色向量和旋轉矩陣進行矩陣乘法。其結果就是橙色向量的新位置。
注:你沒必要知道矩陣如何相乘,因為HLSL已經內置了相關函數。
驚喜不驚喜?更牛X的是你甚至可以使用上面的矩陣將任意二維向量順時針旋轉90度。對于濾鏡來講,這就意味著我們只需為每個像素構造一次旋轉矩陣,就可以為整個核所使用。
接下來該使用旋轉矩陣旋轉核了。
旋轉核
首先,修改GetKernelMeanAndVariance()函數使其能夠接受2×2的矩陣。這是因為,我們得在Kuwahara.usf 中構建這旋轉矩陣并把它傳入其中。將GetKernelMeanAndVariance()改為:
float4 GetKernelMeanAndVariance(float2 UV, float4 Range, float2x2 RotationMatrix)接著把里層for循環的第一行改為:
float2 Offset = mul(float2(x, y) * TexelSize, RotationMatrix);mul()函數將使用偏移量和RotationMatrix進行矩陣乘法。這樣就可以以當前像素旋轉了。
接下來,我們要構造旋轉矩陣。
構造旋轉矩陣
我們使用正弦(sine)和 余弦(cosine)來構造旋轉矩陣:
關閉 Global.usf并打開Kuwahara.usf,然后將下面的代碼添加到變量列表的底端:
float Angle = GetPixelAngle(UV); float2x2 RotationMatrix = float2x2(cos(Angle), -sin(Angle), sin(Angle), cos(Angle));第一行計算出當前像素的角度,第二行使用該角度創建旋轉矩陣。
最后,我們將RotationMatrix傳給每一個核。把GetKernelMeanAndVariance()改為如下形式:
GetKernelMeanAndVariance(UV, Range, RotationMatrix)這就是方向性桑原濾鏡的全部啦!關閉Kuwahara.usf并回到PP_Kuwahara,點擊應用并關閉它。
下面就是原始的桑原濾鏡和方向性桑原濾鏡的對比。請尤其注意,方向性桑原濾鏡不再有那些塊狀斑了。
注: 我們可以使用PPI_Kuwahara修改濾鏡的大小。我建議將濾鏡設為X半徑大于Y半徑。這將提高核沿邊緣走向的大小,對于提高方向性有一定好處。
總結
以上是生活随笔為你收集整理的Unreal Engine 4 手绘风滤镜(Paint Filter)即 桑原滤镜(Kuwahara Filter)教程(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【NFC】 NfcA/NfcB/NfcF
- 下一篇: 高通工具过滤_高通QXDM|高通诊断监视