Unity3D Shader编程之八 Unity5新版Shader模板源码解析&运动模糊(径向模糊)屏幕特效的实现

发表于2016-07-20
评论0 2.2k浏览

       本系列文章由@浅墨_毛星云 出品,转载请注明出处。  

文章链接: http://blog.csdn.net/poem_qianmo/article/details/49405909

作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442

本文工程使用的Unity3D版本: 5.2.1 

   概要:本文对Unity5中全新的三种Shader模板的源码进行了解析,然后还讲解了运动模糊屏幕特效的实现方法。


前言

       时隔9个月,终于有了一些稍微空闲的时间,可以进行一些更新了。

       鉴于以后可以用来写博客的时间肯定不会非常充裕,个人觉得再讲Shader的基础写法比较拖节奏,所以最好是把力气用在刀刃上,在有时间更新的时候,主要讲一些更加前沿、实用的技巧,以及进阶一些的Shader的写法。

        另外,从本次开始,每次更新的标题也就不取那么主题化了(比如之前的暗黑城堡、静谧之秋之类的)。人老了,渐渐文艺不起来了,还是实在一点好。:D

        好的,就交代这么多。接下来就按新的风格试着写几次。。

        看一组程序截图之后,便开始我们的正文。如下图,一份运动模糊屏幕特效的效果对比。

       原始场景效果图:

       

 

开启运动模糊特效后的场景效果图:

 

 

       图先就上这两张。文章末尾有更多的运行截图,并提供了源工程的下载。先放出可运行的exe下载,如下:

【可运行的exe游戏场景请点击这里下载试玩】

        好的,正文开始。


一、Unity5中新的Shader体系简析

        Unity5和之前的书写模式有了一定的改变。Unity5时代的Shader Reference官方文档也进一步地变得丰满。

        主要需要了解到的是,在原来的Unity中,若想要新建一个Shader源文件,不考虑compute shader的话,仅有一种Shader模板供选择。而自从Unity5.1起(好像是Unity5.1)

想在Unity5.1之后的版本中新建Shader,【右键在Project窗口中单击】->【Create】,会出现如下的四个选项:



       而由于暂时不考虑compute shader。所以,新版Unity中有三种基本的Shader模板分别为:

  • Standard Surface Shader标准表面着色器
  • Unlit Shader 无灯光着色器
  • Image Effect Shader 图像特效着色器
二、Unity5中新的Shader模板源码解析

        下面,对Unity5中三种基本Shader模板进行逐行注释与思路解析。

       可以点击这里跳转到Github,查看详细注释好的三种Shader模板的源码。


 标准表面着色器(Standard Surface Shader)模板源码解析

        在Unity中,我们若要实现新的表面着色器时,可以根据这个模板,进行一步添加子着色器和新的参数与特性。

       这个Shader模板的脉络很清晰,先是定义一些属性,然后在SubShader中设置渲染模式,层次细节LOD的值,然后开启一个CG编程语言模块,写一些编译指令#pragma,声明一下变量让属性值在CG块中可见,定义输入结构,然后填充一下表面着色函数即可。注意:专门强调一句,SurfaceShader不能使用Pass,一使用就报错,我们直接在SubShader中实现和填充代码就可以了。

       Standard Surface Shader模板详细注释的Shader代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
