(下)Unity5新版Shader模板源码解析&运动模糊屏幕特效的实现...
Shader材质球TA图形学渲染引擎 6034 55
实名

通过了实名认证的内容创造者

发布于 2015-10-28 10:25:16

您需要 登录 才可以下载或查看,没有账号?注册

x
【已获得授权转载】
承接上文:(上)Unity5新版Shader模板源码解析&运动模糊屏幕特效的实现 (注意。原本叫长。为方便观看。特分上下篇幅。)

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


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

Shader实现部分
脚本实现部分

下面我们对运动模糊屏幕特效的实现分别进行简单的描述。
可以点击这里跳转到Github,查看详细注释好的运动模糊屏幕特效的实现源码。


3.1 Shader实现部分
先看一下Shader代码的写法,因为基本上已经逐行注释,就不花时间和笔墨仔细讲解了,详细注释的代码如下:
  1. Shader "浅墨Shader编程/Volume8/运动模糊特效标准版"
  2. {
  3.     //------------------------------------【属性值】------------------------------------
  4.     Properties
  5.     {
  6.         _MainTex("主纹理 (RGB)", 2D) = "white" {}
  7.         _IterationNumber("迭代次数", Int)=16
  8.     }

  9.     //------------------------------------【唯一的子着色器】------------------------------------
  10.     SubShader
  11.     {   
  12.         //--------------------------------唯一的通道-------------------------------
  13.         Pass
  14.         {
  15.             //设置深度测试模式:渲染所有像素.等同于关闭透明度测试(AlphaTest Off)
  16.             ZTest Always

  17.             //===========开启CG着色器语言编写模块===========
  18.             CGPROGRAM

  19.             //编译指令: 指定着色器编译目标为Shader Model 3.0
  20.             #pragma target 3.0

  21.             //编译指令:告知编译器顶点和片段着色函数的名称
  22.             #pragma vertex vert
  23.             #pragma fragment frag

  24.             //包含辅助CG头文件
  25.             #include "UnityCG.cginc"

  26.             //外部变量的声明
  27.             uniform sampler2D _MainTex;
  28.             uniform float _Value;
  29.             uniform float _Value2;
  30.             uniform float _Value3;
  31.             uniform int _IterationNumber;

  32.             //顶点输入结构
  33.             struct vertexInput
  34.             {
  35.                 float4 vertex : POSITION;//顶点位置
  36.                 float4 color : COLOR;//颜色值
  37.                 float2 texcoord : TEXCOORD0;//一级纹理坐标
  38.             };

  39.             //顶点输出结构
  40.             struct vertexOutput
  41.             {
  42.                 half2 texcoord : TEXCOORD0;//一级纹理坐标
  43.                 float4 vertex : SV_POSITION;//像素位置
  44.                 fixed4 color : COLOR;//颜色值
  45.             };


  46.             //--------------------------------【顶点着色函数】-----------------------------
  47.             // 输入:顶点输入结构体
  48.             // 输出:顶点输出结构体
  49.             //---------------------------------------------------------------------------------
  50.             vertexOutput vert(vertexInput Input)
  51.             {
  52.                 //【1】声明一个输出结构对象
  53.                 vertexOutput Output;

  54.                 //【2】填充此输出结构
  55.                 //输出的顶点位置为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口
  56.                 Output.vertex = mul(UNITY_MATRIX_MVP, Input.vertex);
  57.                 //输出的纹理坐标也就是输入的纹理坐标
  58.                 Output.texcoord = Input.texcoord;
  59.                 //输出的颜色值也就是输入的颜色值
  60.                 Output.color = Input.color;

  61.                 //【3】返回此输出结构对象
  62.                 return Output;
  63.             }

  64.             //--------------------------------【片段着色函数】-----------------------------
  65.             // 输入:顶点输出结构体
  66.             // 输出:float4型的颜色值
  67.             //---------------------------------------------------------------------------------
  68.             float4 frag(vertexOutput i) : COLOR
  69.             {
  70.                 //【1】设置中心坐标
  71.                 float2 center = float2(_Value2, _Value3);
  72.                 //【2】获取纹理坐标的x,y坐标值
  73.                 float2 uv = i.texcoord.xy;
  74.                 //【3】纹理坐标按照中心位置进行一个偏移
  75.                 uv -= center;
  76.                 //【4】初始化一个颜色值
  77.                 float4 color = float4(0.0, 0.0, 0.0, 0.0);
  78.                 //【5】将Value乘以一个系数
  79.                 _Value *= 0.085;
  80.                 //【6】设置坐标缩放比例的值
  81.                 float scale = 1;

  82.                 //【7】进行纹理颜色的迭代
  83.                 for (int j = 1; j < _IterationNumber; ++j)
  84.                 {
  85.                     //将主纹理在不同坐标采样下的颜色值进行迭代累加
  86.                     color += tex2D(_MainTex, uv * scale + center);
  87.                     //坐标缩放比例依据循环参数的改变而变化
  88.                     scale = 1 + (float(j * _Value));
  89.                 }

  90.                 //【8】将最终的颜色值除以迭代次数,取平均值
  91.                 color /= (float)_IterationNumber;

  92.                 //【9】返回最终的颜色值
  93.                 return  color;
  94.             }

  95.             //===========结束CG着色器语言编写模块===========
  96.             ENDCG
  97.         }
  98.     }
  99. }
