【Modern OpenGL】光照映射 LightingMaps

说明:跟着learnopengl的内容学习,不是纯翻译,只是自己整理记录。
强烈推荐原文,无论是内容还是排版。 原文链接
本文地址:http://blog.csdn.net/aganlengzi/article/details/50542414

光照映射 Lighting maps

在前面的教程中,我们讨论了使每个对象都使用一种独有的材质以便光照对它们产生不同的作用。这能够达到在相同的光照场景中,不同物体能够表现出不同的外观(颜色),但是这好像还是不够,这次的教程是以一个物体为对象,使其不同的部分产生不同的光照效果。

在上一个教程中,我门为整个物体定义一种材质,但是在真实的世界中,即使是同一个物体可能由不同的材质构成。比如说一辆汽车,它的外表是由光亮的金属构成的,它有可以反映周围环境的窗子,它的轮胎是不反光的,所以不会产生高光光斑,但是它的车圈却是十分光亮的(洗干净的话)。一辆汽车的环境光或者镜面反射光也是和整个物体的环境光或者漫反射光不同的,因为一辆车可以表现出不同的环境光或者漫反射光。总的来说,这样的物体中的不同组成部分都有不同的材质属性。

所以前面教程中实现的材质系统对于这种更为复杂的应用场景就不适用了。所以在下面的教程中将要介绍漫反射光和镜面反射光映射。它们允许我们对物体中的漫反射和镜面反射部分由更细粒度的操作。并且也能够间接影响环境光。

漫反射映射 Diffuse maps

我们想要做的是有某种方法可以对渲染物体的每个片段(fragment)都能够指定它的漫反射光。类似于一个能够根据片段在物体中的位置来决定它的颜色值的系统。

上面的这个目标听上去是不是很熟悉?实际上我们已经在使用这样的技术了——我们前面讲解特别多的纹理贴图:根据物体中片段的位置来决定它使用纹理中某个位置的颜色值。只是在光照场景中,我们把它叫做漫反射映射(这是3D艺术家们对它的称呼),因为一个纹理图片中包含了物体中每个点应该显示的漫反射颜色。

我们将会使用下面这张图来展示漫反射映射的原理,这张图和我们前面用到的木箱子图不同的地方在于,这张图的边缘是金属材质的:

在shader中使用漫反射映射的方法和纹理映射的方式是一样的。但是这次我们将这个纹理存成sampler2D格式,并且将其包含到我们之前定义的材质结构体中——用漫反射映射来替换之前定义的vec3类型的漫反射光向量。

这是我们第一次使用sample2D类型的变量,需要记住的是它是一个隐含类型,我们不能实例化这个类型的变量,只能用作uniform类型的变量。如果我们不是将这种类型实例化为uniform类型,GLSL就会报错。同样,对于包含这个类型的结构体也是一样。

在结构体中,我们还将环境光的vec3类型的向量去掉了,因为几乎在所有的情况下,环境光和漫反射光表现出来的效果是一致的,所以没有必要单独存储它。所以目前我们得到的材质结构体是下面这个样子:

struct Material {
    sampler2D diffuse;
    vec3      specular;
    float     shininess;
}; 
...
in vec2 TexCoords;

如果你坚持需要使用环境光,那么也可以不删除,但是在效果上,环境光对整个物体产生的作用及效果是一致的,所以如果想要像我们正在做的为物体中的每个片段都生成不同的环境光,你需要类似地使用另一个纹理。

需要注意的是:我们需要在片段处理程序中使用纹理坐标,所以我们声明一个另外的输入参数,也就是上面的TexCoords。然后我们在使用的时候只需要利用纹理坐标从纹理中进行采样就好了(这和纹理贴图并没有什么区别啊)。

vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));  

还有,不要忘记将环境光的材质颜色设置成和漫反射相同的颜色,因为我们已经将这个值从材质结构体中删除了:

vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

以上,就是我们在使用漫反射映射之前需要准确的所有工作。如你所见,它真的和前面教程中使用纹理映射的时候一样的。但是它的确在视觉效果上有大幅提高(后面看吧)。为了真正使用它,我们需要更新顶点数据和纹理坐标,并且将它们传递到片段处理程序中,加载纹理并且将纹理绑定到正确的纹理单元。

更新后的顶点数据可以在这儿得到,这份顶点数据包含每个顶点的顶点位置坐标、法向量和纹理坐标。同时我们还需要将顶点处理程序进行修改以便其能够接收纹理坐标作为顶点属性,并且将其传递给片段处理程序,如下所示:

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
layout (location = 2) in vec2 texCoords;
...
out vec2 TexCoords;

