2.5 投光物
2.5.1 平行光
1、当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行,例如太阳。不论物体和/或者观察者的位置,看起来好像所有的光都来自于同一个方向。当假设光源处于无限远处的模型时,它就被称为定向光。
2、通过定义光线方向向量即可模拟一个定向光。
struct LightDirect {
vec3 lightDir;
vec3 ambient;
vec3 diffuse;
vec3 specular;
};
3、直接使用光的direction
向量来计算lightDir
向量。
vec3 ambient= lightDirect.ambient*texture(material.diffuse,TextCoords).rgb;
vec3 lightDir=normalize(lightDirect.lightDir);//直接获取光照方向
vec3 norm=normalize(Normal);
float diff=max(dot(norm,lightDir),0.0);
vec3 diffuse= lightDirect.diffuse*diff*texture(material.diffuse,TextCoords).rgb;
vec3 viewDir=normalize(viewPos-Frapos);
vec3 reflectDir=reflect(-lightDir,norm);
float spec=pow(max(dot(viewDir,reflectDir),0.0),material.shiness);
vec3 specular= lightDirect.specular*spec*texture(material.specular,TextCoords).rgb;
FragColor=vec4(ambient+diffuse+specular,1.0);
4、定义平行光类LightDirectional
//构造函数
LightDirectional::LightDirectional(Shader * _shader, glm::vec3 _angles, glm::vec3 _ambient, glm::vec3 _diffuse, glm::vec3 _specular):
shader(_shader),angles(_angles), ambient(_ambient),
diffuse(_diffuse), specular(_specular)
{
UpdateDirection();
}
//通过欧拉角计算光源方向
void LightDirectional::UpdateDirection()
{
direction = glm::vec3(0, 0, 1.0f);
direction = glm::rotateZ(direction, angles.z);
direction = glm::rotateY(direction, angles.y);
direction = glm::rotateX(direction, angles.x);
direction = -1.0f*direction;//方向应该为目标物指向光源,所以要取反
}
5、创建平行光对象
//创建对象
LightDirectional *myLightDirect=new LightDirectional(myShader,glm::vec3(glm::radians(75.0f),0,0),
glm::vec3(0.5f, 0.5f, 0.5f), glm::vec3(0.5f, 0.5f, 0.5f), glm::vec3(1.0f, 1.0f, 1.0f));
//对uniform变量进行赋值
myLightDirect->shader->setVec3f("lightDirect.lightDir", myLightDirect->direction);
myLightDirect->shader->setVec3f("lightDirect.ambient", myLightDirect->ambient);
myLightDirect->shader->setVec3f("lightDirect.diffuse", myLightDirect->diffuse);
myLightDirect->shader->setVec3f("lightDirect.specular", myLightDirect->specular);
2.5.2 点光源
1、点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减。想象作为投光物的灯泡和火把,它们都是点光源。
2、衰减:在现实世界中,灯在近处会非常亮,随着距离的增加,光源的亮度一开始会下降非常快,到远处亮度会下降的非常缓慢了。所以,需根据片段距光源的距离计算了衰减值,再将衰减值乘以光的强度向量。
该衰减函数可以通过以下函数来表达:
3、点光源的结构体,有光源位置,增加衰减函数参数。
struct LightPoint {
vec3 lightPos;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float constant;
float linear;
float quadratic;
};
4、光照强度表示,将衰减值包含到光照计算中,将它分别乘以漫反射和镜面光颜色。如果我们使用多于一个的光源,所有的环境光分量将会开始叠加,在这种情况下也可以衰减环境光照。
//漫反射光照
vec3 lightDir = normalize(lightPoint.lightPos-Frapos);
vec3 norm = normalize(Normal);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = lightPoint.diffuse*diff*texture(material.diffuse, TextCoords).rgb;
//衰减
float dist = length(lightPoint.lightPos - Frapos);//光源衰减,离光源越远,光照越弱
float attenuation = 1.0 / (lightPoint.constant + lightPoint.linear * dist + lightPoint.quadratic * (dist * dist));
FragColor = vec4((ambient + (diffuse + specular)*attenuation), 1.0);
5、定义点光源类,通常衰减参数设为常量。
constant=1.0f;
linear=0.09f;
quadratic=0.032f;
2.5.3 聚光
1、聚光是位于环境中某个位置的光源,它只朝一个特定方向照射光线,只有在聚光方向的特定半径内的物体才会被照亮,如路灯或手电筒。
2、聚光是用一个世界空间位置、一个方向和一个切光角来表示的,切光角指定了聚光的半径。对于每个片段,我们会计算片段是否位于聚光的切光方向之间,如果是的话,我们就会相应地照亮片段。
- lightDir:从片段指向光源的向量。
- SpotDir:聚光所指向的方向。
- Phiϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
- Thetaθ:
LightDir
向量和SpotDir
向量之间的夹角。在聚光内部的话θ值应该比ϕ值小,而cosθ值应该比cosϕ值大。
3、聚光的结构体,包括光源的位置,光源方向,光照分量,切光角的余弦值
struct LightSpot {
vec3 lightPos;
vec3 spotDir;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float cosPhi;
};
4、切光角Phi将余弦结果传递到片段着色器中,是因为片段着色器中LightDir
和SpotDir
向量的点积,返回的是Theta角余弦值。因此,通过比较cosPhi与cosTheta,可决定片段是否在聚光的内部。
vec3 lightDir = normalize(lightSpot.lightPos- Frapos);
float cosTheta = dot(lightDir, lightSpot.spotDir);//(片段与光源向量,光源指向)
float spotRatio;
if (cosTheta>lightSpot.cosPhiInner)
{
//inner
spotRatio = 1.0f;
}
else
{
spotRatio = 0;
}
FragColor = vec4((ambient + (diffuse + specular)*spotRatio), 1.0);
聚光仅会照亮聚光圆锥内的片段,当一个片段遇到聚光圆锥的边缘时,它会完全变暗,没有一点平滑的过渡。一个真实的聚光将会在边缘处逐渐减少亮度。
5、为了平滑边缘,要改变点光源的分量值,模拟聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)。让光从内圆锥逐渐减暗,直到外圆锥的边界。
float cosTheta = dot(lightDir, normalize(lightSpot.spotDir));//(片段与光源向量,光源指向)
float spotRatio;
if (cosTheta>lightSpot.cosPhiInner)
{
//inner
spotRatio = 1.0f;
}
else if (cosTheta > lightSpot.cosPhiOutter)
{
//middle
spotRatio = (lightSpot.cosPhiOutter - cosTheta) / (lightSpot.cosPhiOutter - lightSpot.cosPhiInner);
}
else
{
//outter
spotRatio = 0;
}
FragColor = vec4((ambient + (diffuse + specular)*spotRatio), 1.0);
6、将相机(视角)的位置和方向,作为光源的位置和方向,即可形成类似于手电筒的光源效果。
myLightSpot->shader->setVec3f("lightSpot.spotDir", -camera.Front);
myLightSpot->shader->setVec3f("lightSpot.lightPos", camera.Position);
2.6 多光源
为了在场景中使用多个光源,将光照计算封装到GLSL函数中,对每个光照类型都创建一个不同的函数。在场景中使用多个光源时,对于每一个光源,它对片段的贡献颜色将会加到片段的输出颜色向量上,结合为一个最终的输出颜色。
out vec4 FragColor;
void main()
{
// 定义一个输出颜色值
vec3 output;
// 将定向光的贡献加到输出中
output += someFunctionToCalculateDirectionalLight();
// 对所有的点光源也做相同的事情
for(int i = 0; i < nr_of_point_lights; i++)
output += someFunctionToCalculatePointLight();
// 也加上其它的光源(比如聚光)
output += someFunctionToCalculateSpotLight();
FragColor = vec4(output, 1.0);
}
1、定向光
vec3 CalclightDirectionnal(LightDirect lightDirect,vec3 norm, vec3 viewDir)
{
vec3 result;
vec3 ambient= lightDirect.ambient*texture(material.diffuse,TextCoords).rgb;
vec3 lightDir=normalize(lightDirect.lightDir);//直接使用光源方向
float diff=max(dot(norm,lightDir),0.0);
vec3 diffuse= lightDirect.diffuse*diff*texture(material.diffuse,TextCoords).rgb;
vec3 reflectDir=reflect(-lightDir,norm);
float spec=pow(max(dot(viewDir,reflectDir),0.0),material.shiness);
vec3 specular= lightDirect.specular*spec*texture(material.specular,TextCoords).rgb;
result = ambient + diffuse + specular;
return result;
}
2、点光源
vec3 CalclightPoint(LightPoint lightPoint, vec3 norm, vec3 viewDir)
{
vec3 result;
vec3 ambient = lightPoint.ambient*texture(material.diffuse, TextCoords).rgb;
vec3 lightDir = normalize(lightPoint.lightPos-Frapos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = lightPoint.diffuse*diff*texture(material.diffuse, TextCoords).rgb;
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shiness);
vec3 specular = lightPoint.specular*spec*texture(material.specular, TextCoords).rgb;
float dist = length(lightPoint.lightPos - Frapos);//光源衰减,离光源越远,光照越弱
float attenuation = 1.0 / (lightPoint.constant + lightPoint.linear * dist + lightPoint.quadratic * (dist * dist));
result = ambient + (diffuse + specular)*attenuation;
return result;
}
3、聚光
vec3 CalclightSpot(LightSpot lightSpot, vec3 norm, vec3 viewDir)
{
vec3 result;
vec3 ambient = lightSpot.ambient*texture(material.diffuse, TextCoords).rgb;
vec3 lightDir = normalize(lightSpot.lightPos - Frapos);//片段与光源向量
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = lightSpot.diffuse*diff*texture(material.diffuse, TextCoords).rgb;
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shiness);
vec3 specular = lightSpot.specular*spec*texture(material.specular, TextCoords).rgb;
float cosTheta = dot(lightDir, normalize(lightSpot.spotDir));//(片段与光源向量,光源指向)
float spotRatio;
if (cosTheta>lightSpot.cosPhiInner)
{
//inner
spotRatio = 1.0f;
}
else if (cosTheta > lightSpot.cosPhiOutter)
{
//middle
spotRatio = (lightSpot.cosPhiOutter - cosTheta) / (lightSpot.cosPhiOutter - lightSpot.cosPhiInner);
}
else
{
//outter
spotRatio = 0;
}
float dist = length(lightSpot.lightPos - Frapos);//光源衰减,离光源越远,光照越弱
float attenuation = 1.0 / (lightSpot.constant + lightSpot.linear * dist + lightSpot.quadratic * (dist * dist));
result = ambient + (diffuse + specular)*attenuation*spotRatio;
return result;
}
4、主函数
void main()
{
vec3 norm = normalize(Normal);
vec3 viewDir = normalize(viewPos - Frapos);
vec3 finalResult = vec3(0, 0, 0);
finalResult += CalclightDirectionnal(lightDirect, norm, viewDir);
finalResult += CalclightPoint(lightPoint, norm, viewDir);
finalResult += CalclightSpot(lightSpot, norm, viewDir);
FragColor = vec4(finalResult, 1.0f);
}