创建一个Phong类型类型的高光反射着色器
一个物体表面的高光,可以简单的理解为它表面的亮度。这种类型的着色器常用于视野特效(view-dependent effects)。这是因为 为了在着色器中获得贴近现实的高光效果,你需要考虑到摄像机和人的朝向因素。而Phong高光效果是最基础和性能较好的一种着色器效果。它根据人的朝向和光的反射方向,通过计算获得一个有方向的反射。 在应用程序中,这种高光模型非常常见,涵盖游戏行业到电影等产业。虽然它在高光反射模型的精确度上不是最接近现实的,但是在大多数情况下,它大致都能满足而且性能不赖。此外,如果你的游戏对象离摄像机很远,就没有必要在提供准确的高光,这对你的高光效果着色器来说是好事。
在这个知识点中,我们会涉及到如何使用表面着色器的**输入(Input)**结构体中的一些新参数进行逐顶点和逐像素的操作。我们会了解它们之间的区别,而且还会讨论什么时候以及为什么要用这两种不同的实现方式,来应对不同的情况。
始前准备
我们按照下面的步骤来学习这次的知识点:
- 分别创建一个新的着色器,材质和游戏对象,为了在后面你容易照到它们,请给它们恰当命名。
- 创建一个新场景,在创建一个新的游戏对象,把着色器应用到材质,然后再把材质应用到游戏对象上。 再添加一个平行光源,这是为了让我们方便看我们着色器代码的高光效果。
操作步骤
请按照下面的步骤创建一个Phong类型的光照模型:
- 此时你可能发现了一个模式,在我们开始写着色器的时候都有的步骤:着色器属性的创建。所以,让我们先在着色器中添加下面的一些属性:
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 }
- 接下来在CGPROGRAM块中的**SubShader{}**块中,添加与之对应的一些变量:
float4 _SpecularColor; sampler2D _MainTex; float4 _MainTint; float _SpecPower;
- 现在我们要添加我们自定义的光照模型,因为我们要计算自己的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; }
- 最后,我们还要告诉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高光着色器最终的计算结果: