Phong、Blinn-Phong光照模型介绍

上次介绍了兰伯特、半兰伯特光照模型及其实现,这次介绍一个进阶一点的光照模型--Phong光照模型。简单来讲,Phong光照模型就是在兰伯特基础上添加了一个specular项,这个东西是用来模拟镜面反射的。通常我们在现实生活中看见的金属物或大或小都有一个特别亮的点(高光点),这是因为金属物的表面比较光滑,呈现较多的镜面反射,而另一方面,非金属的东西表面比较粗糙,呈现更多的漫反射。(漫反射、镜面反射什么的有空我开个新篇专门介绍下,这里就一笔带过了)
所以Phong模型的公式就是亮度 = ambient(环境光)+ diffuse(漫反射)+ specular(镜面反射/高光)。在这里ambient(环境光)是用来模拟间接光的。
之前的介绍里我们已经知道了如何得到ambient项和diffuse项了,这边的specular项如何得到呢?我们来看一张图


图中v代表观察方向,n是法线,l是光线方向,而这个r对于我们要求的specular项是很重要的,所以要先得到这个r。
这个r要怎么算呢?unity中有个专门的方法reflect来计算这个东西,只要把法线和光线的方向取反(这里取反的意思是如果向量是从a到b,那么传进去的就是从b到a的向量,添加一个负号就行)传入即可。为什么要取反?看图

一般来说我们在shader里会得到光到某个顶点的一个向量,而实际上我们在计算时需要的是顶点到光的向量,所以要取反。
得到r之后根据Phong这个人的研究(它的全名叫Bui Tuong Phong(裴祥风),是个越南出生的美国计算机科学家),specular = I * k * pow(max(0,dot(R,V)), gloss),其中I是入射光的颜色,k是镜面反射系数,gloss代表光滑程度。
而Blinn这个人(全名James F. Blinn)对Phong模型进行了一些修改,由于要得到r的计算量比较大(图中可以看出得到r需要向量点乘,标量与向量相乘以及向量之间相加),所以Blinn引入了一个半角向量h的概念,h=v+l,来代替r,这样计算量就比之前小了不少。
两个模型的对比

现在我们来看如何实现Phong模型
首先我们在vertex shader中得到世界坐标下的法线以及顶点

o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;

然后在fragment shader中计算specular项

float3 reflection = normalize(reflect(-worldLight,normal));
float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
float3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0.0,dot(reflection,viewDir)),_Gloss);

最后加上ambient、diffuse和一些控制参数
float4 finalCol = float4((ambient + diffuse + specular * spec.rgb * _SpecularScale) * col.rgb,1);

而对于Blinn-Phong,其余都一样,只是把r这个向量替换成半角向量h

float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
float3 halfDir = normalize(viewDir + worldLight);
float3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(halfDir,normal)),_Gloss);

其中spec.rgb控制高光点的颜色,_SpecularScale控制高光范围,_Gloss控制光滑程度。

这里我想说下saturatemax这两个内置函数,他们从结果上来看是没有区别的,但是从编译角度出发,他们在不同的平台会编译出不同的结果。这篇博客里说微软有saturate modifier,所以在DirectX平台下会对saturate特别优化,所以saturate会比max快,而其他的情况就不是了。根据这个博客的实验结论是:

Conclusion:
In most cases, saturate(x) is faster or as good as max(0, x), and it is free. saturate(x) can be performed as fast as x.
PowerVR doesn’t have saturate modifier for ‘float’ and ‘half’ variables. That is, saturate modifier is available only for ‘fixed’ variables. This was the only case that saturate(x) was slower than max(0, x). For fixed variables, saturate(x) and x had same performance.
It seems like Tegra 3 has saturate modifier, additionally, it also has max(0, x) modifier in some specific cases. Tegra 3 might have very complicated architecture. The performance is unpredictable! However, saturate(x) was always better than or as good as max(0, x).
Adreno has saturate modifier. saturate(x) is cost free.
Mali might have ‘max(0, x)’ modifier as well as saturate(x). saturate(x), max(0, x) and x had same performance.

翻译一下大概:
1.saturate大部分情况下快于或者等于max。
2.powervr平台没有针对floathalf做saturate modifier,saturate modifier只用于 fixed变量。所以saturate不如max的情况仅此一家,而对于fixed变量来说是一样的。
3.Tegra 3架构复杂,谁快谁漫不好判断,但大势是saturate大部分情况下快于或者等于max。

  1. Adreno有saturate modifier,saturate快。
  2. Mali(它竟然是种GPU,我都不知道)两者一样

由于这些结论是2014年得出的,现在到底适不适合我也不清楚(手边没有那么多不同的gpu设备可以测试的),个人认为这两个函数可以互相替换。

另外根据这里来看,unity会根据平台不同把saturate编译成不同的代码。结论就是:

Unity应该是根据各个平台每种操作耗时的平衡,选择同等运算结果下最优的等价操作来代替saturate。

项目地址

参考
Unity Shader-Phong光照模型与Specular

你可能感兴趣的:(Phong、Blinn-Phong光照模型介绍)