Unity Shader-Phong光照模型与Specular

目录(?)[+]

简介


学完了兰伯特光照模型,再来学习一个更加高级一点的光照模型-Phong光照模型。光除了漫反射,还有镜面反射。一些金属类型的材质,往往表现出一种高光效果,用兰伯特模型是模拟不出来的,所以就有了Phong模型。Phong模型主要有三部分构成,第一部分是上一篇中介绍了的Diffuse,也就是漫反射,第二部分是环境光,在非全局光照的情况下,我们一般是通过一个环境光来模拟物体的间接照明,这个值在shader中可以通过一个宏来直接获取,而第三部分Specular,也就是高光部分的计算,是一种模拟镜面反射的效果,也是本篇文章重点介绍的内容。


在现实世界中,粗糙的物体一般会是漫反射,而光滑的物体呈现得较多的就是镜面反射,最明显的现象就是光线照射的反射方向有一个亮斑。再来复习一下镜面反射的概念:当平行入射的光线射到这个反射面时,仍会平行地向一个方向反射出来,这种反射就属于镜面反射,其反射波的方向与反射平面的法线夹角(反射角),与入射波方向与该反射平面法线的夹角(入射角)相等,且入射波、反射波,及平面法线同处于一个平面内。反射光的亮度不仅与光线的入射角有关,还与观察者视线和物体表面之间的角度有关。镜面反射通常会造成物体表面上的“闪烁”和“高光”现象,镜面反射的强度也与物体的材质有关,无光泽的木材很少会有镜面反射发生,而高光泽的金属则会有大量镜面反射。


Phong光照模型


Phong光照模型中主要的部分就是对高光的计算,首先来看下面这张图片:
Unity Shader-Phong光照模型与Specular
理想情况下,光源射出的光线,通过镜面反射,正好在反射光方向观察,观察者可以接受到的反射光最多,那么观察者与反射方向之间的夹角就决定了能够观察到高光的多少。夹角越大,高光越小,夹角越小,高光越大。而另一个影响高光大小的因素是表面的光滑程度,表面越光滑,高光越强,表面月粗糙,高光越弱。L代表光源方向,N代表顶点法线方向,V代表观察者方向,R代表反射光方向。首先需要计算反射光的方向R,反射光方向R可以通过入射光方向和法向量求出,R + L = 2dot(N,L)N,进而推出R = 2dot(N,L)N - L。关于R计算的推导,可以看下面这张图:
Unity Shader-Phong光照模型与Specular

不过在cg中,我们不用这么麻烦,cg为我们提供了一个计算反射光方向的函数reflect函数,我们只需要传入入射光方向(光源方向的反方向)和表面法线方向,就可以计算得出反射光的方向。然后,我们通过dot(R,V)就可以得到反射光方向和观察者方向之间的夹角余弦值了。下面给出冯氏反射模型公式:

I(spcular) = I * k * pow(max(0,dot(R,V)), gloss) ,其中I为入射光颜色向量,k为镜面反射系数,gloss为光滑程度。

通过上面的公式,我们可以看出,镜面反射强度跟反射向量与观察向量的余弦值呈指数关系,指数为gloss,该系数反映了物体表面的光滑程度,该值越大,表示物体越光滑,反射光越集中,当偏离反射方向时,光线衰减程度越大,只有当视线方向与反射光方向非常接近时才能看到高光现象,镜面反射光形成的光斑较亮并且较小;该值越小,表示物体越粗糙,反射光越分散,可以观察到光斑的区域越广,光斑大并且强度较弱。

Phong光照模型在Unity中的实现


