美文网首页
OpenGL #08 Lighting maps

OpenGL #08 Lighting maps

作者: MisakiMel | 来源:发表于2019-11-02 14:46 被阅读0次

  在上一节中,我们学习到了每一个物体都应该有属于自己的材质(Materials),但这远远不够,一个物体的任意一部分对光的反射方式都是相同的吗?显然不是,一个物体本身也会包含各种材质,例如一个边缘被镶了铁框的木箱,铁制部分的镜面反射肯定比木制部分要强很多。不同的部分对光的处理方式不一,这使得我们需要扩展之前的系统,引入漫反射贴图镜面光贴图(map)。这允许我们对物体的漫反射分量(以及间接地对环境光分量,它们几乎总是一样的)和镜面光分量有着更精确的控制。

漫反射贴图


  要想实现一个物体不同部分能有不同的漫反射光照处理,即对物体的漫反射分量有着精确的控制,我们应该需要对这个物体的每一个片段都单独设置其漫反射颜色,那么如何根据片段在物体上的位置来确定这个位置的漫反射颜色呢?这其实跟我们之前学过的纹理那一节有很大的相似之处,纹理是为了获取该片段所在位置的颜色,而漫反射贴图获取的是漫反射颜色。原理上是类似的,且操作起来也是类似的。
  为此,我们需要一张物体表面图来进行试验,就拿刚才举的例子,一个有钢边框的木箱


  上一节中,我们为物体定义了一个材质结构体,里面存放的是进行三种光照处理时应该表现的颜色值,现在我们要让物体在不同的部分表现不同的漫反射颜色,所以需要漫反射这个变量的类型,为sampler2D
struct Material{
    sampler2D diffuse;
    vec3 specular;

    float shininess;
};

  由于漫反射光照和环境光照的要表现的颜色其实是一样的,只是光照来源不一样,所以把原本的diffuseambient合并为sampler2D diffuse;sampler2D是一种存储纹理数据的类型。
  片段着色器除了要获得纹理数据意外,还要获得纹理坐标,然后使用函数从纹理采样片段的漫反射颜色值:

#version 330 core
...
struct Material{
    sampler2D diffuse;
    vec3 specular;

    float shininess;
};
...
in vec2 texCoords;

void main()
{
      float ambientStrength = 0.1;
      vec3 ambient = light.ambient * texture(material.diffuse, texCoords).rgb;

      ...
      vec3 diffuse = light.diffuse * diff * texture(material.diffuse, texCoords).rgb;
      ...
}

  in vec2 texCoords;,我们需要在从顶点着色器中把纹理坐标传递给片段着色器,而顶点着色器的纹理坐标,可以通过顶点链接方式传递,最重要的,就是要在主函数中提供纹理坐标,更新后的顶点数据可以在这里找到。在顶点着色器里定义一个接收纹理坐标的顶点属性:

#version 330 core
layout (location = 6) in vec3 aPos; 
layout (location = 7) in vec3 aNormal; 
layout (location = 8) in vec2 aTextCoord;

...
out vec2 texCoords;
void main()
{
    ...
    texCoords = aTextCoord;
}

  然后顶点链接方式要做出一定的调整:

        //链接顶点属性
        glVertexAttribPointer(6, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void*)0);
        glEnableVertexAttribArray(6);
        glVertexAttribPointer(7, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void*)(sizeof(float) * 3));
        glEnableVertexAttribArray(7);
        glVertexAttribPointer(8, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void*)(sizeof(float) * 6));
        glEnableVertexAttribArray(8);

  纹理坐标有了,还差纹理数据,从之前学习纹理流程来看,现在需要加载纹理图,然后把数据传递给sampler2D diffuse;

//加载纹理图
        unsigned int textureBuffer3;
        textureBuffer3 = LoadImageToGPU("container2.png", GL_RGBA, GL_RGBA, 6);

  LoadImageToGPU只是把之前在学习纹理时的加载图片流程封装成一个函数而已:

unsigned int LoadImageToGPU(const char* imageName, GLint internalFormat, GLenum format, int textureSlot) {
    unsigned int textureBuffer;
    glGenTextures(1, &textureBuffer);
    glActiveTexture(GL_TEXTURE0 + textureSlot);
    glBindTexture(GL_TEXTURE_2D, textureBuffer);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    int width, height, nrChannels;
    unsigned char *data = stbi_load(imageName, &width, &height, &nrChannels, 0);
    glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
    stbi_image_free(data);

    return textureBuffer;
}

  由于我在加载纹理图时,把数据放在了6号通道上,所以要指引sampler2D diffuse;去6号管道里拿纹理数据:shader.setInt("material.diffuse", 6);,然后记得在渲染之前激活通道,并把数据灌进通道里:

//在渲染循环里
        // bind diffuse map
        glActiveTexture(GL_TEXTURE6);
        glBindTexture(GL_TEXTURE_2D, textureBuffer3);

        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);

  现在我们就可以看看效果了:



  可以看到,纹理图上的颜色已经很好地展现在箱子的每一个顶点上,但镜面光照有点奇怪,它不应在木制材料上有如此强烈的镜面高光,回想一下,我们只是在漫反射上按纹理图做出了不一致的处理,但镜面光照仍在按照上节的思路。我们也应该为镜面光照提供镜面光贴图。

镜面光贴图


  在处理的思路上,应与漫反射贴图别无二致,我们可以自行再走一遍,提升熟练度。
  我们同样可以使用一个专门用于镜面高光的纹理贴图。这也就意味着我们需要生成一个黑白的(如果你想得话也可以是彩色的)纹理,来定义物体每部分的镜面光强度。下面是一个镜面光贴图(Specular Map)的例子:


  由于箱子大部分都由木头所组成,而且木头材质应该没有镜面高光,所以漫反射纹理的整个木头部分全部都转换成了黑色。箱子钢制边框的镜面光强度是有细微变化的,钢铁本身会比较容易受到镜面高光的影响,而裂缝则不会。
  在片段着色器上,修改材质结构体里的镜面高光变量为sampler2D类型,并通过函数完成采样:
#version 330 core
out vec4 fragColor;  

struct Material{
    sampler2D diffuse;
    sampler2D specular;

    float shininess;    
};

...

in vec2 texCoords;


void main()
{
    ...
    vec3 viewDir = normalize(viewPos - fragPos);
    vec3 reflectDir = reflect(-lightDir, normalize(Normal));
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = light.specular * spec * texture(material.specular, texCoords).rgb;

    vec3 result = ambient + diffuse + specular;

    fragColor = vec4(result, 1.0);
}

  顶点着色器提供纹理坐标,在学习漫反射贴图时就已经提供了,不再赘述。
  加载纹理图,并把纹理数据传递给sampler2D specular;

        unsigned int textureBuffer4;
        textureBuffer4 = LoadImageToGPU("container2_specular.png", GL_RGBA, GL_RGBA, 7);

  贴图会放在7号通道上:shader.setInt("material.specular", 7);,在渲染前绑定好纹理图:

        // bind diffuse map
        glActiveTexture(GL_TEXTURE6);
        glBindTexture(GL_TEXTURE_2D, textureBuffer3);

        glActiveTexture(GL_TEXTURE7);
        glBindTexture(GL_TEXTURE_2D, textureBuffer4);

        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 36);

  现在我们可以看看最终效果如何:



  可以看到现在只在铁制边框处有明显的镜面高光,效果较为真实。

相关文章

网友评论

      本文标题:OpenGL #08 Lighting maps

      本文链接:https://www.haomeiwen.com/subject/cywcbctx.html