4 - 光照贴图

实际上,每个物体可以 同时拥有多种材质。例如一辆汽车,它同时有金属般光泽的外壳,也有比较粗糙的轮胎。接下来引入 漫反射贴图(Map)镜面光贴图,允许我们更精确的控制这两个分量。

漫反射贴图

介绍

漫反射贴图(Diffuse Map),它是一个表现了物体所有的漫反射颜色的纹理图像。接下来尝试将这张漫反射贴图引入:

采样

先修改一下之前定义的Material结构体,将环境光颜色去掉(因为大多数情况下,环境光颜色和漫反射颜色相同),然后将漫反射颜色类型变成sampler2D:

// 材质结构体
struct Material 
{
    sampler2D diffuse;
    vec3 specular;
    float shininess;
};
    
// 改一下两个量的计算方式
vec3 ambient = light.ambient * texture(material.diffuse, TexCoords).rgb;
vec3 diffuse = texture(material.diffuse, TexCoords).rgb * (light.diffuse / (r * r) ) * max(0.0, dot(norm, lightDir));

然后像之前那样,引入纹理:

  • 修改顶点数据:在这里

  • 解析顶点坐标属性:

    // 解析纹理坐标属性
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    glEnableVertexAttribArray(2);
  • 加载纹理:

    // 创建并绑定纹理
    unsigned int diffuseTexture;
    glGenTextures(1, &diffuseTexture);
    glBindTexture(GL_TEXTURE_2D, diffuseTexture);
    // 设定环绕/过滤方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 加载纹理图片
    stbi_set_flip_vertically_on_load(true);
    int texWidth, texHeight, texNrChannels;
    unsigned char* data = stbi_load("./images/container2.png", &texWidth, &texHeight, &texNrChannels, 0);
    if (data)
    {
        // 生成纹理和Mipmap
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "ERROR: Failed to load texture" << std::endl;
    }
    // 释放内存
    stbi_image_free(data);
  • 告诉sampler2D用哪个纹理:

    cubeShader.setInt("material.diffuse", 0);

最后结果如下(为了展示成果,暂时去掉了光的衰减):

镜面光贴图

从上边可以发现,物体的边框是钢制的,应该有更强的镜面光,同时木头部分给的镜面光也太过高了。因此要引入镜面光贴图,更加灵活的控制物体不同部分的镜面光分量。

介绍

如图,镜面光贴图是 黑白的,用来定义物体每部分的镜面光强度:

在片段着色器中,接下来会取样对应的颜色值并将它乘以光源的镜面强度,一个像素越白,物体的镜面光分量越亮。

制作这种贴图可以用PhotoShopGimp之类的工具,将漫反射纹理适当修改即可。

采样

和漫反射贴图同理,但不要忘了将不同纹理贴图绑定到不同的GL_TEXTURE上:

cubeShader.setInt("material.diffuse", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseTexture);
cubeShader.setInt("material.specular", 1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specTexture);

最后结果如下(为了展示成果,暂时去掉了光的衰减):

可以看到,现在只有箱子的金属边框有高光了。

其他贴图

除了上边的贴图,还有 法线/凹凸贴图(Normal/Bump Map),反射贴图(Reflection Map)和自发光贴图(Emission Map)等贴图,增添物体细节。

自发光/放射光贴图

添加一个叫做放射光贴图(Emission Map)的东西,它是一个储存了每个片段的发光值(Emission Value)的贴图。发光值是一个包含(假设)光源的物体发光(Emit)时可能显现的颜色,这样的话物体就能够忽略光照条件进行发光(Glow)。

游戏中某个物体在发光的时候,你通常看到的就是放射光贴图:

img

接下来尝试将下图纹理作为自发光贴图添加到箱子上,产生这些字母都在发光的效果:

(同理,加个自发光项即可)

本文代码详见这里

参考资料

  • 光照贴图 - LearnOpenGL CN (learnopengl-cn.github.io)