![ef8612e288252da861cf761d9da99b00.gif](https://i-blog.csdnimg.cn/blog_migrate/ec04221cf894ce5c2d2c9c289f63ff64.gif)
本文转载于拉Box小能手知乎作者
原文链接:https://zhuanlan.zhihu.com/p/72497359
本文为学习LearnOpenGL的学习笔记,如有书写和理解错误还请大佬扶正;
教程链接:
https://link.zhihu.com/?target=https%3A//learnopengl-cn.github.io/02%2520Lighting/06%2520Multiple%2520lights/
一,光照
现实世界的光照是极其复杂的,而且会受到诸多因素的影响,有限的计算能力所无法模拟的。因此此篇介绍OpenGL的光照使用的是简化的光照模型---冯氏光照模型(Phong Shading),对现实的情况进行近似,这样处理起来会更容易一些,而且看起来效果差不多一样。
冯氏光照模型的主要结构由3个分量组成:
1,环境(Ambient)光照
即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。为了模拟这个,我们会使用一个环境光照常量,它永远会给物体一些颜色。
伪代码参考:
//环境光照在场景里的表现非常简单,用光的颜色乘以一个很小的常量环境因子
float ambientStrength = 0.1; //强度
vec3 ambient = ambientStrength * lightColor; //强度*颜色
2,漫反射(Diffuse)光照
模拟光源对物体的方向性影响(Directional Impact)。它是冯氏光照模型中视觉上最显著的分量。物体的某一部分越是正对着光源,它就会越亮。
![7383b4f3038a7733fa8ad6d19fc5816d.png](https://i-blog.csdnimg.cn/blog_migrate/c90b70a4beaee1680b5c6941657957b6.jpeg)
图左上方有一个光源,它所发出的光线落在物体的一个片段上。我们需要测量这个光线是以什么角度接触到这个片段的。如果光线垂直于物体表面,这束光对物体的影响会最大化,片段会更亮;
为了测量光线和片段的角度,我们使用一个叫做法向量(Normal Vector)的向量,它是垂直于片段表面的一个向量(这里以黄色箭头表示);
这两个向量之间的角度通过点乘计算出来;
伪代码参考:
//着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal; //传入法线向量
..
worldPos= vec3(model * vec4(aPos, 1.0)); //对传入的顶点数据 进行世界空间矩阵转换
in vec3 worldPos; //片段的世界位置
uniform vec3 lightPos; //灯光的位置
vec3 norm = normalize(Normal); //归一化法线向量
vec3 lightDir = normalize(lightPos - worldPos); //归一化 灯光方向
float diff = max(dot(norm, lightDir), 0.0); //求夹角
vec3 diffuse = diff * lightColor; //乘以灯光颜色
3,镜面(Specular)光照
模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。
![ea3c4cd0cf42b8c2974dd76d97a13d57.png](https://i-blog.csdnimg.cn/blog_migrate/a89ff4f763de31abc2fdd618d83a36d0.jpeg)
和漫反射光照一样,镜面光照也是依据光的方向向量和物体的法向量来决定的,但是它也依赖于观察方向,例如玩家是从什么方向看着这个片段的。
我们通过反射法向量周围光的方向来计算反射向量。然后我们计算反射向量和视线方向的角度差,如果夹角越小,那么镜面光的影响就会越大。它的作用效果就是,当我们去看光被物体所反射的那个方向的时候,我们会看到一个高光。
伪代码参考:
uniform vec3 viewPos; //摄像机位置 观察方向
float specularStrength = 0.5; //镜面高光强度
vec3 viewDir = normalize(viewPos - worldPos); // 摄像机方向
vec3 reflectDir = reflect(-lightDir, norm); //反射向量方向
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); //求夹角 算镜面高光权重
vec3 specular = specularStrength * spec * lightColor; //强度*权重*颜色
4,合并光照效果
伪代码参考:
vec3 result = (ambient + diffuse + specular) * objectColor; //物体颜色*
FragColor = vec4(result, 1.0);
![6f4965d7aecbf9a6fb9a2ff428adba4f.png](https://i-blog.csdnimg.cn/blog_migrate/1df6034d91bcc4037d807774338c1933.jpeg)
二,光源类型
1,平行光
当一个光源处于很远的地方时,来自光源的每条光线就会近似于互相平行。不论相对物体或者观察者的位置,看起来好像所有的光都来自于同一个方向。当我们使用一个假设光源处于无限远处的模型时,它就被称为平行光/定向光,因为它的所有光线都有着相同的方向,它与光源的位置是没有关系的,平行光可以模拟太阳光。
![cd7ebc7cc02cc55265968ed8ce6e327e.png](https://i-blog.csdnimg.cn/blog_migrate/9de39bd984233663655c54e47938cc47.jpeg)
因为所有的光线都是平行的,所以物体与光源的相对位置是不重要的,因为对场景中每一个物体光的方向都是一致的,因此对场景中每个物体的光照计算将会是类似的。
伪代码参考:
//定义一个光线方向向量而不是位置向量来模拟一个定向光
//着色依然为冯氏模型
//对light.direction向量取反。我们目前使用的光照计算需求一个从片段至光源的光线方向
vec3 lightDir = normalize(-light.direction);
//根据方向 采用冯氏光照模型 求出最后的结果
result = ambient +diffuse +specular
2,点光源
点光源是处于世界中某一个位置的光源,它会朝着所有方向发光,但光线会随着距离逐渐衰减。例如灯泡和火把,它们都是点光源。
![ac05cec237cf50c34dd7b87f161cfe62.png](https://i-blog.csdnimg.cn/blog_migrate/40417759080c58830457e15f603e8982.jpeg)
衰减
在现实世界中,灯在近处通常会非常亮,但随着距离的增加光源的亮度一开始会下降非常快,但在远处时剩余的光强度就会下降的非常缓慢了;
下面这个公式根据片段距光源的距离计算了衰减值,之后我们会将它乘以光的强度向量:
![f8265f2935e36732924f7de1c0274d99.png](https://i-blog.csdnimg.cn/blog_migrate/03df2544e11bfe08bb4d76ff42b30ced.png)
d:代表了片段距光源的距离;
Kc:常数项 ,常数项通常保持为1.0,它的主要作用是保证分母永远不会比1小,否则的话在某些距离上它反而会增加强度;
Kl:一次项,一次项会与距离值相乘,以线性的方式减少强度;
Kq:二次项,二次项会与距离的平方相乘,让光源以二次递减的方式减少强度,二次项在距离比较小的时候影响会比一次项小很多,但当距离值比较大的时候它就会比一次项更大了;
由于二次项的存在,光线会在大部分时候以线性的方式衰退,直到距离变得足够大,让二次项超过一次项,光的强度会以更快的速度下降。这样的结果就是,光在近距离时亮度很高,但随着距离变远亮度迅速降低,最后会以更慢的速度减少亮度。下面这张图显示了在100的距离内衰减的效果:
![8d9a5d458604ea5dae43a7816f9da341.png](https://i-blog.csdnimg.cn/blog_migrate/f7de9aa61c0eb5e79ab4f4e52605c105.jpeg)
选择适当的值
下面这个表格显示了模拟一个(大概)真实的,覆盖特定半径(距离)的光源时,这些项可能取的一些值。
![343025ffd591bf419072fb29802c1aab.png](https://i-blog.csdnimg.cn/blog_migrate/d0ae3161767a0de2a87aec0c5685744b.jpeg)
伪代码参考:
//着色器
//声明所需的变量 常数项 一次项以及二次项
uniform float constant;
uniform float linear;
uniform float quadratic;
//求出 片段与灯光的距离
float distance = length(light.position - FragPos);
//根据距离算出衰减
float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance));
//计算点光源的漫反射与镜面高光 采用冯氏光照模型
//最后乘上衰减因子 算出最终光照
diffuse *= attenuation;
specular *= attenuation;
//求出最后结果
result = ambient +diffuse +specular
//渲染逻辑端
//在渲染端 传入适当的值 这里选取光源影响距离 50 作为参考
pointlightingShader.setFloat("light.constant", 1.0f);
pointlightingShader.setFloat("light.linear", 0.09f);
pointlightingShader.setFloat("light.quadratic", 0.032f);
3,聚光灯
聚光是位于环境中某个位置的光源,它只朝一个特定方向而不是所有方向照射光线。这样的结果就是只有在聚光方向的特定半径内的物体才会被照亮,其它的物体都会保持黑暗。聚光很好的例子就是路灯或手电筒。
OpenGL中聚光灯是用一个世界空间位置、一个方向和一个切光角(Cutoff Angle)来表示的,切光角指定了聚光的半径(是圆锥的半径不是距光源距离那个半径)。对于每个片段,我们会计算片段是否位于聚光的切光方向之间(也就是在锥形内),如果是的话,我们就会相应地照亮片段。下面这张图会让你明白聚光是如何工作的:
![f5cc46b80042c6f4b4e582b2817d2969.png](https://i-blog.csdnimg.cn/blog_migrate/c73ae7457fb1b2a4ce5cdfc550fdead3.jpeg)