上一節初步學習了使用cubeMap創建天空包圍盒,本節繼續深入Cubemap這個主題,學習環境紋理貼圖。
本節內容整理自: 1.www.learnopengl.com cubemaps
環境紋理貼圖
同上一節的Cubemap創建天空包圍盒有些類似,創建環境紋理貼圖也是對當前待渲染物體,從包圍的環境紋理上採樣作爲這個物體的紋理而渲染出的逼真效果。本節介紹環境紋理貼圖主要的方式包括:reflection(反射貼圖)和refraction(折射貼圖)。
Reflection 反射貼圖
在上一節cubemaps中,我們提到對立方體紋理進行採樣,需要使用3維向量(s,t,r),而當立方體中心處於原點時,立方體的頂點位置就可以作爲這個採樣的座標。對於反射貼圖,我們也同樣需要一個紋理座標,不過這個向量的計算過程如下圖所示(來自 www.learnopengl.com):
圖中向量I表示觀察向量,注意它從觀察者位置指出,N表示頂點對應的法向量,而計算出來的反射向量R則作爲從Cubemap採樣的向量。在光照基礎,一節我們已經見過使用reflect函數計算反射向量了,這裏再次說明下。我們計算向量的過程都可以在世界座標系或者相機座標系,只要統一一個座標系即可。這裏我們使用世界座標系,在頂點着色器中,實現爲:
#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
out vec3 FragNormal;
out vec3 FragPos;
void main()
{
gl_Position = projection * view * model * vec4(position, 1.0);
FragPos = vec3(model * vec4(position, 1.0)); // 在世界座標系中指定
mat3 normalMatrix = mat3(transpose(inverse(model)));
FragNormal = normalMatrix * normal; // 計算法向量經過模型變換後值
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
上面在世界座標系中計算轉換後的法向量時使用了公式:
FragPos=vec3(model∗vec4(position,1.0));(變換後頂點位置)
FragNormal=mat3(transpose(inverse(model)))∗normal(變換後法向量)
對於公式不熟悉的,可以回過頭去參考光照基礎一節。
在片元着色器中實現爲:
#version 330 core
in vec3 FragNormal;
in vec3 FragPos;
uniform samplerCube envText; // 環境紋理
uniform vec3 cameraPos;
out vec4 color;
void main()
{
vec3 viewDir = normalize(FragPos - cameraPos); // 注意這裏向量從觀察者位置指出
vec3 reflectDir = reflect(viewDir, normalize(FragNormal));
color = texture(envText, reflectDir); // 使用反射向量採樣環境紋理
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
注意上面計算過程中向量的方向和單位化。這裏輸入的環境紋理依然是我們的天空包圍盒cubemap紋理。繪製上一節的立方體,使用反射貼圖得到的效果如下圖所示:
這個貼圖得到的效果是,立方體的表面反射包圍盒的紋理。
如果使用球體模型的話,則能得到更逼真的效果:
這個球體模型,可以從我的github下載。
Refraction 折射貼圖
與反射相對應,當光從一種材質進入另一種材質時將會發生折射,滿足折射定律。這裏我們採用折射後得到的向量,作爲採樣紋理的向量,計算如下圖所示(來自
www.learnopengl.com):
這裏的I爲入射向量,從觀察者位置指出,N仍然是法向量,而得到的折射向量R作爲採樣紋理的向量。可以看出當光從空氣進入水中時,發生了折射現象,折射向量R與原始的入射光線I發生了偏離。
在計算折射向量時,需要使用到折射率,這個參數,從一種材質進入另一種材質,實際計算時使用兩種材質的折射率的比例。實現refraction效果是,頂點着色器部分與上面相同,片元着色器需要修改,計算折射向量,如下:
#version 330 core
in vec3 FragNormal;
in vec3 FragPos;
uniform samplerCube envText; // 環境紋理
uniform vec3 cameraPos;
out vec4 color;
void main()
{
float indexRation = 1.00 / 1.52;
vec3 viewDir = normalize(FragPos - cameraPos); // 注意這裏向量從觀察者位置指出
vec3 refractDir = refract(viewDir, normalize(FragNormal), indexRation);
color = texture(envText, refractDir); // 使用 折射向量 採樣環境紋理
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
上面着色器中,refractive的第三個參數是折射率的比例,這裏我們模擬的是從空氣進入玻璃。繪製上面的立方體,得到的效果像是透明玻璃:
環境紋理貼圖和模型加載
在前面AssImp紋理加載一節,我們使用AssImp庫加載了一個納米戰鬥服模型,這裏對模型使用反射環境貼圖加以改進。之前使用的是diffuse map 和specular map,這裏將使用的方法稱之爲reflection map。通過加載模型中的reflection map,決定渲染的物體中哪部分需要做反射環境貼圖以及環境貼圖的強度係數,而不是像上面球體那樣,整個執行反射環境貼圖。
利用AssImp加載Reflection map,遇到的麻煩在於,AssImp對Reflection map默認支持不好,因此這裏使用的技巧是修改obj資源和代碼,做出調整。你可以從我的github下載,修改後的模型。
在資源中使用aiTextureType_AMBIENT作爲reflection map,在model.h代碼中添加處理:
// 獲取Reflection 注意: AssImp對Reflection支持不好 所以這裏採用ambient_map
// 除了這裏的代碼 還需要修改對應的obj文件
std::vector<Texture> reflectionTexture;
this->processMaterial(materialPtr, sceneObjPtr, aiTextureType_AMBIENT, reflectionTexture);
textures.insert(textures.end(), reflectionTexture.begin(), reflectionTexture.end());
- 1
- 2
- 3
- 4
- 5
同時在mesh.h中也要添加相應的處理,在片元着色器中修改爲:
#version 330 core
in vec3 FragNormal;
in vec3 FragPos;
in vec2 TextCoord;
uniform samplerCube envText; // 環境紋理
uniform sampler2D texture_diffuse0;
uniform sampler2D specular_diffuse0;
uniform sampler2D texture_reflection0; // 反射map
uniform vec3 cameraPos;
out vec4 color;
void main()
{
vec4 diffuseColor = texture(texture_diffuse0, TextCoord);
float relefctIntensity = texture(texture_reflection0, TextCoord).r;
vec4 reflectColor = vec4(0.0, 0.0, 0.0, 0.0);
if(relefctIntensity > 0.1) // 決定是否開啓
{
vec3 viewDir = normalize(FragPos - cameraPos);
vec3 reflectDir = reflect(viewDir, normalize(FragNormal));
reflectColor = texture(envText, reflectDir) * relefctIntensity; // 使用反射向量採樣環境紋理 使用強度係數控制
}
color = diffuseColor + reflectColor;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
這裏通過讀取reflection map,採樣紋理後,獲取一個強度係數,根據強度係數來決定是否開啓refleciton map,如果開啓則輸出顏色爲diffuse map和reflection map的混合結果。
渲染時只輸出diffuse map或者reflection map,以及最終得到的效果,對比如下圖所示:
對頭部進行放大,我們看到了反射貼圖的效果:
反射貼圖爲模型的表面增加了環境成分,顯得更加逼真,而且模型在移動過程中,貼圖能夠動態變換,反映位置的改變。
最後的說明
在實現本節內容過程中,需要注意,使用反射或者折射時需要提供物體表面法向量,不僅需要修改頂點屬性數據,還要修改對應的頂點着色器,如果修改錯誤,可能產生錯誤效果如下圖:
從圖中結果,可以看到渲染基本的圖形出錯,可以立馬聯想到頂點屬性數據配置出錯。
同時在加載模型,使用reflection map時,天空包圍盒使用的紋理單元,要更新爲3, 因爲前面加載了其他的specular map、diffuse map、reflection map:
glActiveTexture(GL_TEXTURE3);
glBindTexture(GL_TEXTURE_CUBE_MAP, skybox.getTextId());
glUniform1i(glGetUniformLocation(shader.programId, "envText"), 3);
- 1
- 2
- 3
如果沒有更新這個配置,導致天空包圍盒的紋理單元被其他紋理佔用,將發生錯誤,例如可能的錯誤結果如下:
關於環境紋理貼圖,還有一項技術稱爲dynamic environment mapping。通過藉助framebuffer,爲每個物體渲染6個包含場景中其他物體的紋理,從而構建一個環境紋理,然後實行貼圖。這種動態貼圖方式,由於在framebuffer中要爲每個物體執行6次場景渲染,在保持較好性能開銷下使用它需要很多技巧,沒有這裏介紹的天空包圍盒這麼容易使用。在後面時間充足時,可以學習下這個技術。