创建各向异性类型的高光反射着色器

各向异性(Anisotropic) 类型可以用来模拟高光或者反射,常用于表面凹槽的方向和高光在垂直方向上的扭曲形变。当你想模拟金属抛光表面的时候这种类型的着色器就会非常有用,因为这种表面并不干净,光滑和明亮。 你可以想象当你看CD或者VCD光盘的数据那面时的高光或者底面被打磨过的金属盘子和盆子。 当你仔细检查这些表面的时候你会发现,表面的这些沟纹是有方向的,通常就是被抛光的方向。当你对这样的表面应用高光时,在垂直方向上会被扭曲。

这个知识点中我们会向你介绍不同的抛光表面高光概念。在将来的一些知识点中,我们会探索如何使用这个知识点介绍的一些概念来获得一些类似于头发的扭曲反射效果,但是在这里我们还是要先学习这个技术的一些原理知识。我们自定义的各向异性着色器将使用下面这个着色器作为参考:

http://wiki.unity3d.com/index.php?title=Anisotropic_Highlight_Shader

下图展示了使用 各向异性(Anisotropic) 着色器能获得的不同类型高光效果: diagram


  • 始前准备

    让我们开始学习新的知识点吧,先按照下面的步骤在场景中创建一个着色器,材质和一些光源:

    1. 创建一个新的场景,在场景中添加一些游戏对象,添加一个平行光源,这样我们好可视化的调试我们的着色器。
    2. 创建一个新的着色器和材质,它们都应用到刚刚创建的游戏对象上去。
    3. 最后,我们需要某种法线贴图,它能指出我们各向异性类型高光的方向。

    下图展示的就是这个知识点要用的各向异性类型的法线贴图。在本书的支持网页中可以获得[就在本书附带的工程代码中,获取方法前面有介绍]:

    https://www.packtpub.com/books/content/support

