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的概念。给出了一版简单的均值模糊效果,并通过降低分辨率增加迭代次数优化了模糊的表现效果。但是这种模糊效果还是不尽人意,不进行迭代时效果很差,迭代次数大时虽然可以得到一些好的效果,但是相应地,性能耗费也是极大的。所以是时候学习一下更先进的模糊效果了,下一篇就是传说中的《高斯模糊》~哇咔咔
  • 相关阅读:
    Flutter form 的表单 input
    FloatingActionButton 实现类似 闲鱼 App 底部导航凸起按钮
    Flutter 中的常见的按钮组件 以及自 定义按钮组件
    Drawer 侧边栏、以及侧边栏内 容布局
    AppBar 自定义顶部导航按钮 图标、颜色 以及 TabBar 定义顶部 Tab 切换 通过TabController 定义TabBar
    清空路由 路由替换 返回到根路由
    应对ubuntu linux图形界面卡住的方法
    [转] 一块赚零花钱
    [转]在树莓派上搭建LAMP服务
    ssh保持连接
  • 原文地址:https://www.cnblogs.com/lancidie/p/8665918.html
Copyright © 2011-2022 走看看