《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"
}
相关推荐
叶帆14 天前
【YFIOs】用C#开发硬件之设备上云
开发语言·unity·c#
久数君14 天前
AI三维建模工具“造形家”:地理场景三维化的高效解决方案
unity·glb·ai算法·ai三维建模工具·地图框选·造形家·城市建筑模型
会思考的猴子15 天前
Unity VFX 属性 Postion 和 TargetPostion
unity
hai31524754315 天前
九章编程法 · 猜数字游戏 (GW-BASIC 重构版) *
人工智能·microsoft·游戏引擎·游戏程序
心前阳光15 天前
Unity资源导入之自动化资源导入
unity·自动化·游戏引擎
心前阳光15 天前
Unity之2021.3.45f2c1发布安卓程序遇到的问题
android·unity·游戏引擎
纪纯15 天前
PicoVR Unity Integration SDK 3.4 常用交互API
unity·游戏引擎·vr·pico
龙智DevSecOps解决方案15 天前
3A 游戏优化技术栈:如何打通引擎级分析工具与 DevOps 持续集成管线?
unity·性能优化·游戏开发·技术美术·perforce·unrealengine
葛兰岱尔15 天前
从 SolidWorks 到 Three.js,从 Inventor 到 Unity——制造业CAD模型“几何-语义一体化“转换,不再是天方夜谭!
开发语言·javascript·unity
鼎艺创新科技15 天前
三维电子沙盘中OSGB倾斜摄影数据的加载与渲染
游戏引擎·cocos2d