前言

最近趁着Steam打折入了好多个游戏,昨天刚刚通关了一个《Ruiner》的游戏。

游戏类似《孤胆枪手》,但是加入了很多技能元素和动作元素,加上游戏本身的卡通渲染+赛博朋克风格,总体感觉还是不错的。

国庆玩了几个大作连刷了几天,有点伤。最近反倒倾向于玩一些小游戏,简单粗暴。不用它三七二十一,莽夫上去就是干!

我发现blog也是这样,最近半年写的blog似乎都有点长,有时候也来点短小精悍的换换口味。今天就来玩一个简单但是又比较好玩的效果-双边滤波。

简介

双边滤波(Bilateral Filter),可能没有高斯滤波那样著名,但是如果说磨皮滤镜,那肯定是无人不知无人不晓了,用双边滤波就可以实现很好的皮肤滤镜效果,不管脸上有多少麻子,用完双边滤波,瞬间变身白富美。下图来自一款磨皮滤镜插件的效果图,左侧为原始效果,右侧为滤镜后的效果。本文中我们也会实现一个双边滤波后处理,可以达到近似的效果。

所谓滤波,是将信号中特定波段频率滤除的操作。正常高斯模糊(高斯滤波)在进行采样的时候,主要是考虑了像素之间的距离关系(空域信息domain),也就是按照正态分布将当前像素点周围像素加权平均得到滤波后的结果,可以得到很好的模糊效果。但是高斯模糊是对整个图像无差异地进行模糊,也就是整张图片全部模糊掉。关于高斯模糊,之前在Unity Shader后处理:高斯模糊这篇blog中详细介绍过,这里不再赘述。

高斯模糊的定义如下:

而双边滤波是高斯滤波进阶版本,可以在模糊的同时保持图像中的边缘信息。除了考虑正常高斯滤波的空域信息(domain)外,还要考虑另外的一个图像本身携带的值域信息(range)。这个值域信息的选择并非唯一的,可以是采样点间像素颜色的差异,可以是采样点像素对应的法线信息,可以是采样点像素对应的深度信息(3D渲染中拿到法线和深度还是要比单纯的2D图像处理可以做的事情多不少哈)。

双边滤波定义如下:

可见,除了正常的图像距离权重c之外,额外添加了图像相似信息权重s,而s是基于图像本身信息获得的,使用c和s相乘的结果作为最终的权重。即在采样图像及周围点时,对于每一个像素点,需要乘以距离权重乘以图片相似性权重相加得到总和,然后除以每一个像素点距离权重乘以相似性权重的和,即:

关于双边滤波对图像进行处理,可以参考《Bilateral Filtering for Gray and Color Images》这篇论文(似乎要出墙),上文高斯滤波定义,双边滤波定义公式均来自该论文。

基于颜色差值的双边滤波

先来看一下基于颜色差值的双边滤波,这是图像处理方面最常用的滤波方式,也是传说中的磨皮滤镜的实现方式。我们的值域信息权重来源于图像本身,也就是采样图像当前像素点,然后对于其周围的像素点,计算周围像素点与当前像素点颜色(转为灰度)后的差值作为权重进行双边滤波操作。

此处本人使用了后处理进行双边滤波操作,由于高斯滤波和双边滤波操作本身属于线性操作,可以拆分成横向纵向两个Pass进行,大大计算的时间复杂度。对于高斯模糊的正态分布函数,对于图像处理可以按照正态分布公式动态生成,不过在游戏这种性能吃紧的后处理中,直接使用预计算好的正态分布值即可。

Shader关键代码如下:

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#关键代码如下:

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);
}

我们把上图的麻子脸妹纸放到场景中的一个片上,原始的照片效果如下,可见皮肤上还是有一些瑕疵的:

使用普通的高斯滤波效果如下,整个图像都模糊了,如果滤镜做成这样,肯定要被打死的:

再看一下基于颜色差值的双边滤波效果,去除了脸上的瑕疵的同时,还保持了细节效果,磨皮效果棒棒哒:

基于法线的双边滤波

下面才是我写这篇blog的出发点,毕竟我不是搞图像处理的,2333。对于3D渲染的场景,我们除了可以得到当前屏幕上显示的图像之外,还可以得到对应的全屏幕的深度值,全屏幕的法线值。使用深度或者法线的差异作为双边滤波的值域信息,可以让我们对3D场景结果滤波时保证边界拐角的地方不被模糊,保持边缘。

我们将上面的Shader稍加修改,这里我们使用了前向渲染开启了CameraDepthNormalTexture,可以得到全场景法线图,然后我们对于每个采样点的权重使用当前像素点法线和周围采样点的法线差异作为权重,直接使用向量点乘表示两个向量的共线程度即可。

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);
}

我们使用一个3D场景,原始效果如下:

高斯滤波效果如下,全糊啦!!!

基于法线的双边滤波效果如下,还能够保持场景的边界效果,仅仅在同一平面内进行模糊:

高斯滤波与两种双边滤波源码

把高斯滤波,基于颜色的双边滤波和基于法线的双边滤波分别作为一个Pass,使用一个后处理效果整合。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);}
}

双边滤波在渲染中的作用

上面我们看到了双边滤波在图像处理方面的作用超级大,而在渲染中,双边滤波也是很有用的一种降噪手段,比高斯滤波要好很多。在很多高级效果,尤其是RayMarching效果中经常需要使用随机噪声来降低计算消耗,但是随之而来的就是会造成结果中包含很多高频噪声,最终的结果就需要使用滤波进行降噪。之前本人在RayMarching体积光效果和屏幕空间反射效果中都使用了高斯模糊进行降噪,体积光本身就是模糊的,使用高斯模糊或者双边滤波本身差异不是很大。屏幕空间反射就可以考虑使用双边滤波进行降噪以达到更清晰的反射效果。不过有时候反射本身就需要糊一点才好看哈。

另一个非常重要的需要使用双边滤波的效果就是SSAO,即屏幕空间环境光遮蔽效果,使用蒙特卡洛积分得到的效果,随机采样数量有限,效果很差,没有去噪的效果SSAO效果如下(仅显示AO遮蔽效果):

使用基于法线的双边滤波去噪之后的SSAO效果,差别还是灰常大滴:

总结

本blog主要实现了一下双边滤波效果,实现了高斯滤波,基于颜色的双边滤波,基于法线的双边滤波效果。使用双边滤波可以在保证图像边缘的情况下达到去噪的目的,可以很容易地实现图像处理的磨皮滤镜,实现Dither RayMarching,SSAO等使用随机采样的渲染效果的去噪。

本打算写一个SSAO的blog,然而写到一半发现双边滤波效果还是挺好玩的,正好又通关了一个小游戏《Runiner》,索性就单独拿出来写一篇blog啦!

