《Unity Shader》12.4.2 实现

本节,我们将会使用上述5×5的高斯核对原图像进行高斯模糊。为此,我们需要进行如下准备工作。


(1)新建一个场景。在本书资源中,该场景名为Scene_12_4。在Unity 5.2中,默认情况下场景将包含一个摄像机和一个平行光,并且使用了内置的天空盒子。在Window → Lighting →Skybox中去掉场景中的天空盒子。

(2)把本书资源中的Assets/Textures/Chapter12/Sakura1.jpg拖曳到场景中,并调整的位置使其可以填充整个场景。注意,Sakura1.jpg的纹理类型已被设置为Sprite,因此可以直接拖曳到场景中。

https://github.com/candycat1992/Unity_Shaders_Book/blob/master/Assets/Textures/Chapter12/sakura1.jpg

(3)新建一个脚本。在本书资源中,该脚本名为GaussianBlur.cs。把该脚本拖曳到摄像机上。

(4)新建一个Unity Shader。在本书资源中,该Shader名为Chapter12-GaussianBlur。

我们首先来编写GaussianBlur.cs脚本。打开该脚本,并进行如下修改。

(1)首先,继承12.1节中创建的基类:

(2)声明该效果需要的Shader,并据此创建相应的材质:

(3)在脚本中,我们还提供了调整高斯模糊迭代次数、模糊范围和缩放系数的参数:

(4)最后,我们需要定义关键的OnRenderImage函数。我们首先来看第一个版本,也就是最简单的OnRenderImage的实现:

(5)在理解了上述代码后,我们可以实现第二个版本的OnRenderImage函数。在这个版本中,我们将利用缩放对图像进行降采样,从而减少需要处理的像素个数,提高性能。

(6)最后一个版本的代码还考虑了高斯模糊的迭代次数:

