美文网首页
OpenGL #07 Materials

OpenGL #07 Materials

作者: MisakiMel | 来源:发表于2019-10-27 15:07 被阅读0次

      上一节我们学习了冯氏光照模型,知道了一个物体被光照所呈现的效果由3部分组成,一是环境光照、二是漫反射光照、三是镜面光照。在现实世界中,不同的物体光照的效果都会不一样(并非颜色上的区别),例如钢比木头具有更强的反射效果,即钢的镜面光照比木头强很多,而木头则会散射很多,以至于产生一个不明显的高光点。为什么不同物体会有不同的光照效果,是因为不同的物体具有不同材质,钢铁表面光滑,而木头粗糙。为了在OpenGL中模拟不同物体有不同光照效果的情况,就要为每个物体定义一个材质的属性。

    物体的材质属性


      不同物体的材质会影响光照结果,那么在我们上节学的光照模型中,光照是由三种光照合成的,也就是说材质会影响这三种光照效果,所以材质属性也应细分为三个部分:

    • ambient 定义了在环境光照下这个物体反射的是什么颜色,这一般是和物体颜色相同的颜色。
    • diffuse 定义了在漫反射光照下这个物体反射的是什么颜色。(和环境光照一样)
    • specular 定义在镜面光照下物体该反射的颜色(一般是一个普通亮度的白光,至可能反射一个物体特定的镜面高光颜色)
        这三个部分别对这三种光照造成不同的影响,最后再合成为真正的光照效果,这样,我们就能够对物体的颜色输出有着精细的控制了。
    #version 330 core
    struct Material {
        vec3 ambient;
        vec3 diffuse;
        vec3 specular;
        float shininess;
    }; 
    
    uniform Material material;
    

      除此之外,我们再添加反光度(shininess)这个分量到材质属性中,它影响的是镜面高光的散射半径,即我们上节提到的那个幂次方,它提供进行幂运算的因子。
      如果我们可以正确地提供这四个数据,就可以正确地指定一个物体的材质,那么我们对这个物体的感知也不同了。为一个物体赋予一款合适的材质是非常困难的,这需要大量实验和丰富的经验,所以由于不合适的材质而毁了物体的视觉质量是件经常发生的事。下面的图片展示了几种现实世界的材质对我们的立方体的影响:


    设置材质


      在上节中,我们仅仅提供一个物体的颜色objectColor当作物体的材质属性,即在计算了所有光照结果后,叠加,再与物体颜色相乘得到真正的光照效果。而定义了材质属性后,我们可以在为每一种光照提供不同的颜色,例如我们可以在计算环境光照和慢反射光照时提供物体真正的颜色,而在计算镜面光照时提供中等亮度的白光。这样在不同的光照的效果会更明显。

    void main()
    {
        vec3 ambient =  lightColor * material.ambient;
    
        vec3 lightDir = normalize(lightPos - fragPos);
        float diff = max(dot(lightDir, normalize(Normal)),0.0);
        vec3 diffuse = lightColor * diff * material.diffuse;
    
        vec3 viewDir = normalize(viewPos - fragPos);
        vec3 reflectDir = reflect(-lightDir, normalize(Normal));
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
        vec3 specular = lightColor * spec *  material.specular;
    
        vec3 result = ambient + diffuse + specular;
    
        fragColor = vec4(result, 1.0);
    }
    

      物体的每个材质属性都乘上了它们对应的光照分量,现在就只剩下在程序中传递材质属性了:

            shader.setVec3("material.ambient", 1.0f, 0.5f, 0.31f);
            shader.setVec3("material.diffuse", 1.0f, 0.5f, 0.31f);
            shader.setVec3("material.specular", 0.5f, 0.5f, 0.5f);
            shader.setFloat("material.shininess", 32.0f);
    

      如我们之前对这3个材质属性的定义一样,我们将该物体在环境光照和漫反射光照下反射的颜色设置为物体本身的颜色,而在镜面光照下反射的是中等亮度的白光。反光度设置得与上节一样,32次方。然后可以看看效果如何:



      可以看到这正方体似乎有点太亮了,尤其是高光点处,且在一些没被光源照到的地方依旧有明亮的颜色反射,似乎不太合乎我们的期望。

    光的属性


      如果你有去翻看上节的代码,那么你可能知道缺少了什么东西了,我们在上节的代码中为了凸显环境光照的效果,在计算环境光照的时候乘了一个环境光照因子,且设为了0.1,这样物体在没受到光照的部分就呈现较暗的效果,。在计算镜面光照的时候,多乘了一个0.5,这样得到的镜面光分量就比较合理,不会反射过亮的白光。而在上面的计算中,这两个因子都没了,所以导致环境光、漫反射和镜面光这三个颜色对任何一个光源(这里是lightColor)都会去全力反射,物体就太亮了。而我们设置的lightColor就是vec3(1.0)白光,代码就亦如这样:

    vec3 ambient  = vec3(1.0) * material.ambient;
    vec3 diffuse  = vec3(1.0) * (diff * material.diffuse);
    vec3 specular = vec3(1.0) * (spec * material.specular);
    

      显然这是不应该的,我们应该为不同的光照效果设置不同强度的光照。也就是说,我们可以像材质之于物体,创建一个结构体记录不同光照效果各自的强度,如环境光照通常会设置为一个比较低的强度。我们可以称呼这个结构体就是光源(属性之于光源),但与我们现实世界的光源有着不同的理解。这个光源包含的的是不同光照情况下的光强信息的集合,而非现实世界中的一个独立个体光源。

    struct Light {
        vec3 position;
    
        vec3 ambient;
        vec3 diffuse;
        vec3 specular;
    };
    
    • position 光源的位置,这个是为了计算漫反射分量的,与环境光照无关。
    • ambient 计算环境光照时提供的光强,应该是一个较低的强度
    • diffuse 光源的漫反射分量通常设置为光所具有的颜色,通常是一个比较明亮的白色
    • specular 通常保持为vec3(1.0f),在计算镜面光照时,光源应该提供最大强度的光照
        有了光源结构体,我们就可以在计算不同情况的光照时提供合适的光强:
    #version 330 core
    out vec4 fragColor;  
    
    struct Material{
        vec3 ambient;
        vec3 diffuse;
        vec3 specular;
    
        float shininess;
    };
    struct Light {
        vec3 position;
    
        vec3 ambient;
        vec3 diffuse;
        vec3 specular;
    };
    
    uniform vec3 viewPos;
    uniform Material material;
    uniform Light light;
    
    in vec3 Normal;
    in vec3 fragPos;
    
    void main()
    {
        vec3 ambient = light.ambient * material.ambient;
    
        vec3 lightDir = normalize(light.position - fragPos);
        float diff = max(dot(lightDir, normalize(Normal)),0.0);
        vec3 diffuse = light.diffuse * diff * material.diffuse;
    
        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 * material.specular;
    
        vec3 result = ambient + diffuse + specular;
    
        fragColor = vec4(result, 1.0);
    }
    

      然后在程序中传递光源信息给light

            shader.setVec3("light.ambient", 0.2f, 0.2f, 0.2f);
            shader.setVec3("light.diffuse", 0.5f, 0.5f, 0.5f); // 将光照调暗了一些以搭配场景
            shader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
            shader.setVec3("light.position", 1.2f, 1.0f, 0);
    

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



      可以看到亮度和颜色比刚才正常了。

    练习


      我们或许可以令光源发出不同颜色的光,看被照射的物体会成呈现什么颜色,我们可以令颜色值做一个随着时间的的正弦变化,然后把该颜色值传给光源结构体,可用glfwGetTime()获取程序的运行时间:

            glm::vec3 lightColor;
            lightColor.x = sin(glfwGetTime() * 2.0f);
            lightColor.y = sin(glfwGetTime() * 0.7f);
            lightColor.z = sin(glfwGetTime() * 1.3f);
    

      记住要放在渲染循环里,不然就不能实时获取到程序的运行时间了,颜色也就不会变化了。然后把这个颜色传给光源的环境光照分量和漫反射分量,而因为镜面高光一般都是白光,所以就镜面光照分量保持白光:

            shader.setVec3("light.ambient", lightColor * glm::vec3(0.2f));
            shader.setVec3("light.diffuse", lightColor * glm::vec3(0.5f)); // 将光照调暗了一些以搭配场景
            shader.setVec3("light.specular", 1.0f, 1.0f, 1.0f);
            shader.setVec3("light.position", 1.2f, 1.0f, 0);
    
      现在我们就可以看到一个颜色动态变化的光源了:

      这个效果与我们上节讨论颜色时得出的结果一致:我们能看到一个物体呈现什么颜色,取决的不是这个物体真正的颜色,而是其所反射的颜色。而这反射的颜色受物体颜色和光源颜色的影响。

    相关文章

      网友评论

          本文标题:OpenGL #07 Materials

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