0 前言
本节笔记对应的内容为光照贴图,部分内容参考自傅老師/OpenGL教學 第二章。
上一节光照03-Lighting:Materials中,我们认识了材质的概念并封装了对应的类。
在上一节中,我们将整个物体的材质定义为一个整体,但现实世界中的物体通常并不只包含有一种材质,而是由多种材质所组成。想想一辆汽车:它的外壳非常有光泽,车窗会部分反射周围的环境,轮胎不会那么有光泽,所以它没有镜面高光,轮毂非常闪亮(如果你洗车了的话)。汽车同样会有漫反射和环境光颜色,它们在整个物体上也不会是一样的,汽车有着许多种不同的环境光/漫反射颜色。总之,这样的物体在不同的部件上都有不同的材质属性。
所以,上一节中的那个材质系统是肯定不够的,它只是一个最简单的模型,所以我们需要拓展之前的系统,引入漫反射和镜面光贴图(Map)。
1 Diffuse maps
我们希望通过某种方式对物体的每个片段单独设置漫反射颜色。有能够让我们根据片段在物体上的位置来获取颜色值得系统吗?
有,一个纹理。我们仅仅是对同样的原理使用了不同的名字:其实都是使用一张覆盖物体的图像,让我们能够逐片段索引其独立的颜色值。在光照场景中,它通常叫做一个漫反射贴图(Diffuse Map)(3D艺术家通常都这么叫它),它是一个表现了物体所有的漫反射颜色的纹理图像。
为了演示漫反射贴图,我们将会使用一个有钢边框的木箱的图片作为纹理。
在着色器中使用漫反射贴图的方法和纹理教程中是完全一样的。但这次我们会将纹理储存为Material结构体中的一个sampler2D
。我们将之前定义的vec3
漫反射颜色向量替换为漫反射贴图。
注意
sampler2D
是所谓的不透明类型(Opaque Type),也就是说我们不能将它实例化,只能通过uniform来定义它。如果我们使用除uniform以外的方法(比如函数的参数)实例化这个结构体,GLSL会抛出一些奇怪的错误。这同样也适用于任何封装了不透明类型的结构体。
in vec2 TexCoord;
struct Material {
vec3 ambient;
sampler2D diffuse;
vec3 specular;
float shininess;
};
......
vec3 diffuse = texture(material.diffuse, TexCoord).rgb *
max(dot(lightDir, Normal), 0) * lightColor;
不要忘记将环境光得材质颜色设置为漫反射材质颜色同样的值。
vec3 ambient = texture(material.diffuse, TexCoord).rgb * ambientColor;
更新后的顶点数据可以在这里找到。顶点数据现在包含了顶点位置、法向量和立方体顶点处的纹理坐标。让我们更新顶点着色器来以顶点属性的形式接受纹理坐标,并将它们传递到片段着色器中:
#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;
}
material.h文件中
unsigned int diffuse;
......
Material(Shader* _shader, unsigned int _diffuse, glm::vec3 _specular, glm::vec3 _ambient, float _shininess);
material.cpp中_diffuse也改成unsigned int,并且导入material
Material* myMaterial = new Material(myShader,
loadImageToGPU("container2.png", GL_RGBA, GL_RGBA, 0),
glm::vec3(1.0f, 1.0f, 1.0f),
glm::vec3(1.0f, 1.0f, 1.0f),
32.0f);
为了设置uniform,我们在shader类中新增一个方法
void Shader::SetUniform1i(const char * paraNameString, int slot)
{
glUniform1i(glGetUniformLocation(ID, paraNameString), slot);
}
回到main中加入uniform,该槽位与纹理引入部分槽位一致。
myMaterial->shader->SetUniform1i("material.diffuse", 0);
运行结果如下
2 Specular maps
你可能会注意到,镜面高光看起来有些奇怪,因为我们的物体大部分都是木头,我们知道木头不应该有这么强的镜面高光的。所以,我们想要让物体的某些部分以不同的强度显示镜面高光。
我们同样可以使用一个专门用于镜面高光的纹理贴图。这也就意味着我们需要生成一个黑白的(较彩色纹理节省内存,颜色可由lightColor决定)纹理,来定义物体每部分的镜面光强度。下面是一个镜面光贴图(Specular Map)的例子:
镜面高光的强度可以通过图像每个像素的亮度来获取。镜面光贴图上的每个像素都可以由一个颜色向量来表示,比如说黑色代表颜色向量
vec3(0.0)
,灰色代表颜色向量vec3(0.5)
。在片段着色器中,我们接下来会取样对应的颜色值并将它乘以光源的镜面强度。一个像素越「白」,乘积就会越大,物体的镜面光分量就会越亮。
由于箱子大部分都由木头所组成,而且木头材质应该没有镜面高光,所以漫反射纹理的整个木头部分全部都转换成了黑色。箱子钢制边框的镜面光强度是有细微变化的。
按照上面改diffuse的方法 把specular改成sample2D类型的 在material的h与cpp文件改一下specular的类型 在片元着色器内的结构体部分改一下,因为坐标都一样,就用漫反射贴图的坐标就好
struct Material {
vec3 ambient;
sampler2D diffuse;
sampler2D specular;
float shininess;
};
vec3 specular =
texture(material.specular, TexCoord).rgb * specularAmount * lightColor;
在main中载入两张纹理图片,并在loop中绑定材质:
Material *myMaterial = new Material(testShader,
loadImageToGPU("container2.png", GL_RGBA, GL_RGBA, Shader::DIFFUSE),
loadImageToGPU("container2_specular.png", GL_RGBA, GL_RGBA, Shader::SPECULAR), glm::vec3(1.0f, 1.0f, 1.0f), 32.0f);
// Set Material -> Textures
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, myMaterial->diffuse);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, myMaterial->specular);
myMaterial->shader->SetUniform1i("material.specular", 1);
运行后可以看到高光只在边框上存在了
通过使用漫反射和镜面光贴图,我们可以给相对简单的物体添加大量的细节。我们甚至可以使用法线/凹凸贴图(Normal/Bump Map)或者反射贴图(Reflection Map)给物体添加更多的细节,但这些将会留到之后的教程中。
「如果这篇文章对你有用,请随意打赏」
如果这篇文章对你有用,请随意打赏
使用微信扫描二维码完成支付

comments powered by Disqus