1. 程式人生 > >Learn OpenGL (十):材質

Learn OpenGL (十):材質

在上一節中,我們指定了一個物體和光的顏色,以及結合環境光和鏡面強度分量,來定義物體的視覺輸出。當描述一個物體的時候,我們可以用這三個分量來定義一個材質顏色(Material Color):環境光照(Ambient Lighting)、漫反射光照(Diffuse Lighting)和鏡面光照(Specular Lighting)。通過為每個分量指定一個顏色,我們就能夠對物體的顏色輸出有著精細的控制了。現在,我們再新增反光度(Shininess)這個分量到上述的三個顏色中,這就有我們需要的所有材質屬性了:


#version 330 core
struct Material {
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
    float shininess;
}; 

uniform Material material;

在片段著色器中,我們建立一個結構體(Struct)來儲存物體的材質屬性。我們也可以把它們儲存為獨立的uniform值,但是作為一個結構體來儲存會更有條理一些。我們首先定義結構體的佈局(Layout),然後使用剛建立的結構體為型別,簡單地宣告一個uniform變數。

你可以看到,我們為每個馮氏光照模型的分量都定義一個顏色向量。ambient材質向量定義了在環境光照下這個物體反射得是什麼顏色,通常這是和物體顏色相同的顏色。diffuse材質向量定義了在漫反射光照下物體的顏色。(和環境光照一樣)漫反射顏色也要設定為我們需要的物體顏色。specular材質向量設定的是鏡面光照對物體的顏色影響(或者甚至可能反射一個物體特定的鏡面高光顏色)。最後,shininess影響鏡面高光的散射/半徑。

這四個元素定義了一個物體的材質,通過它們我們能夠模擬很多現實世界中的材質。devernay.free.fr上的一個表格展示了幾種材質屬性,它們模擬了現實世界中的真實材質。下面的圖片展示了幾種現實世界的材質對我們的立方體的影響:

設定材質

我們在片段著色器中建立了一個材質結構體的uniform,所以下面我們希望修改一下光照的計算來順應新的材質屬性。由於所有材質變數都儲存在結構體中,我們可以從uniform變數material中訪問它們:

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;
    FragColor = vec4(result, 1.0);
}

可以看到,我們現在在需要的地方訪問了材質結構體中的所有屬性,並且這次是根據材質的顏色來計算最終的輸出顏色的。物體的每個材質屬性都乘上了它們對應的光照分量。

我們現在可以在程式中設定適當的uniform,對物體設定材質了。GLSL中的結構體在設定uniform時並沒有什麼特別之處。結構體只是作為uniform變數的一個封裝,所以如果想填充這個結構體的話,我們仍需要對每個單獨的uniform進行設定,但這次要帶上結構體名的字首:

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

我們將環境光和漫反射分量設定成我們想要讓物體所擁有的顏色,而將鏡面分量設定為一箇中等亮度的顏色,我們不希望鏡面分量在這個物體上過於強烈。我們將反光度保持為32。現在我們能夠程式中非常容易地修改物體的材質了。

執行程式,你應該會得到下面這樣的結果:

但它看起來很奇怪不是嗎?

光的屬性

這個物體太亮了。物體過亮的原因是環境光、漫反射和鏡面光這三個顏色對任何一個光源都會去全力反射。光源對環境光、漫反射和鏡面光分量也具有著不同的強度。前面的教程,我們通過使用一個強度值改變環境光和鏡面光強度的方式解決了這個問題。我們想做一個類似的系統,但是這次是要為每個光照分量都指定一個強度向量。如果我們假設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);

所以物體的每個材質屬性對每一個光照分量都返回了最大的強度。對單個光源來說,這些vec3(1.0)值同樣可以分別改變,而這通常就是我們想要的。現在,物體的環境光分量完全地影響了立方體的顏色,可是環境光分量實際上不應該對最終的顏色有這麼大的影響,所以我們會將光源的環境光強度設定為一個小一點的值,從而限制環境光顏色:

vec3 ambient = vec3(0.1) * material.ambient;

我們可以用同樣的方式修改光源的漫反射和鏡面光強度。這和我們在上一節中所做的極為相似,你可以說我們已經建立了一些光照屬性來影響每個單獨的光照分量。我們希望為光照屬性建立一個與材質結構體類似的結構體:

struct Light {
    vec3 position;

    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

uniform Light light;

一個光源對它的ambient、diffuse和specular光照有著不同的強度。環境光照通常會設定為一個比較低的強度,因為我們不希望環境光顏色太過顯眼。光源的漫反射分量通常設定為光所具有的顏色,通常是一個比較明亮的白色。鏡面光分量通常會保持為vec3(1.0),以最大強度發光。注意我們也將光源的位置新增到了結構體中。

和材質uniform一樣,我們需要更新片段著色器:

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

我們接下來在程式中設定光照強度:

lightingShader.setVec3("light.ambient",  0.2f, 0.2f, 0.2f);
lightingShader.setVec3("light.diffuse",  0.5f, 0.5f, 0.5f); // 將光照調暗了一些以搭配場景
lightingShader.setVec3("light.specular", 1.0f, 1.0f, 1.0f); 

現在我們調整了光照對物體材質的影響,我們應該能得到一個更類似於上一節的視覺效果。但這次我們有了對光照和物體材質的完全掌控: