OpenGL核心技术之光照技术

发表于2017-04-06
评论0 1k浏览

光照模型在各大商业引擎中都会使用,而且光照的使用直接影响着场景的渲染,所以对于游戏品质的提升非常重要,本篇文章主要是给大家介绍关于游戏引擎中最重要的也是使用最多的光照模型Blinn-Phong。如果有对关于Blinn-Phong光照模型的实现不了解的,希望这篇文章可以帮助你去了解。

Phong光照很棒,而且性能较高,但是它的镜面反射在某些条件下会失效,特别是当发光值属性低的时候,对应一个非常大的粗糙的镜面区域。下面的图片展示了,当我们使用镜面的发光值为1.0时,一个带纹理地板的效果:



你可以看到,镜面区域边缘迅速减弱并截止。出现这个问题的原因是在视线向量和反射向量的角度不允许大于90度。如果大于90度的话,点乘的结果就会是负数,镜面的贡献成分就会变成0。你可能会想,这不是一个问题,因为大于90度时我们不应看到任何光,对吧?

错了,这只适用于漫散射部分,当法线和光源之间的角度大于90度时意味着光源在被照亮表面的下方,这样光的散射成分就会是0.0。然而,对于镜面光照,我们不会测量光源和法线之间的角度,而是测量视线和反射方向向量之间的。看看下面的两幅图:


现在看来问题就很明显了。左侧图片显示Phong反射的θ小于90度的情况。我们可以看到右侧图片视线和反射之间的角θ大于90度,这样镜面反射成分将会被消除。通常这也不是问题,因为视线方向距离反射方向很远,但如果我们使用一个数值较低的发光值参数的话,镜面半径就会足够大,以至于能够贡献一些镜面反射的成份了。在例子中,我们在角度大于90度时消除了这个贡献(如第一个图片所示)。

1977年James F. Blinn引入了Blinn-Phong着色,它扩展了我们目前所使用的Phong着色。Blinn-Phong模型很大程度上和Phong是相似的,不过它稍微改进了Phong模型,使之能够克服我们所讨论到的问题。它放弃使用反射向量,而是基于我们现在所说的一个叫做半程向量(halfway vector)的向量,这是个单位向量,它在视线方向和光线方向的中间。半程向量和表面法线向量越接近,镜面反射成份就越大。


当视线方向恰好与反射向量对称时,半程向量就与法线向量重合。这样观察者距离原来的反射方向越近,镜面反射的高光就会越强。

这里,你可以看到无论观察者往哪里看,半程向量和表面法线之间的夹角永远都不会超过90度(当然除了光源远远低于表面的情况)。这样会产生和Phong反射稍稍不同的结果,但这时看起来会更加可信,特别是发光值参数比较低的时候。Blinn-Phong着色模型也正是早期OpenGL固定函数输送管道(fixed function pipeline)所使用的着色模型。

得到半程向量很容易,我们将光的方向向量和视线向量相加,然后将结果归一化(normalize);

翻译成GLSL代码如下:

[html] view plain copy
 
  1. vec3 lightDir = normalize(lightPos - FragPos);  
  2. vec3 viewDir = normalize(viewPos - FragPos);  
  3. vec3 halfwayDir = normalize(lightDir + viewDir);  

实际的镜面反射的计算,就成为计算表面法线和半程向量的点乘,并对其结果进行约束(大于或等于0),然后获取它们之间角度的余弦,再添加上发光值参数:

[html] view plain copy
 
  1. float spec = pow(max(dot(normal, halfwayDir), 0.0), shininess);  
  2. vec3 specular = lightColor * spec;  

除了我们刚刚讨论的,Blinn-Phong没有更多的内容了。Blinn-Phong和Phong的镜面反射唯一不同之处在于,现在我们要测量法线和半程向量之间的角度,而半程向量是视线方向和反射向量之间的夹角。

Blinn-Phong着色的一个附加好处是,它比Phong着色性能更高,因为我们不必计算更加复杂的反射向量了。

引入了半程向量来计算镜面反射后,我们再也不会遇到Phong着色的骤然截止问题了。下图展示了两种不同方式下发光值指数为0.5时镜面区域的不同效果:



