zoukankan      html  css  js  c++  java
  • Unity5中新的Shader体系简析

    一、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模板的源码。

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

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

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

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

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

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

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

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

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

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

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

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

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

    2.2.1 TRANSFORM_TEX宏

    TRANSFORM_TEX宏的定义为:

    [cpp] view plain copy
     
     
     
      
     
     在CODE上查看代码片派生到我的代码片
    1. #define TRANSFORM_TEX(tex,name) (tex.xy *name##_ST.xy + name##_ST.zw)  

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

    2.2.2 UNITY_TRANSFER_FOG宏

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

    [cpp] view plain copy
     
     
     
      
     
     在CODE上查看代码片派生到我的代码片
    1. #if (SHADER_TARGET < 30) ||defined(SHADER_API_MOBILE)  
    2.               //手机端或者Shader Mode 2.0: 计算每个顶点的雾效因子  
    3.               #define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord =unityFogFactor  
    4.        #else  
    5.               //Shader Mode 3.0和PC和游戏机: 计算每像素的雾距离,和每像素的雾效因子  
    6.               #define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord = (outpos).z  
    7.        #endif  

    2.2.3 UNITY_APPLY_FOG宏

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

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

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

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

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

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

    2.4 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,查看详细注释好的运动模糊屏幕特效的实现源码。

    3.1 Shader实现部分

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

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

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

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

    3.2 脚本实现部分

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

    3.2.1 Shader文件的获取

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

    [csharp] view plain copy
     
    1. Shader "xxxx"    
    2. {    
    3.      
    4. }    

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

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

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

    [csharp] view plain copy
     
    1. Shader "浅墨Shader编程/Volume8/运动模糊特效标准版"    
    2. {    
    3.  ……    
    4. }    

    3.2.2 OnRenderImage函数与Blit函数

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

    [csharp] view plain copy
     
    1. void OnRenderImage(RenderTexture sourceTexture,RenderTexture destTexture);    

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

    [csharp] view plain copy
     
    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函数用得太多了。

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

    [csharp] view plain copy
     
    1. void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)    
    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.     }    

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

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

    3.3 关于如何使用此特效

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

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

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

    四、最终的效果展示

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

     

    下面放几张测试截图。

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

     

    城镇中的原始效果:

     

    运动模糊后的效果:

     

    海港原始效果:

     

    海港运动模糊后的效果:

     

    城镇草丛前的效果:

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

     

    沙滩原始效果:

    沙滩运动模糊后的效果:

    海面原始效果:

    海面运动模糊后的效果:

     

  • 相关阅读:
    Zabbix 管理界面中文出现乱码解决方法
    zabbix应用监控实践
    linux系统基础优化
    linux下创建软链--laravel软链
    php使用webservice调用C#服务端/调用PHP服务端
    php+ajax远程加载避免重复提交
    date
    函数类型
    DOM及其扩展
    字符串的截取方法
  • 原文地址:https://www.cnblogs.com/lancidie/p/7827631.html
Copyright © 2011-2022 走看看