Shader "浅墨Shader编程/Volume8/Surface Shader模板" 
       //------------------------------------【属性值】------------------------------------ 
       Properties 
       
              //主颜色 
              _Color("Color", Color) = (1,1,1,1) 
              //主纹理 
              _MainTex("Albedo (RGB)", 2D) = "white" {} 
              //光泽度 
              _Glossiness("Smoothness", Range(0,1)) = 0.5 
              //金属度 
              _Metallic("Metallic", Range(0,1)) = 0.0 
       
    
       //------------------------------------【唯一的子着色器】------------------------------------ 
       SubShader 
       
              //【注意:Surface Shader不能使用Pass,直接在SubShader中实现即可】 
    
              //渲染类型设置:不透明 
              Tags{"RenderType" = "Opaque"
    
              //细节层次设为:200 
              LOD200 
    
              //===========开启CG着色器语言编写模块=========== 
              CGPROGRAM 
    
              //编译指令:告知编译器表明着色函数的名称为surf 
              //Standard表示光照模型为Unity标准版光照模型 
              //fullforwardshadows表示在正向渲染路径中支持所有阴影类型 
              #pragma surface surf Standard fullforwardshadows 
    
              //编译指令: 指定着色器编译目标为Shader Model 3.0 
              #pragma target 3.0 
    
              //变量的声明 
              sampler2D _MainTex; 
    
              //表面输入结构体 
              struct Input 
              
                     float2 uv_MainTex;//纹理坐标 
              }; 
    
              //变量的声明 
              half _Glossiness; 
              half _Metallic; 
              fixed4 _Color; 
    
              //--------------------------------【表面着色函数】----------------------------- 
              //输入:表面输入结构体 
              //输出:Unity内置的SurfaceOutputStandard结构体 
              //SurfaceOutputStandard原型如下: 
              /*
                     struct SurfaceOutputStandard
                     {
                            fixed3 Albedo;                  // 漫反射颜色
                            fixed3 Normal;                  // 切线空间法线
                            half3 Emission;                 //自发光
                            half Metallic;                           // 金属度;取0为非金属, 取1为金属
                            half Smoothness;             // 光泽度;取0为非常粗糙, 取1为非常光滑
                            half Occlusion;                 // 遮挡(默认值为1)
                            fixed Alpha;                      // 透明度
                     };
              */ 
              //--------------------------------------------------------------------------------- 
              void surf(Input IN, inout SurfaceOutputStandard o) 
              
                     //【1】漫反射颜色为主纹理对应的纹理坐标,并乘以主颜色 
                     fixed4c = tex2D(_MainTex, IN.uv_MainTex) * _Color; 
                     //【2】将准备好的颜色的rgb分量作为漫反射颜色 
                     o.Albedo= c.rgb; 
                     //【3】金属度取自属性值 
                     o.Metallic= _Metallic; 
                     //【4】光泽度也取自属性值 
                     o.Smoothness= _Glossiness; 
                     //【5】将准备好的颜色的alpha分量作为Alpha分量值 
                     o.Alpha= c.a; 
              
    
              //===========结束CG着色器语言编写模块=========== 
              ENDCG 
       
       //备胎为漫反射 
       FallBack"Diffuse" 
}


       接着来看Unity5的第二种Shader模板,无灯光着色器(Unlit Shader)模板。


无灯光着色器(Unlit Shader)模板源码解析

        Unlit Shader,简单来说,就是直接采用漫反射纹理,不考虑场景中的任何灯光效果。使用无灯光着色器的话,也就不能使用任何镜面或者法线效果了。Unlit系的Shader基本原理和其他Shader无异,但是计算量更小,更快速,更高效。

       而在Unity内置的各种着色器中,有如下的四种是Unlit系的:



 

       好的,已经稍微解释了下什么是Unlit Shader。下面一起看一下Unity为我们提供的无灯光着色器模板的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
