OpenGL學(xué)習(xí)20——立方體貼圖

立方體貼圖(Cubemaps)

  • 立方體貼圖(cubemap)是一種紋理闸拿,包含來自立方體每個平面的6個獨立的2D紋理:一個紋理立方體必尼。立方體貼圖的一種有用特性就是它可以使用方向矢量進行索引/采樣愕撰。下面展示一個1X1X1單位立方體览露,棕色為方向矢量的圖示:(圖片取自書中
    立方體貼圖采樣
  • 方向矢量的大小并不重要,OpenGL通過方向矢量與紋理面的撞擊點檢索和采樣相應(yīng)紋理值丽惶。

1. 創(chuàng)建立方體貼圖

  • 立方體貼圖與其他紋理類型相似炫七,我們在操作前創(chuàng)建并綁定到相應(yīng)的紋理單元。
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
  • 因為立方體貼圖包含6個紋理蚊夫,這意味著我們需要為每個面的紋理調(diào)用glTexImage2D函數(shù)诉字。由于有6個面,OpenGL為我們提供了6個特別的紋理目標(biāo)。如下表所示:
紋理目標(biāo) 方向
GL_TEXTURE_CUBE_MAP_POSITIVE_X
GL_TEXTURE_CUBE_MAP_NEGATIVE_X
GL_TEXTURE_CUBE_MAP_POSITIVE_Y
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y
GL_TEXTURE_CUBE_MAP_POSITIVE_Z
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z
  • 上面表格的紋理目標(biāo)值與其他OpenGL枚舉值相似壤圃,值為int類型陵霉,并且線性遞增。因此我們可以從GL_TEXTURE_CUBE_MAP_POSITIVE_X開始循環(huán)所有紋理目標(biāo)伍绳。
int width, height, nrChannels;
unsigned char* data;
for(unsigned int i = 0; i < texture_faces.size(); i++)
{
    data = stb_load(texture_faces[i].c_str(), & width, &height, &nrChannels, 0);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
}
  • 為立方體貼圖指定裁剪和過濾方法踊挠。
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
  • 對于立方體貼圖,在著色器中我們需要使用另外一種取樣器類型samplerCube并使用函數(shù)texture來進行采樣冲杀。而且我們應(yīng)該使用vec3類型的方向矢量而不是vec2效床。
in vec3 textureDir;   // 方向矢量:3D紋理的坐標(biāo)
unfirom samplerCube cubemap;   // 立方體貼圖紋理取樣器

void main()
{
    FragColor = texture(cubemap, textureDir);
}

2. 天空盒(Skybox)

  • 一個天空盒就是一個包含整個場景和6張周圍環(huán)境圖像的立方體,能夠給予觀察者更大視覺空間的錯覺权谁。一般天空盒圖像具有下面圖像所展示的模式:(圖片取自書中
    天空盒紋理圖像

2.1 加載天空盒

  • 因為天空盒其實就是個立方體貼圖剩檀,因此加載與立方體貼圖相似,下面我們將加載封裝成一個函數(shù)旺芽。
unsigned int loadCubemap(std::vector<std::string> faces)
{
    unsigned int textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
    int width, height, nrChannels;
    unsigned char* data;
    for(unsigned int i = 0; i < faces.size(); i++)
    {
        unsigned char* data = stbi_load(faces[i].c_str(), & width, &height, &nrChannels, 0);
        if(data)
        {
            glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
            stbi_image_free(data);
        }
        else
        {
            std::cout << "Cubemap failed to load at path: " << faces[i] << std::endl;
            stbi_image_free(data);
        }
    }
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    return textureID;
}
  • 接下來沪猴,我們只要指定圖像路徑,就可以將天空盒加載為紋理數(shù)據(jù)進行使用采章。
std::vector<std::string> faces = {
    "right.jpg",
    "left.jpg",
    "top.jpg",
    "bottom.jpg",
    "front.jpg",
    "back.jpg"
};
unsigned int cubemapTexture = loadCubemap(faces);

2.2 顯示天空盒

  • 因為當(dāng)立方體使用立方體貼圖時可以使用本地位置作為紋理坐標(biāo)运嗜,因此我們只需提供位置矢量而無需設(shè)置紋理坐標(biāo)。要渲染天空盒我們可以定義如下頂點著色器悯舟。
#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
    TexCoords = aPos;
    gl_Position = projection * view * vec4(aPos, 1.0);
}
  • 片元著色器則從頂點著色器接收紋理位置進行紋理取樣担租。
#version 330 core
out vec4 FragColor;

in vec3 TexCoords;

uniform samplerCube skybox;

void main()
{
    FragColor = texture(skybox, TexCoords);
}
  • 要渲染天空盒,我們禁用深度測試抵怎,并將其首先在場景中進行繪制奋救。
glDepthMask(GL_FALSE);
skyboxShader.use();
// ... 設(shè)置視矩陣和投影矩陣
glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthMask(GL_TRUE);
// ... 繪制剩下的場景
  • 這時候,我們運行程序并拉近視角便贵,會出現(xiàn)如下渲染效果菠镇。


    天空盒視角問題渲染
  • 上面的渲染問題,是因為當(dāng)前的視矩陣會對天空盒的位置進行變換承璃,我們需要移除視矩陣中的平移變換,只保留旋轉(zhuǎn)蚌本。從基本光照的章節(jié)我們知道盔粹,我們從4X4的矩陣中抽取左上角的3X3矩陣就可以去除平移變換。
glm::vec4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
  • 渲染效果程癌,這時移動視角將只有立方體發(fā)送變化舷嗡。


    天空盒渲染
  • 上面的渲染我們是先繪制天空盒然后繪制立方體,這樣我們需要渲染天空盒的每個片元嵌莉。通過使用早期深度測試(early depth testing)我們可以丟棄不可見的片元提高性能进萄。但是如果最后才繪制天空盒,則會遮擋立方體,不管是在有深度測試或無的情況下中鼠。因此可婶,我們需要使用一個小技巧來騙過深度緩沖區(qū),讓它認(rèn)為天空盒擁有最大的深度值1.0援雇,所以天空盒在有物體處在它前面時都會通不過深度測試矛渴。從坐標(biāo)系統(tǒng)章節(jié)我們知道頂點著色器運行后會執(zhí)行透視除法(perspective division),將坐標(biāo)的xyz分量除以w分量惫搏,那么我們將z分量的值設(shè)為與w分量一樣具温,則透視除法后標(biāo)準(zhǔn)設(shè)備坐標(biāo)的z分量的值則為1。最后我們將深度測試的運算符設(shè)置為GL_LEQUAL筐赔。
void main()
{
    TexCoords = aPos;
    vec4 pos = projection * view * vec4(aPos, 1.0);
    gl_Position = pos.xyww;
}

3. 環(huán)境映射(Environment mapping)

  • 像上一小節(jié)那樣使用環(huán)境立方體貼圖的技術(shù)我們稱為環(huán)境映射(environment mapping)技術(shù)铣猩,其中最流行的兩種:反射和折射。

3.1 反射

  • 反射(Reflection)就是物體(或部分物體)反射周圍環(huán)境的屬性茴丰,即基于視角物體的顏色不同程度地等于環(huán)境的顏色剂习,例如鏡子就是一個反射物體。
  • 下面的圖示展示了我們?nèi)绾斡嬎惴瓷涫噶拷匣Γ⑹褂迷撌噶坎蓸恿⒎襟w貼圖鳞绕。(圖片取自書中
    反射

    從上圖可知,我們可以基于視角矢量\overline{I}繞物體法向量\color{red}{\overline{N}}來計算反射矢量\color{green}{\overline{R}}.
  • GLSL有個內(nèi)置函數(shù)reflect可用于計算反射矢量尸曼,然后我們就可以使用反射矢量\color{green}{\overline{R}}來索引/采樣立方體貼圖们何,獲取環(huán)境的顏色值,最終的渲染效果就是物體似乎反射了天空盒控轿。
  • 基于前面渲染天空盒的場景冤竹,我們修改立方體的片元著色器,為其添加反射屬性茬射。
#version 330 core
out vec4 FragColor;

in vec3 Normal;
in vec3 Position;

uniform vec3 cameraPos;
uniform samplerCube skybox;

void main()
{
    vec3 I = normalize(Position - cameraPos);
    vec3 R = reflect(I, normalize(Normal));
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
  • 調(diào)整頂點著色器鹦蠕,輸出法向量和位置變量到片元著色器。
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aNormal;

out vec3 Normal;
out vec3 Position;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    Normal = mat3(transpose(inverse(model))) * aNormal;
    Position = vec3(model * vec4(aPos, 1.0));
    gl_Position = projection * view * vec4(Position, 1.0);
}
  • 因為立方體使用到了法向量在抛,因此頂點數(shù)據(jù)原先的紋理坐標(biāo)需要替換成法向量钟病,具體可以參考前面章節(jié)的頂點數(shù)據(jù)。同時記得設(shè)置相機位置的uniform變量cameraPos刚梭。
  • 最后我們渲染綁定立方體和天空盒肠阱。
glBindVertexArray(cubeVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
  • 渲染效果。


    反射渲染

3.2 折射(Refraction)

  • 另外一種形式的環(huán)境映射稱為折射(refraction)朴读,與反射相似屹徘。折射是由于光穿過不同材質(zhì)時光發(fā)生了方向的改變。如下圖所示:(圖片取自書中
    折射

    與反射相似衅金,我們有視角矢量\color{gray}{\overline{I}}噪伊,法向量\color{red}{\overline{N}}簿煌,結(jié)果是折射矢量\color{green}{\overline{R}}
  • 使用GLSL內(nèi)置函數(shù)refract可以很容易計算折射矢量鉴吹,函數(shù)需要一個法向量姨伟,一個視角矢量和兩種材質(zhì)折射率之間的比率值。
  • 常見材質(zhì)折射率如下表所示:
材質(zhì) 折射率
空氣 1.00
1.33
1.309
玻璃 1.52
鉆石 2.42
  • 假設(shè)我們的立方體由玻璃制成拙寡,那么場景中光線由空氣進入玻璃發(fā)生折射授滓,其比率值為\frac{1.00}{1.52}=0.658
  • 上面反射的渲染中我們已經(jīng)設(shè)定了法向量肆糕,和相機位置等數(shù)據(jù)般堆,對于折射,我們只需修改片元著色器即可將屬性變更為折射诚啃。
void main()
{
    float ratio = 1.00 / 1.52;
    vec3 I = normalize(Position - cameraPos);
    vec3 R = refract(I, normalize(Normal), ratio);
    FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
  • 渲染效果淮摔。


    折射渲染
  • 注意:對于精確的物理結(jié)果,我們還需對光線離開物體時計算一次折射始赎,現(xiàn)在我們只是使用單向折射和橙。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市造垛,隨后出現(xiàn)的幾起案子魔招,更是在濱河造成了極大的恐慌,老刑警劉巖五辽,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件办斑,死亡現(xiàn)場離奇詭異,居然都是意外死亡杆逗,警方通過查閱死者的電腦和手機乡翅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來罪郊,“玉大人蠕蚜,你說我怎么就攤上這事』陂希” “怎么了靶累?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長橄维。 經(jīng)常有香客問我尺铣,道長,這世上最難降的妖魔是什么争舞? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮澈灼,結(jié)果婚禮上竞川,老公的妹妹穿的比我還像新娘店溢。我一直安慰自己,他們只是感情好委乌,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布床牧。 她就那樣靜靜地躺著,像睡著了一般遭贸。 火紅的嫁衣襯著肌膚如雪戈咳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天壕吹,我揣著相機與錄音著蛙,去河邊找鬼。 笑死耳贬,一個胖子當(dāng)著我的面吹牛踏堡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咒劲,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼顷蟆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了腐魂?” 一聲冷哼從身側(cè)響起帐偎,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛔屹,沒想到半個月后削樊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡判导,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年嫉父,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眼刃。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡绕辖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出擂红,到底是詐尸還是另有隱情仪际,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布昵骤,位于F島的核電站树碱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏变秦。R本人自食惡果不足惜成榜,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹦玫。 院中可真熱鬧赎婚,春花似錦刘绣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至撩嚼,卻和暖如春停士,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背完丽。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工恋技, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人舰涌。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓猖任,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓷耙。 傳聞我的和親對象是個殘疾皇子朱躺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內(nèi)容