UnityShader官方案例讲解——SurfaceShader(4)

“Without purpose, the days would have ended, as such days always end, in disintegration.”

– , Explorer

step1
经世界坐标空间位置的切片

Shader"Example / AutisticPatient SurfaceShader8"
{
    Properties
    {
        _MainTex("MainTex",2D) = "white"{}
        _Bump("Bump",2D) = "Bump"{}
    }

    SubShader
    {
        Tags{"RenderType" = "Opaque"}
        CGPROGRAM
        #pragma surface surf Lambert

        //cull off是关闭阴影面剔除
        //这里说一下Culling指令:culling是阴影面剔除的一种优化技术
        //通常情况下,所有的多边形都有正反两面,而背面往往是摄像机看不见的,所以会将看不见的那面剔除掉
        //cull back : 剔除背面 
        //cull front : 剔除正面
        Cull off

        struct Input
        {
            float2 uv_MainTex;
            float2 uv_Bump;
            //世界空间位置,是个三维向量
            float3 worldPos;
        }

        sampler2D _MainTex;
        sampler2D _Bump;

        void surf(Input IN,inout SurfaceOutput o)
        {
            //clip(x) : 如果输入向量中的任何元素小于0,则丢弃当前像素。 
            //frac() 得作用很简单 ,举个例子:在frac函数内计算出的值若为1.5,frac函数将会取出0.5作为最终值输出
            //也就是说frac里的计算结果如果小于0.5,那么这个像素点就被丢弃了
            clip(frac( (IN.worldPos.y + IN.worldPos.z * 0.1) * 5) - 0.5);

            //着色器会找到贴图上对应的UV坐标点,直接使用这个点的颜色信息rgb来进行着色.
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;

            //这里放弃以前的通俗解释
            //UnpackNormal是标准法线解压函数,直接看源码
            o.Normal = UnpackNormal (tex2D (_BumpMap, IN.uv_BumpMap));
        }

            //这里打开UnityCG.cginc,找到这个函数
            //下面这些解释都是风宇冲前辈的经验之谈,想看更详细的内容百度:风宇冲
            inline fixed3 UnpackNormal(fixed4 packednormal)
            {   

                //该函数进行了预定义,如果是移动平台或者OpenGL ES,那么断定使用的是RGB法线贴图,否则则为DXT5nm贴图。
                //但实际上移动平台也可以用压缩格式的法线贴图,而Windows也能使用RGB法线贴图。
                #if defined(SHADER_API_GLES) && defined(SHADER_API_MOBILE)
                return packednormal.xyz * 2 - 1;
                #else
                fixed3 normal;
                normal.xy = packednormal.wy * 2 - 1;
                normal.z = sqrt(1 - normal.x*normal.x - normal.y * normal.y);
                return normal;
                #endif
            }


        ENDCG
    }
}

最后一段对UnpackNormal的解释大家不理解也没关系,不影响接下来的学习,循序渐进嘛。
还是上一个官方的效果图吧
这里写图片描述

step2
使用顶点修改器进行法线挤压

沿法线移动顶点使游戏物体变得臃肿
沿着法线的方向移动顶点的位置,可以想象,其实就相当于把整个模型表面的顶点沿着法线方向“拉长了一截”,,,,而法线是垂直于各自的三角面的,,,所以,,,,它就“胖了”