https://github.com/candycat1992/Unity_Shaders_Book/blob/master/Assets/Scripts/Chapter12/GaussianBlur.cs

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GaussianBlur : PostEffectsBase
{
    public  Shader  gaussianBlurShader;
    private  Material  gaussianBlurMaterial  =  null;
    public  Material  material  {
        get  {
            gaussianBlurMaterial = CheckShaderAndCreateMaterial(gaussianBlurShader, gaussianBlurMaterial);
            return  gaussianBlurMaterial;
        }
    }
    // Blur  iterations  -  larger  number  means  more  blur. 高斯模糊迭代次数
    [Range(0,  4)]
    public  int  iterations  =  3;

    // Blur  spread  for  each  iteration  -  larger 模糊范围
    [Range(0.2f,  3.0f)]
    public  float  blurSpread  =  0.6f;

    //缩放系数
    [Range(1,  8)]
    public  int  downSample  =  2;

    //在高斯核维数不变的情况下,_BlurSize越大,模糊程度越高,但采样数却不会受到影响。但过大的_BlurSize值会造成虚影,这可能并不是我们希望的。
    //downSample越大,需要处理的像素数越少,同时也能进一步提高模糊程度,但过大的downSample可能会使图像像素化

    /* 第一版
    void  OnRenderImage(RenderTexture  src, RenderTexture  dest)  {
        if  (material  !=  null)  { 
            int rtW = src.width; //rendertexturewidth
            int rtH = src.height; //rendertexturewidth
            RenderTexture  buffer  =  RenderTexture.GetTemporary(rtW,  rtH,  0); //分配了一块与屏幕图像大小相同的缓冲区,高斯模糊需要调用两个Pass,我们需要使用一块中间缓存来存储第一个Pass执行完毕后得到的模糊结果

            //  Render  the  vertical  pass
            Graphics.Blit(src,  buffer,  material,  0); //使用Shader中的第一个Pass(即使用竖直方向的一维高斯核进行滤波)对src进行处理,并将结果存储在了buffer中
            //  Render  the  horizontal  pass
            Graphics.Blit(buffer,  dest,  material,  1); //调用Graphics.Blit(buffer, dest, material, 1),使用Shader中的第二个Pass(即使用水平方向的一维高斯核进行滤波)对buffer进行处理,返回最终的屏幕图像
            RenderTexture.ReleaseTemporary(buffer); //释放之前分配的缓存
        } else {
            Graphics.Blit(src,  dest);
        }
    }
    */
    
    /*第二版
    void  OnRenderImage(RenderTexture  src, RenderTexture  dest)  {
        if  (material  !=  null)  { 
            int rtW = src.width/downSample;
            int rtH = src.height/downSample; 
            RenderTexture  buffer  =  RenderTexture.GetTemporary(rtW,  rtH,  0); 
            buffer.filterMode  =  FilterMode.Bilinear;
            //第一个版本代码不同的是,我们在声明缓冲区的大小时,使用了小于原屏幕分辨率的尺寸,并将该临时渲染纹理的滤波模式设置为双线性
            //图像进行降采样

           
            Graphics.Blit(src,  buffer,  material,  0); 
            Graphics.Blit(buffer,  dest,  material,  1); 
            RenderTexture.ReleaseTemporary(buffer); 
        } else {
            Graphics.Blit(src,  dest);
        }
    }
    */

    void  OnRenderImage(RenderTexture  src, RenderTexture  dest)  {
        if  (material  !=  null)  { 
            int rtW = src.width/downSample;
            int rtH = src.height/downSample; 
            RenderTexture  buffer0  =  RenderTexture.GetTemporary(rtW,  rtH,  0); 
            buffer0.filterMode  =  FilterMode.Bilinear;
            //第一个版本代码不同的是,我们在声明缓冲区的大小时,使用了小于原屏幕分辨率的尺寸,并将该临时渲染纹理的滤波模式设置为双线性
            //图像进行降采样

            Graphics.Blit(src,  buffer0);
            for  (int  i  =  0;  i  <  iterations;  i++){
                material.SetFloat("_BlurSize",  1.0f  +  i  *  blurSpread);
                RenderTexture  buffer1  =  RenderTexture.GetTemporary(rtW,  rtH,  0);
                //  Render  the  vertical  pass
                Graphics.Blit(buffer0,  buffer1,  material,  0);
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0  =  buffer1;
                buffer1  =  RenderTexture.GetTemporary(rtW,  rtH,  0);
                //  Render  the  horizontal  pass
                Graphics.Blit(buffer0,  buffer1,  material,  1);
                RenderTexture.ReleaseTemporary(buffer0);
                buffer0  =  buffer1;
            }

            Graphics.Blit(buffer0,  dest);
            RenderTexture.ReleaseTemporary(buffer0);
        } else {
            Graphics.Blit(src,  dest);
        }
        //在迭代开始前,我们首先定义了第一个缓存buffer0,并把src中的图像缩放后存储到buffer0中
        //在迭代过程中,我们又定义了第二个缓存buffer1
        //执行第一个Pass时,输入是buffer0,输出是buffer1,完毕后首先把buffer0释放,再把结果值buffer1存储到buffer0中,重新分配buffer1
        //然后再调用第二个Pass,重复上述过程
        //迭代完成后,buffer0将存储最终的图像,我们再利用Graphics.Blit(buffer0, dest)把结果显示到屏幕上,并释放缓存。
    }



    
}

下面,我们来实现Shader的部分。打开Chapter12-GaussianBlur,进行如下修改。

(1)我们首先需要声明本例使用的各个属性:

2)在本节中,我们将第一次使用CGINCLUDE来组织代码。我们在SubShader块中利用CGINCLUDE和ENDCG语义来定义一系列代码:

(3)在CG代码块中,定义与属性对应的变量:

(4)分别定义两个Pass使用的顶点着色器。下面是竖直方向的顶点着色器代码:

(5)定义两个Pass共用的片元着色器:

(6)然后,我们定义了高斯模糊使用的两个Pass:

(7)最后,关闭该Shader的Fallback:

完成后返回编辑器,并把Chapter12-GaussianBlur拖曳到摄像机的GaussianBlur.cs脚本中的gaussianBlurShader参数中。当然,我们可以在GaussianBlur.cs的脚本面板中将gaussianBlurShader参数的默认值设置为Chapter12-GaussianBlur,这样就不需要以后使用时每次都手动拖曳了。

