冯氏光照模型分为三个部分:
- 环境光 Ambient 即使在完全黑暗的情况下,世界上也通常会有一些光亮(比如:月光、远处分散的光源),物体不会是完全黑暗的。
- 漫反射光 Diffuse 视觉上最显著的分量。物体的某一部分越是正对着光源,那么这部分就越亮。
- 镜面光 Specular 模拟有光泽物体上的亮点。这个颜色会更加倾向于光的颜色。
环境光照
void main()
{
//1、设置一个很小的常量环境因子,来假设场景中一些发散的光
float ambientStrength = 0.1;
//2、环境光 = 常量环境因子 * 光的颜色
vec3 ambient = ambientStrength * lightColor;
}
漫反射光照!!
首先明确这几点:
1、计算漫反射光照需要的数据是:
- 法向量 Normal:一个垂直于顶点表面的向量。
- 定向的光线 = 光源的位置向量 - 顶点的位置向量
2、几乎所有的光照计算都是在片段着色器中运行的。因此需要将相关数据传递到片段着色器中进行计算。
- 需要将法向量从顶点着色器传递到片段着色器;
- 世界空间中的顶点位置 = (局部空间)顶点位置属性 * 模型矩阵 该计算在顶点着色器中完成,并声明一个输出变量将结果传递到片段着色器中。
3、计算光照时,通常不关心一个向量的模长或它的位置,我们只关心它们的方向。所以,几乎所有的计算都使用单位向量完成,因为这简化了大部分的计算(比如点乘)。
4、两个单位向量的夹角越小,它们点乘的结果越倾向于1。当两个向量的夹角为90度的时候,点乘会变为0。点乘结果越大,光对片段颜色的影响就越大。
1. 法向量
已经简单地把法线数据手工添加到顶点数据中,接下来只需将其传递到片段着色器。
但是我们应用一个不等比缩放时,法向量就不会再垂直于对应的表面了,这样光照就会被破坏。于是新的解决办法为:在顶点着色器中,使用inverse和transpose函数生成一个法线矩阵,这两个函数对所有类型矩阵都有效。注意我们还要把被处理过的矩阵强制转换为3×3矩阵,来保证它失去了位移属性以及能够乘以vec3的法向量。
顶点着色器:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
// 顶点着色器中输出法向量
out vec3 Normal;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
//Normal = aNormal;
Normal = mat3(transpose(inverse(model))) * aNormal;
}
片段着色器:
// 在片段着色器中接受这个变量
in vec3 Normal;
2. 定向光线
1)光源位置(是一个静态变量,可以直接设置)
简单地在片段着色器中把它声明为uniform:uniform vec3 lightPos;
然后在渲染循环中(渲染循环的外面也可以,因为它不会改变)更新uniform:lightingShader.setVec3("lightPos", lightPos);
2)顶点位置 (顶点数组中的数据是在局部空间中的,而我们计算光照效果是世界空间中。因此需要将顶点数据与模型矩阵相乘,来将坐标转换到世界空间中)
// 在顶点着色器中完成乘法,然后声明输出变量FragPos,将结果输出到片段着色器中。
out vec3 FragPos;
out vec3 Normal;
void main()
{
gl_Position = projection * view * model * vec4(aPos, 1.0);
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = aNormal;
}
// 最后,在片段着色器中添加相应的输入变量:`in vec3 FragPos;`
3) 计算定向光线
// 定向光线 = 光源位置向量 - 片段位置向量
// 记得最后将其标准化normalize
vec3 lightDir = normalize(lightPos - FragPos);
3. 已知法向量和定向光线,来计算漫反射光
//1、标准化法向量
vec3 norm = normalize(Normal);
// 2、标准化定向光线向量
vec3 lightDir = normalize(lightPos - FragPos);
// 3、将两者进行点乘,计算出光源对物体影响的大小,由此确定物体的最终颜色
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
镜面光照
计算反射向量和视线方向的角度差,如果夹角越小,那么镜面光的影响就会越大。
新增向量“观察者的位置”:即摄像机的位置,通过在片段着色器中设置uniform这个全局变量,将摄像机的位置传递给片段着色器。
// 1、设置一个合适的镜面光分量
float specularStrength = 0.5;
// 2、计算视线方向向量
vec3 viewDir = normalize(viewPos - FragPos);
// 3、计算反射向量
vec3 reflectDir = reflect(-lightDir, norm);
// 4、将两者进行点乘,计算出镜面光影响的大小
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
// 5、计算镜面分量
vec3 specular = specularStrength * spec * lightColor;
最后计算最终的冯氏光照
// 三个向量相加,并乘以物体的颜色,就是最终的光照后物体的颜色了
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);