Phong和Blinn-Phong着色之间另一个细微差别是,半程向量和表面法线之间的角度经常会比视线和反射向量之间的夹角更小。结果就是,为了获得和Phong着色相似的效果,必须把发光值参数设置的大一点。通常的经验是将其设置为Phong着色的发光值参数的2至4倍。

下图是Phong指数为8.0和Blinn-Phong指数为32的时候,两种specular反射模型的对比:


你可以看到Blinn-Phong的镜面反射成分要比Phong锐利一些。这通常需要使用一点小技巧才能获得之前你所看到的Phong着色的效果,但Blinn-Phong着色的效果比默认的Phong着色通常更加真实一些。

这里我们用到了一个简单像素着色器,它可以在普通Phong反射和Blinn-Phong反射之间进行切换:

[html] view plain copy
 
  1. void main()  
  2. {  
  3.     [...]  
  4.     float spec = 0.0;  
  5.     if(blinn)  
  6.     {  
  7.         vec3 halfwayDir = normalize(lightDir + viewDir);    
  8.         spec = pow(max(dot(normal, halfwayDir), 0.0), 16.0);  
  9.     }  
  10.     else  
  11.     {  
  12.         vec3 reflectDir = reflect(-lightDir, normal);  
  13.         spec = pow(max(dot(viewDir, reflectDir), 0.0), 8.0);  
  14.     }  

最后把顶点着色器代码给读者展示如下:

[html] view plain copy
 
  1. #version 330 core  
  2. layout (location = 0) in vec3 position;  
  3. layout (location = 1) in vec3 normal;  
  4. layout (location = 2) in vec2 texCoords;  
  5.   
  6. // Declare an interface block; see 'Advanced GLSL' for what these are.  
  7. out VS_OUT {  
  8.     vec3 FragPos;  
  9.     vec3 Normal;  
  10.     vec2 TexCoords;  
  11. } vs_out;  
  12.   
  13. uniform mat4 projection;  
  14. uniform mat4 view;  
  15.   
  16. void main()  
  17. {  
  18.     gl_Position = projection * view * vec4(position, 1.0f);  
  19.     vs_out.FragPos = position;  
  20.     vs_out.Normal = normal;  
  21.     vs_out.TexCoords = texCoords;  
  22. }  

片段着色器代码如下所示:

[html] view plain copy
 
  1. #version 330 core  
  2. out vec4 FragColor;  
  3.   
  4. in VS_OUT {  
  5.     vec3 FragPos;  
  6.     vec3 Normal;  
  7.     vec2 TexCoords;  
  8. } fs_in;  
  9.   
  10. uniform sampler2D floorTexture;  
  11. uniform vec3 lightPos;  
  12. uniform vec3 viewPos;  
  13. uniform bool blinn;  
  14.   
  15. void main()  
  16. {             
  17.     vec3 color = texture(floorTexture, fs_in.TexCoords).rgb;  
  18.     // Ambient  
  19.     vec3 ambient = 0.05 * color;  
  20.     // Diffuse  
  21.     vec3 lightDir = normalize(lightPos - fs_in.FragPos);  
  22.     vec3 normal = normalize(fs_in.Normal);  
  23.     float diff = max(dot(lightDir, normal), 0.0);  
  24.     vec3 diffuse = diff * color;  
  25.     // Specular  
  26.     vec3 viewDir = normalize(viewPos - fs_in.FragPos);  
  27.     vec3 reflectDir = reflect(-lightDir, normal);  
  28.     float spec = 0.0;  
  29.     if(blinn)  
  30.     {  
  31.         vec3 halfwayDir = normalize(lightDir + viewDir);    
  32.         spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);  
  33.     }  
  34.     else  
  35.     {  
  36.         vec3 reflectDir = reflect(-lightDir, normal);  
  37.         spec = pow(max(dot(viewDir, reflectDir), 0.0), 8.0);  
  38.     }  
  39.     vec3 specular = vec3(0.3) * spec; // assuming bright white light color  
  40.     FragColor = vec4(ambient + diffuse + specular, 1.0f);  
  41. }  

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引