https://github.com/candycat1992/Unity_Shaders_Book/blob/master/Assets/Shaders/Chapter12/Chapter12-GaussianBlur.shader

cs 复制代码
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Custom/Chapter12-GaussianBlur"
{
    Properties{
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _BlurSize ("Blur Size", Float) = 1.0
    }
    SubShader {
        CGINCLUDE
        #include "UnityCG.cginc"

        sampler2D _MainTex;  
		half4 _MainTex_TexelSize;
		float _BlurSize;

        struct v2f {
            float4 pos : SV_POSITION;
            half2 uv[5]: TEXCOORD0;
        };

        v2f vertBlurVertical(appdata_img v) {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            half2 uv = v.texcoord;

            o.uv[0] = uv;
            o.uv[1] = uv + float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize; //属性_BlurSize相乘来控制采样距离
            o.uv[2] = uv - float2(0.0, _MainTex_TexelSize.y * 1.0) * _BlurSize;
            o.uv[3] = uv + float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;
			o.uv[4] = uv - float2(0.0, _MainTex_TexelSize.y * 2.0) * _BlurSize;

            return o;
        }

        v2f vertBlurHorizontal(appdata_img v) {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            half2 uv = v.texcoord;

            o.uv[0] = uv;
            o.uv[1] = uv + float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
            o.uv[2] = uv - float2(_MainTex_TexelSize.x * 1.0, 0.0) * _BlurSize;
            o.uv[3] = uv + float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;
            o.uv[4] = uv - float2(_MainTex_TexelSize.x * 2.0, 0.0) * _BlurSize;

            return o;
        }

        fixed4 fragBlur(v2f i) : SV_Target {
            float weight[3] = {0.4026, 0.2442, 0.0545};
            fixed3 sum = tex2D(_MainTex, i.uv[0]).rgb * weight[0];
            for (int it = 1; it < 3; it++) {
                sum += tex2D(_MainTex, i.uv[it*2-1]).rgb * weight[it];
                sum += tex2D(_MainTex, i.uv[it*2]).rgb * weight[it];
            }
            return fixed4(sum, 1.0);
        }
        ENDCG

        ZTest Always Cull Off ZWrite Off
        Pass {
            NAME "GAUSSIAN_BLUR_VERTICAL" //为Pass定义名字,可以在其他Shader中直接通过它们的名字来使用该Pass,而不需要再重复编写代码。
            CGPROGRAM
            #pragma vertex vertBlurVertical  
			#pragma fragment fragBlur

            ENDCG  
        }
        Pass {  
			NAME "GAUSSIAN_BLUR_HORIZONTAL"
            CGPROGRAM  
            #pragma vertex vertBlurHorizontal  
			#pragma fragment fragBlur
            ENDCG
        }
    }
    FallBack "Diffuse"
}
相关推荐
June bug1 天前
【领域知识】休闲游戏一次发版全流程:Google Play + Apple App Store
unity
星夜泊客1 天前
C# 基础:为什么类可以在静态方法中创建自己的实例?
开发语言·经验分享·笔记·unity·c#·游戏引擎
dzj20211 天前
PointerEnter、PointerExit、PointerDown、PointerUp——鼠标点击物体,则开始旋转,鼠标离开或者松开物体,则停止旋转
unity·pointerdown·pointerup
心前阳光1 天前
Unity 模拟父子关系
android·unity·游戏引擎
在路上看风景1 天前
26. Mipmap
unity
咸鱼永不翻身1 天前
Unity视频资源压缩详解
unity·游戏引擎·音视频
在路上看风景1 天前
4.2 OverDraw
unity
在路上看风景1 天前
1.10 CDN缓存
unity
ellis19702 天前
Unity插件SafeArea Helper适配异形屏详解
unity
nnsix2 天前
Unity Physics.Raycast的 QueryTriggerInteraction枚举作用
unity·游戏引擎