Shader "浅墨Shader编程/Volume8/无灯光着色器(Unlit Shader)模板" 
       //------------------------------------【属性值】------------------------------------ 
       Properties 
       
              //主纹理 
              _MainTex("Texture", 2D) = "white" {} 
       
    
       //------------------------------------【唯一的子着色器】------------------------------------ 
       SubShader 
       
              //渲染类型设置:不透明 
              Tags{ "RenderType"="Opaque"
    
              //细节层次设为:100 
              LOD 100 
    
              //--------------------------------唯一的通道------------------------------- 
              Pass 
              
                     //===========开启CG着色器语言编写模块=========== 
                     CGPROGRAM 
    
                     //编译指令:告知编译器顶点和片段着色函数的名称 
                     #pragma vertex vert 
                     #pragma fragment frag 
    
                     //着色器变体快捷编译指令:雾效。编译出几个不同的Shader变体来处理不同类型的雾效(关闭/线性/指数/二阶指数) 
                     #pragma multi_compile_fog 
    
                     //包含头文件 
                     #include"UnityCG.cginc" 
    
                     //顶点着色器输入结构 
                     struct appdata 
                     
                            float4 vertex : POSITION;//顶点位置 
                            float2 uv : TEXCOORD0;//纹理坐标 
                     }; 
    
                     //顶点着色器输出结构 
                     struct v2f 
                     
                            float2 uv : TEXCOORD0;//纹理坐标 
                            UNITY_FOG_COORDS(1)//雾数据 
                            float4 vertex : SV_POSITION;//像素位置 
                     }; 
    
                     //变量声明 
                     sampler2D _MainTex; 
                     float4 _MainTex_ST; 
                       
                     //--------------------------------【顶点着色函数】----------------------------- 
                     //输入:顶点输入结构体 
                     //输出:顶点输出结构体 
                     //--------------------------------------------------------------------------------- 
                     v2f vert (appdata v) 
                     
                            //【1】实例化一个输入结构体 
                            v2f o; 
                            //【2】填充此输出结构 
                            //输出的顶点位置(像素位置)为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口 
                            o.vertex= mul(UNITY_MATRIX_MVP, v.vertex); 
                            //【3】用UnityCG.cginc头文件中内置定义的宏,根据uv坐标来计算真正的纹理上对应的位置(按比例进行二维变换)                  
                            o.uv= TRANSFORM_TEX(v.uv, _MainTex); 
                            //【4】用UnityCG.cginc头文件中内置定义的宏处理雾效,从顶点着色器中输出雾效数据 
                            UNITY_TRANSFER_FOG(o,o.vertex); 
    
                            //【5】返回此输出结构对象 
                            return o; 
                     
                       
                     //--------------------------------【片段着色函数】----------------------------- 
                     //输入:顶点输出结构体 
                     //输出:float4型的像素颜色值 
                     //--------------------------------------------------------------------------------- 
                     fixed4 frag (v2f i) : SV_Target 
                     
                            //【1】采样主纹理在对应坐标下的颜色值 
                            fixed4 col = tex2D(_MainTex, i.uv); 
    
                            //【2】用UnityCG.cginc头文件中内置定义的宏启用雾效 
                            UNITY_APPLY_FOG(i.fogCoord,col);            
    
                            //【3】返回最终的颜色值 
                            return col; 
                     
    
                     //===========结束CG着色器语言编写模块=========== 
                     ENDCG 
              
       
}


       不难分析得到,无灯光着色器是一种顶点&片段着色器,这边模板给出的是单子着色器,单通道的写法。

        并且,无灯光着色器中使用了一些UnityCG.cginc头文件中内置的宏,比如说TRANSFORM_TEX、UNITY_TRANSFER_FOG、UNITY_APPLY_FOG。接下来分别把这三个宏简单解释一下。


TRANSFORM_TEX宏

       TRANSFORM_TEX宏的定义为:

1
#define TRANSFORM_TEX(tex,name) (tex.xy *name##_ST.xy + name##_ST.zw)

       其位于UnityCG.cginc(Unity5.2.1版本)的第266行。其可以根据uv坐标来计算真正的纹理上对应的位置(按比例进行二维变换),组合上上文中定义的float4 _MainTex_ST,便可以计算真正的纹理上对应的位置。


UNITY_TRANSFER_FOG宏

        UNITY_TRANSFER_FOG宏的作用是从顶点着色输出雾数据。在UnityCG.cginc(Unity5.2.1版本)的第772行起,具体定义如下:

1
2
3
4
5
6
7
#if (SHADER_TARGET < 30) ||defined(SHADER_API_MOBILE) 
              //手机端或者Shader Mode 2.0: 计算每个顶点的雾效因子 
              #define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord =unityFogFactor 
       #else 
              //Shader Mode 3.0和PC和游戏机: 计算每像素的雾距离,和每像素的雾效因子 
              #define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord = (outpos).z 
       #endif

UNITY_APPLY_FOG宏

        UNITY_APPLY_FOG宏的定义稍微有些长,从UnityCG.cginc(Unity 5.2.1版本)的第787行起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#if defined(FOG_LINEAR) || defined(FOG_EXP)|| defined(FOG_EXP2) 
       #if(SHADER_TARGET < 30) || defined(SHADER_API_MOBILE) 
              //mobile or SM2.0: fog factor was already calculated per-vertex, so just lerp thecolor 
              #defineUNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_FOG_LERP_COLOR(col,fogCol,coord) 
       #else 
              //SM3.0 and PC/console: calculate fog factor and lerp fog color 
              #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_CALC_FOG_FACTOR(coord);UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor) 
       #endif 
#else 
       #define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) 
#endif 
    
#ifdef UNITY_PASS_FORWARDADD 
       #define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0)) 
