UnityShader 从入门到盖棺(三)

gta5_car

Phong和Blinn模型

上一篇我们讲到了Lambert模型,这次我们讲一下Phong和Blinn光照模型。这两种模型都是基于Lambert模型加入了高光部分。

接下来的实现,我们用于完善我们上次房子场景的周边环境。这是我上次构建完房子场景后的一个大胆想法,就是把所有知识点放在我们的房子场景上,然后大家看到场景的一部分可能就能想起某个点(→_→)。

来,老规矩,上结果图。

sc1

我们先来实现phong光照模型。实现之前,我们在场景中摆两个球,然后创建一个新的材质,名为3_BallPhong。材质的Shader先选择上一篇的2_WallDiffuse。然后贴图使用Textures/3_MetalDiffuse。然后我们就可以看到结果如下

dc1

dc2

这是上一篇的效果,只应用了漫反射光。下面我们开始加入phong模型中的高光,所谓的高光其实是光照照射到表面的镜面反射部分,就跟镜子反射光照一样的。所以我们需要算出光照通过表面直接反射到人眼的部分,只需要算出光反射向量和视线向量的夹角即可。

math

创建Shader文件3_CopperPhong.shader, 然后把上一篇的2_WallDiffuse.shader的内容全部复制过来。把片段着色器代码修改成下面的样子

fixed4 frag(v2f i) : SV_Target {
	float3 worldNormal = normalize(i.worldNormal);
	float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
	fixed4 albedo = tex2D(_MainTex, i.uv);

	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT * albedo.rgb;


    float lambert = saturate(dot(worldNormal, worldLightDir));
    fixed3 diffuse = _LightColor0.rgb * albedo.rgb  * lambert;

    // phong高光部分
    // 算出光照和表面的反射向量,Unity提供内置函数reflect帮我们完成。
    float3 worldRefelctDir = normalize(reflect(-worldLightDir, worldNormal));
    float3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
    // 用反射向量和视线向量的夹角模拟高光的影响程度
    float spec = saturate(dot(worldRefelctDir, worldViewDir));
    fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * spec;

	return fixed4(ambient + diffuse + specular, albedo.a); // 输出物体主颜色

然后我们把一开始创建的3_BallPhong.mat材质的Shader改成我们新写的这个Shader,注意这里用到了一个_SpecularColor变量,他是一个颜色变量,是作为输入参数传进来的。读者应该记得怎么添加一个输入颜色变量吧,不记得的可以去看看源码的Properties属性块复习一下,这个变量声明的代码我没有贴出来的。我们在属性面板把这个颜色调整为金属球的颜色。

pickcolor

然后效果图如下

fc1

大家可以看到,加入高光后铜球的一部分的确很亮。但是现实生活中金属球的高光好像范围没有那么大。事实上,上面的高光系数计算漏了phong光照模型里很重要的一步(我自己做的时候漏了 - -)。那就是对高光系数做一个幂次方,让高光范围变窄,因为现实中,金属高光只有直接反射到人眼的最明显,然后随着角度变大,衰减很快。因此我们再定义一个输入变量_Shiness(这是一个RANGE变量,代表用户可以在一个范围内选取某个值)来完善高光系数的计算。

// 在属性块中定义_Shiness变量
_Shiness ("shiness", RANGE(8.0, 64.0)) = 8.0

// 在CGPROGRAM-ENDCG代码中声明_Shiness变量
float _Shiness;

// 修改Spec系数的计算
float spec = pow(saturate(dot(worldRefelctDir, worldViewDir)), _Shiness);

修改代码后的结果图如下,读者可以自己调整_Shiness查看效果。

fc2

理论上,向光面的高光分量可以无限趋向于0,但不应该为0。但是phong光照模型在反射光照和视线夹角大于90度时,是会被截断为0的。在某些情况下,这样的截断会导致高光区域出现明显的断层。可以参考LearnOpengl上的效果图

接下来我们实现Blinn光照模型。Blinn光照模型在Phong光照模型上做了一点修改。提出了半程向量,即光照法向和视线方向的中间向量。然后用这个半程向量和法线进行点乘来算高光系数。读者会发现,用这样的计算方法,向光面的高光系数就不会小于0。

创建一个新的Shader文件,命名为3_CopperBlinn.shader。把3_CopperPhong的代码复制到这里。然后修改spec系数部分为

// blinn高光部分
float3 worldViewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
// 算出半程向量
float3 halfDir = normalize(worldLightDir + worldViewDir);
// 用反射向量和视线向量的夹角模拟高光的影响程度
float spec = pow(saturate(dot(worldNormal, halfDir)), _Shiness);
fixed3 specular = _LightColor0.rgb *_SpecularColor.rgb * spec;

新建一个材质,应用3_CopperBlinn.shader,把这个材质应用到右边的球体,然后把shiness系数调到32(shiness系数相同下,blinn光照的高光范围比phong光照要大,读者可以想想原因)。效果图如下

bc1

好了,以上就是phong光照模型和blinn光照模型。这两个模型没有绝对的正确与否,大家都是经验模型。实际用哪个需要在不同的情况下考虑。最后我们再看下最终场景图,大家可以对比一下一开始的lambert模型。

sc1

结语

第三篇的分享结束了,有疑问就留言,之后的源码会越来越少,发现不对劲可以到Github上看源码。

但不管怎样,支持作者环节必不可缺。

vxpay

链接

github地址:https://github.com/gjbian/Unity-Shader-Study

上一篇:UnityShader 从入门到盖棺(二)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章