组成
基本方法是把进入摄像机的光线分成4个部分,每个部分分别有一种分发来计算其贡献度
自发光(emissive):物体本身产生的光
高光反射(specular):光线从光源照到物体表面反射到眼睛里的光
漫反射(diffuse):这个部分是光线从光源照到物体表面时,物体向各个方向产生的光。
环境光(ambient):这个部分用来描述其他间接的光。我觉的可以理解成整体的一个基础颜色
公式
自发光
通常用一个颜色常量表示整体的自发光颜色,或者是采样一张自发光贴图再乘以一个自发光系数
高光反射
分为Phong模型和Blinn-Phong模型,分别是左图和右图。计算高光反射需要知道表面法线、视角方向、光源方向、反射方向
Phong模型代码
float3 r = 2 * dot(normal, clightDir) * normal - clightDir;
float3 v = normalize(cCameraPos - worldPos);
float specular = pow(max(dot(v, r), 0.0), cgloss);
float3 specularColor = cLightColor * cSpecularColor * specular;
Blinn-Phong模型代码
float3 v = normalize(cCameraPos - worldPos);
float3 h = normalize(v + clightDir);
float specular = pow(max(dot(normal, h), 0.0), cgloss);
float3 specularColor = cLightColor * cSpecularColor * specular;
和上面的Phong模型相比,Blinn模型避免了反射方向r的计算,而是引入了一个新的矢量h。相比于计算向量r计算向量h的运算更少,所以现在比较常用的是Blinn-Phong模型
漫反射
一般做法是先采样diffuse贴图,然后根据是否有透贴的需求,对Alpha小于某个值(例如0.5),认为是透明的,进行裁剪。然后进行光照计算
float4 diffuse = tDiffuse.Sample(sDiffuse, iTexCoord.xy);
#ifdef ALPHAMASK
if (diffuse .a < 0.5)
discard;
#endif
float4 diffColor = cDiffColor * diffuse;
float diff = max(dot(inormal, clightDir), 0.0);
float4 diffuseColor = cLightColor * diffColor * diff;
环境光
通常用一个颜色常量表示环境光
最终结果
color = emissiveColor + specularColor + diffuseColor + cAmbientColor;
逐像素光照与逐顶点光照
逐顶点光照模型也就是我们常说的高洛德着色(Gouraud shading),而逐像素光照模型则是Phong着色(Phong shading)
两者计算公式其实都是一样的用的上面说的这套计算公式,区别就是一个在顶点着色器里计算,另一个在像素着色器里计算。由于像素个数远比顶点个数多得多,所以Gourand着色性能要明显优于Phong着色。但是Phong效果要优于Gouraud,因为如果是Gouraud,顶点着色器计算完之后像素着色器里拿到的颜色是插值之后的颜色。试想一下一个三角面上的一个像素,由3个顶点插值得出最终颜色。而Phong着色在像素着色器里计算的,可以每个像素根据插值下来的UV去采样贴图,这样结果比较精准。
举个例子,就好比一个有序的整型数组,像素就好比数组里的第几个元素。Gouraud着色就好比,知道第几个元素以及数组长度,然后根据最大值和最小值算一下,value = Min + (Max - Min) * index / length; 而Phong着色,则是直接拿index在数组中对应的元素,如value = nums[index];
此外,与它们平级的还有一个逐多边形着色(Flat shading),对整个三角形只计算一次光照值。通常计算光照的位置为三角形中心,表面法向量为三角形法向量。使用flat着色,物体由多边形构成的本质表露无遗,没有任何光泽可言,所以也基本没人用。
模拟卡通风格
Toon着色(也叫Celshading,卡通着色)是一种非相片感的着色技术,常常被用来模拟手绘风格。它的一个特点就是颜色层次比较明显。
做法就是将计算diffuse用到的余弦值(dot(normal,lightDir),等价于normal和lightDir夹角的余弦值),映射到几个固定值。例如值为[0,0.25)的映射成0,[0.25,0.5)映射成0.25,[0.5,0.75)映射成0.5,[0.75,1.0)映射成0.75。这样的话,光照结果就会出现一层层的感觉。
通过下面公式进行映射
cosine = floor( cosine * 4) * 0.25;