#else 
       #define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor) 
#endif

       可以发现,UNITY_APPLY_FOG宏的作用是从顶点着色器中输出雾效数据,将第二个参数中的颜色值作为雾效的颜色值,且在正向附加渲染通道(forward-additive pass)中,自动设置纯黑色(fixed4(0,0,0,0))的雾效。其在定义中借助了UNITY_APPLY_FOG_COLOR宏,而我们也可以使用UNITY_APPLY_FOG_COLOR来指定特定颜色的雾效。

 

图像特效着色器(Image Effect Shader) 模板源码解析

        这里的图像特效一般指的就是屏幕图像特效,在Camera加上各种滤镜,比如说屏幕溅血,像素化,色调的调整,画面模糊等效果。其也是一个顶点&片段着色器,且一般主要的操作集中在片段着色函数中。Unity为我们提供的模板,经过详细注释后的源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
Shader "浅墨Shader编程/Volume8/图像特效Shader模板" 
       //------------------------------------【属性值】------------------------------------ 
       Properties 
       
              //主纹理 
              _MainTex("Texture", 2D) = "white" {} 
       
    
       //------------------------------------【唯一的子着色器】------------------------------------ 
       SubShader 
       
              //关闭剔除操作 
              Cull Off 
              //关闭深度写入模式 
              ZWrite Off 
              //设置深度测试模式:渲染所有像素.等同于关闭透明度测试(AlphaTestOff) 
              ZTest Always 
    
              //--------------------------------唯一的通道------------------------------- 
              Pass 
              
                     //===========开启CG着色器语言编写模块=========== 
                     CGPROGRAM 
    
                     //编译指令:告知编译器顶点和片段着色函数的名称 
                     #pragma vertex vert 
                     #pragma fragment frag 
                       
                     //包含头文件 
                     #include"UnityCG.cginc" 
    
                     //顶点着色器输入结构 
                     struct appdata 
                     
                            float4 vertex : POSITION;//顶点位置 
                            float2 uv : TEXCOORD0;//一级纹理坐标 
                     }; 
    
                     //顶点着色器输出结构(v2f,vertex to fragment) 
                     struct v2f 
                     
                            float2 uv : TEXCOORD0;//一级纹理坐标 
                            float4 vertex : SV_POSITION;//像素位置 
                     }; 
    
                     //--------------------------------【顶点着色函数】----------------------------- 
                     //输入:顶点输入结构体 
                     //输出:顶点输出结构体 
                     //--------------------------------------------------------------------------------- 
                     //顶点着色函数 
                     v2f vert (appdata v) 
                     
                            //【1】实例化一个输入结构体 
                            v2f o; 
    
                            //【2】填充此输出结构 
                            //输出的顶点位置(像素位置)为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口 
                            o.vertex= mul(UNITY_MATRIX_MVP, v.vertex); 
                            //输入的UV纹理坐标为顶点输出的坐标 
                            o.uv= v.uv; 
    
                            //【3】返回此输出结构对象 
                            return o; 
                     
    
                     //变量的声明 
                     sampler2D _MainTex; 
    
                     //--------------------------------【片段着色函数】----------------------------- 
                     //输入:顶点输出结构体 
                     //输出:float4型的像素颜色值 
                     //--------------------------------------------------------------------------------- 
                     fixed4 frag (v2f i) : SV_Target 
                     
                            //【1】采样主纹理在对应坐标下的颜色值 
                            fixed4 col = tex2D(_MainTex, i.uv); 
                            //【2】将颜色值反向 
                            col= 1 - col; 
    
                            //【3】返回最终的颜色值 
                            return col; 
                     
    
                     //===========结束CG着色器语言编写模块=========== 
                     ENDCG 
              
       
}

Shader模板中文注释格式调整版替换

       其实可以将Unity5中自带的上述三个着色器模板,替换成上文中贴出来的、经过详细注释和格式调整的Shader模板,这样在每次新建Shader时,就已经得到了具有很高可读性的Shader模板了,非常便捷。

       一定要吐槽的是,Unity5.2.1自带的三个Shader模板的缩进和空格完全是混用的,导致在通过他们新建出来的Shader里面写代码的时候,格式非常混乱,十分影响新版Unity中Shader的编码体验。很明显,准备此Shader模板的Unity开发人员的编码习惯有点欠缺,得在这里点名批评,轻喷一下。

        浅墨在一发现他们格式有问题的时候就马上替换掉了,所以现在在Unity中写Shader代码的体验是非常棒的。这边也教大家如何替换掉自带的3个模板。

