美文网首页
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