Lambert、Half Lambert光照模型介绍

2019-09-29  本文已影响0人  上善若水_2019

今天我来介绍下图形学中比较基础的一种光照模型,Lambert光照模型
这个模型最早是在1760年(真够早的=_=!)由Johann Heinrich Lambert在其著作Photometria中提出,它定义了一个理想的无光泽表面或者漫反射表面,无论观察者从哪个角度去观察这个表面,此表面的亮度永远不变,即可说这个表面的亮度是各向同性的。
在Lambert光照模型中,表面亮度遵循Lambert 余弦定理,即亮度=max(L•n,0)(L代表光线向量,n代表法线向量),具体推导参考DirectX11 兰伯特余弦定理(Lambert)
L•n小于零也就是光线向量和法线向量的夹脚超过90度时,这个表面成为了一个背光面,从公式来看直接取值为0了,那么颜色就是全黑,但全黑并不好看,而实际上即使一个物体(或者说一个表面)没有接受到光的直接照射,我们也可能看到它。这其实是由物体间的光的反射造成的,也就是间接光照,不过在这篇博客中我并不打算详细说,因为间接光是种比较高级的渲染,在这里我们一般用环境光(ambient)代替间接光。而全黑不好看的问题是由valve公司(这公司应该不用科普了,在游戏圈太有名了)将Lambert光照改进为half lambert模型解决的,这个half lambert技术据说第一次应用在了《半条命》。
下面我们来看看如何在unity中实现。
首先我想说的是我都以逐像素计算的方式实现,逐顶点也可以,但效果没有逐像素好看,原因就要涉及到渲染管线的东西了。

image
首先我们在vertex shader中会拿到模型所有的各种顶点、法线等信息,将它变换到合适的坐标系下,进行插值计算再传给fragment shader(DirectX叫pixel shader),这里的插值计算就是其他地方看见的光栅化。从图上我们可以看出一个vertex并不和一个fragment对应,fragment是由vertex插值得出的。那么这里可以得出一个结论:

我们从vertex shader传递过来的法线方向,只代表了这一个顶点的顶点法线方向,而到了pixel阶段,这个像素所对应的法线等参数相当于其周围几个顶点进行插值后的结果。我们用这一个像素点对应的法线方向与光照方向进行计算,就可以获得该像素点在光照条件下的颜色值,而不是先计算好颜色再插值得到结果。

现在我们来看代码
o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
首先我们要在vertex shader中获取法线向量并将它转到世界坐标下,这里很重要的一点是不要用mul((float3x3)unity_ObjectToWorld,v.normal)这样的方式来转换,对于法线来说这是不对的!详细可以参考法线变换详解(Normal Transform)这篇博客,我这里简单说下。因为对于模型来说它可以进行非统一拉伸,就是x方向、y方向和z方向进行不同程度的拉伸,而进行这种拉伸后如果再用mul(unity_ObjectToWorld,v.normal)会造成法线与表面不垂直,所以这里一定要mul(v.normal,(float3x3)unity_WorldToObject)这么干!
当然unity自带了一个方法叫UnityObjectToWorldNormal,也可以调用这个方法把normal从模型坐标转换到世界坐标下。
然后在fragment shader里准备好法线向量float3 normal = normalize(i.worldNormal);和光线向量float3 worldLight = normalize(_WorldSpaceLightPos0.xyz);我们关心的是光线和法线之间的角度,所以把他俩都归一化一下之后再算点乘。
最后我们通过Lambert余弦定理计算最终输出结果:

float3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0.0,dot(normal,worldLight));
float4 finalCol = float4((ambient + diffuse) * col.rgb,1);

而半Lambert模型就是在算diffuse这项时用点乘后的值乘以0.5再加上0.5,让它从[-1,1]区间变到了[0,1],而Lambert光照模型对于负数是直接算作零的,也就是说点乘的区间在[0,1],那么经过操作会变成[0.5,1],也就是说从观感上暗的地方不再那么暗了。

float3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(normal,worldLight) * 0.5 + 0.5);
float4 finalCol = float4(((diffuse * diffuse) + ambient) * col.rgb,1);

其中_Diffuse参数可以用来调整颜色。而对diffuse平方则是官方文档上那么写的,大概不平方有点太亮了,和之前的结果差异有点大(个人猜测)

To soften the diffuse contribution from local lights, the dot product from the Lambertian model is scaled by ½, add ½ and squared.

项目地址

参考
Unity Shader-兰伯特光照模型与Diffuse Shader
Lambert光照模型
Half Lambert

上一篇 下一篇

猜你喜欢

热点阅读