Unity中Shader模板的位置是…UnityEditorDataResourcesScriptTemplates,比如说Unity安装在D:ProgramFiles路径下,整体路径就是:

D:ProgramFilesUnityEditorDataResourcesScriptTemplates。

       在此路径下的3个txt,即为对应的三个Shader模板文件:

  • 83-Shader__Standard SurfaceShader-NewSurfaceShader.shader.txt
  • 84-Shader__UnlitShader-NewUnlitShader.shader.txt
  • 85-Shader__Image EffectShader-NewImageEffectShader.shader

       这边已经将调整好格式,详细注释的三种模板准备好了,下载之后,找到上面提到的…UnityEditorDataResourcesScriptTemplates目录。替换掉对应的3个txt文件即可。需要注意的是,如果你想自己DIY Shader模板,需要将txt保存为UTF-8编码格式,否则可能会出现乱码。

       替换的模板下载地址在这里:

 【Unity5-Shader模板中文注释格式调整版替换文件】下载

        另外还有一个小细节可以提一下。如果你安装了两个或者两个以上的Unity5.1之后版本的Unity,如果你替换你当前使用的Unity路径下的模板文件后,新建的模板文件没有改变的话,你试着将所有的Unity5.1之后版本的路径下的这三个模板文件都进行替换,应该就可以实现想要的替换效果。浅墨的机器上就是同时存在Unity5.2.1和Unity5.2.0,然后使用Unity5.2.1,替换掉Unity5.2.1路径下的三个模板文件后,并没有发生变换。之后我按图索骥,替换了Unity 5.2.0版路径下的三个模板文化,才使得替换的模板文件生效。这估计是Unity多版本共存时,自身的一个小bug。

 

三、运动模糊屏幕特效的实现

        关于运动模糊特效,如果把握要要点的话,实现起来其实比较简单,就是一个脚本文件配合一个Shader,便可以实现较为出色的运动模糊特效。而其中的脚本文件用于控制Shader中的外部参数。

       也就是说一个屏幕特效通常分为两部分来实现:

  • Shader实现部分
  • 脚本实现部分

       下面我们对运动模糊屏幕特效的实现分别进行简单的描述。 

       可以点击这里跳转到Github,查看详细注释好的运动模糊屏幕特效的实现源码。

 

 Shader实现部分

       先看一下Shader代码的写法,因为基本上已经逐行注释,就不花时间和笔墨仔细讲解了,详细注释的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
