一、冯氏光照模型
冯氏光照模型(Phong Lighting Model)主要由环境(Ambient)、漫反射(Diffuse)和镜面(Specular)光照这三部分组成,各效果如下:
1、环境光照
即使在黑暗的情况下,世界上通常也仍然有一些光亮(月亮、远处的光),所以物体几乎永远不会是完全黑暗的。我们可以使用一个环境光照(Ambient Lighting)常量来模拟这个。
如何把环境光照添加到场景呢?我们可以用光的颜色乘以一个很小的常量环境因子,再乘以物体的颜色,然后将最终结果作为片段的颜色:
void main()
{
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
vec3 result = ambient * objectColor;
FragColor = vec4(result, 1.0);
}
2、漫反射光照
模拟光源对物体的方向性影响(Directional Impact)。物体的某一部分越是正对着光源,它就会越亮。
从上图我们可以看出,光线越垂直于物体表面,这束光对物体的影响会越大,即物体越亮。为了测量光线和片段的角度,我们可以使用一个叫做法向量(Normal Vector)的东西,它是垂直于片段表面的一个向量(图中以黄色箭头表示)。
计算漫反射光照需要法向量和定向的光线!!
2.1 法向量
法向量是一个垂直于顶点表面的(单位)向量。我们可以使用叉乘对立方体所有的顶点计算法向量,但是由于3D立方体不是一个复杂的形状,所以我们可以简单地把法线数据手工添加到顶点数据中,然后更新顶点着色器:
#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;
}
在片段着色器添加:
in vec3 Normal;
2.2 定向的光线
定向的光线,作为光源的位置与片段的位置之间向量差的方向向量。我们可以通过光的位置向量和片段的位置向量计算该光线。
由于光源的位置是一个静态变量,我们在片段着色器中把它声明为uniform:
uniform vec3 lightPos;
然后在渲染循环中将lightPos向量传进去:
lightingShader.setVec3("lightPos", lightPos);
对于片段的位置,我们可以通过把顶点位置属性乘以模型矩阵(不是观察和投影矩阵)来获取。这个在顶点着色器中很容易完成:
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;
至此,我们便可以计算定向的光线了——光源位置向量与片段位置向量之间的向量差。
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
接着,可以计算漫反射分量了。对norm和lightDir向量进行点乘,计算光源对当前片段实际的漫反射影响。结果值再乘以光的颜色,得到漫反射分量。两个向量之间的角度越大,漫反射分量就会越小:
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
将环境光分量和漫反射分量相加,然后把结果乘以物体的颜色,可以获得片段最后的输出颜色:
vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);
3、镜面光照
模拟有光泽物体上面出现的亮点。镜面光照的颜色相比于物体的颜色会更倾向于光的颜色。
和漫反射光照一样,镜面光照也决定于光的方向向量和物体的法向量,但是它也决定于人(摄像机)的观察方向。可以把物体表面设想为一面镜子,那么镜面光照最强的地方就是我们看到表面上反射光的地方,如下图:
我们通过根据法向量翻折入射光的方向来计算反射向量。然后我们计算反射向量与观察方向的角度差,它们之间夹角越小,镜面光的作用就越大,我们看到的效果就越亮。
观察向量是我们计算镜面光照时需要的一个额外变量,我们可以使用观察者(摄像机)的世界空间位置和片段的位置来计算它。之后我们计算出镜面光照强度,用它乘以光源的颜色,并将它与环境光照和漫反射光照部分加和。
观察者(摄像机)的世界空间位置:
lightingShader.setVec3("viewPos", camera.Position);
高光强度:
float specularStrength = 0.5;//镜面强度
视线方向向量,以及对应的沿着法线轴的反射向量:
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
镜面分量:
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
先计算视线方向与反射方向的点乘(并确保它不是负值),然后取它的32次幂。这个32是高光的反光度(Shininess)。一个物体的反光度越高,反射光的能力越强,散射得越少,高光点就会越小。不同反光度的视觉效果影响如下:
最后,把它加到环境光分量和漫反射分量里,再用结果乘以物体的颜色:
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = vec4(result, 1.0);
demo下载:点击跳转
效果如下:
觉得有帮助的话,打赏一下呗。。