现实世界中,我们看到的任何物体都受光照的影响。没有光,我们也就看不到任何东西,因为光,我们才能感知到这个丰富的世界。而在3D渲染中为了能获得更加真实的渲染效果,光照计算就不可或缺。
在Shader的光照计算中,主要有 环境光,漫反射光,镜面高光。光源类型分为:平行光,点光源,聚光灯。本文我们将通过对LayaAir引擎 BlinnPhongMaterial 材质中光照计算进行讲解,以方便我们的学习。
漫反射光
漫反射代表了从一个表面相等地向所有方向反射出去的方向光,光的放射量与光到达表面的入射角度成正比。无论视点在哪里,表面上的一个点的漫反射都是一样的。
漫反射光的计算模型主要有 兰伯特光照模型和 半兰伯特光照模型。在 BlinnPhongMaterial 中使用的是 兰伯特光照模型。
兰博特光照模型
规范化的向量 N 和 L 的点积是两个向量之间夹角的一个度量。两个向量之间的夹角越小,点积的值越大,而表面会受到更多的入射光照。背向光源的表面将产生负的点积值,因此在公式中的 max(N · L , 0) 项确保了这样的表面不会显示漫反射光照。
在 BlinnPhongMaterial 中计算漫反射使用了 Lighting.glsl 库中的函数LayaAirBlinnPhongLight ,该函数接收的输入光向量(L)为光源 到3D模型点的方向,所以在计算 N和L的点积时对输入的光向量lightVec取反。
兰伯特漫发射渲染效果:
半兰博特光照模型
实际上,我们在现实世界中经常会发现,即使我们让一个物体不被光直接照射,我们也可能会看到物体,虽然亮度不是很高。半兰伯特模型可以使计算出来的光照结果大于0,又整体提升了亮度,使非直接受光面不是单纯的置为黑色。
兰伯特计算N和L向量的点积值的值域为[-1 ,1]:
dot(N, L) 值域: -1.0 –> 1.0
半兰伯特就是将N和L的点积值域转换到 [0 , 1]:
dot(N, L)* 0.5 + 0.5 值域: 0.0 –> 1.0
半兰伯特渲染效果:
环境光
环境光看起来并不是来自某个方向的,相反它看起来像是来自所有方向,所以环境光并不依赖于光源的位置。环境光是一个全局的光照颜色。
BlinnPhongMaterial材质中获取环境光的函数调用:
GlobalIllumination.glsl库中的函数:layaGIBase
获得环境光后,在最后的片段颜色输出时,将 环境光 和 漫反射光 进行计算:
将环境光(globalDiffuse)和 漫反射光 (diffuse)相加,再乘以 纹理采样颜色,即可得到纹理颜色+环境光颜色+漫反射光颜色的 最终输出值。
镜面反射高光
镜面反射高光代表了从一个表面主要的反射方向附近被反射的光,镜面反射高光在非常光滑和光泽的表面上是最显著的。不像漫反射,镜面反射的作用依赖于观察者的位置(即摄像机的位置),如果观察者不在一个能够接收反射光线的位置上,观察者将不可能在表面上看到一个镜面反射高光。镜面反射高光不仅受光源和材质的镜面反射颜色的影响,而且受表面的光泽度的影响。越光泽的材质的高光区域越小,而不那么光泽的材质的高光区域则分散的很开。
BlinnPhong光照模型:
当视向量(V)和半角向量(H)之间的夹角很小时,材质的镜面反射外表将变得很明显。N和H的点积的幂确保了镜面反射外表当H和V分开的时候能够迅速的减弱。如何N和H的点积为负,将强制限定为0,这能确保镜面反射高光不会出现在背向光源的几何表面。
BlinnPhongMaterial材质中镜面高光的计算:
specColorIntensity:高光强度值(0 至 1),控制了高光区域的大小。 gloss:控制高光的强弱。
通过函数LayaAirBlinnPhongLight计算出镜面反射高光后,只需要将高光颜色叠加最后的颜色输入值中:
镜面高光渲染效果:
获取光源
在shader代码中,我们主要通过引擎提供的几个uniform参数来获取场景中的光源数据,如平行光,点光源,聚光灯。
u_DirationLightCount:平行光的数量
u_LightBuffer: 灯光数据。LayaAir引擎将场景中的灯光数据存入一张贴图中,在shader代码中计算正确的UV坐标,就可以获取到灯光数据。
u_LightClusterBuffer:灯光聚类纹理,存储 点光源 和聚光灯 相对于当前 3D模型的信息。
平行光
平行光的数据结构:
BlinnPhongMaterial材质中获取平行光:
getDirectionLight函数需要入 灯光数据贴图和平行光编号。
点光源
是从空间中的一个点,向四周均匀的发光,被照点离光源的位置越远,光照越弱。超出点光源照射范围的物体将不受此光源的影响。
点光源的数据结构:
BlinnPhongMaterial材质中获取点光源:因为点光源和聚光灯都是有照射范围,只有在它的范围内的物体才受光照的影响,所以对一个渲染片段,我们只需要计算在光源有效范围内的光照。通过函数getClusterInfo ,可以找出对此片段有效的点光源和聚光灯数据。
函数 getClusterInfo 的实现:函数需要的参数 (聚类纹理(u_LightClusterBuffer) , 视图矩阵 (u_View) , 摄影机视口 (u_Viewport) , 投影参数 (u_ProjectionParams))都是引擎为我们提供的uniform变量;片段在世界空间中的位置(position)需要我们在顶点着色器中计算;相对坐标信息 (gl_FragCoord)是glsl内置变量。
获得点光源后,就可以计算 漫反射光和 镜面反射高光:
在点光源的计算中,漫反射和镜面反射高光的计算和 平行光是一样的,唯一的差别是需要对计算的光照结果进行一个距离的衰减。在 Lighting.glsl 库中,有一个衰减函数:
聚光灯
是从空间中的一个点,向一定方位发光,被照点离光源的位置越远,光照越弱;被照点离中心方向越远,光照越弱。
聚光灯的数据结构:
在聚光灯的计算中,我们需要继续距离衰减 和 角度衰减
角度衰减:将圆锥体分成两部分:一个内部圆锥和一个外部圆锥,内部圆锥发出固定强度的光,在内部圆锥以外强度平滑地逐渐减少。
通过以上内容的介绍,我们可以了解到:如何在LayaAir引擎中获取3中灯光数据(平行光,点光源,聚光灯),如何去计算经典光照模型(环境光,漫反射光,镜面反射高光)。在我们的自定义shader开发中,只需要去获取对应的光源,就可以调用 Lighting.glsl 库中的函数去计算对应光源的光照结果。
LayaAirBlinnPhongDiectionLight:平行光 漫反射和镜面高光计算函数 LayaAirBlinnPhongPointLight:点光源 漫反射和镜面高光计算函数 LayaAirBlinnPhongSpo:聚光灯 漫反射和镜面高光计算函数
平行光的代码:
点光源的代码:
聚光灯的代码:
END
作者简介
本篇文章的作者唐涛,LayaAir引擎社区的一名游戏开发者,从事了12年游戏前端开发的游戏爱好者。他常说的一句话是:代码世界是我的乐园,shader编程是乐园中最美的花朵!
唐涛不仅投稿了本篇技术分享的内容,还投稿了基于LayaAir引擎的视频课程《webGL Shader从入门到精通》。该课通过99个视频课程,结合LayaAir 2.x引擎,从基础原理入门开始到大量的自定义shader效果实战。帮助开发者更好的学习和使用shader,开发出更丰富炫酷的3D效果!