平行光:当使用一个假设光源处于无限远处的模型时,被称为定向光。因为它的光线都有着相同的方向,与光源的位置无关。
理解:太阳光便为平行光
定义一个光线方向向量而不是位置向量来模拟一个定向光
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)));
你可以看到,我们并没有给切光角设置一个角度值,反而是用角度值计算了一个余弦值,将余弦结果传递到片段着色器中。这样做的原因是在片段着色器中,我们会计算LightDir
和SpotDir
向量的点积,这个点积返回的将是一个余弦值而不是角度值,所以我们不能直接使用角度值和余弦值进行比较。
接下来就是计算θ值,并将它和切光角ϕ对比,来决定是否在聚光的内部:
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不应该比光的切光角更小才是在聚光内部吗?这并没有错,但不要忘记角度值现在都由余弦值来表示的。
角度越大,余弦值越小