创建一个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详细了解:
Not view dependent half4 Lighting Name You choose (SurfaceOutput s, half3 lightDir, half atten); View-dependent half4 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 是我们没有讲到的,就是这里的视角方向: Phong 光照模型给我们呈现了反射表面的最终光强,它由两部分得出:他的的漫反射颜色( diffuse color )和高光值( Specular value ),如下所示:
漫反射部分 D 是来自 Lambertian 光照模型,这部分没有改变过:
高光部分 S 的定义如下:
这里,,p 是高光的力度,就是在着色器中的 _SpecPower 。唯一不知道参数就是 R ,这是光线 L 根据法线方向 N 计算出来的反射光。在向量的代数运算中,它可以通过下面的表达式计算出来:
这个公式就是着色器中下面的这行代码的具体含义:
float3 reflectionVector = normalize(2.0 * s.Normal * NdotL - lightDir);
这能让法线有偏向光的效果;当法向量朝向远离光的方向时,反射光线的方向就会越接近入射光线的方向。 下面的图片能给帮助你更好的理解。而产生图中那样的调试效果的脚本,你可以从本书的支持网页中下载,链接:https://www.packtpub.com/support/code-downloads[记得在下面第二张图中的位置输入书名]:
下图展示了我们的 Phong 高光着色器最终的计算结果:
希望它能帮到你或者帮你找到思路(^_^)选个表情,或者留个评论吧!