从0开始的OpenGL学习(二十八)-Blinn-Phong

本文只要解决一个问题:

如何使用Blinn-Phong光照模型使高光更柔和、更平滑?

引言

在光照文章中,我们介绍了Phong光照模型来模拟真实的场景。看起来效果不错,但还是有点细微差别,啥差别呢,别急,你往下看。

Phong光照模型的缺陷

在开始之前,请先下载这里的源码配置一个新工程。编译运行一下代码,你会看到这样的效果:

从0开始的OpenGL学习(二十八)-Blinn-Phong_第1张图片
Phong光照模型

这就是Phong光照模型的缺陷。什么,你说你看到的不是这样?哦,忘了说了,要把镜面高光的光泽度信息改成1.0。在光泽度为1.0的情况下,Phong光照模型的缺陷就暴露无遗了。

啥是光泽度?这问题问的好,笔者也快忘了,赶紧翻翻之前写过的文章。哦,原来是高光的集中度。找到片元着色器中的这一行代码pow (max(dot(viewDir, reflectDir), 0.0), 32.0);,把32.0改成1.0你就能看到上图中的效果了。Phong光照模型的缺陷也只有在这种特定情况下才会显现。

可以非常清楚地看到,在边缘部分,镜面高光的区域被立刻切断了!出现这种问题的原因是,在Phong光照模型中,观察向量和反射向量之间的夹角不允许大于90度。一旦夹角大于90的,观察方向和反射方向的点积就会变负数,而我们在计算的时候会把这个负数截断到0,这样,镜面高光部分就完全看不到了。

你可能会这样想,我们的观察向量和反射方向夹角不会大于90度的,对吧?

大错特错!要知道,我们的观察角度可以是四面八方的,没有什么法律规定我们只能沿着反射方向观察,如果我们观察的方向和光源是在同一边呢?这样视线方向和反射方向的夹角就可能大于90度,产生不真实的效果:


从0开始的OpenGL学习(二十八)-Blinn-Phong_第2张图片
产生不真实效果的原理

从图上看就非常明显了。当我们的观察方向在法向量右边时,没问题,反射方向和视线的夹角theta不会大于90度。但是,如果我们的观察方向在法向量的左边,我们的观察方向和反射方向的夹角就可能大于90度了。这种情况下,误差就会产生。

Blinn-Phong光照模型

1977年,一个名叫James F. Blinn的人提出了另一种计算镜面高光的方法。运用这种方法的Phong光照模型就称为Blinn-Phong光照模型。从名字上就可以看出,这种模型并没有对Phong模型进行大改动(否则就是Blinn光照模型了)。没错,它只是对计算镜面高光的算法进行了改动,换了一种镜面高光的计算方法。

Blinn的镜面高光计算原理是:不用反射向量计算,取而代之的是用一种名叫半角向量(halfway vector)的向量代替。半角向量是根据观察方向和光线方向来进行计算的,取观察向量和光线向量夹角的一半作为新向量的方向。这样,观察方向和反射方向的夹角就无关紧要了:


从0开始的OpenGL学习(二十八)-Blinn-Phong_第3张图片
半角向量的原理

这样,当观察方向和反射方向就完美的重合时,半角向量就进化成法向量了。

让我们来看看如何计算这个半角向量。这里有一个简单的公式:


从0开始的OpenGL学习(二十八)-Blinn-Phong_第4张图片
半角向量计算公式

公式非常简单,还记的向量相加的意义吗?没错,就是将第二个向量平移到第一个向量的终点,然后从第一个向量的起点到第二个向量的终点所形成的一个新向量。我们将光线向量和观察向量都规范化,这样相加得出的向量必定能平分两个向量的夹角。最后,再将其规范化成单位向量就大工告成了!

用GLSL来实现就是:

vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwarDir = normalize(lightDir + viewDir);

于是,镜面高光量的计算就变成了:

float spec = pow(max(dot(normal, halfwarDir), 0.0, shininess);
vec3 specular = lightColor * spec;

使用法向量和半角向量来代替观察向量和反射向量计算点积,再计算光泽度的幂指数值,就成为了镜面高光度。

好了,Blinn-Phong光照模型就这些东西,简单吧?编译运行代码,我们来看看结果:


Blinn-Phong光照模型的高光计算

还有一点微妙的区别是,通常反射向量和观察向量的夹角要比半角向量和法向量的夹角要大些。因此,如果想要用Blinn-Phong模型得到类似Phong模型的效果,计算时,用的光泽度信息要大点。经验表明,Blinn-Phong模型的光泽度大小要是Phong模型的2到4倍。

下面两张图显示了两种模型在同一个角度观察的显示区别。Phong模型的光泽度为8,Blinn-Phong模型的光泽度为16:


从0开始的OpenGL学习(二十八)-Blinn-Phong_第5张图片
PHONG

从0开始的OpenGL学习(二十八)-Blinn-Phong_第6张图片
BLINN_PHONG

在同一个位置就可以很明显地看到区别,使用Blinn-Phong模型的光看上去聚拢程度更高。相对而言,Blinn-Phong的效果就更加真实。

最后,给出一个观察相同角度不同模型的小技巧。我们可以在片元着色器中定义一个bool变量用作开关,当按下键盘上的b键时,开启Blinn-Phong模式,再按一次b键就关闭Blinn-Phong模式。这样,就能很明显地看出差别。修改后的片元着色器代码如下:

if (blinn) {
    spec = pow (max (dot(normal, halfwayDir), 0.0), 16.0);
}
else  {
    spec = pow (max(dot(viewDir, reflectDir), 0.0), 8.0);
}

最后,按照我们一贯的作风,提供完整的代码以供参考。

总结

在这一章中我们就只学了一个知识点就是Blinn的处理镜面高光的方法。简单点说,就是使用法向量和半角向量的点积值代替Phong模型中反射向量和观察向量的点积值。半角向量是光线向量和观察向量相加的结果向量,表示平分两个向量的夹角。在结束的时候,提到了由于夹角值的差距,使用Blinn-Phong模型计算高光时要将光泽度信息设置成Phong模型的2到4倍较为合适。

好了,学习结束,休息、休息~

下一篇
目录
上一篇

参考资料

www.learnopengl.com(非常好的网站,建议学习)

你可能感兴趣的:(从0开始的OpenGL学习(二十八)-Blinn-Phong)