上一篇实现了简单光照模型,使用的光照都来自于空间中的一个点
但现实世界中,我们有很多种类的光照,比如:
1.定向光(Directional Light)
2.点光源(Point Light)
3.聚光(Spotlight)
本文意图就是分别实现这几种光源。
1.定向光(也叫平行光)
例子:太阳光
当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行
因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的
可以定义一个光线方向向量 而不是位置向量来模拟一个定向光
数据结构
struct DirLight {
vec3 direction; //方向
vec3 ambient; //ambient强度
vec3 diffuse; //diffuse强度
vec3 specular; //specular强度
};
光照公式
//计算平行光颜色
vec3 CalcDirLight(DirLight light)
{
//贴图
vec3 diffuseCol = texture(material.diffuse, uv).rgb;
vec3 specularCol = texture(material.specular, uv).rgb;
//环境光
vec3 ambient = light.ambient * diffuseCol;
//漫反射
vec3 lightDir = (light.direction);
vec3 normalDir = normalize(normalWS);
vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * light.diffuse * diffuseCol;
//高光反射
vec3 viewDir = normalize(cameraPos - positionWS);
vec3 halfDir = normalize(viewDir + lightDir);
vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), material.shininess) * light.specular * specularCol;
vec3 result = ambient + diffuse + specular;
return result;
}
2.点光源
例子:灯泡
分散在场景中的点,会朝着所有方向发光,会随着距离逐渐衰减
衰减:随着光线传播距离的增长逐渐削减光的强度通常叫做衰减(Attenuation)
衰减公式:1 / (常数项 + 一次项*距离 + 二次项 + 距离的平方)
一般根据希望覆盖的距离设置,可以查表:
http://www.ogre3d.org/tikiwiki/tiki-index.php?page=-Point+Light+Attenuation
数据结构
struct PointLight{
vec3 position; //位置
vec3 ambient; //ambient强度
vec3 diffuse; //diffuse强度
vec3 specular; //specular强度
float constant; //衰减公示常数项
float linear; //衰减公式一次项
float quadratic;//衰减公式二次项
};
光照公式
//计算点光源颜色
vec3 CalcPointLight(PointLight light)
{
//计算和光源的距离
float distance = length(light.position - positionWS);
//衰减公式
float attenuation = 1.0 /
( light.constant
+ light.linear * distance
+ light.quadratic * (distance * distance) );
//贴图
vec3 diffuseCol = texture(material.diffuse, uv).rgb;
vec3 specularCol = texture(material.specular, uv).rgb;
//环境光
vec3 ambient = light.ambient * diffuseCol;
//漫反射
vec3 lightDir = normalize(light.position - positionWS);
vec3 normalDir = normalize(normalWS);
vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * light.diffuse * diffuseCol;
//高光反射
vec3 viewDir = normalize(cameraPos - positionWS);
vec3 halfDir = normalize(viewDir + lightDir);
vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), material.shininess) * light.specular * specularCol;
vec3 result = (ambient + diffuse + specular) * attenuation;
return result;
}
3.聚光灯
位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线
用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的
原理:计算LightDir向量和SpotDir向量之间的点积,并将它与切光角值对比
数据结构
struct SpotLight {
vec3 position;
vec3 direction; //方向
vec3 ambient; //ambient强度
vec3 diffuse; //diffuse强度
vec3 specular; //specular强度
float cutOff; //半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
float outerCutOff;//更大的切光角,用来做渐变
};
计算光照
//计算聚光灯颜色
vec3 CalcSpotLight(SpotLight light)
{
//指向聚光灯的方向
vec3 lightDir = normalize(light.position - positionWS);
//和光源方向做点乘(取反保证方向一致)
float theta = dot(-lightDir, normalize(light.direction));
//余弦值比较为了性能
//余弦值越接近1.0,它的角度就越小
if (theta > light.cutOff)
{
//贴图
vec3 diffuseCol = texture(material.diffuse, uv).rgb;
vec3 specularCol = texture(material.specular, uv).rgb;
//环境光
vec3 ambient = light.ambient * diffuseCol;
//漫反射
vec3 normalDir = normalize(normalWS);
vec3 diffuse = max(0.0, dot(normalDir, lightDir)) * light.diffuse * diffuseCol;
//高光反射
vec3 viewDir = normalize(cameraPos - positionWS);
vec3 halfDir = normalize(viewDir + lightDir);
vec3 specular = pow(max(0.0, dot(normalDir, halfDir)), material.shininess) * light.specular * specularCol;
vec3 result = (ambient + diffuse + specular);
//为了创建一种看起来边缘平滑的聚光
// 需要模拟聚光有一个内圆锥(Inner Cone)和一个外圆锥(Outer Cone)
float intensity = clamp((theta - light.cutOff) / (light.cutOff - light.outerCutOff), 0.0, 1.0);
result *= intensity;
return vec3(result);
}
else
{
//不在区域内,目前是黑色
return vec3(0.0);
}
}
在一个shader里面声明这些,可以叠加所有光照
void main()
{
vec3 dirLightResult = CalcDirLight(dirLight);
vec3 result = dirLightResult;
for(int i = 0; i < NR_POINT_LIGHTS; i++)
{
vec3 pointLightResult = CalcPointLight(pointLights[i]);
result += pointLightResult;
}
vec3 spotLightResult = CalcSpotLight(spotLight);
result += spotLightResult;
FragColor = vec4(result * objectColor, 1.0);
}
效果如图: