创建一个Phong类型类型的高光反射着色器

一个物体表面的高光,可以简单的理解为它表面的亮度。这种类型的着色器常用于视野特效(view-dependent effects)。这是因为 为了在着色器中获得贴近现实的高光效果,你需要考虑到摄像机和人的朝向因素。而Phong高光效果是最基础和性能较好的一种着色器效果。它根据人的朝向和光的反射方向,通过计算获得一个有方向的反射。 在应用程序中,这种高光模型非常常见,涵盖游戏行业到电影等产业。虽然它在高光反射模型的精确度上不是最接近现实的,但是在大多数情况下,它大致都能满足而且性能不赖。此外,如果你的游戏对象离摄像机很远,就没有必要在提供准确的高光,这对你的高光效果着色器来说是好事。

在这个知识点中,我们会涉及到如何使用表面着色器的**输入(Input)**结构体中的一些新参数进行逐顶点和逐像素的操作。我们会了解它们之间的区别,而且还会讨论什么时候以及为什么要用这两种不同的实现方式,来应对不同的情况。


  • 始前准备

    我们按照下面的步骤来学习这次的知识点:

    1. 分别创建一个新的着色器,材质和游戏对象,为了在后面你容易照到它们,请给它们恰当命名。
    2. 创建一个新场景,在创建一个新的游戏对象,把着色器应用到材质,然后再把材质应用到游戏对象上。 再添加一个平行光源,这是为了让我们方便看我们着色器代码的高光效果。

  • 操作步骤

    请按照下面的步骤创建一个Phong类型的光照模型:

    1. 此时你可能发现了一个模式,在我们开始写着色器的时候都有的步骤:着色器属性的创建。所以,让我们先在着色器中添加下面的一些属性:
    Properties
    {
    _MainTint ("Diffuse Tint", Color) = (1,1,1,1)
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _SpecularColor ("Specular Color", Color) = (1,1,1,1)
    _SpecPower ("Specular Power", Range(0,30)) = 1
    }
    
    1. 接下来在CGPROGRAM块中的**SubShader{}**块中,添加与之对应的一些变量:
    float4 _SpecularColor;
    sampler2D _MainTex;
    float4 _MainTint;
    float _SpecPower;
    
    1. 现在我们要添加我们自定义的光照模型,因为我们要计算自己的Phong高光。如果你现在还不能理解下面的代码也不用担心;在原理介绍的部分我们会解释每一行代码的作用。在着色器的**SubShader{}**中添加下述代码:
    fixed4 LightingPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir,fixed atten)
    {
      // Reflection
      float NdotL = dot(s.Normal, lightDir);
      float3 reflectionVector = normalize(2.0 * s.Normal * NdotL - lightDir);
      // Specular
      float spec = pow(max(0, dot(reflectionVector, viewDir)), _SpecPower);
      float3 finalSpec = _SpecularColor.rgb * spec;
      // Final effect
      fixed4 c;
      c.rgb = (s.Albedo * _LightColor0.rgb * max(0,NdotL) * atten) + (_LightColor0.rgb * finalSpec);
      c.a = s.Alpha;
      return c;
    }
    
    1. 最后,我们还要告诉CGPROGRAM块,需要用我们自定义的光照模型而不是Unity内建的光照模型。 按照下面的步骤修改**#pragma**指示:
    CPROGRAM
    #pragma surface surf Phong
    

    下图演示了我们自定义的Phong光照模型的效果,里面的反射算法也是我们自己的:


  • 原理介绍

    我们现在先把光照函数放一放,因为着色器剩下的部分你应该感到很熟悉了。

    在上一个知识点中,我们使用的光照函数只提供了光的方向,lightDir。Unity本身有一些现成的光照函数配置可以使用,其中一个就是视角方向,viewDir。可以根据下表或者下面的这个链接https://docs.unity3d.com/Manual/SL-SurfaceShaderLighting.html详细了解:

    这样写会告诉着色器我们要创建我们自己的基于视角的着色器。请注意,你声明的的光线函数的名字要跟**#pragma指示那里保持一致[#pragma指示那里省略了前缀“Lighting”**],否则Unity没有办法定位到你的光照模型。

    Phong类型光照模型中的这部分内容,我们用下面的图来描述。L是光的方向(R是跟它成对出现的反射光线方向),N是物体表面的法线方向。我们在前面讲解Lambertian光照模型的时候讲到过,V是我们没有讲到的,就是这里的视角方向:

    $$ I = D + S $$

    漫反射部分D是来自Lambertian光照模型,这部分没有改变过:

    $$ D = N \cdot L $$

    高光部分S的定义如下:

    $$ S = (R \cdot V)^{p} $$

    这里,,p 是高光的力度,就是在着色器中的**_SpecPower**。唯一不知道参数就是R,这是光线L根据法线方向N计算出来的反射光。在向量的代数运算中,它可以通过下面的表达式计算出来:

    $$ R = 2N \cdot (N \cdot L) - L $$

    这个公式就是着色器中下面的这行代码的具体含义:

    float3 reflectionVector = normalize(2.0 * s.Normal * NdotL - lightDir);  
    

    这能让法线有偏向光的效果;当法向量朝向远离光的方向时,反射光线的方向就会越接近入射光线的方向。 下面的图片能给帮助你更好的理解。而产生图中那样的调试效果的脚本,你可以从本书的支持网页中下载,链接:https://www.packtpub.com/support/code-downloads[记得在下面第二张图中的位置输入书名]:

    下图展示了我们的Phong高光着色器最终的计算结果: