不同的光照

在光照这一节中,我们简单的介绍了光照。但是实际上光还可以有多种形态,我们只要对其进行简单的数学操作,就可以得到不同的效果。

平行光

何谓平行光,其实就是光线的方向不会改变。例如太阳光,在同一时刻如果我们在地面插上两根等长的棍子,我们会发现棍子的影子应当是同等长度的。所以当我们要模拟平行光时,我们便只要将lightDir修改为一个定值即可,即不需要再根据像素位置和光源位置来计算了。

1
2
3
4
5
6
7
struct Light_parallel{
vec3 direction;
vec3 ambient;
vec3 diffuse;
vec3 specular;

};

计算的时候直接利用direction来计算即可。

点光源(Plus)

在之前我们已经模拟了点光源了,但是那个点光源其实还是有一点bug,就是它的照射范围是无穷的,即无论离多远都会被点光源照亮。但实际上这是不可能的,虽然光可以传播无穷远,但是其会在传播的路径上不断地被损耗,所以光强实际上会随着距离的变大而逐渐变小。所以这就需要我们用一种函数来模拟光强的这种性质,但好在这仍然十分的简单:Fatt=1.0Kc+Kld+Kqd2F_{att}=\frac{1.0}{K_c+K_l*d+K_q*d^2},这个公式中d代表了光的传播距离,而二次项、一次项、常数项系数分别决定着强度衰减的速度。这里已经提前测好了传播距离和kc、kl和kq的关系。

距离 常数项 一次项 二次项
7 1 0.7 1.8
13 1 0.35 0.44
20 1 0.22 0.2
32 1 0.14 0.07
50 1 0.09 0.032
65 1 0.07 0.017
100 1 0.045 0.0075

而我们最后也只要在计算最后乘以光照强度即可,这样就可以让光的传播距离控制在一个范围里面。

1
2
float k=1.0/(distance*distance*torch.quadratic+distance*torch.linear+torch.constant);
FragColor=vec4((ambientLight+diffuseLight+specularLight)*k,1.f)

手电筒

要想实现一个类似手电筒的光照效果,我们则需要将光聚拢在一个光锥里面,怎么样使得光形成这种形状呢?我们知道要控制圆锥张开的大小,其实只要控制母线和垂线的夹角即可,所以当光线和光源方向大于我们设定的这个值的时候,即可判断这束光在圆锥外面,所以不用渲染,而当这个角度小于我们设定的值的时候,光束就会在里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Light_torch{
vec3 position;
vec3 direction;
float cutOff;
float outerCutOff;

vec3 ambient;
vec3 diffuse;
vec3 specular;

float constant;
float linear;
float quadratic;

};

这里我们通常将阈值设定成角度的余弦,因为如果是设成角度的话,我们计算出光线和光源方向之间夹角的余弦后还要对余弦求反余弦,但是反余弦是很消耗算力的,所以我们直接将阈值设定成余弦值即可。

但是这里仍然有一个问题,就是明暗交接将会变得十分锐利,这很有违和感,我们希望明暗之间有一个缓冲区,可以逐渐由明转暗,这样我们就需要在光锥外面再套上一个光锥,作为我们的外圈层。这样在内圈和外圈之间的光线就可以在(0,1)直接坐线性插值,达到一个逐渐变化的效果。

1
2
3
float theta=dot(ligtDir,normalize(-torch.direction));
float epsilon=torch.cutOff-torch.outerCutOff;
float intensity=clamp((theta-torch.outerCutOff)/epsilon,0.f,1.f);

当然手电筒通常都是跟随玩家移动的,也就是说我们通常会将手电筒光源的位置放在相机的位置,并且还要随着相机移动和旋转,所以这也就意味着光源的postiondirection要和摄影机保持一直。这样我们就可以做出比较真实的手电筒光效了,这十分像恐怖片中的常见,所以也广泛运用于恐怖游戏中。