在上一节材质中,我们讨论了让每个物体都拥有自己独特的材质从而对光照做出不同的反应的方法。这样能够很容易在一个光照的场景中给每个物体一个独特的外观,但这个材质系统是远远不够的,它只是一个最简单的模型,所以我们需要拓展之前的系统,引入 漫反射贴图 和 镜面光贴图(Map)。这允许我们对物体的漫反射分量(以及间接地对环境光分量,它们几乎总是一样的)和镜面光分量有着更精确的控制。
在上一节中,我们将整个物体的材质定义为一个整体,但现实世界中的物体通常并不只包含有一种材质,而是由多种材质所组成。想想一辆汽车:它的外壳非常有光泽,车窗会部分反射周围的环境,轮胎不会那么有光泽,所以它没有镜面高光,轮毂非常闪亮。汽车同样会有漫反射和环境光颜色,它们在整个物体上也不会是一样的,汽车有着许多种不同的环境光/漫反射颜色。总之,这样的物体在不同的部件上都有不同的材质属性。
漫反射贴图
我们希望通过某种系统对物体的每个片段单独设置漫反射颜色。这听起来很熟悉,而且事实上这个系统我们已经使用很长时间了。这就是我们之前讨论过的纹理。
我们仅仅是对同样的原理使用了不同的名字:其实都是使用一张覆盖物体的图像,让我们能够逐片段索引其独立的颜色值。在光照场景中,它通常叫做一个漫反射贴图(Diffuse Map)(在PBR之前通常都这么叫它),它是一个表现了物体所有的漫反射颜色的纹理图像。
为了演示漫反射贴图,我们将会使用新的纹理图片,一个有钢边框的木箱
在着色器中使用漫反射贴图的方法和纹理教程中是完全一样的。但这次我们会将纹理的sampler2D
变量储存在Material
结构体中。
注意sampler2D是所谓的不透明类型(Opaque Type),也就是说我们不能将它实例化,只能通过uniform来定义它。如果我们使用除uniform以外的方法(比如函数的参数)实例化这个结构体,GLSL会抛出一些奇怪的错误。这同样也适用于任何封装了不透明类型的结构体。
将之前定义的vec3
漫反射颜色向量替换为漫反射贴图。同时也移除了环境光材质颜色向量,因为环境光颜色在几乎所有情况下都等于漫反射颜色,所以我们不需要将它们分开储存。更新后的Material
:
struct Material {
sampler2D diffuse;
vec3 specular;
float shininess;
};
...
in vec2 TexCoords;
如果你仍想将环境光颜色设置为一个(漫反射值之外)不同的值,你也可以保留这个环境光的vec3,但整个物体仍只能拥有一个环境光颜色。如果想要对不同片段有不同的环境光值,你需要对环境光值单独使用另外一个纹理。
我们将在片段着色器中再次需要纹理坐标,所以我们声明一个额外的输入变量TexCoords
。接下来我们只需要从纹理中采样片段的漫反射颜色值即可:
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
将环境光的材质颜色设置为漫反射材质颜色同样的值。
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
这就是 使用漫反射贴图的全部步骤 了。这并不是什么新的东西,但这能够极大地提高视觉品质。
为了让它正常工作,我们还需要使用纹理坐标更新顶点数据,将它们作为顶点属性传递到片段着色器,加载材质并绑定材质到合适的纹理单元。顶点数据现在包含了顶点位置、法向量 和立方体顶点处的纹理坐标:
float vertices[] = {
// positions // normals // texture coords
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f,