Unity Shader:Blur

花了一晚上的时间终于看懂Image Effect中的Blur,其实很简单,就是一下子没有理解到。

原理:使用两个一维[1*7]的高斯滤波模板,一个用在x方向,另一个用在y方向。高斯滤波有模糊的效果。

js脚本参数:

Down Sample:OnRenderImage中获取的图像进行降采样,其实就是把要处理的纹理变小。有利于加快shader运行速度。

Blur Size:在使用高斯模板时,相邻像素点的间隔。越大间隔越远,图像越模糊。但过大的值会导致失真。

Blur Iterations:迭代次数,越大模糊效果越好,但消耗越大。

Blur Type:两个不同的shader,后一个是前一个的优化版本,但差别不大。

具体代码分析:

function OnRenderImage (source : RenderTexture, destination : RenderTexture) {
        if(CheckResources() == false) {
            Graphics.Blit (source, destination);
            return;
        }

        var widthMod : float = 1.0f / (1.0f * (1<<downsample)); // 降采样系数的倒数,用于调整降采样后,相邻像素的间隔

        // blurMaterial.SetVector ("_Parameter", Vector4 (blurSize * widthMod, -blurSize * widthMod, 0.0f, 0.0f));
        source.filterMode = FilterMode.Bilinear;

        var rtW : int = source.width >> downsample; // >> 是除法的优化
        var rtH : int = source.height >> downsample;

        // downsample
        var rt : RenderTexture = RenderTexture.GetTemporary (rtW, rtH, 0, source.format);

        rt.filterMode = FilterMode.Bilinear;      // 对应的shader的Pass 0
        Graphics.Blit (source, rt, blurMaterial, 0); //首先对图像进行降采样,同时进行简单的模糊

        var passOffs = blurType == BlurType.StandardGauss ? 0 : 2; // 选择不同的blurtype,就调用不同的shader pass

        for(var i : int = 0; i < blurIterations; i++) {
            var iterationOffs : float = (i*1.0f);        // _Parameter.x 记录的是 相邻像素的间隔,随着迭代次数增大
            blurMaterial.SetVector ("_Parameter",
                Vector4 (blurSize * widthMod + iterationOffs, -blurSize * widthMod - iterationOffs, 0.0f, 0.0f));

            // vertical blur 垂直滤波
            var rt2 : RenderTexture = RenderTexture.GetTemporary (rtW, rtH, 0, source.format);
            rt2.filterMode = FilterMode.Bilinear;
            Graphics.Blit (rt, rt2, blurMaterial, 1 + passOffs); // 对应着shader的pass 1,2
            RenderTexture.ReleaseTemporary (rt);
            rt = rt2;

            // horizontal blur 水平滤波
            rt2 = RenderTexture.GetTemporary (rtW, rtH, 0, source.format);
            rt2.filterMode = FilterMode.Bilinear;
            Graphics.Blit (rt, rt2, blurMaterial, 2 + passOffs); // 对应着shader的pass 3,4
            RenderTexture.ReleaseTemporary (rt);
            rt = rt2;
        }

        Graphics.Blit (rt, destination);

        RenderTexture.ReleaseTemporary (rt);
    }    

接着分析shader文件:

先看5个pass,分别是用在上文cs脚本中的Bilt函数中。

SubShader {
      ZTest Off Cull Off ZWrite Off Blend Off
      Fog { Mode off }  

    // 0
    Pass { 

        CGPROGRAM

        #pragma vertex vert4Tap
        #pragma fragment fragDownsample
        #pragma fragmentoption ARB_precision_hint_fastest 

        ENDCG

        }

    // 1
    Pass {
        ZTest Always
        Cull Off

        CGPROGRAM 

        #pragma vertex vertBlurVertical
        #pragma fragment fragBlur8
        #pragma fragmentoption ARB_precision_hint_fastest 

        ENDCG
        }    

    // 2
    Pass {
        ZTest Always
        Cull Off

        CGPROGRAM

        #pragma vertex vertBlurHorizontal
        #pragma fragment fragBlur8
        #pragma fragmentoption ARB_precision_hint_fastest 

        ENDCG
        }    

    // alternate blur
    // 3
    Pass {
        ZTest Always
        Cull Off

        CGPROGRAM 

        #pragma vertex vertBlurVerticalSGX
        #pragma fragment fragBlurSGX
        #pragma fragmentoption ARB_precision_hint_fastest 

        ENDCG
        }    

    // 4
    Pass {
        ZTest Always
        Cull Off

        CGPROGRAM

        #pragma vertex vertBlurHorizontalSGX
        #pragma fragment fragBlurSGX
        #pragma fragmentoption ARB_precision_hint_fastest 

        ENDCG
        }
    }    

pass 0:在降采样的同时,进行简单地模糊处理。