Shader "浅墨Shader编程/Volume8/运动模糊特效标准版"  
    //------------------------------------【属性值】------------------------------------ 
    Properties 
    
        _MainTex("主纹理 (RGB)", 2D) = "white" {} 
        _IterationNumber("迭代次数", Int)=16  
    
   
    //------------------------------------【唯一的子着色器】------------------------------------ 
    SubShader 
    {    
        //--------------------------------唯一的通道------------------------------- 
        Pass 
        
            //设置深度测试模式:渲染所有像素.等同于关闭透明度测试(AlphaTest Off) 
            ZTest Always 
   
            //===========开启CG着色器语言编写模块=========== 
            CGPROGRAM 
   
            //编译指令: 指定着色器编译目标为Shader Model 3.0 
            #pragma target 3.0 
   
            //编译指令:告知编译器顶点和片段着色函数的名称 
            #pragma vertex vert 
            #pragma fragment frag 
   
            //包含辅助CG头文件 
            #include "UnityCG.cginc" 
   
            //外部变量的声明 
            uniform sampler2D _MainTex; 
            uniform float _Value; 
            uniform float _Value2; 
            uniform float _Value3; 
            uniform int _IterationNumber; 
   
            //顶点输入结构 
            struct vertexInput 
            
                float4 vertex : POSITION;//顶点位置 
                float4 color : COLOR;//颜色值 
                float2 texcoord : TEXCOORD0;//一级纹理坐标 
            }; 
   
            //顶点输出结构 
            struct vertexOutput 
            
                half2 texcoord : TEXCOORD0;//一级纹理坐标 
                float4 vertex : SV_POSITION;//像素位置 
                fixed4 color : COLOR;//颜色值 
            }; 
   
   
            //--------------------------------【顶点着色函数】----------------------------- 
            // 输入:顶点输入结构体 
            // 输出:顶点输出结构体 
            //--------------------------------------------------------------------------------- 
            vertexOutput vert(vertexInput Input) 
            
                //【1】声明一个输出结构对象 
                vertexOutput Output; 
   
                //【2】填充此输出结构 
                //输出的顶点位置为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口 
                Output.vertex = mul(UNITY_MATRIX_MVP, Input.vertex); 
                //输出的纹理坐标也就是输入的纹理坐标 
                Output.texcoord = Input.texcoord; 
                //输出的颜色值也就是输入的颜色值 
                Output.color = Input.color; 
   
                //【3】返回此输出结构对象 
                return Output; 
            
   
            //--------------------------------【片段着色函数】----------------------------- 
            // 输入:顶点输出结构体 
            // 输出:float4型的颜色值 
            //--------------------------------------------------------------------------------- 
            float4 frag(vertexOutput i) : COLOR 
            
                //【1】设置中心坐标 
                float2 center = float2(_Value2, _Value3); 
                //【2】获取纹理坐标的x,y坐标值 
                float2 uv = i.texcoord.xy; 
                //【3】纹理坐标按照中心位置进行一个偏移 
                uv -= center; 
                //【4】初始化一个颜色值 
                float4 color = float4(0.0, 0.0, 0.0, 0.0); 
                //【5】将Value乘以一个系数 
                _Value *= 0.085; 
                //【6】设置坐标缩放比例的值 
                float scale = 1; 
   
                //【7】进行纹理颜色的迭代 
                for (int j = 1; j < _IterationNumber; ++j) 
                
                    //将主纹理在不同坐标采样下的颜色值进行迭代累加 
                    color += tex2D(_MainTex, uv * scale + center); 
                    //坐标缩放比例依据循环参数的改变而变化 
                    scale = 1 + (float(j * _Value)); 
                
   
                //【8】将最终的颜色值除以迭代次数,取平均值 
                color /= (float)_IterationNumber; 
   
                //【9】返回最终的颜色值 
                return  color; 
            
   
            //===========结束CG着色器语言编写模块=========== 
            ENDCG 
        
    
}


       可以发现,这是一个单子着色器、单通道的顶点&片段着色器,顶点着色函数vert中基本上都是写的比较中规中矩的代码,精髓之处在于片段着色器frag中,用一个for

循环,将像素颜色按照一条直线(uv * scale + center)进行了迭代采样累加,最终将采样的颜色的总和除以采样次数,得到了想要实现的运动模糊效果。

 

 脚本实现部分

        脚本文件的实现方面,如下的即个点是要提出来专门讲一下的,即Shader文件的获取方法和OnRenderImage函数、Blit函数。

 

Shader文件的获取

       Shader文件的获取可以使用Shader.Find函数实现。需要注意,Shader.Find函数参数应该和Shader代码中的名称一致,也就是下面的代码框架中xxx的值,而不是Shader的文件名:

1
2
3
4
Shader "xxxx" 
    
}


       举个例子,脚本代码如果是这样:

1
CurShader = Shader.Find ("浅墨Shader编程/Volume8/运动模糊特效标准版");


       那么获取到的Shader,文件名是任意的,但Shader代码框架肯定是这样:

1
2
3
4
Shader "浅墨Shader编程/Volume8/运动模糊特效标准版" 
 …… 
}


 OnRenderImage函数与Blit函数

       OnRenderImage()函数是MonoBehaviour中提供的一个可供我们重写的函数,它在unity完成所有图片的渲染后被调用。所以我们想实现屏幕特效,主要依靠它来实现。而OnRenderImage函数的函数原型是:

1
void OnRenderImage(RenderTexture sourceTexture,RenderTexture destTexture);

       另外,我们需要配合一个Graphics.Blit函数,实现从源纹理到目标渲染纹理的拷贝过程,其原型如下三种:

