Phong 照明模型

注意:此示例为 WIP,将使用图表,图像,更多示例等进行更新。

什么是 Phong?

Phong 是一个非常基本,但看起来很真实的表面光模型,有三个部分:环境光,漫反射光和镜面光。

环境照明:

环境照明是理解和计算的三个部分中最简单的。环境照明是一种充满场景的光线,可以在各个方向均匀地照亮物体。

环境照明中的两个变量是环境强度和环境颜色。在片段着色器中,以下内容适用于环境:

in vec3 objColor;

out vec3 finalColor;

uniform vec3 lightColor;

void main() {
   float ambientStrength = 0.3f;
   vec3 ambient = lightColor * ambientStrength;
   finalColor = ambient * objColor;
}

漫射照明:

漫射照明比环境光稍微复杂一些。漫射光是定向光,基本上意味着面向光源的面将被更好地照亮,并且由于光如何击中它们而指向的面将变暗。

注意:漫反射光照需要使用每个面的法线,我不会在这里展示如何计算。如果你想学习如何操作,请查看 3D 数学页面。

为了模拟计算机图形中光的反射,使用双向反射分布函数(BRDF)。BRDF 是一种函数,它给出了沿出射方向反射的光与从入射方向入射的光之间的关系。

完美的漫反射表面具有 BRDF,其对于所有入射和出射方向具有相同的值。这大大减少了计算,因此它通常用于模拟漫射表面,因为它在物理上是合理的,即使在现实世界中没有纯粹的漫射材料。这种 BRDF 被称为朗伯反射,因为它符合兰伯特的余弦定律。

朗伯反射通常用作漫反射的模型。该技术使得所有闭合多边形(例如 3D 网格内的三角形)在渲染时在所有方向上均匀地反射光。根据法向量和光矢量之间的角度计算扩散系数。

f_Lambertian = max( 0.0, dot( N, L )

其中 N 是表面的法向量,L 是朝向光源的向量。

这个怎么运作

通常,2 个矢量的积等于 2 个矢量之间的角度的余弦乘以两个矢量的幅度(长度)。

dot( A, B ) == length( A ) * length( B ) * cos( angle_A_B ) 

由此得出,2 个单位矢量的积等于 2 个矢量之间角度的余弦,因为单位矢量的长度是 1。

uA = normalize( A )
uB = normalize( B )
cos( angle_A_B ) == dot( uA, uB )

StackOverflow 文档

如果我们看一下角度 -90°和 90°之间的 cos(x) 函数,那么我们可以看到它在 0°的角度下最大为 1,在 90°的角度下它下降到 0 和 -90°。

StackOverflow 文档

这种行为正是我们想要的反射模型。当表面的光动量和光源的方向是相同的方向(角度在 0°之间)时,我们想要一个最大的反射。相反,如果矢量是正交归一化的(其间的角度是 90°),那么我们想要最小的反射,并且我们希望在 0°和 90°的两个边界之间平滑且连续的功能运行。

StackOverflow 文档

如果每个顶点计算光,则计算基元的每个角的反射。在基元之间,反射根据其重心坐标进行插值。在球面上看到产生的反射:

StackOverflow 文档

好的,所以从片段着色器开始,我们需要四个输入。

  • 顶点法线(应该在缓冲区中并由顶点属性指针指定)
  • 片段位置(应从顶点着色器输出到 frag 着色器)
  • 光源位置(均匀)
  • 浅色(均匀)
in vec3 normal;
in vec3 fragPos;
    
out vec3 finalColor;
    
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 objColor;

主要内部是我们需要做数学的地方。漫射照明的整个概念基于法线方向和光线方向之间的角度。角度越大,直到 90°的光线越少,根本没有光线。

在我们开始计算光量之前,我们需要光方向矢量。这可以通过简单地从片段位置减去光位置来检索,该片段位置从指向片段位置的光位置返回矢量。

vec3 lightDir = lightPos-fragPos;

此外,继续并规范化 normallightDir 向量,使它们的长度相同。

normal  = normalize(normal);
lightDir = normalize(lightDir);

现在我们有了向量,我们可以计算它们之间的差异。为此,我们将使用点积函数。基本上,这需要 2 个向量并返回形成的角度的 cos()。这是完美的,因为在 90 度它将产生 0 并且在 0 度它将产生 1.结果,当光直接指向物体时它将完全点亮,反之亦然。

float diff = dot(normal, lightDir);

我们还需要对计算的数字做一件事,我们需要确保它总是正面的。如果你考虑一下,负数在上下文中没有意义,因为这意味着光在脸后面。我们可以使用 if 语句,或者我们可以使用 max() 函数返回最多两个输入。

diff = max(diff, 0.0);

完成后,我们现在可以计算片段的最终输出颜色。

vec3 diffuse = diff * lightColor;
finalColor = diffuse * objColor;

它应该如下所示: StackOverflow 文档

高光照明:

在进展中工作,稍后再回来查看。

综合

正在进行中,请稍后再回来查看。

下面的代码和图像显示了这三个照明概念的组合。