Shader"Example / AutisticPatient SurfaceShader9"
 {
    Properties 
    {
      _MainTex ("Texture", 2D) = "white" {}
      //range是在Inspector面板中定义了一个滑动条,用于面板控制,不需要对代码进行修改
      _Amount ("Extrusion Amount", Range(-1,1)) = 0.5
    }

    SubShader 
    {
      //子着色器标签,渲染非透明物体
      Tags { "RenderType" = "Opaque" }

      CGPROGRAM
      //定义表面着色器surf,光照模型Lambert , 顶点着色器vert
      #pragma surface surf Lambert vertex:vert

      struct Input 
      {
          float2 uv_MainTex;
      };

      float _Amount;

      //顶点着色器,vertex shader的函数名必须和#pragma 编译指令下的名称一致,否则shader找不到入口
      void vert (inout appdata_full v) 
      {
          //appdata_full 输入了一个顶点,并将顶点和当前的法线进行重叠,最后用float值进行倍增。
          //还不理解的话建议拆开来看,当然要遵循运算符优先法则
          //物体的法线 * float值 进行扩大
          //然后顶点的xyz坐标与扩大后的法线进行叠加,顶点的xyz就会在法线的方向基础上进行不断的++
          v.vertex.xyz += v.normal * _Amount;
      }

      sampler2D _MainTex;

      //表面着色器
      void surf (Input IN, inout SurfaceOutput o) 
      {
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

上效果图,能看出“肿”就可以了
这里写图片描述

这里写图片描述

step3
逐顶点计算的自定义数据

Shader"Example / AutisticPatient SurfaceShader10"
 {
    Properties 
    {
      _MainTex ("Texture", 2D) = "white" {}
    }

    SubShader 
    {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Lambert vertex:vert

      struct Input 
      {
          float2 uv_MainTex;
          float3 customColor;
      };

      //在这顶点函数内,Input结构做了输出,在表面着色器内做了输入
      //以本函数为例,o.customColor输入进来并做了修改,然后作为输入传进了surf函数
      void vert (inout appdata_full v, out Input o)
      {
          //用Unity内置的宏初始化参数
          UNITY_INITIALIZE_OUTPUT(Input,o);

          //将法线的绝对值赋值给输出结构体下的customColor
          o.customColor = abs(v.normal);
      }

      sampler2D _MainTex;

      void surf (Input IN, inout SurfaceOutput o) 
      {     
          ///获取纹理的UV坐标值,并将颜色值作为最终输出赋给标准输出结构体的Albedo颜色属性  
          o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;

          //这里Albedo颜色属性进行了倍增
          //现在的Input结构体力的customColor不在是最初的那个啥都没有的float3了
          //这里的customColor是经过顶点函数处理过的,它现在是abs(v.Normal),这么说理解了吧
          o.Albedo *= IN.customColor;
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

效果图
这里写图片描述

step4
最终颜色修改器 (Final Color Modifier)

这是一个将色调应用于最终颜色的简单着色器。这与仅将色调应用于表面反射率 (Albedo) 颜色不同:此色调也会影响来自光照贴图、光探头和类似额外来源的任何颜色。好厉害的赶脚?

Shader"Example / AutisticPatient SurfaceShader11"
{
    Properties 
    {
      _MainTex ("Texture", 2D) = "white" {}
      _ColorTint ("Tint", Color) = (1.0, 0.6, 0.6, 1.0)
    }

    SubShader 
    {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      //最终颜色修改器(Final Color ) 函数,该函数将修改由着色器计算的最终颜色。使用着色器编译指令
      #pragma surface surf Lambert finalcolor:mycolor

      struct Input 
      {
          float2 uv_MainTex;
      };

      fixed4 _ColorTint;

      //与编译指令对应,名字必须一致
      void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
      {
          //color值会因受到_ColorTint值的改变而改变
          //也就是说我们可以在Inspector面板中进行调试
          color *= _ColorTint;
      }

      sampler2D _MainTex;

      void surf (Input IN, inout SurfaceOutput o) 
      {
           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

其实我们还可以在mycolor函数中大做文章,让效果更绚

step5

使用最终颜色修改器自定义雾

最终颜色修改器 (Final Color Modifier)(见上文)的常见应用为实现完全自定义的雾 (Fog)。雾 (Fog) 需要影响最终计算出的像素着色器颜色,这恰恰是(Final Color Modifier)修改器的用处所在。

Shader"Example / AutisticPatient SurfaceShader12"
 {
    //定义需要的属性
    Properties 
    {
      _MainTex ("Texture", 2D) = "white" {}
      _FogColor ("Fog Color", Color) = (0.3, 0.4, 0.7, 1.0)
    }

    SubShader 
    {
      //渲染非透明物体,最近这些小的shader对标签的要求没那么严苛,以后会接触到更多的标签 
      Tags { "RenderType" = "Opaque" }

      CGPROGRAM
      //编译指令,指定表面着色器,光照模型,最终颜色修改器,和顶点着色器
      #pragma surface surf Lambert finalcolor:mycolor vertex:myvert

      struct Input 
      {
          float2 uv_MainTex;
          half fog;
      };

      void myvert (inout appdata_full v, out Input data)
      {
          //主要是将叫[data]的变量清空改成Input类型。
          //initialize的中文含义便是初始化,试着理解
          UNITY_INITIALIZE_OUTPUT(Input,data);

          //mul,矩阵相乘运算函数,不需要开发者自己手动去进行矩阵相乘,有兴趣的同学可以自己实现一下这个功能
          //UNITY_MATRIX_MVP是model、view、projection三个矩阵相乘出来的4x4的矩阵。
          //v.vertex是一个float4的变量,可理解成4x1的矩阵(其实可以称作向量),两者相乘,则得出一个float4,这个值就是视角窗口的坐标值了
          float4 hpos = mul (UNITY_MATRIX_MVP, v.vertex);

          //这个fog的浮点值就是其强度,值越小就fog就越黑
          //再看后面这个点积,点积运算公式:|x||y|cos(夹角)
          //这个仔细一想,不难理解,其实就是为了达到一种扩散的效果,因此两个一样的向量相乘(因为cos值为1),其实就是直接对坐标做平方扩展,这样fog就更有雾的感觉。
          data.fog = min (1, dot (hpos.xy, hpos.xy) * 0.1);
      }

      fixed4 _FogColor;
      void mycolor (Input IN, SurfaceOutput o, inout fixed4 color)
      {
          fixed3 fogColor = _FogColor.rgb;

          //官方的宏定义:正向渲染时的额外通道
          //不太好理解,做个小改变将fogColor改成红色(1,0,0),外面的fog颜色改成白色,在试试效果,就会明白了
          #ifdef UNITY_PASS_FORWARDADD
          fogColor = (1,0,0);
          #endif

          //lerp函数的作用,即是在 第一个元素 与 第二个元素 之间利用 第三个元素 进行线性插值
          //举例来说,当 IN.fog = 0 时,插值结果为color.rgb ,IN.fog = 1 时, 插值结果为fogColor。
          //这样我们就可以通过设定IN.fog的值来控制雾化的百分比
          //详细点,当IN.fog更接近0的时候,整个模型会显示大部分color.rgb,和小部分fogColor,反之同理
          color.rgb = lerp (color.rgb, fogColor, IN.fog);
      }

      sampler2D _MainTex;
      void surf (Input IN, inout SurfaceOutput o)
      {
           o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb;
      }
      ENDCG
    } 
    Fallback "Diffuse"
  }

step6

线性雾 (Linear Fog)

Shader"Example / AutisticPatient SurfaceShader13"
{
    Properties
    {
        _MainTex("MainTex",2D) = "white"{}
    }

    SubShader
    {
          Tags { "RenderType"="Opaque" }
          //细节层次设为:200 
          //它是Level of Detail的缩写,在这里例子里我们指定了其为200
          LOD 200

          CGPROGRAM
          #pragma surface surf Lambert finalcolor:mycolor vertex:myvert

          sampler2D _MainTex;
          //变量名顾名思义
          //uniform是常量,也就是在shader代码中不可被改变的量
          uniform half4 unity_FogColor;
          uniform half4 unity_FogStart;
          uniform half4 unity_FogEnd;

          struct Input 
          {
              float2 uv_MainTex;
              half fog;
          };


          void myvert (inout appdata_full v, out Input data) 
          {
              //主要是将叫[data]的变量清空改成Input类型。
              //initialize的中文含义便是初始化,试着理解
              UNITY_INITIALIZE_OUTPUT(Input,data);

              //mul,矩阵相乘运算函数,不需要开发者自己手动去进行矩阵相乘
              //mul计算结束后得到的是一个float4,然后取出其xyz
              //length函数用于取一个向量的长度,如果是float3则采取如下形式:
              //float length(float3 v)
              //{
              //  return sqrt(dot(v,v));
              //}
              float pos = length(mul (UNITY_MATRIX_MV, v.vertex).xyz);
              float diff = unity_FogEnd.x - unity_FogStart.x;
              float invDiff = 1.0f / diff;

              //clamp(x,a,b)       如果x 值小于 a,则返回a;如果 x 值大于 b,返回b;否则,返回 x
              data.fog = clamp ((unity_FogEnd.x - pos) * invDiff, 0.0, 1.0);
          }

          //跟上一篇一样,不赘述了
          void mycolor (Input IN, SurfaceOutput o, inout fixed4 color) 
          {
              fixed3 fogColor = unity_FogColor.rgb;
              //正向渲染时的额外通道
              #ifdef UNITY_PASS_FORWARDADD
              fogColor = 0;
              #endif
              color.rgb = lerp (fogColor, color.rgb, IN.fog);
           }

           void surf (Input IN, inout SurfaceOutput o) 
           {
              half4 c = tex2D (_MainTex, IN.uv_MainTex);
              o.Albedo = c.rgb;
              o.Alpha = c.a;
           }

    }

}

从这几篇开始,我已经开始慢慢的放弃一些代码的注释了,原因呢(实在是直白的就略过了)

本系列结束了,都是些简单到不能再简单的东西,感觉就像是复习了一遍3D数学基础,和API

接下来会进行官网的其他系列翻译,我在发表之前会吸收很多前辈的经验,当然肯定有理解的不够深刻的地方,欢迎指正,共同学习。

所有的我参考过的博客都会放第二个文章里

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值