点击此处复制文本
可以发现,这是一个单子着色器、单通道的顶点&片段着色器,顶点着色函数vert中基本上都是写的比较中规中矩的代码,精髓之处在于片段着色器frag中,用一个for
循环,将像素颜色按照一条直线(uv * scale + center)进行了迭代采样累加,最终将采样的颜色的总和除以采样次数,得到了想要实现的运动模糊效果。


3.2 脚本实现部分

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



3.2.1 Shader文件的获取



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

  1. Shader "xxxx"
  2. {  
  3.    
  4. }
点击此处复制文本

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

  1. CurShader = Shader.Find ("浅墨Shader编程/Volume8/运动模糊特效标准版");
点击此处复制文本



那么获取到的Shader,文件名是任意的,但Shader代码框架肯定是这样:
  1. Shader "浅墨Shader编程/Volume8/运动模糊特效标准版"
  2. {  
  3. ……  
  4. }
点击此处复制文本


3.2.2 OnRenderImage函数与Blit函数

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


  1. void OnRenderImage(RenderTexture sourceTexture,RenderTexture destTexture);
点击此处复制文本
另外,我们需要配合一个Graphics.Blit函数,实现从源纹理到目标渲染纹理的拷贝过程,其原型如下三种:
  1. public static void Blit(Texture source,RenderTexture dest);  
  2. public static void Blit(Texture source,RenderTexture dest, Material mat, int pass = -1);  
  3. 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. void OnRenderImage(RenderTexture sourceTexture, RenderTexturedestTexture)  
  2. {  
  3.     //着色器实例不为空,就进行参数设置  
  4.     if (CurShader != null)  
  5.     {  
  6.         //设置Shader中的外部变量  
  7.         material.SetFloat("_IterationNumber", IterationNumber);  
  8.         material.SetFloat("_Value", Intensity);  
  9.         material.SetFloat("_Value2", OffsetX);  
  10.         material.SetFloat("_Value3", OffsetY);  
  11.         material.SetFloat("_Value4", blurWidth);  
  12.         material.SetVector("_ScreenResolution", new Vector4(sourceTexture.width, sourceTexture.height, 0.0f, 0.0f));  
  13.    
  14.         //拷贝源纹理到目标渲染纹理,加上我们的材质效果  
  15.         Graphics.Blit(sourceTexture, destTexture, material);  
  16.     }  
  17.     //着色器实例为空,直接拷贝屏幕上的效果。此情况下是没有实现屏幕特效的  
  18.     else
  19.     {  
  20.         //直接拷贝源纹理到目标渲染纹理  
  21.         Graphics.Blit(sourceTexture, destTexture);  
  22.     }
点击此处复制文本


最后看一下详细注释后的脚本完整实现代码:
  1. using UnityEngine;using System.Collections;

  2. [ExecuteInEditMode]

  3. public class MotionBlurEffects : MonoBehaviour
  4. {

  5.     //-------------------变量声明部分-------------------
  6.     #region Variables
  7.     public Shader CurShader;//着色器实例
  8.     private Vector4 ScreenResolution;//屏幕分辨率
  9.     private Material CurMaterial;//当前的材质

  10.     [Range(5, 50)]
  11.     public float IterationNumber = 15;
  12.     [Range(-0.5f, 0.5f)]
  13.     public float Intensity = 0.125f;
  14.     [Range(-2f, 2f)]
  15.     public float OffsetX = 0.5f;
  16.     [Range(-2f, 2f)]
  17.     public float OffsetY = 0.5f;


  18.     public static float ChangeValue;
  19.     public static float ChangeValue2;
  20.     public static float ChangeValue3;
  21.     public static float ChangeValue4;
  22.     #endregion


  23.     //-------------------------材质的get&set----------------------------
  24.     #region MaterialGetAndSet
  25.     Material material
  26.     {
  27.         get
  28.         {
  29.             if (CurMaterial == null)
  30.             {
  31.                 CurMaterial = new Material(CurShader);
  32.                 CurMaterial.hideFlags = HideFlags.HideAndDontSave;
  33.             }
  34.             return CurMaterial;
  35.         }
  36.     }
  37.     #endregion

  38.     //-----------------------------------------【Start()函数】---------------------------------------------  
  39.     // 说明:此函数仅在Update函数第一次被调用前被调用
  40.     //--------------------------------------------------------------------------------------------------------
  41.     void Start()
  42.     {
  43.         //依此赋值
  44.         ChangeValue = Intensity;
  45.         ChangeValue2 = OffsetX;
  46.         ChangeValue3 = OffsetY;
  47.         ChangeValue4 = IterationNumber;

  48.         //找到当前的Shader文件
  49.         CurShader = Shader.Find("浅墨Shader编程/Volume8/运动模糊特效标准版");

  50.         //判断是否支持屏幕特效
  51.         if (!SystemInfo.supportsImageEffects)
  52.         {
  53.             enabled = false;
  54.             return;
  55.         }
  56.     }

  57.     //-------------------------------------【OnRenderImage()函数】------------------------------------  
  58.     // 说明:此函数在当完成所有渲染图片后被调用,用来渲染图片后期效果
  59.     //--------------------------------------------------------------------------------------------------------
  60.     void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
  61.     {
  62.         //着色器实例不为空,就进行参数设置
  63.         if (CurShader != null)
  64.         {
  65.             //设置Shader中的外部变量
  66.             material.SetFloat("_IterationNumber", IterationNumber);
  67.             material.SetFloat("_Value", Intensity);
  68.             material.SetFloat("_Value2", OffsetX);
  69.             material.SetFloat("_Value3", OffsetY);
  70.             material.SetVector("_ScreenResolution", new Vector4(sourceTexture.width, sourceTexture.height, 0.0f, 0.0f));

  71.             //拷贝源纹理到目标渲染纹理,加上我们的材质效果
  72.             Graphics.Blit(sourceTexture, destTexture, material);
  73.         }
  74.         //着色器实例为空,直接拷贝屏幕上的效果。此情况下是没有实现屏幕特效的
  75.         else
  76.         {
  77.             //直接拷贝源纹理到目标渲染纹理
  78.             Graphics.Blit(sourceTexture, destTexture);
  79.         }
  80.          
  81.     }


  82.     //-----------------------------------------【OnValidate()函数】--------------------------------------  
  83.     // 说明:此函数在编辑器中该脚本的某个值发生了改变后被调用
  84.     //--------------------------------------------------------------------------------------------------------
  85.     void OnValidate()
  86.     {
  87.         //将编辑器中的值赋值回来,确保在编辑器中值的改变立刻让结果生效
  88.         ChangeValue4 = IterationNumber;
  89.         ChangeValue = Intensity;
  90.         ChangeValue2 = OffsetX;
  91.         ChangeValue3 = OffsetY;

  92.     }

  93.     //-----------------------------------------【Update()函数】------------------------------------------  
  94.     // 说明:此函数在每一帧中都会被调用
  95.     //--------------------------------------------------------------------------------------------------------
  96.     void Update()
  97.     {
  98.         if (Application.isPlaying)
  99.         {
  100.             //赋值
  101.             IterationNumber = ChangeValue4;
  102.             Intensity = ChangeValue;
  103.             OffsetX = ChangeValue2;
  104.             OffsetY = ChangeValue3;

  105.         }

  106.         //找到对应的Shader文件
  107. #if UNITY_EDITOR
  108.         if (Application.isPlaying != true)
  109.         {
  110.             CurShader = Shader.Find("浅墨Shader编程/Volume8/运动模糊特效标准版");

  111.         }
  112. #endif
  113.     }


  114.     //-----------------------------------------【OnDisable()函数】---------------------------------------  
  115.     // 说明:当对象变为不可用或非激活状态时此函数便被调用  
  116.     //--------------------------------------------------------------------------------------------------------
  117.     void OnDisable()
  118.     {
  119.         if (CurMaterial)
  120.         {
  121.             DestroyImmediate(CurMaterial);
  122.         }
  123.     }
  124. }
点击此处复制文本

3.3 关于如何使用此特效

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

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


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

四、最终的效果展示

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


下面是场景实例运行的截图:




















五、后记

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

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

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



附: 本文相关下载链接清单回复可见:
尊敬的游客,如果您要查看本帖关注 或 回复可见内容请关注回复后刷新页面查看!


本系列文章由@浅墨_毛星云 出品
        文章链接: http://blog.csdn.net/poem_qianmo/article/details/49405909
        作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442

评分

参与人数 1活跃度 +6 展开 理由
knightzzz + 6 【给力】阅贴无数,楼主最强!

查看全部评分

还没有设置签名!您可以在此展示你的链接,或者个人主页!
使用道具 <
a-xing  发表于 2015-10-28 10:26:47  
2#
元素z~~~~~~~~~~~~~~
回复 收起回复
使用道具
Gladys  发表于 2015-10-28 13:28:54  
3#
元素那么大,我想来看看!
回复 收起回复
使用道具
野得像驴  发表于 2015-10-28 13:54:42  
4#
立刻提起了精神。
回复 收起回复
使用道具
suyu1225  发表于 2015-10-28 16:48:49  
5#
ddddddddddddddddddddddddddddddddd
回复 收起回复
使用道具
gudengchou  发表于 2015-10-28 18:32:28  
6#
写的不错,学习了
回复 收起回复
使用道具
gudengchou  发表于 2015-10-28 18:32:32  
7#
写的不错,学习了
回复 收起回复
使用道具
gudengchou  发表于 2015-10-28 18:32:45  
8#
写的不错,学习了
回复 收起回复
使用道具
gudengchou  发表于 2015-10-28 18:32:52  
9#
写的不错,学习了
回复 收起回复
使用道具
gudengchou  发表于 2015-10-28 18:32:53  
10#
写的不错,学习了
回复 收起回复
使用道具
正雨2015  发表于 2015-10-29 08:37:43  
11#
{:1_145:}
回复 收起回复
使用道具
maoyaohau123  发表于 2015-10-30 09:25:24  
12#
正是本尊想要的!
回复 收起回复
使用道具
Als@  发表于 2015-11-5 22:33:18  
13#
元素那么大,我想来看看!
回复 收起回复
使用道具
果核  发表于 2015-11-6 17:49:07  
14#
{:1_146:}  不错
回复 收起回复
使用道具
美术-韩林  发表于 2015-11-10 10:18:28  
15#
天下武功出少林,世界资源入元素!
回复 收起回复
使用道具
Tide丶  发表于 2015-11-12 13:06:25  
16#
天下武功出少林,世界资源入元素!
回复 收起回复
使用道具
b8601166  发表于 2015-11-25 14:28:18  
17#
这个非常好,非常感谢
回复 收起回复
使用道具
残笑未了  发表于 2015-12-28 18:24:27  
19#
感谢楼主分享
回复 收起回复
使用道具
cascyhui  发表于 2016-1-14 07:13:34  
20#
元素帖子强,满满正能量!
回复 收起回复
使用道具
1234下一页
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表