OpenGL学习笔记14-Lighting maps

Lighting maps

在前一章中,我们讨论了每个物体都有其自身独特的材料对光线产生不同反应的可能性。与其他对象相比,这对于给予每个对象一个独特的外观是很好的,但仍然没有提供对象的视觉输出的太多灵活性。

在前一章中,我们为一个整体对象定义了材质。然而,现实世界中的对象通常不是由单一的材料组成,而是由多个材料组成。试想一辆汽车:它的外部是由一种闪亮的织物组成的,它的车窗可以部分地反射周围的环境,它的轮胎几乎都是闪亮的,所以它们没有高光,它的轮辋非常闪亮(如果你真的把车洗干净了的话)。汽车的漫反射色和环境色对整个物体来说是不一样的;汽车显示许多不同的环境/漫反射颜色。总的来说,这样一个物体的不同部分有不同的物质属性。

所以前一章的材质系统除了最简单的模型外还不足以满足所有的模型,所以我们需要通过引入漫反射和高光贴图来扩展这个系统。这使得我们可以更精确地影响一个物体的漫反射(和间接的环境成分,因为它们应该是一样的)和镜面成分。

Diffuse maps

我们想要的是为每个单独的片段设置一个物体的漫反射颜色。某种基于碎片在物体上的位置来获取颜色值的系统?

这听起来应该很熟悉,我们已经使用这样的系统一段时间了。这听起来就像我们在前面的章节中广泛讨论过的纹理,基本上就是:纹理。我们只是为相同的基本原理使用了不同的名称:使用一个包裹在对象周围的图像,我们可以为每个片段索引唯一的颜色值。在有光的场景中,这通常被称为漫反射贴图(在PBR之前,3D艺术家通常是这样称呼它们的),因为纹理图像代表了所有物体的漫反射颜色。

为了演示漫反射贴图,我们将使用一个带有钢边界的木制容器的如下图片following image:

 

在着色器中使用漫反射贴图就像我们在纹理章节中展示的一样。但是这一次我们将纹理作为sampler2D存储在材质结构中。我们将之前定义的vec3漫反射颜色向量替换为漫反射贴图。

请记住,sampler2D是一个所谓的不透明类型,这意味着我们不能实例化这些类型,而只能将它们定义为统一类型。如果struct实例化的不是统一的(比如函数参数),那么GLSL会抛出奇怪的错误;因此,这同样适用于持有这种不透明类型的任何结构。

我们也移除了环境材质的颜色向量,因为环境色等于漫反射色,现在我们用光来控制环境色。所以没有必要单独存储:


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

如果你有点固执,仍然想要设置环境色为一个不同的值(除了漫反射值),你可以保持环境色为vec3,但这样环境色对整个对象来说仍然是相同的。要为每个片段获得不同的环境值,你必须为环境值单独使用另一种纹理。

注意,我们将再次在片段着色器中需要纹理坐标,所以我们声明了一个额外的输入变量。然后我们简单地从纹理中采样来获取片段的漫反射颜色值:


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

另外,不要忘记将环境材质的颜色设置为与漫反射材质相同的颜色:


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

这就是使用漫反射贴图所需要的。正如你所看到的,这并不是什么新东西,但它确实在视觉质量上提供了戏剧性的提高。为了让它工作,我们需要用纹理坐标更新顶点数据,将它们作为顶点属性转移到片段着色器,加载纹理,并将纹理绑定到适当的纹理单元。

更新后的顶点数据可以在这里here.找到。顶点数据现在包括立方体每个顶点的顶点位置、法向量和纹理坐标。让我们更新顶点着色器,接受纹理坐标作为顶点属性,并将它们转发到片段着色器:


#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
...
out vec2 TexCoords;

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

确保更新两个VAOs的顶点属性指针以匹配新的顶点数据,并加载容器图像作为纹理。在渲染立方体之前,我们要给材质分配正确的纹理单元。扩散均匀采样器,并将容器纹理绑定到这个纹理单元:


lightingShader.setInt("material.diffuse", 0);
...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);

现在使用漫反射贴图,我们在细节上得到了巨大的提升,这一次容器真的开始发光了。您的容器现在可能看起来像这样:

 

