Shaderlab Notizen 6 Unity5新版Shad

作者: CarlDonitz | 来源:发表于2016-10-22 14:05 被阅读79次

    一、Unity5中的Shader

    • Standard Surface Shader标准表面着色器
    • Unlit Shader无灯光着色器
    • Image Effect Shader图像特效着色器

    二、Unity5中Shader源码解析
    2.1 Standard Surface Shader(标准表面着色器)
    Standard Surface Shader模板的脉络很清晰,先是定义一些属性,然后在SubShader中设置渲染模式,层次细节LOD的值,然后开启一个CG编程语言模块,写一些编译指令#pragma,声明一下变量让属性值在CG块中可见,定义输入结构,然后填充一下表面着色函数即可。
    ps:SurfaceShader不能使用Pass,直接在SubShader中实现和填充代码即可。

    举个栗子:

    Shader "Shader/Standard 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"
    }
    

    2.2 Unlit Shader(无灯光着色器)
    Unlit Shader,简单来说,就是直接采用漫反射纹理,不考虑场景中的任何灯光效果。使用无灯光着色器的话,也就不能使用任何镜面或者法线效果了。

    内置Unlit系的着色器有如下四种:

    Paste_Image.png

    举个栗子:

    Shader "Shader/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
                  }
           }
    }
    

    ps:无灯光着色器中使用了一些UnityCG.cginc头文件中内置的宏,比如说TRANSFORM_TEX、UNITY_TRANSFER_FOG、UNITY_APPLY_FOG。

    2.2.1 TRANSFORM_TEX宏

    定义:

    #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宏

    定义:

    #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_TRANSFER_FOG宏的作用是从顶点着色输出雾数据。在UnityCG.cginc(Unity5.2.1版本)的第772行起

    2.2.3 UNITY_APPLY_FOG宏

    定义:

    #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来指定特定颜色的雾效。

    2.3 Image Effect Shader(图像特效着色器)
    这里的图像特效一般指的就是屏幕图像特效,在Camera加上各种滤镜,比如说屏幕溅血,像素化,色调的调整,画面模糊等效果。其也是一个顶点&片段着色器,且一般主要的操作集中在片段着色函数中。

    举个栗子:

    Shader "Shader/Image Effect 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实现部分
    脚本实现部分

    3.1 Shader实现部分

    Shader "Shader/运动模糊特效"
    {
        //------------------------------------【属性值】------------------------------------
        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
            }
        }
    }
    

    ps:这是一个单子着色器、单通道的顶点&片段着色器,顶点着色函数vert中基本上都是写的比较中规中矩的代码,精髓之处在于片段着色器frag中,用一个for循环,将像素颜色按照一条直线(uv * scale + center)进行了迭代采样累加,最终将采样的颜色的总和除以采样次数,得到了想要实现的运动模糊效果。

    3.2 脚本实现部分

    3.2.1 Shader文件的获取
    Shader文件的获取可以使用Shader.Find函数实现。

    CurShader = Shader.Find (“Name”);
    {
         …...
    }
    

    3.2.2 OnRenderImage函数与Blit函数

    nRenderImage()函数是MonoBehaviour中提供的一个可供重写的函数,它在unity完成所有图片的渲染后被调用。

    OnRenderImage函数的函数原型:

    void OnRenderImage(RenderTexture sourceTexture,RenderTexture destTexture);
    

    Graphics.Blit函数的函数原型:

    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);
    

    ps:其中,第一个参数,Texture类型的source,原始纹理;
    第二个参数,RenderTexture类型的dest,目标渲染纹理,若为null,表示直接将原始纹理copy到屏幕上。
    第三个参数,Material类型的mat,使用的材质(就是Shader),根据不同材质的准备,就是在这里实现后期的效果的;
    第四个参数,int类型的pass,默认值-1,表示使用所有的pass,用于指定使用的pass。

    核心代码:

    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);
        }
    

    完整代码:

    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/Name");
    
            //判断是否支持屏幕特效
            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);
            }
        }
    }
    

    3.3 使用方法
    拖拽

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

    相关文章

      网友评论

        本文标题:Shaderlab Notizen 6 Unity5新版Shad

        本文链接:https://www.haomeiwen.com/subject/iqhhuttx.html