UnityShader-BilateralFilter(双边滤波,磨皮滤镜)相关推荐

  1. OpenCV入门系列 —— bilateralFilter双边滤波

    OpenCV入门系列 -- bilateralFilter双边滤波 前言 程序说明 输出结果 代码示例 总结 前言 随着工业自动化.智能化的不断推进,机器视觉(2D/3D)在工业领域的应用和重要程度也 ...

  2. 图像处理(六)递归双边滤波磨皮

    递归双边滤波 原文地址:http://blog.csdn.net/hjimce/article/details/45421207 作者:hjimce 递归双边滤波是双边滤波的一种加速算法,加速比非常大 ...

  3. OpenCV(十一)图像滤波(平滑处理)(平均、中值、高斯、双边滤波)

    目录 一.基础理论 1.图像噪声 1-1.椒盐噪声 1-2.高斯噪声 2.滤波 3.线性滤波 1.概述 2.线性滤波原理: 二.均值滤波(cv::blur())(简单滤波) 1.原理 2.API 三. ...

  4. 作业总结:磨皮滤镜(双边滤波bilateralFilter)代码实现

    双边滤波是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空间与信息和灰度相似性,达到保边去噪的目的,具有简单.非迭代.局部处理的特点.之所以能够达到保边去噪的滤波效 ...

  5. 双边滤波算法的简易实现bilateralFilter

    没怎么看过双边滤波的具体思路,动手写一写,看看能不能突破一下. 最后,感觉算法还是要分开 水平 与 垂直 方向进行分别处理,才能把速度提上去. 没耐性写下去了,发上来,给大伙做个参考好了. 先上几张效 ...

  6. 双边滤波的人脸磨皮算法(宋茜)

    本文将介绍双边滤波的原理以及在人脸磨皮中的应用. 文章目录 双边滤波原理 人脸磨皮算法 双边滤波原理 双边滤波器是保边滤波器中广为人知的一种.保边滤波器指在滤波过程中能够有效的保留图片中的边缘信息的一 ...

  7. OpenCV medianBlur、GaussianBlur和bilateralFilter (中值滤波、高斯滤波、双边滤波)

    ::返回OpenCV算子速查表 中值模糊.高斯模糊和双边滤波 1. 函数定义 1.1 中值模糊 1.2 高斯模糊 1.3 双边滤波 2. 例程 1. 函数定义 1.1 中值模糊 OpenCV官方文档 ...

  8. Opencv C++成长之路(八):高斯双边滤波 (图像处理磨皮)

    滤波结果 原图像 高斯双边滤波结果 与高斯滤波的区别 两者的区别在于,高斯滤波在滤波时会将图像中各个颜色区域的边缘同区域本身一起模糊掉,而高斯双边滤波则是对各个区域的交界边缘有所保留. 对于高斯滤波来 ...

  9. 双边滤波(bilateralFilter)原理及C++实现

    写在前面 双边滤波是一种非线性滤波,能够达到去噪保边的效果.相比高斯滤波,双边滤波多了一种掩膜,也就是还考虑了灰度相似性,所以双边滤波是结合图像的空间邻近度和像素值相似度的一种折衷处理. 先看看对比效 ...

最新文章

  1. 支付宝支付 第五集:二维码生成工具
  2. 转入肥胖基因改造RNA,作物增产50%
  3. [bzoj3926][Zjoi2015]诸神眷顾的幻想乡
  4. 字符串的原样输入输出python_Python字符串输入输出简述
  5. elasticsearch api中的Bulk API操作
  6. Flex中的Base64加解密
  7. [Silverlight]16进制颜色转ARGB及Color转Int32
  8. 常见电脑字符编码总结
  9. 连载 | 知识图谱发展报告 2018 -- 前言
  10. 用 GDI 操作 EMF 文件[5]: GetEnhMetaFileDescription - 获取 EMF 文件的说明文本
  11. 解决IIS出现“由于权限不足而无法读取配置文件”的问题
  12. 人造地球卫星由哪些系统组成?
  13. 说说 JavaEye 网站架构
  14. 分享一些学习和获取资料的网站
  15. 器件选型-OLED液晶显示原理和选型
  16. 判断两条直线的位置关系
  17. 4399小游戏 十滴水 求解器(输出路径的bfs)
  18. 如何将已下载音乐导入到iPhone的网易云音乐中
  19. 黑苹果macOS机型对照表
  20. 2、视觉基础知识问答

热门文章

  1. 计算机毕业设计 基于JavaWeb的图书查询管理系统(源码+论文)
  2. 82.纯CSS液体加载特效
  3. docker跑codalab_在CodaLab上提交MURA竞赛的结果
  4. 港科夜闻|广东省委常委、副省长王曦到访香港科技大学
  5. VSCODE 空格键自动补全
  6. 基于SpringBoot+SSM实现的Dota2资料库智能管理平台
  7. dedecms内链 arc.archives.class.php,DedeCms5.5全站自动给关键字加内链的修改方法
  8. c语言转义字符x1f,C语言常用转义字符、ASCII、优先级对照表(1).doc
  9. elasticsearch映射及字段类型
  10. python智能写作_Python学习与技术博客写作的利器