上一章研究了定向光,很显然光有太阳光无法满足现实的真实需求,现实中还会有其他类型的光源,比如点光源。我们以前使用的白炽灯炮就是点光源。
在learnOpenGL中:
点光源:
这时一种放射状的光线。我们研究光照模型时用的就是点光源,他需要位置,当我们没有做光的衰减。距离点光源越远的地方光的因子应该越弱。需要做衰减。
点光源的特点:
有位置,可与每个顶点的位置计算出光的方向,在现实中距离很近时衰减得很快,在距离较远时衰减得很慢。这时就需要一种满足需求的算法:
d:距离光源的距离,肯定是正数(大于等于0)。
Kc:产量因子,通常为1.0,它的作用是保证分母永远不会比1小。
Kl:一次项因子,影响线性的亮度变化。
Kq:二次项因子,影响衰减的速度,距离越远衰减越慢。
一般选用100的距离就足够了。这个表在learnOpenGL中是经过实验证明的。
struct Light
{
vec3 position;
vec3 ambientFactor;
vec3 diffuseFactor;
vec3 specularFactor;
float constant;//常数项
float linear;//一次项
float quadratic;//二次项
};
发送变量的时候选一种距离的这些项发送就好。
float distance = length(light.position - FragPos);
float attenuation = 1.0f / (light.constant + light.linear*distance +light.quadratic*(distance*distance));
左后去作用于每一种反射光的颜色就好。前面章节怎么去计算反射光的颜色说得很清楚了这里就省略了。
聚光灯:
在生活中聚光灯实际上可以看成白炽灯加一个灯罩,这样光的方向就被控制在一定范围内了。生活中案例很多,如手电筒,路灯,探照灯甚至歌厅舞台的射灯。我们可以去看聚光灯的OpenGL中的工作机制:
Φ:默认的允许照射的范围
θ:计算出的光的方向和聚光灯方向的夹角
当θ小于等于Φ我们认为在范围内允许片段点亮,如蓝色和绿色光线。否则被认为在范围外不被点亮,如棕色光。
聚光灯特点:
这里就能看出聚光灯需要一个方向,需要一个默认角度范,需要一个位置,其他的就可以通过计算获得结果。
struct Light
{
vec3 position;
vec3 direction;
float cutOff;//Φ的余玄值,通常我们都是使用余玄值去运算
...//这些因子与之前相同
};
float theta = dot(lightDir, normalize(-light.direction));
if(theta > light.cutOff)
{
// 执行光照计算
}
else // 否则使用环境光,使得场景不至于完全黑暗
color = vec4(light.ambient*vec3(texture(material.diffuse,TexCoords)), 1.0f);
可以看出使用余玄值的好处:角度越大,余玄值越小。我们只需认可余玄值小的让他点亮。
我们还可以结合上面的衰减的原理使聚光灯更真实。更可以通过传入相机位置,来做相机的手电筒,这时聚光的的方向就要变化,始终是相机的视线方向(视点-相机位置)
边缘的模糊化:
上面的聚光灯还不够真实,生活中的聚光灯边缘不会有特别清晰的分界线,而是模糊的,逐渐变弱的。那么怎么能够模拟这种效果喃。
我们在加一个半径范围r,当角度θ小于Φ光强乘以1.0,如果大于Φ小于r就逐渐变暗(从1.0递减),如果大于r就什么都不做。
I:就是衰弱的强度。
ε:内切的余玄值与外切的余玄值之差,Φ-r。
注意:公式里都是余玄值,我们不会直接拿角来算。
这样就保证了衰减性,而且值在0.0到1.0之间。
为了避免运算出来的片段完全黑暗我们把衰减因子的最小值设置为环境光因子的大小。
float intensity=light.ambientFactor;
float epsilon = light.cutOff - light.outerCutOff;//light.outerCutOff是r。
float theta = dot(lightDir, normalize(-light.direction));
if(thetaouterCutOff&&theta < light.cutOff){
intensity=(theta - light.outerCutOff) / epsilon ;
if(intensity
经过巧妙的处理我们发现上面的代码可以用下面的代替。避免了做判断。clamp函数是用来固定,保证值在0.0到1.0之内。
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;//light.outerCutOff是r。
float intensity = clamp((theta - light.outerCutOff) / epsilon,0.0, 1.0);//当然0.0就不要去相乘了,我们可以把强度给写成环境光因子的强度到1.0。这样来规避完全黑暗。
最终修改。
float theta = dot(lightDir, normalize(-light.direction));
float epsilon = light.cutOff - light.outerCutOff;
float intensity = clamp((theta - light.outerCutOff) / epsilon,light.ambientFactor, 1.0);
是不是这个函数简化了很多步骤喃。当然他是shader的函数。
目录
VSC++2019+QT+OpenGL
QT+OpenGL一之绘制立方体(三角形图元)
QT+OpenGL二之纹理贴图
QT+OpenGL三之矩阵简解
QT+OpenGL四之相机的移动和旋转
QT+OpenGL五之绘制不同的模型(vao,vbo机制)
QT+OpenGL六之天空盒
QT+OpenGL七之使用EBO
QT+OPenGL八之模型准备
QT+OPenGL九之模型解码
QT+OPenGL十之光照模型
QT+OPenGL十一之漫反射和镜面反射贴图
QT+OPenGL十二之定向光
QT+OPenGL十三之真正的点光源和聚光灯
QT+OPenGL十四之多光源混合的问题
QT+OPenGL十五之深度缓冲区
QT+OPenGL十六之模板缓冲区
QT+OPenGL十七帧缓冲区(离屏渲染)
QT+OPenGL十八抗锯齿
QT+OPenGL十九镜面反射效率调整
QT+OPenGL二十Gamma校正