投光物

平行光:当使用一个假设光源处于无限远处的模型时,被称为定向光。因为它的光线都有着相同的方向,与光源的位置无关。

理解:太阳光便为平行光

定义一个光线方向向量而不是位置向量来模拟一个定向光

 1 struct Light {
 2     // vec3 position; // 使用定向光就不再需要了
 3     vec3 direction;
 4 
 5     vec3 ambient;
 6     vec3 diffuse;
 7     vec3 specular;
 8 };
 9 ...
10 void main()
11 {
12   vec3 lightDir = normalize(-light.direction);
13   ...
14 }

光照计算中采用的都是一个从片段到光源的光线方向。light.direction是人们的习惯,由光源指向物体的方向。所以需要取反。

二、点光源

是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减,例如:灯泡

衰减

随着光线传播距离的增长逐渐消减光的强度通常叫做衰减

随距离减少光强度的一种方式是使用一个线性方程。这样的方程能够随着距离的增长线性地减少光的强度,从而让远处的物体更暗。然而,这样的线性方程通常会看起来比较假。在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了。所以,我们需要一个不同的公式来减少光的强度。

d:代表片段距光源的距离。

Kc:常数项;Kl:一次项;Kq:二次项。

常数项通常保持为1.0,作用为:保证值永远小于1,否则的话在某些距离上反而会增加强度

一次项会与距离值相乘,以线性的方式较少强度

二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候就会比一次项更大了。

由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:

实现衰减:

为了实现衰减,在片段着色器中我们还需要三个额外的值:也就是公式中的常数项、一次项和二次项。它们最好储存在之前定义的Light结构体中。

然后我们将在OpenGL中设置这些项:我们希望光源能够覆盖50的距离,所以我们会使用表格中对应的常数项、一次项和二次项:

1 cubeShader.setFloat("light.constant",1.0f);
2 cubeShader.setFloat("light.linear",0.09f);
3 cubeShader.setFloat("light.quadratic",0.032f);

计算衰减:

length函数为GLSL内建的函数

1 float distance = length(light.position - fragPos);
2 float attenuation = 1.0/(light.constant + light.linear * distance + light。quadratic *(distance,distance));

接下来,我们将包含这个衰减值到光照计算中,将它分别乘以环境光、漫反射和镜面光颜色。

1 ambient  *= attenuation; 
2 diffuse  *= attenuation;
3 specular *= attenuation;

 聚光

聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒

  • LightDir:从片段指向光源的向量。
  • SpotDir:聚光所指向的方向。
  • Phiϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
  • Thetaθ:LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小。

所以我们要做的就是计算LightDir向量和SpotDir向量之间的点积(还记得它会返回两个单位向量夹角的余弦值吗?),并将它与切光角ϕ值对比。

在片段着色器中我们需要的值有聚光的位置向量(来计算光的方向向量)、聚光的方向向量和一个切光角。我们可以将它们储存在Light结构体中:

struct Light {
    vec3  position;
    vec3  direction;
    float cutOff;
    ...
};

手电筒(Flashlight)是一个位于观察者位置的聚光,通常它都会瞄准玩家视角的正前方。

lightingShader.setVec3("light.position",  camera.Position);
lightingShader.setVec3("light.direction", camera.Front);
lightingShader.setFloat("light.cutOff",   glm::cos(glm::radians(12.5f)));

你可以看到,我们并没有给切光角设置一个角度值,反而是用角度值计算了一个余弦值,将余弦结果传递到片段着色器中。这样做的原因是在片段着色器中,我们会计算LightDirSpotDir向量的点积,这个点积返回的将是一个余弦值而不是角度值,所以我们不能直接使用角度值和余弦值进行比较。

接下来就是计算θ值,并将它和切光角ϕ对比,来决定是否在聚光的内部:

float theta = dot(lightDir, normalize(-light.direction));

if(theta > light.cutOff) 
{       
  // 执行光照计算
}
else  // 否则,使用环境光,让场景在聚光之外时不至于完全黑暗
  color = vec4(light.ambient * vec3(texture(material.diffuse, TexCoords)), 1.0);

你可能奇怪为什么在if条件中使用的是 > 符号而不是 < 符号。theta不应该比光的切光角更小才是在聚光内部吗?这并没有错,但不要忘记角度值现在都由余弦值来表示的。

角度越大,余弦值越小

原文地址:https://www.cnblogs.com/keguniang/p/10020187.html