v2f_tap vert4Tap ( appdata_img v )
        {
            v2f_tap o;

            o.pos = mul (UNITY_MATRIX_MVP, v.vertex);        // 取像素周围的点
            o.uv20 = v.texcoord + _MainTex_TexelSize.xy;
            o.uv21 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h,-0.5h);
            o.uv22 = v.texcoord + _MainTex_TexelSize.xy * half2(0.5h,-0.5h);
            o.uv23 = v.texcoord + _MainTex_TexelSize.xy * half2(-0.5h,0.5h);        

            return o;
        }                    

        fixed4 fragDownsample ( v2f_tap i ) : SV_Target
        {
            fixed4 color = tex2D (_MainTex, i.uv20);
            color += tex2D (_MainTex, i.uv21);
            color += tex2D (_MainTex, i.uv22);
            color += tex2D (_MainTex, i.uv23);
            return color / 4;
        }

接下来的pass 1,2 和pass 3, 4,都是分别在x y两个方向进行高斯滤波。

先看看高斯滤波模板:

static const half4 curve4[7] = { half4(0.0205,0.0205,0.0205,0), half4(0.0855,0.0855,0.0855,0), half4(0.232,0.232,0.232,0),
            half4(0.324,0.324,0.324,1), half4(0.232,0.232,0.232,0), half4(0.0855,0.0855,0.0855,0), half4(0.0205,0.0205,0.0205,0) };

这是 [1*7]的模板,对中间点像素的左右两边各3个像素,总共7个像素进行加权求和,得到新的像素值。

pass 1,2的只有vert函数不一样,分别是取水平和垂直方向的偏差值。

v2f_withBlurCoords8 vertBlurHorizontal (appdata_img v)
        {
            v2f_withBlurCoords8 o;
            o.pos = mul (UNITY_MATRIX_MVP, v.vertex);

            o.uv = half4(v.texcoord.xy,1,1);
            o.offs = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _Parameter.x; // 水平方向的偏差值

            return o;
        }

        v2f_withBlurCoords8 vertBlurVertical (appdata_img v)
        {
            v2f_withBlurCoords8 o;
            o.pos = mul (UNITY_MATRIX_MVP, v.vertex);

            o.uv = half4(v.texcoord.xy,1,1);
            o.offs = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _Parameter.x; // 垂直方向的偏差值

            return o;
        }    

        half4 fragBlur8 ( v2f_withBlurCoords8 i ) : SV_Target
        {
            half2 uv = i.uv.xy;
            half2 netFilterWidth = i.offs;
            half2 coords = uv - netFilterWidth * 3.0;  // 这里从中心点偏移3个间隔,从最左边或者是最上边开始进行加权累加

            half4 color = 0;
              for( int l = 0; l < 7; l++ )
              {
                half4 tap = tex2D(_MainTex, coords);
                color += tap * curve4[l]; // 像素值乘上对应的权值
                coords += netFilterWidth; // 移到下一个像素
              }
            return color;
        }

在pass 1,2中的uv值都是float2向量,然而寄存器可以一次性储存float4,即可以一个float4值存储两个uv值。并且像素着色器函数中,计算相邻像素的步骤,可以放在顶点着色器中。于是就有下面这个版本:

        v2f_withBlurCoordsSGX vertBlurHorizontalSGX (appdata_img v)
        {
            v2f_withBlurCoordsSGX o;
            o.pos = mul (UNITY_MATRIX_MVP, v.vertex);

            o.uv = v.texcoord.xy;
            half2 netFilterWidth = _MainTex_TexelSize.xy * half2(1.0, 0.0) * _Parameter.x;
            half4 coords = -netFilterWidth.xyxy * 3.0;
            // 计算左右相邻各3个像素的坐标
            o.offs[0] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h);
            coords += netFilterWidth.xyxy;
            o.offs[1] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h);
            coords += netFilterWidth.xyxy;
            o.offs[2] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h);

            return o;
        }        

        v2f_withBlurCoordsSGX vertBlurVerticalSGX (appdata_img v)
        {
            v2f_withBlurCoordsSGX o;
            o.pos = mul (UNITY_MATRIX_MVP, v.vertex);

            o.uv = half4(v.texcoord.xy,1,1);
            half2 netFilterWidth = _MainTex_TexelSize.xy * half2(0.0, 1.0) * _Parameter.x;
            half4 coords = -netFilterWidth.xyxy * 3.0;
            // 计算上下相邻各3个像素的坐标
            o.offs[0] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h);
            coords += netFilterWidth.xyxy;
            o.offs[1] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h);
            coords += netFilterWidth.xyxy;
            o.offs[2] = v.texcoord.xyxy + coords * half4(1.0h,1.0h,-1.0h,-1.0h);

            return o;
        }    

        half4 fragBlurSGX ( v2f_withBlurCoordsSGX i ) : SV_Target
        {
            half2 uv = i.uv.xy;

            half4 color = tex2D(_MainTex, i.uv) * curve4[3]; // 中间像素,乘上对应的权值

              for( int l = 0; l < 3; l++ )
              {
                half4 tapA = tex2D(_MainTex, i.offs[l].xy);
                half4 tapB = tex2D(_MainTex, i.offs[l].zw);
                color += (tapA + tapB) * curve4[l]; // 由于模板是对称的,可以使用相同的权值
              }

            return color;

        }

结论:

通过调试,发现使用downsampler为1,iteration为2时,调整blursize可以得到较好的效果,并且性能较好。但blursize为0时,还是模糊图像,想做成那种从清晰到模糊的动画,估计还要调整一下代码。

时间: 2024-11-05 11:26:37

Unity Shader:Blur的相关文章

Unity Shader:Projective Texture Mapping

Projective-Texture-Mapping是用于投影一个texture到一个物体上.通过设置一个摄像机在投影点上,获取投影摄像机的 VP 矩阵,来获取相对于纹理的uv坐标.具体描述见:GPU 编程与CG 语言之阳春白雪下里巴人 中的12.3节. Shader "Custom/Projective-Texture-Mapping" { Properties { _Tex ("Proj Tex", 2D) = "white" {} _Col

Unity Shader入门精要学习笔记 - 第3章 Unity Shader 基础

来源作者:candycat   http://blog.csdn.net/candycat1992/article/ 概述 总体来说,在Unity中我们需要配合使用材质和Unity Shader才能达到需要的效果.一个最常见的流程是. 1)创建一个材质 2)创建一个Unity Shader,并把它赋给上一步创建的材质 3)把材质赋给要渲染的对象 4)在材质面板中调整Unity Shader的属性,以得到满意的效果 下图显示了Unity Shader和材质是如何一起工作来控制物体的渲染的. Uni

【Unity Shader编程】之十五 屏幕高斯模糊(Gaussian Blur)后期特效的实现

本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://blog.csdn.net/poem_qianmo/article/details/51871531 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 本文工程使用的Unity3D版本: 5.2.1  本篇文章将分析如何在Unity中基于Shader实现高斯模糊屏幕后期特效. 首先放出最终的实现效果.如下几幅图,是在Unity中使用本文所实现的Shader得到的高斯模糊屏幕

Unity shader教程-第一课:写shader和应用shader的流程

这是我们Unity shader(着色器)教程的第一课,在这节课中,你会学到怎么样来用程序来编写一个在Unity中能使用的着色器:漫反射着色器.这节课的内容主要让我们熟悉创建shader和应用shader的流程. 准备工作: 安装Unity版本4.6以上 创建一个新的工程 菜单GameObject | 3D Object | Plane创建出一个平面,作为我们的地面 菜单GameObject | 3D Object | Sphere创建出球来,反复多次创建多个 注意: 1. 创建地面后选中该物体

Unity shader教程-第三课:实践!同一个shader,多个material。

本文首发地址:http://98jy.net/article/18 更多更及时的文章请关于98教育 这节课我们开始利用我们学到的Properties知识来改动代码,用一个shader实现如下图的几个不同的球体: 步骤: 使用MonoDevelop打开shader文件 保证Properties里面是如下内容 _EmissiveColor ("Emissive Color", Color) = (1, 1, 1, 1) _AmbientColor ("Ambient Color&

Unity Shader实现描边效果

http://gad.qq.com/article/detail/28346 描边效果是游戏里面非常常用的一种效果,一般是为了凸显游戏中的某个对象,会给对象增加一个描边效果.本篇文章和大家介绍下利用Shader实现描边效果,一起来看看吧. 最近又跑回去玩了玩<剑灵>,虽然出了三年了,感觉在现在的网游里面画面仍然算很好的了,剑灵里面走近或者选中NPC的一瞬间,NPC就会出现描边效果,不过这个描边效果是渐变的,会很快减弱最后消失(抓了好久才抓住一张图....) 还有就是最常见的LOL中的塔,我们把

unity shader序列帧动画代码,顺便吐槽一下unity shader系统

http://www.cnblogs.com/hellohuan/archive/2014/01/10/3512784.html 一.看到UNITY论坛里有些人求unity shader序列帧动画,写shader我擅长啊,就顺势写了个CG的shader.代码很简单,就是变换UV采样序列帧贴图,美术配置行数列数以及变换速度. Shader "HELLOHUAN/Hello_Sequence" { Properties { _Color ("Main Color", C

【Unity Shader编程】之十四 边缘发光Shader(Rim Shader)的两种实现形态

本系列文章由@浅墨_毛星云 出品,转载请注明出处.   文章链接:http://blog.csdn.net/poem_qianmo/article/details/51764028 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 本文工程使用的Unity3D版本: 5.2.1  这篇文章主要讲解了如何在Unity3D中分别使用Surface Shader和Vertex & Fragment Shader来编写边缘发光Shader. 一.最终实现的效果

Unity Shader入门教程(一)

参考文献:http://www.360doc.com/content/13/0923/15/12282510_316492286.shtml Unity Shader是着色器,将纹理.网格信息输入,得到材质的一段程序,具体是个什么东西,还需要亲自实践才知道.一个Unity大神推荐我:如果要学计算机图形编程(游戏编程的基础),可以先学习UnityShader,往后再学习OpenGL和DX.不说废话,依我的风格,都是直接看实例,笔者的教程偏向于傻瓜式的,应该适合入门. 前提:安装了Unity和VS,