1
0
mirror of https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git synced 2025-08-22 20:25:28 +08:00

05-01, fix #108, update readme

This commit is contained in:
Krasjet
2018-09-02 17:49:11 -04:00
parent e846221598
commit 3367f88807
42 changed files with 68 additions and 71 deletions

View File

@@ -3,38 +3,35 @@
原文 | [Advanced Lighting](http://learnopengl.com/#!Advanced-Lighting/Advanced-Lighting)
---|---
作者 | JoeyDeVries
翻译 | [Django](http://bullteacher.com/)
校对 | gjy_1992
翻译 | Krasjet
校对 | 暂未校对
!!! note
本节暂未进行完全的重写,错误可能会很多。如果可能的话,请对照原文进行阅读。如果有报告本节的错误,将会延迟至重写之后进行处理
在光照教程中我们简单的介绍了Phong光照模型它给我们的场景带来的基本的现实感。Phong模型看起来还不错但本章我们把重点放在一些细微差别上。
在[光照](../02 Lighting/02 Basic Lighting.md)小节中,我们简单地介绍了冯氏光照模型,它让我们的场景有了一定的真实感。虽然冯氏模型看起来已经很不错了,但是使用它的时候仍然存在一些细节问题,我们将在这一节里讨论它们
## Blinn-Phong
Phong光照很棒,而且性能较高,但是它的镜面反射在某些条件下会失效特别是当发光值属性低的时候对应一个非常大的粗糙的镜面区域。下面的图片展示了当我们使用镜面的发光值为1.0时,一个带纹理地板的效果:
冯氏光照不仅对真实光照有很好的近似,而且性能也很高。但是它的镜面反射会在一些情况下出现问题特别是物体反光度很低时会导致大片粗糙的高光区域。下面这张图展示了当反光度为1.0时地板会出现的效果:
![](../img/05/01/advanced_lighting_phong_limit.png)
可以看到,镜面区域边缘迅速减弱并截止。出现这个问题的原因是在视线向量和反射向量的角度不允许大于90度。如果大于90度的话点乘的结果就会是负数镜面的贡献成分就会变成0。你可能会想这不是一个问题因为大于90度时我们不应看到任何光对吧
可以看到,镜面高光区域边缘出现了一道很明显的断层。出现这个问题的原因是观察向量和反射向量间的夹角不能大于90度。如果点积的结果为负数镜面光分量会变为0.0。你可能会觉得当光线与视线夹角大于90度时你应该不会接收到任何光才对所以这不是什么问题。
错了这只适用于漫散射部分当法线和光源之间的角度大于90度时意味着光源在被照表面的下方,这样光的散射成分就会是0.0。然而,对于镜面光照,我们不会测量光源法线之间的角度,而是测量视线反射方向向量之间的。看看下面的两幅图:
然而这种想法仅仅只适用于漫反射分量。当考虑漫反射光的时候如果法线和光源夹角大于90度光源会处于被照表面的下方,这个时候光照的漫反射分量的确是为0.0。但是,在考虑镜面高光时,我们测量的角度并不是光源法线的夹角,而是视线反射光线向量的夹角。看一下下面这两张图:
![](../img/05/01/advanced_lighting_over_90.png)
现在看来问题就很明显了。左侧图片显示Phong反射的θ小于90度的情况。我们可以看到右侧图片视线反射之间的角θ大于90度样镜面反射成分将会被消除。通常这也不是问题,因为视线方向离反射方向很远但如果我们使用一个数值较低的发光值参数的话镜面半径就会足够大以至于能够贡献一些镜面反射的成份了。在例子中我们在角度大于90度时消除了这个贡献如第一个图片所示
现在问题就应该很明显了。左图中是我们熟悉的冯氏光照中的反射向量,其中$\theta$角小于90度。而右图中视线反射方向之间的夹角明显大于90度种情况下镜面光分量会变为0.0。这在大多数情况下都不是什么问题,因为观察方向离反射方向都非常远。然而,当物体的镜面光分量非常小时,它产生的镜面高光半径足以让这些相反方向的光线对亮度产生足够大的影响。在这种情况下就不能忽略它们对镜面光分量的贡献了
1977年James F. Blinn引入了Blinn-Phong着色它扩展了我们目前所使用的Phong着色。Blinn-Phong模型很大程度上和Phong是相似的不过它稍微改进了Phong模型使之能够克服我们所讨论到的问题。它放弃使用反射向量而是基于我们现在所说的一个叫做半程向量halfway vector)的向量,这是个单位向量,它在视线方向和光线方向的中间。半程向量和表面法线向量越接近,镜面反射成份就越大。
1977年James F. Blinn在冯氏着色模型上加以拓展,引入了<def>Blinn-Phong</def>着色模型。Blinn-Phong模型与冯氏模型非常相似但是它对镜面光模型的处理上有一些不同让我们能够解决之前提到的问题。Blinn-Phong模型不再依赖于反射向量而是采用了所谓的<def>半程向量</def>(Halfway Vector),即光线与视线夹角一半方向上的一个单位向量。当半程向量法线向量越接近,镜面光分量就越大。
![](../img/05/01/advanced_lighting_halfway_vector.png)
当视线方向恰好与(想象中的)反射向量对齐时,半程向量就与法线向量重合。这样观察者视线越接近原本反射方向,镜面反射的高光就会越强。
当视线正好与(现在不需要的)反射向量对齐时,半程向量就与法线完美契合。所以当观察者视线越接近原本反射光线的方向,镜面高光就会越强。
这里,你可以看到无论观察者往哪里看,半程向量表面法线之间的夹角永远都不会超过90度当然除了光源远远低于表面的情况。这样会产生和Phong反射稍稍不同的结果但这时看起来会更加可信特别是发光值参数比较低的时候。Blinn-Phong着色模型正是早期OpenGL固定函数输送管道fixed function pipeline所使用的着色模型。
现在,不论观察者向哪个方向看,半程向量表面法线之间的夹角都不会超过90度除非光源在表面以下)。它产生的效果会与冯氏光照有些许不同,但是大部分情况下看起来会更自然一点,特别是低高光的区域。Blinn-Phong着色模型正是早期固定渲染管线时代时OpenGL所采用的光照模型。
得到半程向量很容易,我们将光的方向向量和视线向量相加然后将结果归一化normalize
获取半程向量的方法很简单,只需要将光线的方向向量和观察向量加到一起,并将结果正规化(Normalize)就可以了:
$$
\(\bar{H} = \frac{\bar{L} + \bar{V}}{||\bar{L} + \bar{V}||}\)
@@ -43,37 +40,33 @@ $$
翻译成GLSL代码如下
```c++
vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 lightDir = normalize(lightPos - FragPos);
vec3 viewDir = normalize(viewPos - FragPos);
vec3 halfwayDir = normalize(lightDir + viewDir);
```
实际的镜面反射的计算就成为计算表面法线和半程向量的点乘并对其结果进行约束大于或等于0然后获取它们之间角的余弦,再添加上发光值参数
接下来,镜面光分量的实际计算只不过是对表面法线和半程向量进行一次约束点乘(Clamped Dot Product),让点乘结果不为负,从而获取它们之间角的余弦值,之后我们对这个值取反光度次方
```c++
float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);
vec3 specular = lightColor * spec;
```
了我们刚刚讨论的,Blinn-Phong没有更多的内容了。Blinn-Phong和Phong的镜面反射唯一不同之处在于现在我们要测量法线半程向量之间的角度,而半程向量是视线方向反射向量间的夹角。
此之外Blinn-Phong模型就没什么好说的了Blinn-Phong与冯氏模型唯一的区别就是Blinn-Phong测量的是法线半程向量之间的夹角,而冯氏模型测量的是观察方向反射向量间的夹角。
!!! Important
Blinn-Phong着色的一个附加好处是它比Phong着色性能更高因为我们不必计算更加复杂的反射向量了。
引入了半程向量来计算镜面反射后我们再也不会遇到Phong着色的骤然截止问题了。下图展示了两种不同方式下发光值指数为0.5时镜面区域的不同效果:
在引入半程向量之后我们现在应该就不会再看到冯氏光照中高光断层的情况了。下面两个图片展示的是两种方法在镜面光分量为0.5时的对比:
![](../img/05/01/advanced_lighting_comparrison.png)
Phong和Blinn-Phong着色之间另一个细微差别是,半程向量表面法线之间的角度经常会比视线和反射向量之间的夹角更小。结果就是为了获得和Phong着色似的效果,必须把发光值参数设置的大一点。通常的经验是将其设置为Phong着色的发光值参数的24倍。
除此之外,冯氏模型与Blinn-Phong模型也有一些细微差别半程向量表面法线的夹角通常会小于观察与反射向量的夹角。所以,如果你想获得和冯氏着色似的效果,必须在使用Blinn-Phong模型时将镜面反光度设置更高一点。通常我们会选择冯氏着色时反光度分量的24倍。
图是Phong指数为8.0Blinn-Phong指数为32的时候两种specular反射模型的对比:
面是冯氏着色反光度为8.0Blinn-Phong着色反光度为32.0时的一个对比:
![](../img/05/01/advanced_lighting_comparrison2.png)
你可以看到Blinn-Phong的镜面反射成分要比Phong锐利一些。这通常需要使用一点小技巧才能获得之前你所看到的Phong着色的效果但Blinn-Phong着色的效果比默认的Phong着色通常更加真实一些
你可以看到Blinn-Phong的镜面光分量会比冯氏模型更锐利一些。为了得到与冯氏模型类似的结果你可能会需要不断进行一些微调但Blinn-Phong模型通常会产出更真实的结果
这里我们用了一个简单像素着色器,它可以在普通Phong反射Blinn-Phong反射间进行切换:
这里我们使用了一个简单的片段着色器,让我们能够在冯氏反射Blinn-Phong反射间进行切换
```c++
void main()
@@ -92,5 +85,4 @@ void main()
}
```
你可以在这里找到这个简单的[demo的源码](http://www.learnopengl.com/code_viewer.php?code=advanced-lighting/blinn_phong)以及[顶点](http://www.learnopengl.com/code_viewer.php?code=advanced-lighting/blinn_phong&type=vertex)和[片段](http://www.learnopengl.com/code_viewer.php?code=advanced-lighting/blinn_phong&type=fragment)着色器。按下b键这个demo就会从Phong切换到Blinn-Phong光照,反之亦然
你可以在[这里](https://learnopengl.com/code_viewer_gh.php?code=src/5.advanced_lighting/1.advanced_lighting/advanced_lighting.cpp)找到这个Demo的源代码。你可以按下`B`键来切换冯氏光照与Blinn-Phong光照。