diagram


  • 操作步骤

    为了获得各向异性效果,我们需要对我们前面创建的着色器进行如下的修改:

    1. 首先得再着色器中添加我们需要用到的一些属性。这些属性允许我们控制很多中艺术效果从而最终决定表面的效果呈现:
    {
    _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _SpecularColor ("specular Color", Color) = (1,1,1,1)
    _Specular ("Specular Amount", Range(0,1)) = 0.5
    _SpecPower ("Specular Power", Range(0,1)) = 0.5
    _AnisoDir ("Anisotropic Direction", 2D) = "" {}
    _AnisoOffset ("Anisotropic Offset", Range(-1,1)) = -0.2
    }
    
    1. 接着我们要让我们的 SubShader{} 代码块跟我们的 属性(Properties) 块关联起来,这样才能让我们使用 属性(Properties) 中的数据:
    sampler2D _MainTex;
    sampler2D _AnisoDir;
    float4 _MainTint;
    float4 _SpecularColor;
    float _AnisoOffset;
    float _Specular;
    float _SpecPower;
    
    1. 接下来我们要创建我们自己的光照函数,用来处理物体表面的各向异性效果。我们的代码如下:
    fixed4 LightingAnisotropic(SurfaceAnisoOutput s, fixed3 lightDir, half3 viewDir, fixed atten)
    {
      fixed3 halfVector = normalize(normalize(lightDir) + normalize(viewDir));
      float NdotL = saturate(dot(s.Normal, lightDir));
      fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection),halfVector);
      float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180)));
      float spec = saturate(pow(aniso, s.Gloss * 128) * s.Specular);
      fixed4 c;
      c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb *
      _SpecularColor.rgb * spec)) * atten;
      c.a = s.Alpha;
      return c;
    }
    
    1. 为了使用我们新的光照函数,我们需要修改**#pragma**指示,让Unity去使用我们的光照函数,而不是Unity内建的光照函数。我们同时还要告诉着色器目标着色器模式是3.0,这样我们可以让我们的程序拥有更多的空间用来存放纹理:
    CGPROGRAM
    #pragma surface surf Anisotropic
    #pragma target 3.0
    
    1. 为了让各向异性法线贴图能使用它自己的UV数据,我们需要再 输入(Input) 结构中添加如下定义代码。 我们其实并不是说完全需要这样做,因为我们也可以使用主帖图上的UV数据,但是如果有它自己单独UV数据的话我们就能单独控制金属抛光效果的截取,这样我们就可以把它进行缩放任何我们想要的大小:
    {
      float2 uv_MainTex;
      float2 uv_AnisoDir;
    };
    
    1. 我们还需要定义 SurfaceAnisoOutput 这个输出结构体:
    struct SurfaceAnisoOutput
    {
      fixed3 Albedo;
      fixed3 Normal;
      fixed3 Emission;
      fixed3 AnisoDirection;
      half Specular;
      fixed Gloss;
      fixed Alpha;
    };
    
    1. 最后,我们需要通过表面函数 surf() 给我们的光照函数传递正确的数据。这样,我们将会获得我们各向异性法线贴图的每个像素信息,然后像下面的代码一样设置我们的高光参数:
    void surf(Input IN, inout SurfaceAnisoOutput o)
    {
      half4 c = tex2D(_MainTex, IN.uv_MainTex) * _MainTint;
      float3 anisoTex = UnpackNormal(tex2D(_AnisoDir, IN.uv_AnisoDir));
      o.AnisoDirection = anisoTex;
      o.Specular = _Specular;
      o.Gloss = _SpecPower;
      o.Albedo = c.rgb;
      o.Alpha = c.a;
    }
    

    各向异性法线贴图允许我们给出表面的方向并且能帮助我们分散物体表面周围的高光效果。下图就是我们的各向异性着色器的效果:

    diagram


    • 原理介绍

      我们按部分来看看这个着色器的核心内容并且解释为什么我们能获得这样的效果。这里我们大部分都讲光照函数部分,因为除此之外的内容对于当前的你来说都能自己理解。

      首先我们定了我们自己的结构体 SurfaceAnisoOutput 。我们之所以这样么做是因为我们需要获得各向异性法线贴图的每个像素信息,而再表面着色器中获得这个的唯一方法就是在 surf() 函数中使用 tex2D() 函数获取。 下面的代码就是我们在着色器中自定义的输出结构体:

      struct SurfaceAnisoOutput
      {
          fixed3 Albedo;
          fixed3 Normal;
          fixed3 Emission;
          fixed3 AnisoDirection;
          half Specular;
          fixed Gloss;
          fixed Alpha;
      };  
      

      SurfaceAnisoOutput 输出结构体是光线函数和表面函数进行交互的中间数据。在我们这个例子中, surf() 中我们把每个像素的纹理信息都保存在了 anisoTex 中,接着把 anisoTex 保存在了 AnisoDirection 变量中,再然后 AnisoDirection 被传递到了表面结构体 SurfaceAnisoOutput 中。一旦我们拥有了这个表面结构体,我们就能在我们的光照函数中使用这每个像素的纹理信息了。

      s.AnisoDirection.  
      

      当我们设置好这部分数据关联后,我们就可以着手我们具体的光照计算了。跟往常不一样,这里我们使用了中间向量,这样我们就不用去进行完整的反射计算和漫反射计算,因为这些计算都需要让顶点的法向量跟光线或光的方向进行点积的运算。我们的Cg代码如下所示:

      fixed3 halfVector = normalize(normalize(lightDir) + normalize(viewDir));
      float NdotL = saturate(dot(s.Normal, lightDir));
      

      之后,我们开始对高光进行具体的修改,以便获得我们想要的视觉效果。我们首先把顶点法线跟我们各向异性法线贴图的顶点相加得到和,再对和进行标准化,然后把标准化后的向量跟前面步骤获得的 中间向量(halfVector) 进行点积操作。参考它跟各向异性法线贴图的点积,当值等于1时,说明表面法线跟 中间向量(halfVector) 平行,当值等于0时,它们相互垂直。最后我们还要用 sin() 函数来修改这个值,这样根据 中间向量(halfVector) 我们基本能获得一个更暗一些的中间亮度,并且最终获得一个环状的效果。所有上面提及的操作都包含在了下面两行Cg代码中:

      fixed HdotA = dot(normalize(s.Normal + s.AnisoDirection), halfVector);
      float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180)));  
      

      最后,我们通过 s.Glossaniso 进行指数放大,从而放大效果, 然后再通过乘以 s.Specular 来全局减少它的强度:

      float spec = saturate(pow(aniso, s.Gloss * 128) * s.Specular);  
      

      这个效果非常适合创建一些更加高级的金属类型的表面,尤其时那些表面被有向抛过光的。当然它也非常适合头发反光或者任何表面有方向性的软表面。下图展示了各向异性光照计算的最终显示效果: diagram