下面看一下冯氏光照模型在Unity中的实现,由于有高光,为了更好的效果,我们将主要的计算放到了fragment shader中进行。不多说,上代码:
[csharp] view plain copy
  1. Shader "ApcShader/SpecularPerPixel"  
  2. {  
  3.     //属性  
  4.     Properties  
  5.     {  
  6.         _Diffuse("Diffuse", Color) = (1,1,1,1)  
  7.         _Specular("Specular", Color) = (1,1,1,1)  
  8.         _Gloss("Gloss", Range(1.0, 255)) = 20  
  9.     }  
  10.   
  11.     //子着色器    
  12.     SubShader  
  13.     {  
  14.         Pass  
  15.         {  
  16.             //定义Tags  
  17.             Tags{ "LightingMode" = "ForwardBase" }  
  18.   
  19.             CGPROGRAM  
  20.             //引入头文件  
  21.             #include "Lighting.cginc"  
  22.   
  23.             //定义函数  
  24.             #pragma vertex vert  
  25.             #pragma fragment frag  
  26.   
  27.             fixed4 _Diffuse;  
  28.             fixed4 _Specular;  
  29.             float _Gloss;  
  30.   
  31.             //定义结构体:应用阶段到vertex shader阶段的数据  
  32.             struct a2v  
  33.             {  
  34.                 float4 vertex : POSITION;  
  35.                 float3 normal : NORMAL;  
  36.             };  
  37.   
  38.             //定义结构体:vertex shader阶段输出的内容  
  39.             struct v2f  
  40.             {  
  41.                 float4 pos : SV_POSITION;  
  42.                 float3 worldNormal : NORMAL;  
  43.                 float3 worldPos : TEXCOORD1;  
  44.             };  
  45.   
  46.             //顶点shader  
  47.             v2f vert(a2v v)  
  48.             {  
  49.                 v2f o;  
  50.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
  51.                 //法线转化到世界空间  
  52.                 o.worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));  
  53.                 //顶点位置转化到世界空间  
  54.                 o.worldPos = mul(_Object2World, v.vertex).xyz;  
  55.                 return o;  
  56.             }  
  57.   
  58.             //片元shader  
  59.             fixed4 frag(v2f i) : SV_Target  
  60.             {  
  61.                 //环境光  
  62.                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse;  
  63.                 //归一化光方向  
  64.                 fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);  
  65.                 //再次归一化worldNorml  
  66.                 fixed3 worldNormal = normalize(i.worldNormal);  
  67.                 //diffuse  
  68.                 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));  
  69.                 //计算反射方向R,worldLight表示光源方向(指向光源),入射光线方向为-worldLight,通过reflect函数(入射方向,法线方向)获得反射方向  
  70.                 fixed3 reflectDir = normalize(reflect(-worldLight, worldNormal));  
  71.                 //计算该像素对应位置(顶点计算过后传给像素经过插值后)的观察向量V,相机坐标-像素位置  
  72.                 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);  
  73.                 //计算高光值,高光值与反射光方向与观察方向的夹角有关,夹角为dot(R,V),最后根据反射系数计算的反射值为pow(dot(R,V),Gloss)  
  74.                 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0.0,dot(reflectDir, viewDir)), _Gloss);  
  75.                 //冯氏模型:Diffuse + Ambient + Specular  
  76.                 fixed3 color = diffuse + ambient + specular;  
  77.                 return fixed4(color, 1.0);  
  78.             }  
  79.             ENDCG  
  80.         }  
  81.     }  
  82.   
  83.     //前面的Shader失效的话,使用默认的Diffuse  
  84.     FallBack "Diffuse"  
  85. }  

我们找一个球球,使用我们的shader看一下效果:
Unity Shader-Phong光照模型与Specular

调整观察的方向以及光滑程度,可以看见亮斑是随着观察的方向改变而变化的:
Unity Shader-Phong光照模型与Specular



Blinn-Phong光照模型


Phong光照模型能够很好地表现高光效果,不过Blinn-Phong光照的缺点就是计算量较大,所以,在1977年,Jim Blinn对Phong光照进行了改进,称之为Blinn-Phong光照模型。