void main()
{
    ...
    TexCoords = texCoords;
}  

注意要保证更新VAO的顶点属性指针使其能够和新的数据相匹配。同时需要加载上面所示的箱子图像作为纹理。还有在绘制立方体箱子之前,我们需要设置使用的纹理单元,把它设置到material.diffuse(现在它只是material.diffuse使用的纹理对象,这个地方时和前面不同的地方),并且把加载的纹理图片绑定到这个纹理对象上。

glUniform1i(glGetUniformLocation(lightingShader.Program, "material.diffuse"), 0);
...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);

现在让我们检验一下效果吧。我跑的效果是下面这个样子的:

本次教程截止目前的所有代码

镜面光映射 Specular maps

你可能意识到在现实生活中,木头箱子本身不应该有镜面反射光中的高亮部分,这对于材质是木头的立方体箱子是正常的。但是我们上面生成的效果是有的,而且非常明显。显然这是不符合真实性的,我们可以通过将材质中的镜面反射部分设置为vec(0.0f)来达到不产生镜面反射光的效果,但是箱子边缘的金属也不会显示出应有的镜面反射光中的高亮部分了。而正常情况下,它们应该显示出一定的反射光,因为它们是光滑的金属。所以我们应该对这种情况进行处理,即对物体的不同部分的镜面光反射强度进行设置设置。这和上面讲的漫反射映射又有些类似,这不是巧合。

我们为镜面反射的高光部分也使用一张纹理。这就意味着我们需要生成一个黑白(或者彩色,如果你喜欢的话)的纹理来定义一个物体不同部分的镜面反射光强度。如下图所示:

镜面反射光的高光强度由图片中的每个像素的亮度来定义。镜面光映射中的每个像素都可以表示成一个颜色向量。举例来说,其中黑色的颜色向量就是vec3(0.0f),我们在上面想要为木质材质设置的颜色。灰色的颜色向量是vec3(0.5f)。在片段处理程序中,我们从纹理中取得相应的颜色值并且和光照强度中的镜面反射光强度相乘。像素中白色越多就代表作用后的效果更亮。

因为这次教程中生成的箱子对象主要是由木质材质构成的,而木质材质是不会反射镜面高光的,所以整个木质部分被设置成黑色,因为黑色不会产生任何反射高光。图中箱子的金属边缘有着变化的比较亮的部分,因为光亮的金属会产生高光而金属上的裂缝并不会产生镜面反射光。

特殊的木质材料(比如十分光滑的木质表面)也会产生镜面反射高光,虽然它们的亮度并不会太强(因为更多的光是被散射出去了),但是出于学习的目的,我们可以简化为木质材质并不会产生镜面反射光。

使用Photoshop或者Gimp程序能够比较方便地得到类似上满展示的漫反射纹理或者镜面反射纹理。可以通过简单的裁剪,改变颜色值或者亮度值来完成图片的转换。

对镜面反射光映射采样 Sampling specular maps

一个镜面反射光映射实际上就是一个纹理,在使用方式上它和其它的纹理类似,和上面介绍的漫反射光映射使用方式更是相似。只需要保证将上述的纹理加载并且绑定到一个纹理单元上。因为我们要同时使用上面讲过的漫反射映射和镜面反射映射,所以我们还需要再利用相关代码使用一个纹理单元(我们一般的机器应该都会支持至少16个纹理单元),原理和上面十分类似:

glUniform1i(glGetUniformLocation(lightingShader.Program, "material.specular"), 1);
...
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap); 

然后更新片段处理程序中的材质属性来接受一个Sample2D类型的隐含变量来作为材质中镜面反射光部分的属性代替之前的vec3类型的向量:

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float     shininess;
}; 

最后,我们需要通过修改片段处理程序来从镜面反射光映射纹理中取得每个片段的镜面反射光属性值并且做相应计算:

vec3 ambient  = light.ambient  * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse  = light.diffuse  * diff * vec3(texture(material.diffuse, TexCoords));  
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
color = vec4(ambient + diffuse + specular, 1.0f);   

通过使用一个镜面反射映射,我们可以以非常细的粒度指定要渲染的物体的每个像素点对光源应该表现出的属性,它就是一个滤光镜,让我们可以对光源和物体之间的作用进行细致控制。

添加以上代码后,我运行的效果如下:

这个箱子看上去更像一个真实的箱子了,不是吗?以上所有代码在这儿

通过使用漫反射和镜面反射映射,我们能够真正为要渲染的对象添加非常细粒度的细节控制。实际上我们可以通过纹理的方式为物体添加更多的细节,比如说法向量映射、反射映射等等,这些内容我们保留在后面的教程中进行讲解。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值