1.平行光
平行光,即光线方向为定值。
在点光源中,我们通过 lightDir = normalize(light.position.xyz - FragPos);
来确定光线照射角度,而在平行光中lightDir(光照角度)是给定的,故我们只需要用给定的光照角度即可。
但是我们如何判定一个光源是平行光还是点光源呢?
我们已知:平行光是一个方向向量,点光源是一个位置向量。
平行光可以改变方向,而不可以移动。点光源可移动。
故如果我们对世界坐标所有对象做一次旋转,则用一个旋转矩阵 乘 平行光可改变平行光照射方向,而用一个旋转矩阵 乘 点光源 可改变点光源位置。
但如果用一个平移矩阵对世界坐标所有对象做一次位移,则只会改变点光源位置。
由先前学习知,vec4(x,y,z,w)
,当w
为0.0f
时,变化矩阵对该向量的位移失效。
故,方向向量就会像这样来表示:vec4(0.2f, 1.0f, 0.3f, 0.0f)
。(w==0)
而位置向量会用vec4(0.2f, 1.0f, 0.3f, 1.0f)
(w == 1)来表示。
这正是旧OpenGL(固定函数式)决定光源是定向光还是位置光源(Positional Light Source)的方法,并根据它来调整光照。
代码实现:
struct Light {
vec4 position;
vec3 color;
};
用position
存储点光源和平行光的数据。
vec3 lightDir;
if(light.position.w != 0.0f ){//点光源
lightDir = normalize(light.position.xyz - FragPos);//该渲染点指向光源
}
else{//平行光
lightDir = normalize(-light.position.xyz);
}
用position.w
来确定是点光源还是平行光。
2.点光源的衰减
F
a
t
t
=
1.0
/
(
K
c
+
K
l
∗
d
+
K
q
∗
d
2
)
Fatt=1.0/(Kc+Kl∗d+Kq∗d^2)
Fatt=1.0/(Kc+Kl∗d+Kq∗d2)
d 代表了片段距光源的距离
定义3个(可配置的)项:常数项Kc、一次项Kl和二次项Kq。
- 常数项通常保持为1.0,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度,这肯定不是我们想要的效果。
- 一次项会与距离值相乘,以线性的方式减少强度。
- 二次项会与距离的平方相乘,让光源以二次递减的方式减少强度。二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了。
设定它们的值取决于很多因素:环境、希望光覆盖的距离、光的类型等
实现衰减
struct Light {
vec4 position;
vec3 color;
float constant;//常数项Kc,恒为1,保证分母永远不会比1小
float linear;//一次项Kl,与距离值相乘,以线性的方式减少强度。
float quadratic;//二次项Kq,与距离的平方相乘,让光源以二次递减的方式减少强度。
};
quadratic 美 /kwɑːˈdrætɪk/ adj. [数] 二次的。
点光源的衰减实现:
vec3 lightDir;
float attenuation = 1.0f;
if(light.position.w != 0.0f ){//点光源
float distance = length(light.position.xyz - FragPos);//点光源距离
attenuation = 1.0 / (light.constant + light.linear * distance + \\
light.quadratic * (distance * distance));点光源强度衰减系数计算
lightDir = normalize(light.position.xyz - FragPos);//该渲染点指向光源
}
else{//平行光
lightDir = normalize(-light.position.xyz);
}
attenuation 美 /əˌtenjuˈeɪʃn/ n. [物] 衰减;变薄;稀释
3.聚光(手电筒,台灯等大多光源)
- LightDir:从片段指向光源的向量。
- SpotDir:聚光所指向的方向。
- Phiϕ:指定了聚光半径的切光角。落在这个角度之外的物体都不会被这个聚光所照亮。
- Thetaθ:LightDir向量和SpotDir向量之间的夹角。在聚光内部的话θ值应该比ϕ值小。
所以我们要做的就是计算LightDir向量和SpotDir向量之间的点积,并将它与切光角ϕ值对比。
代码
首先传入片元着色器数据
lightShader.setVec3("light.direction", -lightpos);
//聚焦(0.0)点
lightShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));
//聚光角度12.5°
通过cos值来计算是否在聚光角度内部
//聚光
vec3 diffuse = vec3(0.0f,0.0f,0.0f);
vec3 specular = vec3(0.0f,0.0f,0.0f);
float costheta = dot(-lightDir,normalize(light.direction));//光线到片元与光线方向的夹角
if(costheta > light.cutOff){
// diffuse漫反射光照值
float diff = max(dot(norm, lightDir), 0.0);
diffuse = (material.diffuseStrength * light.color) * diff * objectColor;
// specular镜面反射值
vec3 viewDir = normalize(viewPos - FragPos);//绘制点指向摄像机
vec3 reflectDir = reflect(-lightDir, norm); //reflect(入射光线,法线),返回反射向量
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//两向量点乘后的32次方,次方数大--集中程度高 vec3 specular = (material.specularStrength * light.color) * spec * specularColor; //反光强度*反光角度权值*光照颜色
specular = material.specularStrength * spec * specularColor;
}
costheta > light.cutOff
时,说明在聚光内,计算漫反射值以及镜面反射值。
运行结果
可以看出,效果并不是很好,有明显的明暗界限,适合用于一些恐怖游戏视角,但这并不是现实手电筒的效果!!!
平滑/软化边缘
I
=
(
θ
−
γ
)
/
ϵ
,
ϵ
=
ϕ
−
γ
I=(θ−γ)/ϵ \\,, ϵ=ϕ−γ
I=(θ−γ)/ϵ,ϵ=ϕ−γ
-
θ
θ
-
γ
γ
-
ϕ
ϕ
-
ϵ
ϵ
-
I
I
首先需要传入内外光圈角度
lightShader.setFloat("light.cutOff", glm::cos(glm::radians(12.5f)));//内光圈角度12.5°
lightShader.setFloat("light.outerCutOff", glm::cos(glm::radians(15.5f)));//外光圈角度15.5°
然后
float costheta = dot(-lightDir,normalize(light.direction));//光线到片元与光线方向的夹角
float epsilon = light.cutOff - light.outerCutOff;//内外光圈cos差
float intensity = clamp((costheta - light.outerCutOff) / epsilon, 0.0, 1.0); //clamp GLSL内置函数,返回介于参数2和参数3之间的数
计算
c
o
s
θ
cosθ
cosθ 和
ϵ
ϵ
ϵ 以及 intensity (强度)
T clamp(T x, T minVal, T maxVal)
函数为GLSL内置函数,返回值x被限定在minVal到maxVal之间。
之后,强度值直接乘以光照分量。
diffuse *= intensity;
specular *= intensity;
完整代码
#version 450 core
out vec4 FragColor;
in vec3 Normal;
in vec3 FragPos;
in vec2 TexCoords;
struct Material {
sampler2D sampler;
sampler2D sampler1;
float shininess;
float ambientStrength;
float diffuseStrength;
float specularStrength;
};
struct Light {
vec4 position;
vec3 direction;//聚光方向
vec3 color;
float cutOff;//内光圈
float outerCutOff;//外光圈
float constant;//常数项Kc,恒为1,保证分母永远不会比1小
float linear;//一次项Kl,与距离值相乘,以线性的方式减少强度。
float quadratic;//二次项Kq,与距离的平方相乘,让光源以二次递减的方式减少强度。
};
uniform Material material;
uniform Light light;
uniform vec3 viewPos;
vec3 getResult(Light light){
//纹理颜色
vec3 objectColor = texture(material.sampler, TexCoords).rgb;
vec3 specularColor = texture(material.sampler1, TexCoords).rgb;
// ambient全局光照值
vec3 ambient = (material.ambientStrength * light.color) * objectColor;
//该点光线方向 及 衰减值
vec3 lightDir;
float attenuation = 1.0f;
if(light.position.w != 0.0f ){//点光源
float distance = length(light.position.xyz - FragPos);//点光源距离
attenuation = 1.0 / (light.constant + light.linear * distance + \\
light.quadratic * (distance * distance));点光源强度衰减系数计算
lightDir = normalize(light.position.xyz - FragPos);//该渲染点指向光源
}
else{//平行光
lightDir = normalize(light.position.xyz);
}
// diffuse漫反射光照值
vec3 norm = normalize(Normal);
float diff = max(dot(norm, lightDir), 0.0);//向量点乘,角度越大数值越小.(不为负数)
vec3 diffuse = (material.diffuseStrength * light.color) * diff * objectColor;//漫反射强度与光线到该点与该平面的夹角有关,夹角越小,光照越弱.
// specular镜面反射值
vec3 viewDir = normalize(viewPos - FragPos);//绘制点指向摄像机
vec3 reflectDir = reflect(-lightDir, norm); //reflect(入射光线,法线),返回反射向量
float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//两向量点乘后的32次方,次方数大--集中程度高 vec3 specular = (material.specularStrength * light.color) * spec * specularColor; //反光强度*反光角度权值*光照颜色
vec3 specular = material.specularStrength * spec * specularColor;
//聚光
float costheta = dot(-lightDir,normalize(light.direction));//光线到片元与光线方向的夹角
float epsilon = light.cutOff - light.outerCutOff;//内外光圈cos差
float intensity = clamp((costheta - light.outerCutOff) / epsilon, 0.0, 1.0); //clamp GLSL内置函数,返回介于参数2和参数3之间的数
diffuse *= intensity;
specular *= intensity;
vec3 result = (ambient + diffuse + specular) * attenuation;//最终颜色 * 距离衰减系数
return result;
}
void main()
{
vec3 result = getResult(light);
FragColor = vec4(result, 1.0);
}
暂无评论内容