关于Blinn-Phong和Phong光照模型的对比,可以参照这张图片:
Unity Shader-Phong光照模型与Specular
Blinn-Phong光照引入了一个概念,半角向量,用H表示。半角向量计算简单,通过将光源方向L和视线方向V相加后归一化即可得到半角向量。Phong光照是比较反射方向R和视线方向V之间的夹角,而Blinn-Phong改为比较半角向量H和法线方向N之间的夹角。半角向量的计算复杂程度要比计算反射光线简单得多,所以Blinn-Phong的性能要高得多,效果比Phong光照相差不多,所以OpenGL中固定管线的光照模型就是Blinn-Phong光照模型。

BlinnPhong光照模型如下:

I(spcular) = I * k * pow(max(0,dot(N,H)), gloss) ,其中I为入射光颜色向量,k为镜面反射系数,gloss为光滑程度。

Blinn-Phong光照在Unity中的实现


[csharp] view plain copy
  1. Shader "ApcShader/BlinnPhongPerPixel"  
  2. {  
  3.     //属性  
  4.     Properties  
  5.     {  
  6.         _Diffuse("Diffuse", Color) = (1,1,1,1)  
  7.         _Specular("Specular", Color) = (1,1,1,1)  
  8.         _Gloss("Gloss", Range(1.0, 256)) = 20  
  9.     }  
  10.   
  11.     //子着色器    
  12.     SubShader  
  13.     {  
  14.         Pass  
  15.         {  
  16.             //定义Tags  
  17.             Tags{ "LightingMode" = "ForwardBase" }  
  18.   
  19.             CGPROGRAM  
  20.             //引入头文件  
  21.             #include "Lighting.cginc"  
  22.   
  23.             //定义函数  
  24.             #pragma vertex vert  
  25.             #pragma fragment frag  
  26.   
  27.             //定义Properties中的变量  
  28.             fixed4 _Diffuse;  
  29.             fixed4 _Specular;  
  30.             float _Gloss;  
  31.   
  32.             //定义结构体:应用阶段到vertex shader阶段的数据  
  33.             struct a2v  
  34.             {  
  35.                 float4 vertex : POSITION;  
  36.                 float3 normal : NORMAL;  
  37.             };  
  38.   
  39.             //定义结构体:vertex shader阶段输出的内容  
  40.             struct v2f  
  41.             {  
  42.                 float4 pos : SV_POSITION;  
  43.                 float3 worldNormal : NORMAL;  
  44.                 float3 worldPos : TEXCOORD1;  
  45.             };  
  46.   
  47.             //顶点shader  
  48.             v2f vert(a2v v)  
  49.             {  
  50.                 v2f o;  
  51.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
  52.                 //法线转化到世界空间  
  53.                 o.worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));  
  54.                 //顶点位置转化到世界空间   
  55.                 o.worldPos = mul(_Object2World, v.vertex).xyz;  
  56.                 return o;  
  57.             }  
  58.   
  59.             //片元shader  
  60.             fixed4 frag(v2f i) : SV_Target  
  61.             {  
  62.                 //环境光  
  63.                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse;  
  64.                 //世界空间下光线方向  
  65.                 fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);  
  66.                 //需要再次normalize  
  67.                 fixed3 worldNormal = normalize(i.worldNormal);  
  68.                 //计算Diffuse  
  69.                 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));  
  70.                 //计算视线方向(相机位置-像素对应位置)  
  71.                 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);  
  72.                 //计算半角向量(光线方向 + 视线方向,结果归一化)  
  73.                 fixed3 halfDir = normalize(worldLight + viewDir);  
  74.                 //计算Specular(Blinn-Phong计算的是)  
  75.                 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);  
  76.                 //结果为diffuse + ambient + specular  
  77.                 fixed3 color = diffuse + ambient + specular;  
  78.                 return fixed4(color, 1.0);  
  79.             }  
  80.                 ENDCG  
  81.         }  
  82.     }  
  83.     //前面的Shader失效的话,使用默认的Diffuse  
  84.     FallBack "Diffuse"  
  85. }  

我们再放一个球,使用Blinn-Phong光照,与Phong光照模型进行对比:
Unity Shader-Phong光照模型与Specular
换个角度再看一下:
Unity Shader-Phong光照模型与Specular

带纹理的Phong光照Shader


只有两个没有纹理的球,是说明不了问题的,下面来看一下带有纹理的Phong光照Shader。首先,我们要思考一个问题,如果我们要使用Specular类型的Shader,那么这个物体一般是金属类型的,这样,这个物体就会呈现金属特有的高光属性。然而,实际上完全是金属的物体并不是很多,现实世界中漫反射和镜面反射是共存的,拿一把刀来说,刀身是金属,刀柄是木头,那么,只有刀身适合这种类型的shader。可能我们最简单的想法是把刀拆成两个部分,刀身用的是Specular,刀柄用Diffuse;但是这种做法很麻烦,而且一个物体通过了两个drall call才能渲染出来。所以,聪明的前辈们总是能想到好的办法,次时代类型游戏中最简单的一种贴图就诞生了---高光贴图(通道)。

所谓高光贴图,或者说成高光通道,就是通过在制作贴图时,把图片的高光信息存储在一个灰度图或者直接存储在贴图的通道内,如果不需要Alpha Test的话,可以直接把高光通道放在Diffuse贴图的Alpha通道。而我们在shader中计算时,通过采样,就可以获得这个贴图中每个像素对应的位置是否是有高光的。这样,在Fragment Shader中可以直接通过这个Mask值乘以高光从而通过一个材质渲染出同一个模型上的不同质地。比如在通道内,0表示无高光,1(255)表示高光最强,那么,不需要高光的地方我们就可以在制作这张图的时候给0,需要高光的,刷上颜色,颜色越接近白色,高光越强。

知道了原理之后,我们就可以找一个人物模型贴图,然后在PhotoShop中给RGB格式的贴图增加一个通道,作为高光通道,然后把需要高光的部分抠出来,其他部分置为黑色
Unity Shader-Phong光照模型与Specular
好吧,作为一个程序员,一直想说,我.......的美工,实在不怎么样。不管图扣得再怎么差,原理没错就好。下面上shader:
[csharp] view plain copy
  1. Shader "ApcShader/BlinnPhongWithTex"  
  2. {  
  3.     //属性  
  4.     Properties  
  5.     {  
  6.         _Diffuse("Diffuse", Color) = (1,1,1,1)  
  7.         _Specular("Specular", Color) = (1,1,1,1)  
  8.         _SpecularScale("SpecularScale", Range(0.0, 5.0)) = 1.0  
  9.         _Gloss("Gloss", Range(0.0, 1)) = 20  
  10.         _MainTex("RGBSpecular", 2D) = "white"{}  
  11.     }  
  12.   
  13.     //子着色器    
  14.     SubShader  
  15.     {  
  16.         Pass  
  17.         {  
  18.             //定义Tags  
  19.             Tags{ "LightingMode" = "ForwardBase" }  
  20.   
  21.             CGPROGRAM  
  22.             //引入头文件  
  23.             #include "Lighting.cginc"  
  24.   
  25.             //定义函数  
  26.             #pragma vertex vert  
  27.             #pragma fragment frag  
  28.   
  29.             //定义Properties中的变量  
  30.             fixed4 _Diffuse;  
  31.             fixed4 _Specular;  
  32.             float _Gloss;  
  33.             float _SpecularScale;  
  34.             sampler2D _MainTex;  
  35.             float4 _MainTex_ST;  
  36.   
  37.             //定义结构体:应用阶段到vertex shader阶段的数据  
  38.             struct a2v  
  39.             {  
  40.                 float4 vertex : POSITION;  
  41.                 float3 normal : NORMAL;  
  42.                 float4 texcoord : TEXCOORD0;  
  43.             };  
  44.   
  45.             //定义结构体:vertex shader阶段输出的内容  
  46.             struct v2f  
  47.             {  
  48.                 float4 pos : SV_POSITION;  
  49.                 float3 worldNormal : NORMAL;  
  50.                 float3 worldPos : TEXCOORD0;  
  51.                 float2 uv : TEXCOORD1;  
  52.             };  
  53.   
  54.             //顶点shader  
  55.             v2f vert(a2v v)  
  56.             {  
  57.                 v2f o;  
  58.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
  59.                 //法线转化到世界空间  
  60.                 o.worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));  
  61.                 //顶点位置转化到世界空间   
  62.                 o.worldPos = mul(_Object2World, v.vertex).xyz;  
  63.                 //转化uv  
  64.                 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);  
  65.                 return o;  
  66.             }  
  67.   
  68.             //片元shader  
  69.             fixed4 frag(v2f i) : SV_Target  
  70.             {  
  71.                 //环境光  
  72.                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;  
  73.                 //世界空间下光线方向  
  74.                 fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);  
  75.                 //需要再次normalize  
  76.                 fixed3 worldNormal = normalize(i.worldNormal);  
  77.                 //计算Diffuse  
  78.                 fixed3 diffuse = _LightColor0.rgb * (dot(worldNormal, worldLight) * 0.5 + 0.5);  
  79.                 //计算视线方向(相机位置-像素对应位置)  
  80.                 fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);  
  81.                 //计算半角向量(光线方向 + 视线方向,结果归一化)  
  82.                 fixed3 halfDir = normalize(worldLight + viewDir);  
  83.                 //计算Specular(Blinn-Phong计算的是)  
  84.                 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);  
  85.                 //纹理采样  
  86.                 fixed4 tex = tex2D(_MainTex, i.uv);  
  87.                 //纹理中rgb为正常颜色,a为一个高光的mask图,非高光部分a值为0,高光部分根据a的值控制高光强弱  
  88.                 fixed3 color = (diffuse  + ambient + specular * tex.a * _SpecularScale) * tex.rgb;  
  89.                 return fixed4(color, 1.0);  
  90.             }  
  91.                 ENDCG  
  92.         }  
  93.     }  
  94.     //前面的Shader失效的话,使用默认的Diffuse  
  95.     FallBack "Diffuse"  
  96. }  
看一下最终效果,左侧为使用了高光的效果,右侧为普通diffuse的效果,可以看出,使用了高光贴图,我们只有上面的刀才表现出了高光,其他部分仍然是正常的。

Unity Shader-Phong光照模型与Specular

高光有个很搭配的后处理,就是Bloom效果(全屏泛光),也是我很喜欢的效果之一。虚幻四的Bloom效果非常好,带有金属的效果加上泛光,让人特别舒服,我们在Unity中用一个简单的Bloom效果,让金属效果更好。
Unity Shader-Phong光照模型与Specular

最后来一张动图,Specular的主要特点就在于当观察者与对象角度有变化时,也就是V向量和R向量之间的角度有变化时,高光效果会有变化,所以只有动起来,才能真正看出Specular与Diffuse的不同。
Unity Shader-Phong光照模型与Specular

Shader的优化


一般情况下,fragment shader是性能的瓶颈,所以优化Shader的重要思路之一就是减少逐像素计算,将计算挪到vertex shader部分,然后通过vertex shader向fragment shader中传递参数。正常情况下,一个物体在屏幕上,逐顶点计算的量级要远远小于物体在屏幕上逐像素计算的量(当然如果物体离相机特别远,光栅化之后在屏幕上只占了很小的一部分时,有可能有反过来的情况,但是有LOD之类的技术的话,远了之后,更换为低模,也会降低顶点数,所以还是逐像素计算的比较可怕,尤其是分辨率大了之后)。当然,我们也不能把所有计算都放在vertex shader中,上一篇文章中说过,如果将高光计算放在vertex shader中,效果很差,下面就来看一下,效果有多差:
Unity Shader-Phong光照模型与Specular
为什么会有这样的结果呢,主要是顶点计算的结果是通过顶点传递好的颜色进行高洛德着色,只是一个颜色的插值。而放在像素着色阶段,是通过顶点传递过来的参数,并且传递到像素阶段时经过了插值计算得到的信息,逐像素计算光照效果得到最终结果。更加详细的解释可以参照上一篇文章。2001年左右第三代modern GPU开始支持vertex shader,而在2003年左右,NVIDIA的GeForce FX和ATI Radeon 9700开始,GPU才开始支持fragment shader,也就是说fragment更先进,可以得到更好的效果。所以,我们只是将一些不会影响效果的计算放在vertex shader中即可。

上面的blinn-phong shader中,我们在fragment shader中计算了世界空间下的ViewDir,我们可以把这个计算移到vertex shader中进行:
[csharp] view plain copy
  1. //blinn-phong shader  
  2. //puppet_master  
  3. //2016.12.11  
  4. Shader "ApcShader/BlinnPhongPerPixel"  
  5. {  
  6.     //属性  
  7.     Properties  
  8.     {  
  9.         _Diffuse("Diffuse", Color) = (1,1,1,1)  
  10.         _Specular("Specular", Color) = (1,1,1,1)  
  11.         _Gloss("Gloss", Range(1.0, 256)) = 20  
  12.     }  
  13.   
  14.     //子着色器    
  15.     SubShader  
  16.     {  
  17.         Pass  
  18.         {  
  19.             //定义Tags  
  20.             Tags{ "LightingMode" = "ForwardBase" }  
  21.   
  22.             CGPROGRAM  
  23.             //引入头文件  
  24.             #include "Lighting.cginc"  
  25.   
  26.             //定义函数  
  27.             #pragma vertex vert  
  28.             #pragma fragment frag  
  29.   
  30.             //定义Properties中的变量  
  31.             fixed4 _Diffuse;  
  32.             fixed4 _Specular;  
  33.             float _Gloss;  
  34.   
  35.             //定义结构体:应用阶段到vertex shader阶段的数据  
  36.             struct a2v  
  37.             {  
  38.                 float4 vertex : POSITION;  
  39.                 float3 normal : NORMAL;  
  40.             };  
  41.   
  42.             //定义结构体:vertex shader阶段输出的内容  
  43.             struct v2f  
  44.             {  
  45.                 float4 pos : SV_POSITION;  
  46.                 float3 worldNormal : NORMAL;  
  47.                 float3 viewDir : TEXCOORD1;  
  48.             };  
  49.   
  50.             //顶点shader  
  51.             v2f vert(a2v v)  
  52.             {  
  53.                 v2f o;  
  54.                 o.pos = mul(UNITY_MATRIX_MVP, v.vertex);  
  55.                 //法线转化到世界空间  
  56.                 o.worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));  
  57.                 //顶点位置转化到世界空间   
  58.                 float3 worldPos = mul(_Object2World, v.vertex).xyz;  
  59.                 //计算视线方向(相机位置 - 像素对应位置)  
  60.                 o.viewDir = _WorldSpaceCameraPos - worldPos;  
  61.                 return o;  
  62.             }  
  63.   
  64.             //片元shader  
  65.             fixed4 frag(v2f i) : SV_Target  
  66.             {  
  67.                 //环境光  
  68.                 fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * _Diffuse;  
  69.                 //世界空间下光线方向  
  70.                 fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);  
  71.                 //需要再次normalize  
  72.                 fixed3 worldNormal = normalize(i.worldNormal);  
  73.                 //计算Diffuse  
  74.                 fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));  
  75.                 //normalize  
  76.                 fixed3 viewDir = normalize(i.viewDir);  
  77.                 //计算半角向量(光线方向 + 视线方向,结果归一化)  
  78.                 fixed3 halfDir = normalize(worldLight + viewDir);  
  79.                 //计算Specular(Blinn-Phong计算的是)  
  80.                 fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir, worldNormal)), _Gloss);  
  81.                 //结果为diffuse + ambient + specular  
  82.                 fixed3 color = diffuse + ambient + specular;  
  83.                 return fixed4(color, 1.0);  
  84.             }  
  85.                 ENDCG  
  86.         }  
  87.     }  
  88.     //前面的Shader失效的话,使用默认的Diffuse  
  89.     FallBack "Diffuse"  
  90. }  
在优化前后,没有特别明显的变化:
Unity Shader-Phong光照模型与Specular