实现漫反射光照模型
顶点/片元着色器的基本结构
在着手写Shader之前,需要对Unity Shader的基本结构有一个认知,基本上Unity Shader包含了Shader,Properties,SubShader, FallBack等语义块
Subshader,Subshader可以理解为Shader中的一个渲染方案,即针对不同的渲染情况,需要编写不同的Subshader,在普通情况下,Shader会从上自下根据以下优先级选取合适的子着色器,第一个被选到的SubShader会用于渲染,其他的被忽略掉。
同样的Pass也是子着色器中的不同的渲染方案,有所不同的是,所有的Pass会被依次执行,Pass越多,计算复杂度越高,性能开销也更大。
漫反射光照模型
1.逐顶点着色
上一节中,我们给出了漫反射部分的计算公式,主要有四个参数 入射光线的强度和颜色,材质的漫发射系数,表面发现n,以及光源方向I. 根据所得计算公式,漫反射光照模型代码如下

这是本书学习中,我们写下的第一个光照模型shader代码,更需要对其逐行解析。
属性之中的内容为漫反射材质的颜色,并且将其初始值设置为白色
UnityObjectToClipPos
顶点着色器最基本的任务就是把顶点位置从模型空间转换到裁切空间,这里我们用的UnityObjectToCilpPos实现这个过程,同时有必要在这里了解一下shader学习中常用几种空间
1.模型空间:建模的时候规定好的一系列坐标,通常情况写Shader时需要获取模型空间的法线,顶点,纹理等等
2.世界空间:略
3.观察空间:也叫做摄像机空间,其他的转换情况以后再继续了解
4.裁切空间:裁切过程中,位于空间的图元被保留,位于空间之外的图元被剔除,裁切的过程遵循正交投影和透视投影矩阵运算
5.屏幕空间:将裁切空间坐标投影到屏幕空间坐标,使用齐次除法
将法线从模型空间转换到世界空间
从之前得到的漫反射计算公式中可知,计算漫反射时,需要光照方向和法线的点乘,这里的点乘需要光照方向和法线处于同一坐标空间的到的结果才有意义,我们通过_WorldSpaceLightPos0.xyz得到的光照方向位于世界空间,所以需要将发现从模型空间转换到世界空间。有意思的是,这里用到的矩阵的名字是unity_WorldToObject,恰好相反。
实际上,将法线转换从模型空间转换到世界空间,需要对其进行逆转置矩阵左乘。这里只解释为什么顶点可以使用常规的矩阵转换,而法线不行。这是因为顶点与法线在齐次坐标中的表示方法不一样。顶点是(x, y, z, 1),而法线是(x, y, z, 0),法线的w值是0,表示这个点的位置无穷远,因此法线是不具备位置信息的,只有方向信息。不像顶点同时具备方向和位置信息。
当模型进行平移、旋转、平移,我们需要对顶点进行矩阵转换,需要用到4X4矩阵,这是因为3X3矩阵无法表达平移转换,因此需要4X4矩阵来完成联合矩阵操作。但如果对顶点和法线使用同样的矩阵方法,会导致原来垂直于模型表面的法线,变换后不再垂直于表面。所以如果继续用对顶点变化的方式来变换法线是不行的。请看下图:

左边是原本的状态,中间是将应用在顶点上的模型变换矩阵应用在法线上的结果,显然是错的。在对法线进行变换的时候,使用的是矩阵的逆矩阵的转置矩阵。上文中的_World2Object,就相当于一个逆矩阵,而在调用mul的时候把v.normal放在了左侧代表着左乘。
通过以上的步骤,我们实现了一个逐顶点的漫反射光照模型,然而对于细分度较低的模型,逐顶点光照会出现一些视觉问题,例如光照交界处有一些锯齿,为了解决这些问题,我们可以使用逐像素的漫反射光照。
1.逐像素着色/逐片元着色
实现逐像素的漫反射光照,只需要将漫反射的计算过程移到片元着色器中,首先需要修改片元着色器的输入参数,将法线的世界空间坐标传入,剩余部分大致不变。代码如下

符合兰伯特定律的漫发射光照模型的问题在于,光照无法照到的区域,模型的外观通常为全黑,因为我们通过saturate使法线和光照方向点乘的结果大于等于0,这会使模型的背光区域看起来像一个平面一样,失去了模型细节表面,为了解决这个问题,半兰伯特模型被提了出来。
半兰伯特模型
法线和光照方向点乘的结果值域为[-1,1], 半兰伯特模型没有使用Max操作来防止点乘结果为负值,而是将点乘的结果从[-1,1]映射到[0,1]中,这样背光面也可以有明暗变化,不同的点积结果会映射到不同的值上,需要注意的是半兰伯特光照模型没有任何的物理依据,仅仅是一个视觉加强技术。
