OpenGL學習: 光照系列2-材質和lighting maps使用

上一節學習了Phong reflection model,但是還存在不足,本節使用材質屬性,以及使用diffuse map和specular map改善上一節的實現。

本節內容整理自: 
1.www.learnopengl.com Materials 
2.www.learnopengl.com Lighting maps

通過本節可以瞭解到

  • 使用材質屬性使不同物體對光有不同反映

  • 對光源的不同部分使用不同的強度

  • 使用diffuse map和specular map使物體不同部分對光有不同反映

材質屬性-不同物體對光有不同反映

現實世界中,不同的物體對光有不同的反映,例如鋼做成的物體通常比土製的花瓶看起來更亮,木質的容器和鋼做成的容器對光的反應也不一樣。對於鏡面反射光,不同的物體接受光照後,高光部分的半徑大小也不一樣。要模擬不同的物體接受光照後的效果,就需要考慮物體的材質屬性,利用材質屬性模擬出不同的效果。 
上一節我們使用了環境光(ambient),漫反射光(diffuse),鏡面反射光(specular)三種成分實現的Phong reflection mode。爲物體指定材質屬性時,也可以爲物體指定這三個不同成分的光的強度作爲材質屬性,同時加上高光係數shininess,整個材質在片元着色器中定義爲如下所示的結構體:

// 材質屬性結構體
struct MaterialAttr
{
    vec3 ambient;   // 環境光
    vec3 diffuse;    // 漫反射光
    vec3 specular;   // 鏡面光
    float shininess; //鏡面高光係數
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

利用上述4個屬性來定義物體的材質,我們可以模擬出很多現實世界的物體,如何配出具有真實效果的物體光照效果需要豐富的經驗,同時也要使用更復雜的模型,後面我們會學習如何加載模型。通過加載豐富的模型,配上光照後,將會更接近現實。

利用上面的材質,我們實現Phone reflection model的片元着色器代碼變爲:

void main()
{    
    // 環境光成分
    vec3 ambient = lightColor * material.ambient;

    // 漫反射光成分
    vec3 norm = normalize(Normal);
    vec3 lightDir = normalize(lightPos - FragPos);
    float diff = max(dot(norm, lightDir), 0.0);
    vec3 diffuse = lightColor * (diff * material.diffuse);

     // 鏡面光成分
     vec3 viewDir = normalize(viewPos - FragPos);
     vec3 reflectDir = reflect(-lightDir, norm);  
     float spec = pow(max(dot(viewDir, 
        reflectDir), 0.0), material.shininess);
     vec3 specular = lightColor * (spec * material.specular);  
     vec3 result = ambient + diffuse + specular;
     color = vec4(result, 1.0f);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

在主程序中設置材質結構體的每個屬性,使用結構體變量的分量來設置。例如定義材質結構體變量爲:

uniform MaterialAttr material;
  • 1

則使用分量來設置屬性各個部分的實現爲:

       // 設置材料光照屬性
        GLint objectAmbientLoc = glGetUniformLocation(shader.programId, "material.ambient");
        GLint objectDiffuseLoc = glGetUniformLocation(shader.programId, "material.diffuse");
        GLint objectSpecularLoc = glGetUniformLocation(shader.programId, "material.specular");
        GLint objectShininessLoc = glGetUniformLocation(shader.programId, "material.shininess");
        glUniform3f(objectAmbientLoc, 1.0f, 0.5f, 0.31f);
        glUniform3f(objectDiffuseLoc, 1.0f, 0.5f, 0.31f);
        glUniform3f(objectSpecularLoc, 0.5f, 0.5f, 0.5f);
        glUniform1f(objectShininessLoc, 32.0f);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

將lightColor設置爲(1.0,1.0,1.0),則執行程序,我們得到的效果如下:

沒有考慮光各個成分強度

可以看到上面圖中的物體太亮了,主要原因是這裏沒有像上一節一樣使用ambientStrength,specularStrength等參數調節光源不同成分的強度,導致各個成分疊加到一起後,強度太大,因此十分明亮。要爲光源不同成分指定不同的強度,這個類似於爲物體指定材質的做法,我們定義光源結構體爲:

// 光源屬性結構體
struct LightAttr
{
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在程序中設置光源屬性,同理可以通過結構體變量的分量來設置,如下:

  GLint lightAmbientLoc = glGetUniformLocation(shader.programId, "light.ambient");
        GLint lightDiffuseLoc = glGetUniformLocation(shader.programId, "light.diffuse");
        GLint lightSpecularLoc = glGetUniformLocation(shader.programId, "light.specular");
        GLint lightPosLoc = glGetUniformLocation(shader.programId, "light.position");
        glUniform3f(lightAmbientLoc, 0.2f, 0.2f, 0.2f);
        glUniform3f(lightDiffuseLoc, 0.5f, 0.5f, 0.5f);
        glUniform3f(lightSpecularLoc, 1.0f, 1.0f, 1.0f);
        glUniform3f(lightPosLoc, lampPos.x, lampPos.y, lampPos.z);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

其中lampPos是事先指定的光源位置,例如定義爲:

   glm::vec3 lampPos(0.8f, 0.8f, 0.5f);
  • 1

更改後的效果爲: 
使用材質後的光照

這個效果同上一節的沒有什麼大的區別,但是我們爲光源的部分成分指定了不同的強度,這代替了上一節在着色器中指定的ambientStrength,specularStrength等參數;同時爲物體使用了材質屬性,更加靈活地控制物體接受光照時的效果。

如果控制光源的屬性隨着時間改變,我們可以得到好玩的效果如下圖所示: 
變化的光源屬性

變換光源可以使用glfwGetTime函數來實現如下:

// 隨時間變化的光源屬性
glm::vec3 lightColor;
lightColor.x = sin(glfwGetTime() * 2.0f);
lightColor.y = sin(glfwGetTime() * 0.7f);
lightColor.z = sin(glfwGetTime() * 1.3f);
glm::vec3 diffuseColor = lightColor   * glm::vec3(0.5f); // 適當減小影響
glm::vec3 ambientColor = diffuseColor * glm::vec3(0.2f);
glUniform3f(lightAmbientLoc, ambientColor.x, ambientColor.y, ambientColor.z);
glUniform3f(lightDiffuseLoc, diffuseColor.x, diffuseColor.y, diffuseColor.z);
glUniform3f(lightSpecularLoc, 1.0f, 1.0f, 1.0f);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

light maps-物體的不同部分對光有不同反映

上面提到了不同的物體對光有不同的反映,實際上同一物體的部分往往不是同一種材料構成的,例如汽車的車身噴漆了往往很光亮,而輪胎的橡膠部分則比較暗淡。爲了更好的模擬現實中物體接受光照效果,我們應該爲物體的不同部分指定不同的材質屬性,而不是整個物體共用一個材質屬性。

不同的部分對應不同的材質屬性,這個有點類似於根據紋理座標獲取不同的紋素,我們這裏的做法同紋理座標類似,不過這裏的做法取名爲light maps。也就是爲物體的不同部分指定不同的座標,根據這個座標獲取從light map獲取不同的材質屬性,對應漫反射有diffuse map,鏡面反射有specular map。當然還包括其他類型的light map,這裏只學習diffuse map和specular map。

使用diffuse map

要使用diffuse map,同紋理座標一樣,我們需要在頂點屬性中添加紋理座標,定義頂點屬性如下:

   // 指定頂點屬性數據 頂點位置 紋理 法向量
    GLfloat vertices[] = {
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f,1.0f,    // A
        0.5f, -0.5f, 0.5f, 1.0f, 0.0f,  0.0f, 0.0f, 1.0f,   // B
        0.5f, 0.5f, 0.5f,  1.0f, 1.0f,   0.0f, 0.0f, 1.0f,  // C
        0.5f, 0.5f, 0.5f,  1.0f, 1.0f,   0.0f, 0.0f, 1.0f,  // C
        -0.5f, 0.5f, 0.5f,  0.0f, 1.0f,  0.0f, 0.0f, 1.0f,  // D
        -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,   // A
        ...省略
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

同時要加載紋理作爲diffuse map,如下:

   GLint diffuseMap = TextureHelper::load2DTexture("../../resources/textures/container_diffuse.png");
    shader.use();
    glUniform1i(glGetUniformLocation(shader.programId, "material.diffuseMap"), 0);
  • 1
  • 2
  • 3

這裏使用了封裝的加載和建立紋理對象的方法 TextureHelper::load2DTexture,關於如何使用紋理,在前面二維紋理映射一節已經介紹過了,如果不清楚,可以回過頭去查看。

在着色器中,重新定義材質屬性爲:

   // 材質屬性結構體
struct MaterialAttr
{
    sampler2D diffuseMap;// 根據位置取不同的材質屬性
    sampler2D specularMap;
    float shininess; //鏡面高光係數
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

物體材質部分,環境光一般和漫反射光相同,只是強度不同,因此計算環境光和漫反射光都使用diffuse map,計算如下:

   // 環境光成分
    vec3    ambient = light.ambient * vec3(texture(material.diffuseMap, TextCoord));

    // 漫反射光成分 此時需要光線方向爲指向光源
    vec3    lightDir = normalize(light.position - FragPos);
    vec3    normal = normalize(FragNormal);
    float   diffFactor = max(dot(lightDir, normal), 0.0);
    vec3    diffuse = diffFactor * light.diffuse * vec3(texture(material.diffuseMap, TextCoord));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

在上面的代碼中,我們通過在材質屬性中定義紋理對象sampler2D,然後使用紋理對象material.diffuseMap來計算環境光和漫反射光成分。如果對鏡面反射光不使用specular map,我們得到的效果如下:

只使用diffuse map

上面的圖中,使用的diffuse map是一個木製的容器,在圖中我們看到高光部分很明顯,但是對於木製容器,對於鏡面光的反映應該不會這麼大,因此我們通過specular map需要調整圖中鏡面光部分。

使用specular map

假若我們想要一個由鋼做成的邊架的木製容器,當接收鏡面光時,肯定是鋼架子很亮,而木製部分的鏡面光比較弱,要達到這樣一種效果,我們可以爲鋼架子部分指定較強的強度,而木製部分強度較弱。如果通過手動來設置肯定很麻煩了,同diffuse map一樣,我們也是用類似的技術處理,這就是specular map。

一般而言鏡面光的light map中,表示強度大可以用接近白色的顏色表示,強度弱則使用接近黑色的顏色表示。最終我們的specular map用下圖來表示(來自www.learnopengl.com light maps):

specular map

在主程序中加載diffuse map和specular map,如下:

   // Section3 準備diffuseMap和specularMap
    GLint diffuseMap = TextureHelper::load2DTexture("../../resources/textures/container_diffuse.png");
    GLint specularMap = TextureHelper::load2DTexture("../../resources/textures/container_specular.png");
    shader.use();
    glUniform1i(glGetUniformLocation(shader.programId, "material.diffuseMap"), 0);
    glUniform1i(glGetUniformLocation(shader.programId, "material.specularMap"), 1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在着色器中使用diffuse map和specular map完成光照計算過程爲:

   void main()
{   
    // 環境光成分
    vec3    ambient = light.ambient * vec3(texture(material.diffuseMap, TextCoord));
    // 漫反射光成分 此時需要光線方向爲指向光源
    vec3    lightDir = normalize(light.position - FragPos);
    vec3    normal = normalize(FragNormal);
    float   diffFactor = max(dot(lightDir, normal), 0.0);
    vec3    diffuse = diffFactor * light.diffuse * vec3(texture(material.diffuseMap, TextCoord));
    // 鏡面反射成分 此時需要光線方向爲由光源指出
    float   specularStrength = 0.5f;
    vec3    reflectDir = normalize(reflect(-lightDir, normal));
    vec3    viewDir = normalize(viewPos - FragPos);
    float   specFactor = pow(max(dot(reflectDir, viewDir), 0.0), material.shininess);
    vec3    specular = specFactor * light.specular * vec3(texture(material.specularMap, TextCoord));
    vec3    result = ambient + diffuse + specular;
    color   = vec4(result , 1.0f);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

最終的效果如下: 
specular map 
當移動物體時,只有鋼架子部分對鏡面光反應比較明顯,而木製部分則仍然很暗淡。

最後的說明

本節通過使用材質屬性,模擬不同物體對光有不同反映;使用diffuse map和specular map,模擬物體的不同部分對光有不同反映;同時爲光源的三個光成分指定了不同的強度,避免了三個光成分和物體疊加計算後,光照太亮的不真實效果。下一節將會介紹不同的光源,以及使用多個光源的方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章