UnityShader-BilateralFilter(双边滤波,磨皮滤镜)
前言
最近趁著Steam打折入了好多個(gè)游戲,昨天剛剛通關(guān)了一個(gè)《Ruiner》的游戲。
游戲類似《孤膽槍手》,但是加入了很多技能元素和動(dòng)作元素,加上游戲本身的卡通渲染+賽博朋克風(fēng)格,總體感覺還是不錯(cuò)的。
國(guó)慶玩了幾個(gè)大作連刷了幾天,有點(diǎn)傷。最近反倒傾向于玩一些小游戲,簡(jiǎn)單粗暴。不用它三七二十一,莽夫上去就是干!
我發(fā)現(xiàn)blog也是這樣,最近半年寫的blog似乎都有點(diǎn)長(zhǎng),有時(shí)候也來點(diǎn)短小精悍的換換口味。今天就來玩一個(gè)簡(jiǎn)單但是又比較好玩的效果-雙邊濾波。
簡(jiǎn)介
雙邊濾波(Bilateral Filter),可能沒有高斯濾波那樣著名,但是如果說磨皮濾鏡,那肯定是無人不知無人不曉了,用雙邊濾波就可以實(shí)現(xiàn)很好的皮膚濾鏡效果,不管臉上有多少麻子,用完雙邊濾波,瞬間變身白富美。下圖來自一款磨皮濾鏡插件的效果圖,左側(cè)為原始效果,右側(cè)為濾鏡后的效果。本文中我們也會(huì)實(shí)現(xiàn)一個(gè)雙邊濾波后處理,可以達(dá)到近似的效果。
所謂濾波,是將信號(hào)中特定波段頻率濾除的操作。正常高斯模糊(高斯濾波)在進(jìn)行采樣的時(shí)候,主要是考慮了像素之間的距離關(guān)系(空域信息domain),也就是按照正態(tài)分布將當(dāng)前像素點(diǎn)周圍像素加權(quán)平均得到濾波后的結(jié)果,可以得到很好的模糊效果。但是高斯模糊是對(duì)整個(gè)圖像無差異地進(jìn)行模糊,也就是整張圖片全部模糊掉。關(guān)于高斯模糊,之前在Unity Shader后處理:高斯模糊這篇blog中詳細(xì)介紹過,這里不再贅述。
高斯模糊的定義如下:
而雙邊濾波是高斯濾波進(jìn)階版本,可以在模糊的同時(shí)保持圖像中的邊緣信息。除了考慮正常高斯濾波的空域信息(domain)外,還要考慮另外的一個(gè)圖像本身攜帶的值域信息(range)。這個(gè)值域信息的選擇并非唯一的,可以是采樣點(diǎn)間像素顏色的差異,可以是采樣點(diǎn)像素對(duì)應(yīng)的法線信息,可以是采樣點(diǎn)像素對(duì)應(yīng)的深度信息(3D渲染中拿到法線和深度還是要比單純的2D圖像處理可以做的事情多不少哈)。
雙邊濾波定義如下:
可見,除了正常的圖像距離權(quán)重c之外,額外添加了圖像相似信息權(quán)重s,而s是基于圖像本身信息獲得的,使用c和s相乘的結(jié)果作為最終的權(quán)重。即在采樣圖像及周圍點(diǎn)時(shí),對(duì)于每一個(gè)像素點(diǎn),需要乘以距離權(quán)重乘以圖片相似性權(quán)重相加得到總和,然后除以每一個(gè)像素點(diǎn)距離權(quán)重乘以相似性權(quán)重的和,即:
關(guān)于雙邊濾波對(duì)圖像進(jìn)行處理,可以參考《Bilateral Filtering for Gray and Color Images》這篇論文(似乎要出墻),上文高斯濾波定義,雙邊濾波定義公式均來自該論文。
基于顏色差值的雙邊濾波
先來看一下基于顏色差值的雙邊濾波,這是圖像處理方面最常用的濾波方式,也是傳說中的磨皮濾鏡的實(shí)現(xiàn)方式。我們的值域信息權(quán)重來源于圖像本身,也就是采樣圖像當(dāng)前像素點(diǎn),然后對(duì)于其周圍的像素點(diǎn),計(jì)算周圍像素點(diǎn)與當(dāng)前像素點(diǎn)顏色(轉(zhuǎn)為灰度)后的差值作為權(quán)重進(jìn)行雙邊濾波操作。
此處本人使用了后處理進(jìn)行雙邊濾波操作,由于高斯濾波和雙邊濾波操作本身屬于線性操作,可以拆分成橫向縱向兩個(gè)Pass進(jìn)行,大大計(jì)算的時(shí)間復(fù)雜度。對(duì)于高斯模糊的正態(tài)分布函數(shù),對(duì)于圖像處理可以按照正態(tài)分布公式動(dòng)態(tài)生成,不過在游戲這種性能吃緊的后處理中,直接使用預(yù)計(jì)算好的正態(tài)分布值即可。
Shader關(guān)鍵代碼如下:
half CompareColor(fixed4 col1, fixed4 col2) {float l1 = LinearRgbToLuminance(col1.rgb);float l2 = LinearRgbToLuminance(col2.rgb);return smoothstep(_BilaterFilterFactor, 1.0, 1.0 - abs(l1 - l2)); }fixed4 frag_bilateralcolor (v2f i) : SV_Target {float2 delta = _MainTex_TexelSize.xy * _BlurRadius.xy;fixed4 col = tex2D(_MainTex, i.uv);fixed4 col0a = tex2D(_MainTex, i.uv - delta);fixed4 col0b = tex2D(_MainTex, i.uv + delta);fixed4 col1a = tex2D(_MainTex, i.uv - 2.0 * delta);fixed4 col1b = tex2D(_MainTex, i.uv + 2.0 * delta);fixed4 col2a = tex2D(_MainTex, i.uv - 3.0 * delta);fixed4 col2b = tex2D(_MainTex, i.uv + 3.0 * delta);half w = 0.37004405286;half w0a = CompareColor(col, col0a) * 0.31718061674;half w0b = CompareColor(col, col0b) * 0.31718061674;half w1a = CompareColor(col, col1a) * 0.19823788546;half w1b = CompareColor(col, col1b) * 0.19823788546;half w2a = CompareColor(col, col2a) * 0.11453744493;half w2b = CompareColor(col, col2b) * 0.11453744493;half3 result;result = w * col.rgb;result += w0a * col0a.rgb;result += w0b * col0b.rgb;result += w1a * col1a.rgb;result += w1b * col1b.rgb;result += w2a * col2a.rgb;result += w2b * col2b.rgb;result /= w + w0a + w0b + w1a + w1b + w2a + w2b;return fixed4(result, 1.0); }C#關(guān)鍵代碼如下:
private void OnRenderImage(RenderTexture source, RenderTexture destination) {var tempRT = RenderTexture.GetTemporary(source.width, source.height, 0, source.format);var blurPass = (int)blurType;filterMaterial.SetFloat("_BilaterFilterFactor", 1.0f - bilaterFilterStrength);filterMaterial.SetVector("_BlurRadius", new Vector4(BlurRadius, 0, 0, 0));Graphics.Blit(source, tempRT, filterMaterial, blurPass);filterMaterial.SetVector("_BlurRadius", new Vector4(0, BlurRadius, 0, 0));Graphics.Blit(tempRT, destination, filterMaterial, blurPass);RenderTexture.ReleaseTemporary(tempRT); }我們把上圖的麻子臉妹紙放到場(chǎng)景中的一個(gè)片上,原始的照片效果如下,可見皮膚上還是有一些瑕疵的:
使用普通的高斯濾波效果如下,整個(gè)圖像都模糊了,如果濾鏡做成這樣,肯定要被打死的:
再看一下基于顏色差值的雙邊濾波效果,去除了臉上的瑕疵的同時(shí),還保持了細(xì)節(jié)效果,磨皮效果棒棒噠:
基于法線的雙邊濾波
下面才是我寫這篇blog的出發(fā)點(diǎn),畢竟我不是搞圖像處理的,2333。對(duì)于3D渲染的場(chǎng)景,我們除了可以得到當(dāng)前屏幕上顯示的圖像之外,還可以得到對(duì)應(yīng)的全屏幕的深度值,全屏幕的法線值。使用深度或者法線的差異作為雙邊濾波的值域信息,可以讓我們對(duì)3D場(chǎng)景結(jié)果濾波時(shí)保證邊界拐角的地方不被模糊,保持邊緣。
我們將上面的Shader稍加修改,這里我們使用了前向渲染開啟了CameraDepthNormalTexture,可以得到全場(chǎng)景法線圖,然后我們對(duì)于每個(gè)采樣點(diǎn)的權(quán)重使用當(dāng)前像素點(diǎn)法線和周圍采樣點(diǎn)的法線差異作為權(quán)重,直接使用向量點(diǎn)乘表示兩個(gè)向量的共線程度即可。
float3 GetNormal(float2 uv) {float4 cdn = tex2D(_CameraDepthNormalsTexture, uv);return DecodeViewNormalStereo(cdn); }half CompareNormal(float3 normal1, float3 normal2) {return smoothstep(_BilaterFilterFactor, 1.0, dot(normal1, normal2)); }fixed4 frag_bilateralnormal (v2f i) : SV_Target {float2 delta = _MainTex_TexelSize.xy * _BlurRadius.xy;float2 uv = i.uv;float2 uv0a = i.uv - delta;float2 uv0b = i.uv + delta; float2 uv1a = i.uv - 2.0 * delta;float2 uv1b = i.uv + 2.0 * delta;float2 uv2a = i.uv - 3.0 * delta;float2 uv2b = i.uv + 3.0 * delta;float3 normal = GetNormal(uv);float3 normal0a = GetNormal(uv0a);float3 normal0b = GetNormal(uv0b);float3 normal1a = GetNormal(uv1a);float3 normal1b = GetNormal(uv1b);float3 normal2a = GetNormal(uv2a);float3 normal2b = GetNormal(uv2b);fixed4 col = tex2D(_MainTex, uv);fixed4 col0a = tex2D(_MainTex, uv0a);fixed4 col0b = tex2D(_MainTex, uv0b);fixed4 col1a = tex2D(_MainTex, uv1a);fixed4 col1b = tex2D(_MainTex, uv1b);fixed4 col2a = tex2D(_MainTex, uv2a);fixed4 col2b = tex2D(_MainTex, uv2b);half w = 0.37004405286;half w0a = CompareNormal(normal, normal0a) * 0.31718061674;half w0b = CompareNormal(normal, normal0b) * 0.31718061674;half w1a = CompareNormal(normal, normal1a) * 0.19823788546;half w1b = CompareNormal(normal, normal1b) * 0.19823788546;half w2a = CompareNormal(normal, normal2a) * 0.11453744493;half w2b = CompareNormal(normal, normal2b) * 0.11453744493;half3 result;result = w * col.rgb;result += w0a * col0a.rgb;result += w0b * col0b.rgb;result += w1a * col1a.rgb;result += w1b * col1b.rgb;result += w2a * col2a.rgb;result += w2b * col2b.rgb;result /= w + w0a + w0b + w1a + w1b + w2a + w2b;return fixed4(result, 1.0); }我們使用一個(gè)3D場(chǎng)景,原始效果如下:
高斯濾波效果如下,全糊啦!!!
基于法線的雙邊濾波效果如下,還能夠保持場(chǎng)景的邊界效果,僅僅在同一平面內(nèi)進(jìn)行模糊:
高斯濾波與兩種雙邊濾波源碼
把高斯濾波,基于顏色的雙邊濾波和基于法線的雙邊濾波分別作為一個(gè)Pass,使用一個(gè)后處理效果整合。Shader代碼如下:
//puppet_master //https://blog.csdn.net/puppet_master //2018.10.15 //雙邊濾波效果Shader Shader "AO/BilateralFilterEffect" {Properties{_MainTex ("Texture", 2D) = "white" {}}CGINCLUDE#include "UnityCG.cginc"struct appdata{float4 vertex : POSITION;float2 uv : TEXCOORD0;};struct v2f{float2 uv : TEXCOORD0;float4 vertex : SV_POSITION;};sampler2D _MainTex;float4 _MainTex_ST;float4 _MainTex_TexelSize;float4 _BlurRadius;float _BilaterFilterFactor;sampler2D _CameraDepthNormalsTexture;v2f vert (appdata v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = v.uv;return o;}fixed4 frag_gaussian (v2f i) : SV_Target{float2 delta = _MainTex_TexelSize.xy * _BlurRadius.xy;fixed4 col = 0.37004405286 * tex2D(_MainTex, i.uv);col += 0.31718061674 * tex2D(_MainTex, i.uv - delta);col += 0.31718061674 * tex2D(_MainTex, i.uv + delta);col += 0.19823788546 * tex2D(_MainTex, i.uv - 2.0 * delta);col += 0.19823788546 * tex2D(_MainTex, i.uv + 2.0 * delta);col += 0.11453744493 * tex2D(_MainTex, i.uv - 3.0 * delta);col += 0.11453744493 * tex2D(_MainTex, i.uv + 3.0 * delta);col /= 0.37004405286 + 0.31718061674 + 0.31718061674 + 0.19823788546 + 0.19823788546 + 0.11453744493 + 0.11453744493;return fixed4(col.rgb, 1.0);}half CompareColor(fixed4 col1, fixed4 col2){float l1 = LinearRgbToLuminance(col1.rgb);float l2 = LinearRgbToLuminance(col2.rgb);return smoothstep(_BilaterFilterFactor, 1.0, 1.0 - abs(l1 - l2));}fixed4 frag_bilateralcolor (v2f i) : SV_Target{float2 delta = _MainTex_TexelSize.xy * _BlurRadius.xy;fixed4 col = tex2D(_MainTex, i.uv);fixed4 col0a = tex2D(_MainTex, i.uv - delta);fixed4 col0b = tex2D(_MainTex, i.uv + delta);fixed4 col1a = tex2D(_MainTex, i.uv - 2.0 * delta);fixed4 col1b = tex2D(_MainTex, i.uv + 2.0 * delta);fixed4 col2a = tex2D(_MainTex, i.uv - 3.0 * delta);fixed4 col2b = tex2D(_MainTex, i.uv + 3.0 * delta);half w = 0.37004405286;half w0a = CompareColor(col, col0a) * 0.31718061674;half w0b = CompareColor(col, col0b) * 0.31718061674;half w1a = CompareColor(col, col1a) * 0.19823788546;half w1b = CompareColor(col, col1b) * 0.19823788546;half w2a = CompareColor(col, col2a) * 0.11453744493;half w2b = CompareColor(col, col2b) * 0.11453744493;half3 result;result = w * col.rgb;result += w0a * col0a.rgb;result += w0b * col0b.rgb;result += w1a * col1a.rgb;result += w1b * col1b.rgb;result += w2a * col2a.rgb;result += w2b * col2b.rgb;result /= w + w0a + w0b + w1a + w1b + w2a + w2b;return fixed4(result, 1.0);}float3 GetNormal(float2 uv){float4 cdn = tex2D(_CameraDepthNormalsTexture, uv);return DecodeViewNormalStereo(cdn);}half CompareNormal(float3 normal1, float3 normal2){return smoothstep(_BilaterFilterFactor, 1.0, dot(normal1, normal2));}fixed4 frag_bilateralnormal (v2f i) : SV_Target{float2 delta = _MainTex_TexelSize.xy * _BlurRadius.xy;float2 uv = i.uv;float2 uv0a = i.uv - delta;float2 uv0b = i.uv + delta; float2 uv1a = i.uv - 2.0 * delta;float2 uv1b = i.uv + 2.0 * delta;float2 uv2a = i.uv - 3.0 * delta;float2 uv2b = i.uv + 3.0 * delta;float3 normal = GetNormal(uv);float3 normal0a = GetNormal(uv0a);float3 normal0b = GetNormal(uv0b);float3 normal1a = GetNormal(uv1a);float3 normal1b = GetNormal(uv1b);float3 normal2a = GetNormal(uv2a);float3 normal2b = GetNormal(uv2b);fixed4 col = tex2D(_MainTex, uv);fixed4 col0a = tex2D(_MainTex, uv0a);fixed4 col0b = tex2D(_MainTex, uv0b);fixed4 col1a = tex2D(_MainTex, uv1a);fixed4 col1b = tex2D(_MainTex, uv1b);fixed4 col2a = tex2D(_MainTex, uv2a);fixed4 col2b = tex2D(_MainTex, uv2b);half w = 0.37004405286;half w0a = CompareNormal(normal, normal0a) * 0.31718061674;half w0b = CompareNormal(normal, normal0b) * 0.31718061674;half w1a = CompareNormal(normal, normal1a) * 0.19823788546;half w1b = CompareNormal(normal, normal1b) * 0.19823788546;half w2a = CompareNormal(normal, normal2a) * 0.11453744493;half w2b = CompareNormal(normal, normal2b) * 0.11453744493;half3 result;result = w * col.rgb;result += w0a * col0a.rgb;result += w0b * col0b.rgb;result += w1a * col1a.rgb;result += w1b * col1b.rgb;result += w2a * col2a.rgb;result += w2b * col2b.rgb;result /= w + w0a + w0b + w1a + w1b + w2a + w2b;return fixed4(result, 1.0);}ENDCGSubShader{Tags { "RenderType"="Opaque" }LOD 100//Pass 0 Gaussian BlurPass{CGPROGRAM#pragma vertex vert#pragma fragment frag_gaussianENDCG}Pass 1 BilateralFiter Blur ColorPass{CGPROGRAM#pragma vertex vert#pragma fragment frag_bilateralcolorENDCG}Pass 2 BilateralFiter Blur NormalPass{CGPROGRAM#pragma vertex vert#pragma fragment frag_bilateralnormalENDCG}} }C#代碼如下:
/********************************************************************FileName: BilateralFilterEffect.csDescription: 高斯濾波,雙邊濾波(基于顏色差值,基于法線)history: 15:10:2018 by puppet_masterhttps://blog.csdn.net/puppet_master *********************************************************************/ using UnityEngine;[ExecuteInEditMode] public class BilateralFilterEffect : MonoBehaviour {public enum BlurType{GaussianBlur = 0,BilateralColorFilter = 1,BilateralNormalFilter = 2,}private Material filterMaterial = null;private Camera currentCamera = null;[Range(1,4)]public int BlurRadius = 1;public BlurType blurType = BlurType.GaussianBlur;[Range(0, 0.2f)]public float bilaterFilterStrength = 0.15f;private void Awake(){var shader = Shader.Find("AO/BilateralFilterEffect");filterMaterial = new Material(shader);currentCamera = GetComponent<Camera>();}private void OnEnable(){currentCamera.depthTextureMode |= DepthTextureMode.DepthNormals;}private void OnDisable(){currentCamera.depthTextureMode &= ~DepthTextureMode.DepthNormals;}private void OnRenderImage(RenderTexture source, RenderTexture destination){var tempRT = RenderTexture.GetTemporary(source.width, source.height, 0, source.format);var blurPass = (int)blurType;filterMaterial.SetFloat("_BilaterFilterFactor", 1.0f - bilaterFilterStrength);filterMaterial.SetVector("_BlurRadius", new Vector4(BlurRadius, 0, 0, 0));Graphics.Blit(source, tempRT, filterMaterial, blurPass);filterMaterial.SetVector("_BlurRadius", new Vector4(0, BlurRadius, 0, 0));Graphics.Blit(tempRT, destination, filterMaterial, blurPass);RenderTexture.ReleaseTemporary(tempRT);} }雙邊濾波在渲染中的作用
上面我們看到了雙邊濾波在圖像處理方面的作用超級(jí)大,而在渲染中,雙邊濾波也是很有用的一種降噪手段,比高斯濾波要好很多。在很多高級(jí)效果,尤其是RayMarching效果中經(jīng)常需要使用隨機(jī)噪聲來降低計(jì)算消耗,但是隨之而來的就是會(huì)造成結(jié)果中包含很多高頻噪聲,最終的結(jié)果就需要使用濾波進(jìn)行降噪。之前本人在RayMarching體積光效果和屏幕空間反射效果中都使用了高斯模糊進(jìn)行降噪,體積光本身就是模糊的,使用高斯模糊或者雙邊濾波本身差異不是很大。屏幕空間反射就可以考慮使用雙邊濾波進(jìn)行降噪以達(dá)到更清晰的反射效果。不過有時(shí)候反射本身就需要糊一點(diǎn)才好看哈。
另一個(gè)非常重要的需要使用雙邊濾波的效果就是SSAO,即屏幕空間環(huán)境光遮蔽效果,使用蒙特卡洛積分得到的效果,隨機(jī)采樣數(shù)量有限,效果很差,沒有去噪的效果SSAO效果如下(僅顯示AO遮蔽效果):
使用基于法線的雙邊濾波去噪之后的SSAO效果,差別還是灰常大滴:
總結(jié)
本blog主要實(shí)現(xiàn)了一下雙邊濾波效果,實(shí)現(xiàn)了高斯濾波,基于顏色的雙邊濾波,基于法線的雙邊濾波效果。使用雙邊濾波可以在保證圖像邊緣的情況下達(dá)到去噪的目的,可以很容易地實(shí)現(xiàn)圖像處理的磨皮濾鏡,實(shí)現(xiàn)Dither RayMarching,SSAO等使用隨機(jī)采樣的渲染效果的去噪。
本打算寫一個(gè)SSAO的blog,然而寫到一半發(fā)現(xiàn)雙邊濾波效果還是挺好玩的,正好又通關(guān)了一個(gè)小游戲《Runiner》,索性就單獨(dú)拿出來寫一篇blog啦!
總結(jié)
以上是生活随笔為你收集整理的UnityShader-BilateralFilter(双边滤波,磨皮滤镜)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《Photoshop+Lightroom
- 下一篇: 徐进的信念:IE工业工程与精益生产管理的