本章节学习目标:

  • 熟悉UnityShaderlab框架
  • 学习Shader代码入门基础

环境配置:


常用语法

clip()          //裁剪,低于括号中的数值的时候被渲染识别舍弃掉
saturate()      //归一化矢量,范围限制掉0-1

一、UnityShaderLab基本框架

Shader "123" //Shader Name  目录结构写法:"CS01/123"
{
    Properties //实例属性,实例在材质面板上的属性
    {
        _Float("Float",Float) = 0.0                     //浮点类型实例
        _Range("Range",Range(0.0,1.0)) = 0.0            //浮点范围类型实例
        _Color("Color",Color) = (0.5,0.5,0.5,0.5)    //颜色类型实例
        _Vector("Vector",Vector) = (1,1,1,1)            //向量类型实例
        _Texture("Texture", 2D) = "white" { }           //贴图类型实例

    }
    SubShader //子材质属性,是shader中给的一个子块,每个块描述了如何在特定条件下渲染对象
    {
        Pass // subshader的组成部分,定义了一个具体的过程,一个subshader可以包含多个Pass,每个Pass代表一次渲染操作
        {
            //Pass内容
            //1、分阶段渲染
            //  - Pass 描述了一个渲染阶段,从顶点处理到片元处理的过程。
            //  - 一个对象可以通过多个 Pass 被多次渲染,例如阴影渲染、光照渲染等。
            //2、渲染控制
            //  - 每个 Pass 可以定义自己的渲染状态(如混合模式、深度测试等),并决定如何处理顶点和片元。
            //3、多Pass支持复杂效果
            //  - 对于需要多次绘制的效果(如多光源、后处理),可以通过多个 Pass 实现。
        }
    }
}

二、UnityShaderLab内容(示例)

Shader "CS02/MiniShader" //Shader的真正名字  可以是路径式的格式
{
    /*材质球参数及UI面板
    https://docs.unity3d.com/cn/current/Manual/SL-Properties.html
    https://docs.unity3d.com/cn/current/ScriptReference/MaterialPropertyDrawer.html
    https://zhuanlan.zhihu.com/p/93194054
    */
    Properties 
    {
        _MainTex ("Texture", 2D) = "" {}
        _Float("Float", Float) = 0.0
        _Slider("Slider", Range(0.0,1.0)) = 0.07
        _Vector("Vector", Vector) = (.34, .85, .92, 1) 
    }
    /*
    这是为了让你可以在一个Shader文件中写多种版本的Shader,但只有一个会被使用。
    提供多个版本的SubShader,Unity可以根据对应平台选择最合适的Shader
    或者配合LOD机制一起使用。
    一般写一个即可
    */
    SubShader
    {
        /*
        标签属性,有两种:一种是SubShader层级,一种在Pass层级
        https://docs.unity3d.com/cn/current/Manual/SL-SubShaderTags.html
        https://docs.unity3d.com/cn/current/Manual/SL-PassTags.html
        */
        Tags { "RenderType"="Opaque" } 
        /*
        Pass里面的内容Shader代码真正起作用的地方,
        一个Pass对应一个真正意义上运行在GPU上的完整着色器(Vertex-Fragment Shader)
        一个SubShader里面可以包含多个Pass,每个Pass会被按顺序执行
        */
        Pass 
        {
            CGPROGRAM  // Shader代码从这里开始
            #pragma vertex vert //指定一个名为"vert"的函数为顶点Shader
            #pragma fragment frag //指定一个名为"frag"函数为片元Shader
            #include "UnityCG.cginc"  //引用Unity内置的文件,很方便,有很多现成的函数提供使用

            //https://docs.unity3d.com/Manual/SL-VertexProgramInputs.html
            struct appdata  //CPU向顶点Shader提供的模型数据
            {
                //冒号后面的是特定语义词,告诉CPU需要哪些类似的数据
                float4 vertex : POSITION; //模型空间顶点坐标
                half2 texcoord0 : TEXCOORD0; //第一套UV
                half2 texcoord1 : TEXCOORD1; //第二套UV
                half2 texcoord2 : TEXCOORD2; //第二套UV
                half2 texcoord4 : TEXCOORD3;  //模型最多只能有4套UV

                half4 color : COLOR; //顶点颜色
                half3 normal : NORMAL; //顶点法线
                half4 tangent : TANGENT; //顶点切线(模型导入Unity后自动计算得到)
            };

            struct v2f  //自定义数据结构体,顶点着色器输出的数据,也是片元着色器输入数据
            {
                float4 pos : SV_POSITION; //输出裁剪空间下的顶点坐标数据,给光栅化使用,必须要写的数据
                float2 uv : TEXCOORD0; //自定义数据体
                //注意跟上方的TEXCOORD的意义是不一样的,上方代表的是UV,这里可以是任意数据。
                //插值器:输出后会被光栅化进行插值,而后作为输入数据,进入片元Shader
                //最多可以写16个:TEXCOORD0 ~ TEXCOORD15。
                float3 normal : TEXCOORD1;
            };

            /*
            Shader内的变量声明,如果跟上面Properties模块内的参数同名,就可以产生链接
            */
            sampler2D _MainTex;
            float4 _MainTex_ST;
            //Unity内置变量:https://docs.unity3d.com/560/Documentation/Manual/SL-UnityShaderVariables.html
            //Unity内置函数:https://docs.unity3d.com/560/Documentation/Manual/SL-BuiltinFunctions.html

            //顶点Shader
            v2f vert (appdata v)
            {
                v2f o;
                float4 pos_world = mul(unity_ObjectToWorld, v.vertex);
                float4 pos_view = mul(UNITY_MATRIX_V, pos_world);
                float4 pos_clip = mul(UNITY_MATRIX_P, pos_view);
                o.pos = pos_clip;
                //o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                //o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord0 * _MainTex_ST.xy + _MainTex_ST.zw;
                //o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.normal = v.normal;
                return o;
            }
            //片元Shader
            half4 frag (v2f i) : SV_Target //SV_Target表示为:片元Shader输出的目标地(渲染目标)
            {
                //fixed4 col = tex2D(_MainTex, i.uv);
                half4 col = float4(i.uv,0.0,0.0);
                return col;
            }
            ENDCG // Shader代码从这里结束
        }
    }
}

