一、投光物
将光投射(Cast)到物体的光源叫做投光物(Light Caster)。
二、平行光
当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行,我们可以称这些光为平行光。当我们使用一个假设光源处于无限远处的模型时,它就被称为定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的。定向光非常好的一个例子就是太阳:
我们可以定义一个光线方向向量而不是位置向量来模拟一个定向光,并直接使用光的direction向量而不是通过position来计算lightDir向量:
struct Light {// vec3 position; // 使用定向光就不再需要了vec3 direction;vec3 ambient;vec3 diffuse;vec3 specular;
};
...
void main()
{vec3 lightDir = normalize(-light.direction);...
}
我们目前使用的光照计算需求一个从片段至光源的光线方向,但人们更习惯定义定向光为一个从光源出发的全局方向。所以我们需要对全局光照方向向量取反来改变它的方向,它现在是一个指向光源的方向向量了。
然后,定义光源的方向就可以了:
lightingShader.setVec3("light.direction", -0.2f, -1.0f, -0.3f);
三、点光源
点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减,如生活中的灯泡和火把:
其实,我们一直都在使用一个(简化的)点光源。我们在给定位置有一个光源,它会从它的光源位置开始朝着所有方向散射光线。然而,我们定义的光源模拟的是永远不会衰减的光线,这看起来像是光源亮度非常的强。现在我们要做的就是将光随距离来衰减。
四、衰减
随着光线传播距离的增长逐渐削减光的强度通常叫做衰减(Attenuation)。在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了。所以,我们可以通过以下公式实现该效果:
d 代表片段到光源的距离
常数项 Kc 通常是1.0,它的作用是保证分母永远不会比1小,因为它可以利用一定的距离增加亮度,这个结果不会影响到我们所寻找的。
一次项 Kl 用于与距离值相乘,这会以线性的方式减少亮度。
二次项 Kq 用于与距离的平方相乘,为光源设置一个亮度的二次递减。二次项在距离比较近的时候相比一次项会比一次项更小,但是当距离更远的时候比一次项更大。
为了实现衰减,在片段着色器中我们需要公式中的常数项、一次项和二次项这3个值:
struct Light {vec3 position; vec3 ambient;vec3 diffuse;vec3 specular;float constant;float linear;float quadratic;
};
然后,在OpenGL中设置这些项:
lightingShader.setFloat("light.constant", 1.0f);
lightingShader.setFloat("light.linear", 0.09f);
lightingShader.setFloat("light.quadratic", 0.032f);
接着,计算距光源的距离,进而计算衰减值:
float distance = length(light.position - FragPos);
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
最后,将包含这个衰减值到光照计算中,将它分别乘以环境光、漫反射和镜面光颜色:
ambient *= attenuation;
diffuse *= attenuation;
specular *= attenuation;
运行效果如下:
五、聚光
聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线,如路灯或手电筒。
OpenGL中聚光是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的:
LightDir:从片段指向光源的向量。
SpotDir:聚光所指向的方向。
ϕ:定义聚光半径的切光角。每个落在这个角度之外的,聚光都不会照亮。
θ:LightDir向量和SpotDir向量之间的角度。θ值应该比Φ值小,这样才会在聚光内。
我们大致要做的是,计算LightDir
向量和SpotDir
向量的点乘,然后再和切光角ϕ对比,以手电筒为例。
手电筒(Flashlight)是一个坐落在观察者位置的聚光,通常瞄准玩家透视图的前面。
我们需要为片段着色器提供聚光的位置向量(来计算光的方向坐标),聚光的方向向量和切光角:
struct Light
{vec3 position;vec3 direction;float cutOff;...
};
设置适当的值传给着色器:
glUniform3f(lightPosLoc, camera.Position.x, camera.Position.y, camera.Position.z);
glUniform3f(lightSpotdirLoc, camera.Front.x, camera.Front.y, camera.Front.z);
glUniform1f(lightSpotCutOffLoc, glm::cos(glm::radians(12.5f)));
最后,计算θ值,用它和ϕ值对比,以决定我们是否在或不在聚光的内部:
void main()
{vec3 lightDir=normalize(FragPos-light.position);float theta = dot(lightDir, normalize(light.direction));vec3 diffuseTexColor = vec3(texture(material.diffuse,TexCoords));vec3 specularTexColor = vec3(texture(material.specular,TexCoords));if(theta > light.cutOff){// 执行光照计算float distance = length(light.position - FragPos);float attenuation = 1.0 / (light.constant + light.linear * distance +light.quadratic * (distance * distance));// ambientvec3 ambient = light.ambient * diffuseTexColor;// diffusevec3 norm = normalize(Normal);vec3 lightDir = normalize(light.position-FragPos);float diff = max(dot(norm, lightDir), 0.0);vec3 diffuse = diff * light.diffuse * diffuseTexColor;// specularvec3 viewDir = normalize(light.position - FragPos);vec3 reflectDir = reflect(-lightDir, norm);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);vec3 specular = spec * light.specular * specularTexColor;// ambient *= attenuation;diffuse *= attenuation;specular *= attenuation;vec3 result = (ambient + diffuse + specular);FragColor = vec4(result, 1.0);}else // 否则使用环境光,使得场景不至于完全黑暗FragColor = vec4(light.ambient * diffuseTexColor, 1.0);}
效果如下:
demo下载:迟点再传
觉得有帮助的话,打赏一下呗。。