您可以在这里找到应用程序的完整源代码here. 。

Specular maps

你可能注意到高光看起来有点奇怪,因为对象是一个容器,主要由木材和木材没有像这样的高光。我们可以通过将对象的高光材质设置为vec3(0.0)来解决这个问题,但这意味着容器的钢质边框也将停止显示高光,而钢质将显示高光。我们想要控制什么部分的对象应该显示高光与不同的强度。这个问题听起来很熟悉。巧合吗?我认为不是。

我们也可以使用纹理贴图来处理高光。这意味着我们需要生成一个黑白(或者你喜欢的颜色)纹理来定义物体每个部分的高光强度。下面是一个高光贴图的例子specular map :

高光的强度来自于图像中每个像素的亮度。高光贴图的每个像素都可以显示为一个颜色矢量,例如,黑色表示颜色矢量vec3(0.0),灰色表示颜色矢量vec3(0.5)。在碎片着色器中,我们取样相应的颜色值,并将这个值与光线的高光强度相乘。一个像素越“白”,乘法的结果就越高,因此物体的高光部分就变得越亮。

因为容器大部分由木材组成,木材作为一种材质应该没有高光,整个漫反射纹理的木材部分被转换为黑色:黑色部分没有任何高光。容器的钢边界有不同的镜面强度与钢本身是相对敏感的镜面高光,而裂缝不是。

从技术上讲,木材也有高光,虽然有一个低得多的亮度值(更多的光散射)和较少的影响,但为了学习的目的,我们可以假装木材没有任何反应高光。

使用Photoshop或Gimp这样的工具,通过剪切一些部分,将其转换为黑白并增加亮度/对比度,将漫反射纹理转换为高光图像是相对容易的。

Sampling specular maps 抽样镜面映射

高光贴图就像其他纹理一样,所以代码和漫反射贴图代码类似。确保正确加载图像并生成纹理对象。由于我们在同一个碎片着色器中使用了另一个纹理采样器,我们必须为高光贴图使用一个不同的纹理单元(见纹理),所以让我们在渲染之前将它绑定到合适的纹理单元:


lightingShader.setInt("material.specular", 1);
...
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);  

然后更新碎片着色器的材质属性,接受sampler2D作为其镜面组件,而不是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));
FragColor = vec4(ambient + diffuse + specular, 1.0);   

通过使用高光贴图,我们可以详细说明物体的哪些部分具有闪亮属性,我们甚至可以控制相应的亮度。高光贴图为我们在漫反射贴图上增加了一层对光线的控制。

如果你不想太主流,你也可以在高光贴图中使用实际的颜色,不仅可以设置每个片段的高光强度,还可以设置高光的颜色。然而实际上,高光的颜色主要是由光源本身决定的,所以它不会产生真实的视觉效果(这就是为什么图像通常是黑白的:我们只关心强度)。

如果你现在运行应用程序,你可以清楚地看到,容器的材料现在非常类似于一个实际的木制容器与钢框架:

您可以在这里here. 找到应用程序的完整源代码。

使用漫射贴图和高光贴图,我们可以为相对简单的物体添加大量的细节。我们甚至可以使用其他纹理贴图,比如法线/凹凸贴图和/或反射贴图,为对象添加更多细节,但这是我们将保留到后面的章节。把你的容器展示给你所有的朋友和家人,并满足于我们的容器有一天会变得更漂亮的事实!

Exercises

 

  • Fool around with the light source's ambient, diffuse and specular vectors and see how they affect the visual output of the container.
  • Try inverting the color values of the specular map in the fragment shader so that the wood shows specular highlights and the steel borders do not (note that due to the cracks in the steel border the borders still show some specular highlight, although with less intensity): solution.
  • Try creating a specular map from the diffuse texture that uses actual colors instead of black and white and see that the result doesn't look too realistic. You can use this colored specular map if you can't generate one yourself: result.
  • Also add something they call an emission map which is a texture that stores emission values per fragment. Emission values are colors an object may emit as if it contains a light source itself; this way an object can glow regardless of the light conditions. Emission maps are often what you see when objects in a game glow (like eyes of a robot, or light strips on a container). Add the following texture (by creativesam) as an emission map onto the container as if the letters emit light: solution; result.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值