创建一个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 光照模型的效果,里面的反射算法也是我们自己的:

    diagram


  • 原理介绍

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

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

    Not view dependenthalf4 Lighting Name You choose (SurfaceOutput s, half3 lightDir, half atten);
    View-dependenthalf4 Lighting Name You choose (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten);

    在我们的这个例子中,我们处理的是一个高光着色器,所以我们需要基于视角的光线函数结构。所以我们需要编写下面的着色器代码:

    CPROGRAM
    #pragma surface surf Pong
    fixed4 LightingPhong (SurfaceOutput s, fixed3 lightDir, half3 viewDir,fixed atten)
    {
      // ...
    }  
    

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

    Phong 类型光照模型中的这部分内容,我们用下面的图来描述。 L 是光的方向( R 是跟它成对出现的反射光线方向), N 是物体表面的法线方向。我们在前面讲解 Lambertian 光照模型的时候讲到过, V 是我们没有讲到的,就是这里的视角方向: diagram Phong 光照模型给我们呈现了反射表面的最终光强,它由两部分得出:他的的漫反射颜色( diffuse color )和高光值( Specular value ),如下所示:

    diagram

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

    diagram

    高光部分 S 的定义如下:

    diagram

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

    diagram

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

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

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

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