1
2
3
public static void Blit(Texture source,RenderTexture dest); 
public static void Blit(Texture source,RenderTexture dest, Material mat, int pass = -1); 
public static void Blit(Texture source,Material mat, int pass = -1);

       其中。

       第一个参数,Texture类型的source,原始纹理。

       第二个参数,RenderTexture类型的dest,目标渲染纹理,若为null,表示直接将原始纹理拷贝到屏幕之上。

       第三个参数,Material类型的mat,使用的材质(其实也就是Shader),根据不同材质的准备,就是在这里实现后期的效果的。

       第四个参数,int类型的pass,有默认值 -1,表示使用所有的pass。用于指定使用哪一个pass。

       说个题外话,其实在很久之前的Win32 API游戏编程中,同样原理和相似用途的Blit函数用得太多了。

        好的,最后看一下实现屏幕特效的核心代码,如下: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture) 
 
    //着色器实例不为空,就进行参数设置 
    if (CurShader != null
    
        //设置Shader中的外部变量 
        material.SetFloat("_IterationNumber", IterationNumber); 
        material.SetFloat("_Value", Intensity); 
        material.SetFloat("_Value2", OffsetX); 
        material.SetFloat("_Value3", OffsetY); 
        material.SetFloat("_Value4", blurWidth); 
        material.SetVector("_ScreenResolution", new Vector4(sourceTexture.width, sourceTexture.height, 0.0f, 0.0f)); 
   
        //拷贝源纹理到目标渲染纹理,加上我们的材质效果 
        Graphics.Blit(sourceTexture, destTexture, material); 
    
    //着色器实例为空,直接拷贝屏幕上的效果。此情况下是没有实现屏幕特效的 
    else 
    
        //直接拷贝源纹理到目标渲染纹理 
        Graphics.Blit(sourceTexture, destTexture); 
    }

              最后看一下详细注释后的脚本完整实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
using UnityEngine; 
using System.Collections; 
   
[ExecuteInEditMode] 
   
public class MotionBlurEffects : MonoBehaviour 
   
    //-------------------变量声明部分------------------- 
    #region Variables 
    public Shader CurShader;//着色器实例 
    private Vector4 ScreenResolution;//屏幕分辨率 
    private Material CurMaterial;//当前的材质 
   
    [Range(5, 50)] 
    public float IterationNumber = 15; 
    [Range(-0.5f, 0.5f)] 
    public float Intensity = 0.125f; 
    [Range(-2f, 2f)] 
    public float OffsetX = 0.5f; 
    [Range(-2f, 2f)] 
    public float OffsetY = 0.5f; 
   
   
    public static float ChangeValue; 
    public static float ChangeValue2; 
    public static float ChangeValue3; 
    public static float ChangeValue4; 
    #endregion 
   
   
    //-------------------------材质的get&set---------------------------- 
    #region MaterialGetAndSet 
    Material material 
    
        get 
        
            if (CurMaterial == null
            
                CurMaterial = new Material(CurShader); 
                CurMaterial.hideFlags = HideFlags.HideAndDontSave; 
            
            return CurMaterial; 
        
    
    #endregion 
   
    //-----------------------------------------【Start()函数】---------------------------------------------   
    // 说明:此函数仅在Update函数第一次被调用前被调用 
    //-------------------------------------------------------------------------------------------------------- 
    void Start() 
    
        //依此赋值 
        ChangeValue = Intensity; 
        ChangeValue2 = OffsetX; 
        ChangeValue3 = OffsetY; 
        ChangeValue4 = IterationNumber; 
   
        //找到当前的Shader文件 
        CurShader = Shader.Find("浅墨Shader编程/Volume8/运动模糊特效标准版"); 
   
        //判断是否支持屏幕特效 
        if (!SystemInfo.supportsImageEffects) 
        
            enabled = false
            return
        
    
   
    //-------------------------------------【OnRenderImage()函数】------------------------------------   
    // 说明:此函数在当完成所有渲染图片后被调用,用来渲染图片后期效果 
    //-------------------------------------------------------------------------------------------------------- 
    void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture) 
    
        //着色器实例不为空,就进行参数设置 
        if (CurShader != null
        
            //设置Shader中的外部变量 
            material.SetFloat("_IterationNumber", IterationNumber); 
            material.SetFloat("_Value", Intensity); 
            material.SetFloat("_Value2", OffsetX); 
            material.SetFloat("_Value3", OffsetY); 
            material.SetVector("_ScreenResolution", new Vector4(sourceTexture.width, sourceTexture.height, 0.0f, 0.0f)); 
   
            //拷贝源纹理到目标渲染纹理,加上我们的材质效果 
            Graphics.Blit(sourceTexture, destTexture, material); 
        
        //着色器实例为空,直接拷贝屏幕上的效果。此情况下是没有实现屏幕特效的 
        else 
        
            //直接拷贝源纹理到目标渲染纹理 
            Graphics.Blit(sourceTexture, destTexture); 
        
           
    
   
   
    //-----------------------------------------【OnValidate()函数】--------------------------------------   
    // 说明:此函数在编辑器中该脚本的某个值发生了改变后被调用 
    //-------------------------------------------------------------------------------------------------------- 
    void OnValidate() 
    
        //将编辑器中的值赋值回来,确保在编辑器中值的改变立刻让结果生效 
        ChangeValue4 = IterationNumber; 
        ChangeValue = Intensity; 
        ChangeValue2 = OffsetX; 
        ChangeValue3 = OffsetY; 
   
    
   
    //-----------------------------------------【Update()函数】------------------------------------------   
    // 说明:此函数在每一帧中都会被调用 
    //--------------------------------------------------------------------------------------------------------  
    void Update() 
    
        if (Application.isPlaying) 
        
            //赋值 
            IterationNumber = ChangeValue4; 
            Intensity = ChangeValue; 
            OffsetX = ChangeValue2; 
            OffsetY = ChangeValue3; 
   
        
   
        //找到对应的Shader文件 
#if UNITY_EDITOR 
        if (Application.isPlaying != true
        
            CurShader = Shader.Find("浅墨Shader编程/Volume8/运动模糊特效标准版"); 
   
        
#endif 
    
   
   
    //-----------------------------------------【OnDisable()函数】---------------------------------------   
    // 说明:当对象变为不可用或非激活状态时此函数便被调用   
    //-------------------------------------------------------------------------------------------------------- 
    void OnDisable() 
    
        if (CurMaterial) 
        
            DestroyImmediate(CurMaterial); 
        
    
}


关于如何使用此特效

       使用方面的话比较简单,把脚本文件拖到主摄像机上面,效果就出来了。

       脚本文件中有如下这些参数可以调整,得到不同的模糊效果:




  • Iteration Number- 迭代次数
  • Intensity - 模糊强度
  • Offset X - X方向上的偏移
  • Offset Y - Y方向上的偏移

四、最终的效果展示

        这边贴几张场景的效果图和使用了屏幕特效后的效果图。需要注意的是,本次的场景效果,除了类似CS/CF的FPS游戏的控制系统以外,还可以使用键盘上的按键【F】,开启或者关闭运动模糊特效。正如下图所展示的:


 

       下面放几张测试截图。

        首先,Unity5中,导出的exe使用了新的片头Logo,质感不错,好评:

 

 

城镇中的原始效果:

 

 

运动模糊后的效果:

 

 

海港原始效果:

 


海港运动模糊后的效果:

 

 

城镇草丛前的效果:




城镇草丛前运动模糊后的效果:

沙滩原始效果 




 

沙滩运动模糊后的效果:


 

海面原始效果:


 

海面运动模糊后的效果:

 

 

五、后记

        本来准备这次更新再稍微剖析一下Unity5中主推的Standard Shader的写法思路的,但发现这篇博文的篇幅已经有点长了,那么,StandardShader就留到下次更新再讲。

       本次的更新大致如此,以后的更新依然是安排在每周一,最近一段时间尽量保证每周都更。

       最后,感谢各位捧场,我们下周再见。

       附: 本博文相关下载链接清单

       【百度云】博文示例场景exe试玩下载

       【百度云】博文示例场景所有资源与源码的Unitypackage合集下载

       【百度云】【Unity5-Shader模板中文注释格式调整版替换文件】下载

       【Github】本文全部Shader代码

      看了上面的文章 热爱游戏创作的你是不是已经开始热血沸腾了呢?是不是迫不及待的想加入游戏团队成为里面的一员呢?
        福利来啦~赶快加入腾讯GAD交流群,人满封群!每天分享游戏开发内部干货、教学视频、福利活动、和有相同梦想的人在一起,更有腾讯游戏专家手把手教你做游戏!
腾讯GAD游戏程序交流群:484290331

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引