渲染常用设置:

    Properties
    {
        _MinTex("MinTex",2D) = "black"{}
        [Enum(UnityEngine.Rendering,CullMode)]_CullMode("CullMode",Float) = 2
        //实例剔除选择按钮到材质面板
        //Enum(UnityEngine.Rendering,CullMode)Unity内置的属性名
        //_CullMode("CullMode",Float) = 2 实例化的名字和默认数值
    }
    SubShader
    {   
        Tags{"Queue" = "Transparent"}   //渲染队列
        Pass
        {
            Zwrite Off   //深度开关
            Blend SrcAlpha OneMinusSrcAlpha  //混合模式开关
            Cull Off //背面剔除开关  1、Cull Off  背面剔除关闭; 2、Cull Front 正面剔除
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            struct appdate
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

制作测试示例

  • 动态波纹案例

本次示例学到了Time场景时间参数、clip参数的使用和控制UV的方式。

```hlsl shader "Unit/ceshi" { Properties { _Color("Color",Color) = (1,1,1,1) _MainTex("MainTex",2D) = "white" _Noistex("Noistex",2D) = "white" _Vector("Vector",Vector) = (0,0,0,0) _Ton("Ton",Float) = 0.0 _Speed("Speed",Vector) = (0,0,0,0) } SubShader { Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Unitycg.cginc"

          struct appdata
          {
              float4 vertex : POSITION;
              float2 uv : TEXCOORD0;
          };

          struct v2f
          {
              float4 vertex : SV_POSITION;
              float2 uv : TEXCOORD0;
          };
          float4 _Color;
          sampler2D _MainTex;
          sampler2D _Noistex;
          float4 _Vector;
          float _Ton;
          float4 _Speed;

          v2f vert(appdata v)
          {
              v2f o;
              o.vertex = UnityObjectToClipPos(v.vertex);
              o.uv = v.uv + _Vector.xy + _Vector.zw ;
              return o;
          }

          float4 frag (v2f i) : SV_Target
          {
              half Maintex = tex2D(_MainTex,i.uv + _Time.y * _Speed.xy);
              half nois = tex2D (_Noistex,i.uv + _Time.y * _Speed.zw);
              clip(Maintex - nois - _Ton);
              return Maintex.xxxx;
          }

          ENDCG
      }
  }

} ```

  • 半透明混合(问题以及解决方案)

关闭深度写入

打开半透明混合

渲染队列排序

```hlsl shader "Unit/ceshi" { Properties { _Color("Color",Color) = (1,1,1,1) _MainTex("MainTex",2D) = "white" // _Noistex("Noistex",2D) = "white" // _Vector("Vector",Vector) = (0,0,0,0) // _Ton("Ton",Float) = 0.0 _Emiss("Emiss",Float) = 1.0 // _Speed("Speed",Vector) = (0,0,0,0) } SubShader { ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "Unitycg.cginc"

          struct appdata
          {
              float4 vertex : POSITION;
              float2 uv : TEXCOORD0;
          };

          struct v2f
          {
              float4 vertex : SV_POSITION;
              float2 uv : TEXCOORD0;
          };
          float4 _Color;
          sampler2D _MainTex;
          sampler2D _Noistex;
          float4 _Vector;
          float _Ton;
          float4 _Speed;
          float _Emiss;

          v2f vert(appdata v)
          {
              v2f o;
              o.vertex = UnityObjectToClipPos(v.vertex);
              o.uv = v.uv + _Vector.xy + _Vector.zw ;
              return o;
          }

          float4 frag (v2f i) : SV_Target
          {
              // half Maintex = tex2D(_MainTex,i.uv + _Time.y * _Speed.xy);
              // half nois = tex2D (_Noistex,i.uv + _Time.y * _Speed.zw);
              // clip(Maintex - nois - _Ton);
              half3 col = _Color.xyz * _Emiss;
              half alpha = saturate(tex2D(_MainTex,i.uv).r * _Color .a * _Emiss);
              return float4(col,alpha);
          }

          ENDCG
      }
  }

} ```

  • 边缘光Shader案例

知识点:

  • 计算摄像机和物体之间的点乘结果计算物体边缘
  • 边缘与中心的光源渐变
  • 预先写深度

```hlsl Shader "Unit/Rim" { Properties { _MainColor("MinColor",Color) = (1,1,1,1) _Emiss("Emiss",Float) = 0.0 _Power("Power",Float) = 0.0 } SubShader { Tags {"Queue" = "Transparent"} //预先写深度的pass Pass { Cull Off ZWrite On ColorMask 0 CGPROGRAM float4 _Color; #pragma vertex vert #pragma fragment frag float4 vert(float4 vertexPos : POSITION) : SV_POSITION { return UnityObjectToClipPos(vertexPos); } float4 frag(void) : COLOR { return _Color; }

          ENDCG
      }
      Pass
      {
          ZWrite On
          Blend SrcAlpha One
          CGPROGRAM
          #pragma vertex vert
          #pragma fragment frag
          #include "UnityCG.cginc"

          struct appdata
          {
              float4 vertex : POSITION;
              float3 normal : NORMAL;
              float2 uv : TEXCOORD0;
          };
          struct v2f
          {
              float4 vertex : SV_POSITION;
              float3 normal_world : NORMAL;
              float2 pos_uv : TEXCOORD0;
              float3 view_world : TEXCOORD1;
          };
          float4 _MainColor;
          float _Emiss;
          float _Power;

          v2f vert(appdata v)
          {
              v2f o;
              o.vertex = UnityObjectToClipPos(v.vertex);
              o.pos_uv = v.uv;
              o.normal_world =normalize( mul(float4(v.normal,0.0),unity_WorldToObject).xyz); //计算世界法线
              float3 pos_world = mul(unity_ObjectToWorld,v.vertex).xyz;
              o.view_world = normalize(_WorldSpaceCameraPos.xyz - pos_world) ; //计算摄像机的世界法线
              return o;
          }

          float4 frag(v2f i) : SV_Target
          {
              half3 normal_world = normalize( i.normal_world);
              half3 view_world = normalize( i.view_world);
              half NdotV = saturate(dot(normal_world,view_world)) ;
              half3 col = _MainColor.xyz *_Emiss;
              float fresnel = pow((1.0 - NdotV) , _Power );
              half alpha =saturate(fresnel *_Emiss); 
              return float4(col,alpha);
          }
          ENDCG

```