1. 程式人生 > >unity shader 後處理實現水墨風格渲染「Low Poly 」變「水墨畫 」

unity shader 後處理實現水墨風格渲染「Low Poly 」變「水墨畫 」

水墨風格渲染

這次學校的比賽打算做一箇中國古代背景的遊戲,所以嘗試做了水墨風格的渲染。
主要按以下四步來實現的效果:

  1. 根據色調和飽和度調整飽和度。
  2. 對影象進行模糊
  3. 水墨風格的物體邊緣
  4. 物體內畫筆筆觸的模擬

接下來我分別就這四步依次展開說明:

調整飽和度

這裡我使用Post-Processing Stack 的Color Grading 來調節飽和度,通過使用Grading Curves來具體調節。根據色調和飽和度調整飽和度。強化部分國畫中常用顏料的顏色部分的飽和度,少見顏料的顏色降低飽和度;飽和度高的強化,低的降低。這裡我調節了Sat VS Sat和Hue VS Sat兩種曲線,保留了部分色調在紅色和綠色附近(也就是所謂的丹青)的飽和度,使其它顏色整體變灰;並使飽和的較高的部分割槽域的飽和度更高,其餘部分有所降低,效果對比如下:
這裡寫圖片描述

這裡寫圖片描述

影象模糊

這裡使用高斯模糊來實現,由於與之後的描邊效果不衝突可以寫在一個Pass裡。我這裡直接用Post-Processing Stack裡的DOF(景深)效果,然後把焦距調到最小,讓全屏都模糊。實際上的效果是幾乎一樣的。
這裡寫圖片描述

水墨描邊

由於上一步我們模糊了影象,所有這裡不能直接用原圖來進行邊緣檢測,我們通過_CameraDepthNormalsTexture,來獲得法線圖然後用法線圖來進行邊緣檢測,這樣我們不僅得到了模糊前圖的邊緣而且,不會講陰影等幾何上在一個平面上的顏色不連續的部分也識別為邊緣。
採用Roberts邊緣運算元來檢測,Roberts運算元計算對角上的畫素之間的差,模板比較簡單,而且可以通過調整中心畫素到角上的距離來控制描邊的粗細。
Roberts


具體程式碼如下:


float rgb2gray(fixed3 col){
    float gray = 0.2125 * col.r + 0.7154 * col.g + 0.0721 * col.b; 
    return gray;
}
fixed4 frag (v2f i) : SV_Target
{
    //......
    float2 texel = _MainTex_TexelSize.xy;
    //判斷是否是邊緣
    fixed3 col0 = tex2D(_CameraDepthNormalsTexture,i.uv+_EdgeWidth*texel*float2(1
,1)).xyz; fixed3 col1 = tex2D(_CameraDepthNormalsTexture,i.uv+_EdgeWidth*texel*float2(1,-1)).xyz; fixed3 col2 = tex2D(_CameraDepthNormalsTexture,i.uv+_EdgeWidth*texel*float2(-1,1)).xyz; fixed3 col3 = tex2D(_CameraDepthNormalsTexture,i.uv+_EdgeWidth*texel*float2(-1,-1)).xyz; float edge = rgb2gray(pow(col0-col3,2)+pow(col1-col2,2)); //....... }

其中_EdgeWidth用來控制邊緣粗細
理論上我們這樣計算出來的edge是一個大於0小於1的很小的數,為了更好的視覺效果我們通過pow()函式來增大edge的值,相當於進行一個對數變換,在變換後部分非邊緣的區域edge也會有個較大的值,我們可以再設定一個閾值來捨棄edge不夠大的部分割槽域。

edge = pow(edge,0.2);
if(edge<_Sensitive)edge=0;
return fixed4(edge,edge,edge,1.0);

以下為_EdgeWidth為3時,不進行任何變換、進行對數變換、設定閾值為0.35時的到的邊緣圖
這裡寫圖片描述

然後用edge的值來控制邊緣與原圖混合,可以設定具體的邊緣顏色。
可以通過噪聲圖或噪聲函式來給邊緣製造水墨感。關於噪聲的內容我就不具體說明了,我這裡用了三維值噪聲的分形疊加,為了是攝像機移動後同一個噪聲紋理與模型相匹配,我通過深度值來獲得畫素世界座標,再用世界座標來生成噪聲,這樣噪聲的樣式就貼到模型上了,而不會像用螢幕座標來取樣時那樣,移動螢幕會顯得不自然。

if(edge<_Sensitive)edge=0;              
else{
    edge=noise;
}
fixed3 finalColor = (edge)*_EdgeColor.xyz+(1-edge)*col*(0.95+0.1*noise);
//非邊緣部分也可以加上較小的噪聲來模擬水墨在紙上擴散的質感
//因為noise在0~1的範圍內所有使係數和的均值為1左右來保證畫面不會更亮或更暗
return fixed4(finalColor,1.0);

這裡寫圖片描述

模擬筆觸

雖然通過模糊顏色上有一定的水墨擴散的感覺,通過描邊可以區分模型的邊緣,但圖片還是有許多不自然的地方。比如顏色擴散太均勻不想“畫”出來的,許多細小的邊緣由於太小而形成了“噪點”。而且由於我使用的噪聲是通過時間座標得到的,部分很遠的地方由於透視而顯得噪聲很亂。所以我們再進行一次濾波來消除這些問題,並模擬畫筆的筆觸。這裡用了一種保留邊緣的濾波,演算法具體的名字我忘了,之前不記得哪裡看到過。具體思路是用中心畫素作為模板的四個角,然後分別計算中心在四個角時的整個區域的均值和方差,然後取方差最小的區域的均值輸出。求知道這個濾波名字的大佬告知一下,我好查一查相關資料,淺墨的這篇文章裡實現的油畫效果也是用簡化後,只取兩個角來計算的。我這裡就直接用兩個角的來算了

for (int j = -4; j <= 0; ++j)  
{  
    for (int k = -4; k <= 0; ++k)  
    {  
        c = tex2D(_MainTex, i.uv +texel*float2(k, j)).xyz;
        m0 += c;   
        s0 += c * c;  
    }  
}
for (int j = 0; j <= 4; ++j)  
{  
    for (int k = 0; k <= 4; ++k)  
    {  
        c = tex2D(_MainTex, i.uv +texel*float2(k, j)).xyz;
        m1 += c;   
        s1 += c * c;  
    }  
}
//取方差小的區域的顏色作為最終輸出顏色  
float4 finalFragColor = 0.;  
float min_sigma2 = 1e+2;                   
m0 /= 25;  
s0 = abs(s0 / 25 - m0 * m0);  
float sigma2 = s0.r + s0.g + s0.b;  
if (sigma2 < min_sigma2)   
{  
    min_sigma2 = sigma2;  
    finalFragColor = float4(m0, 1.0);  
}                     
m1 /= 25  ;
s1 = abs(s1 / 25 - m1 * m1);    
sigma2 = s1.r + s1.g + s1.b;  
if (sigma2 < min_sigma2)   
{  
    min_sigma2 = sigma2;  
    finalFragColor = float4(m1, 1.0);  
}
return finalFragColor;

注意這裡是對描邊後的影象進行的操作,最終效果如下這裡寫圖片描述
可以調節最開始的濾波範圍和描邊粗細,邊緣顏色等引數來調整效果
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
對比後處理前原效果
這裡寫圖片描述
嗯,感覺更醜了。