zoukankan      html  css  js  c++  java
  • Unity Shader-后处理:简单均值模糊

    一.简介

     
    今天来学习一下后处理中比较常用的一种效果,屏幕模糊效果。模糊效果,在图像处理中经常用到,Photoshop中也有类似的滤镜。我们在游戏中也会经常用到。因为屏幕模糊效果是一些高级后处理效果的基础,比如景深等效果都需要用屏幕模糊效果来实现,所以我们首先看一下屏幕模糊效果,然后通过屏幕模糊,进一步学习景深效果与运动模糊效果的实现。

    所谓模糊,也就是不清楚,清晰的图片,各个像素之间会有明显的过渡,而如果各个像素之间的差距不是很大,那么图像就会模糊了,极端一点的情况,当一张图片所有的像素之间颜色都差不多时,那么这张图片也就是一个纯色的图片了。模糊操作就是让像素间的颜色差距变小,比如A点是红色,A点周围的点是绿色,模糊就像用一把刷子,将A点和周围的点的颜色混合起来,变成最终的颜色。而怎样混合,按照不同的权值进行混合,就可以达到不同的效果了。比如均值模糊,以及著名的高斯模糊。

    影响模糊程度的重要因素是模糊半径,模糊半径越大,模糊程度越大,模糊半径越小,模糊程度越小。那么,模糊半径是什么?所谓模糊半径,也就是我们采样的一个范围,比如我们模糊的半径很小,只是把像素和它周围的一圈定点混合,那么模糊的程度就很小,而如果我们加大模糊半径,极端情况是每个顶点都取了周围所有点,也就是整张图的像素平均值,那么这张图的颜色就会偏向一种颜色。
     

    二.均值模糊

     
    最简单的,我们看一下简单的模糊,直接用周围像素求和平均,我们混合的最终图像,在某一点的权重仅仅跟模糊半径有关。换句话说,比如模糊半径为1,那么,我们取一个像素点,以及他周围的一圈像素点,一共九个点,直接取平均,那么每个点的权重设置为1/9。这也就是所谓的均值模糊。我们看一下均值模糊的例子。
    shader部分:
    [cpp] view plain copy
     
    1. Shader "Custom/SimpleBlurEffect"   
    2. {  
    3.   
    4.     Properties  
    5.     {  
    6.         _MainTex("Base (RGB)", 2D) = "white" {}  
    7.     }  
    8.   
    9.     //通过CGINCLUDE我们可以预定义一些下面在Pass中用到的struct以及函数,  
    10.     //这样在pass中只需要设置渲染状态以及调用函数,shader更加简洁明了  
    11.     CGINCLUDE  
    12.     //cg文件,包含了unity内置的一些cg函数  
    13.     #include "UnityCG.cginc"  
    14.       
    15.     //blur结构体,从blur的vert函数传递到frag函数的参数  
    16.     struct v2f_blur  
    17.     {  
    18.         float4 pos : SV_POSITION; //顶点位置  
    19.         float2 uv  : TEXCOORD0;   //纹理坐标  
    20.         float2 uv1 : TEXCOORD1;  //周围纹理1  
    21.         float2 uv2 : TEXCOORD2;  //周围纹理2  
    22.         float2 uv3 : TEXCOORD3;  //周围纹理3  
    23.         float2 uv4 : TEXCOORD4;  //周围纹理4  
    24.     };  
    25.   
    26.     //用到的变量  
    27.     sampler2D _MainTex;  
    28.     //XX_TexelSize,XX纹理的像素相关大小width,height对应纹理的分辨率,x = 1/width, y = 1/height, z = width, w = height  
    29.     float4 _MainTex_TexelSize;  
    30.     //模糊半径  
    31.     float _BlurRadius;  
    32.   
    33.     //vertex shader  
    34.     v2f_blur vert_blur(appdata_img v)  
    35.     {  
    36.         v2f_blur o;  
    37.         o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
    38.         o.uv = v.texcoord.xy;  
    39.         //计算uv上下左右四个点对于blur半径下的uv坐标  
    40.         o.uv1 = v.texcoord.xy + _BlurRadius * _MainTex_TexelSize * float2( 1,  1);  
    41.         o.uv2 = v.texcoord.xy + _BlurRadius * _MainTex_TexelSize * float2(-1,  1);  
    42.         o.uv3 = v.texcoord.xy + _BlurRadius * _MainTex_TexelSize * float2(-1, -1);  
    43.         o.uv4 = v.texcoord.xy + _BlurRadius * _MainTex_TexelSize * float2( 1, -1);  
    44.   
    45.         return o;  
    46.     }  
    47.   
    48.     //fragment shader  
    49.     fixed4 frag_blur(v2f_blur i) : SV_Target  
    50.     {  
    51.         fixed4 color = fixed4(0,0,0,0);  
    52.   
    53.         color += tex2D(_MainTex, i.uv );  
    54.         color += tex2D(_MainTex, i.uv1);  
    55.         color += tex2D(_MainTex, i.uv2);  
    56.         color += tex2D(_MainTex, i.uv3);  
    57.         color += tex2D(_MainTex, i.uv4);  
    58.           
    59.         //相加取平均,据说shader中乘法比较快  
    60.         return color * 0.2;  
    61.     }  
    62.   
    63.     ENDCG  
    64.   
    65.     //子着色器  
    66.     SubShader  
    67.     {  
    68.         //pass 0: blur effect  
    69.         Pass  
    70.         {  
    71.             ZTest Always  
    72.             Cull Off  
    73.             ZWrite Off  
    74.             Fog{ Mode Off }  
    75.   
    76.             //直接调用vert_blur和frag_blur  
    77.             CGPROGRAM  
    78.             #pragma vertex vert_blur  
    79.             #pragma fragment frag_blur  
    80.             ENDCG  
    81.         }  
    82.     }  
    83. }  
    C#脚本部分:
    [csharp] view plain copy
     
    1. using UnityEngine;  
    2. using System.Collections;  
    3.   
    4. //编辑状态下也运行  
    5. [ExecuteInEditMode]  
    6. //继承自PostEffectBase  
    7. public class SimpleBlurEffect : PostEffectBase  
    8. {  
    9.     //模糊半径  
    10.     public float BlurRadius = 1.0f;  
    11.   
    12.     void OnRenderImage(RenderTexture source, RenderTexture destination)  
    13.     {  
    14.         if (_Material)  
    15.         {  
    16.   
    17.             //blur   
    18.             _Material.SetFloat("_BlurRadius", BlurRadius);  
    19.             Graphics.Blit(source, destination, _Material);  
    20.   
    21.         }  
    22.     }  
    23. }  
    注意,此处的PostEffectBase为各种后处理效果的基类,在上一篇文章: Unity Shader-后处理:简单的颜色调整(亮度,饱和度,对比度)中有该类的完整实现,此处不予贴出代码。
     
    效果如下图所示:
     
    原图效果
     
    blurRadius = 1
     
    blurRadius = 5
     
    从上面的模糊效果我们看到,模糊半径越大,模糊的效果越明显。但是!这种效果看起来一点都不舒服,有种近视的赶脚,完全不是平滑的模糊效果,就更不要说进一步的毛玻璃之类的全模糊效果了。


    三.均值模糊的改进

     
    既然,一次模糊我们感觉效果不是很尽人意,那么,我们可以尝试迭代模糊,也就是用上一次模糊的输出作为下一次模糊的输入,迭代之后的模糊效果更加明显。先看一下代码,这次,我们的shader代码和上面的一样,没有变动,仅仅是修改了脚本,增加了降分辨率和迭代的两个操作。
    [csharp] view plain copy
     
    1. using UnityEngine;  
    2. using System.Collections;  
    3.   
    4. //编辑状态下也运行  
    5. [ExecuteInEditMode]  
    6. //继承自PostEffectBase  
    7. public class SimpleBlurEffect : PostEffectBase  
    8. {  
    9.     //模糊半径  
    10.     public float BlurRadius = 1.0f;  
    11.     //降分辨率  
    12.     public int downSample = 2;  
    13.     //迭代次数  
    14.     public int iteration = 3;  
    15.   
    16.     void OnRenderImage(RenderTexture source, RenderTexture destination)  
    17.     {  
    18.         if (_Material)  
    19.         {  
    20.             //申请RenderTexture,RT的分辨率按照downSample降低  
    21.             RenderTexture rt1 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0, source.format);  
    22.             RenderTexture rt2 = RenderTexture.GetTemporary(source.width >> downSample, source.height >> downSample, 0, source.format);  
    23.             
    24.             //直接将原图拷贝到降分辨率的RT上  
    25.             Graphics.Blit(source, rt1);  
    26.   
    27.             //进行迭代,一次迭代进行了两次模糊操作,使用两张RT交叉处理  
    28.             for(int i = 0; i < iteration; i++)  
    29.             {  
    30.                 //用降过分辨率的RT进行模糊处理  
    31.                 _Material.SetFloat("_BlurRadius", BlurRadius);  
    32.                 Graphics.Blit(rt1, rt2, _Material);  
    33.                 Graphics.Blit(rt2, rt1, _Material);  
    34.             }  
    35.   
    36.             //将结果拷贝到目标RT  
    37.             Graphics.Blit(rt1, destination);  
    38.   
    39.             //释放申请的两块RenderBuffer内容  
    40.             RenderTexture.ReleaseTemporary(rt1);  
    41.             RenderTexture.ReleaseTemporary(rt2);  
    42.         }  
    43.     }  
    44. }  
    结果如下:
     
    blurRadius = 1, downSample = 2,  iteration = 3 
     
    blurRadius = 1, downSample = 2, iteration = 5
     
    我们看到,通过迭代以及降低分辨率,我们的模糊效果更加明显了,当迭代次数较大时,会有一种毛玻璃的效果。这里,虽然迭代次数增加了,会耗费更多的性能,但是相应地,我们也降低了分辨率,也减少了采样等计算操作的消耗。
     

    四.RenderTexture介绍

     
    这里,我们通过多次处理,包括降分辨率以及迭代,完成了模糊操作,这里我们需要临时存储上一次处理过的中间输出,所以就需要用渲染中常用的一个概念RenderTexture。
     
    关于RenderTexture,简要介绍一下,我们在渲染场景时,一般都是直接输出到帧缓存,然后输出到屏幕上,然而有的时候,我们并不想直接输出结果,而是需要对这个渲染的结果进行处理,所以,我们就将渲染的结果输出到了一张纹理上,也就是RenderTexture,这也是所有后处理的基础。Unity的RenderTexture还是很好用的,我们不仅仅可以在后处理时使用,还可以通过把摄像机的输出设置到某个RT上,然后用这张RT作为一些类似镜子的物体上,就可以实现镜面效果或者屏幕效果。
     
    不过RenderTexture还是很耗费资源的,一张大的RenderTexture是屏幕分辨率大小的一张图片,而且是完全不能够压缩的,所以当后处理中RenderTexture用得多时,内存消耗很大,在手机,尤其是大屏手机,内存比较小的情况下,后处理叠加时很可能会由于内存耗尽而崩溃。所以,我们在使用RenderTexture时需要慎重考虑。如果效果可以接受,我们就可以考虑降低RenderTexture的分辨率,这样,输出的画面效果可能会打一些折扣,但是性能会有很大的提高。而我们的模糊效果,本身降低分辨率就会导致画面比较模糊,所以在这里,我们完全可以放心大胆地降低RT的分辨率,既可以提升效果,又可以大大地减少开销。
     
    这里还有一点,由于OnRenderImage函数每帧在渲染之前都会调用,之前曾经担心会不会每一帧在这里申请RT,然后释放,会不会有很高的GC?经过查找了一些资料,发现Unity这里是进行过处理的,RenderTexture是之前申请好的一块内存区域,我们可以直接使用,而不需要考虑GC的问题,正如这两个函数的名字一样,RenderTexture.GetTemporary和RenderTexture.ReleaseTemporary一样。并且,本人亲测,使用Profile挂了一下这个脚本,发现的确没有GC:
     


     

    五.总结

     
    本篇文章介绍了模糊的原理以及RenderTexture的概念。给出了一版简单的均值模糊效果,并通过降低分辨率增加迭代次数优化了模糊的表现效果。但是这种模糊效果还是不尽人意,不进行迭代时效果很差,迭代次数大时虽然可以得到一些好的效果,但是相应地,性能耗费也是极大的。所以是时候学习一下更先进的模糊效果了,下一篇就是传说中的《高斯模糊》~哇咔咔
  • 相关阅读:
    搭建负载均衡的环境(利用虚拟机上的四台centos)
    java的IO,AIO简单对比
    【每日分享】关于漏测
    安装xampp后,遇到的各种问题
    端口占用问题——netstat命令
    随笔
    AJAX 状态值(readyState)与状态码(status)详解
    CSS 实践:实现下拉菜单的方法
    css3动画总结
    判断手机运营商
  • 原文地址:https://www.cnblogs.com/lancidie/p/8665918.html
Copyright © 2011-2022 走看看