LearnOpenGL->立方体贴图
立方體貼圖
立方體貼圖
在本節(jié)中,我們將討論的是將多個紋理組合起來映射到一張紋理上的一種紋理類型:立方體貼圖(Cube Map)。
簡單來說,立方體貼圖就是一個包含了6個2D紋理的紋理,每個2D紋理都組成了立方體的一個面:一個有紋理的立方體。你可能會奇怪,這樣一個立方體有什么用途呢?為什么要把6張紋理合并到一張紋理中,而不是直接使用6個單獨的紋理呢?立方體貼圖有一個非常有用的特性,它可以通過一個方向向量來進行索引/采樣。假設(shè)我們有一個1x1x1的單位立方體,方向向量的原點位于它的中心。使用一個橘黃色的方向向量來從立方體貼圖上采樣一個紋理值會像是這樣:
方向向量的大小并不重要,只要提供了方向,OpenGL就會獲取方向向量(最終)所擊中的紋素,并返回對應(yīng)的采樣紋理值。
如果我們假設(shè)將這樣的立方體貼圖應(yīng)用到一個立方體上,采樣立方體貼圖所使用的方向向量將和立方體(插值的)頂點位置非常相像。這樣子,只要立方體的中心位于原點,我們就能使用立方體的實際位置向量來對立方體貼圖進行采樣了。接下來,我們可以將所有頂點的紋理坐標(biāo)當(dāng)做是立方體的頂點位置。最終得到的結(jié)果就是可以訪問立方體貼圖上正確面(Face)紋理的一個紋理坐標(biāo)。
【在立方體貼圖中,我們不需要傳入一個紋理的坐標(biāo)采用,可以直接利用傳入的位置向量來對傳入的立方體貼圖進行采樣】
如果我們假設(shè)將這樣的立方體貼圖應(yīng)用到一個立方體上,采樣立方體貼圖所使用的方向向量將和立方體(插值的)頂點位置非常相像。這樣子,只要立方體的中心位于原點,我們就能使用立方體的實際位置向量來對立方體貼圖進行采樣了。接下來,我們可以將所有頂點的紋理坐標(biāo)當(dāng)做是立方體的頂點位置。最終得到的結(jié)果就是可以訪問立方體貼圖上正確面(Face)紋理的一個紋理坐標(biāo)。
創(chuàng)建立方體貼圖
立方體貼圖是和其它紋理一樣的,所以如果想創(chuàng)建一個立方體貼圖的話,我們需要生成一個紋理,并將其綁定到紋理目標(biāo)上,之后再做其它的紋理操作。這次要綁定到GL_TEXTURE_CUBE_MAP:
unsignedint textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);因為立方體貼圖包含有6個紋理,每個面一個,我們需要調(diào)用glTexImage2D函數(shù)6次,參數(shù)和之前教程中很類似。但這一次我們將紋理目標(biāo)(target)參數(shù)設(shè)置為立方體貼圖的一個特定的面,告訴OpenGL我們在對立方體貼圖的哪一個面創(chuàng)建紋理。這就意味著我們需要對立方體貼圖的每一個面都調(diào)用一次glTexImage2D。
由于我們有6個面,OpenGL給我們提供了6個特殊的紋理目標(biāo),專門對應(yīng)立方體貼圖的一個面。
紋理目標(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 | 前 |
和OpenGL的很多枚舉(Enum)一樣,它們背后的int值是線性遞增的,所以如果我們有一個紋理位置的數(shù)組或者vector,我們就可以從GL_TEXTURE_CUBE_MAP_POSITIVE_X開始遍歷它們,在每個迭代中對枚舉值加1,遍歷了整個紋理目標(biāo):
【也就是說我們可以以GL_TEXTURE_CUBE_MAP_POSITION_X+i的形式分別對六個面進行傳入】;
int width, height, nrChannels; unsignedchar *data; for(unsignedint i = 0; i < textures_faces.size(); i++) {data = stbi_load(textures_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);//這部分為修改的部分,此函數(shù)傳入的是立方體貼圖的相關(guān)數(shù)據(jù); }這里我們有一個叫做textures_faces的vector,它包含了立方體貼圖所需的所有紋理路徑,并以表中的順序排列。這將為當(dāng)前綁定的立方體貼圖中的每個面生成一個紋理。
我們也需要設(shè)定它的環(huán)繞和過濾方式:
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); //注意這里要設(shè)置為GL_CLAMP_TO_EDGE;GL_TEXTURE_WRAP_R是為紋理的R坐標(biāo)設(shè)置了環(huán)繞方式,它對應(yīng)的是紋理的第三個維度(和位置的z一樣)。我們將環(huán)繞方式設(shè)置為GL_CLAMP_TO_EDGE,這是因為正好處于兩個面之間的紋理坐標(biāo)可能不能擊中一個面(由于一些硬件限制),所以通過使用GL_CLAMP_TO_EDGE,OpenGL將在我們對兩個面之間采樣的時候,永遠(yuǎn)返回它們的邊界值。
【可以試一下,如果采用GL_REPEAT會看到明顯的縫隙】;
在繪制使用立方體貼圖的物體之前,我們要先激活對應(yīng)的紋理單元,并綁定立方體貼圖,這和普通的2D紋理沒什么區(qū)別。
在片段著色器中,我們使用了一個不同類型的采樣器,samplerCube,我們將使用texture函數(shù)使用它進行采樣,但這次我們將使用一個vec3的方向向量而不是vec2。使用立方體貼圖的片段著色器會像是這樣的:
in vec3 textureDir; // 代表3D紋理坐標(biāo)的方向向量 uniform samplerCube cubemap; // 立方體貼圖的紋理采樣器voidmain(){ FragColor = texture(cubemap, textureDir); } //samplerCube會對立方體貼圖的紋理單元進行采樣;看起來很棒,但為什么要用它呢?恰巧有一些很有意思的技術(shù),使用立方體貼圖來實現(xiàn)的話會簡單多了。其中一個技術(shù)就是創(chuàng)建一個天空盒(Skybox)。
天空盒
天空盒是一個包含了整個場景的(大)立方體,它包含周圍環(huán)境的6個圖像,讓玩家以為他處在一個比實際大得多的環(huán)境當(dāng)中。游戲中使用天空盒的例子有群山、白云或星空。下面這張截圖中展示的是星空的天空盒,它來自于『上古卷軸3』:
立方體貼圖能完美滿足天空盒的需求:我們有一個6面的立方體,每個面都需要一個紋理。在上面的圖片中,他們使用了夜空的幾張圖片,讓玩家產(chǎn)生其位于廣袤宇宙中的錯覺,但實際上他只是在一個小小的盒子當(dāng)中。
天空盒圖像通常有以下的形式:
如果你將這六個面折成一個立方體,你就會得到一個完全貼圖的立方體,模擬一個巨大的場景。一些資源可能會提供了這樣格式的天空盒,你必須手動提取六個面的圖像,但在大部分情況下它們都是6張單獨的紋理圖像。
之后我們將在場景中使用這個(高質(zhì)量的)天空盒,它可以在這里下載到。
加載天空盒
因為天空盒本身就是一個立方體貼圖,加載天空盒和之前加載立方體貼圖時并沒有什么不同。為了加載天空盒,我們將使用下面的函數(shù),它接受一個包含6個紋理路徑的vector:
//skybox天空盒的生成函數(shù);//注意返回的是skybox的ID; unsigned int loadCubemap(std::vector<std::string> faces)//這里傳入的是一個包含了六個面的數(shù)組; {unsigned int textureID;//一個紋理ID;glGenTextures(1, &textureID);glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);//此處要綁定的對象是GL_TEXTURE_CUBE_MAP;注意已經(jīng)不是GL_TEXTURE_2D;//設(shè)置紋理選項;//此處用上GL_CLAMP_TO_EDGE的原因是因為正好處于兩個面之間的紋理坐標(biāo)可能不能擊中一個面(由于一些硬件限制),所以通過使用GL_CLAMP_TO_EDGE,OpenGL將在我們對兩個面之間采樣的時候,永遠(yuǎn)返回它們的邊界值。glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, 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_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,GL_LINEAR);int width, height, nrchannels;for (unsigned int i = 0; i < faces.size(); i++)//對六個面分別進行紋理的加載;{unsigned char* data = stbi_load(faces[i].c_str(), &width, &height, &nrchannels, 0);//獲取對應(yīng)一個面的紋理數(shù)據(jù);if (data)//如果獲取到則進行生成;{//對六個面的處理一般為右左上下前后;glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//生成紋理;//glGenerateMipmap(GL_TEXTURE_CUBE_MAP);stbi_image_free(data);//釋放數(shù)據(jù);}else{std::cout << "Cubemap texture failed to load at path:" << std::endl;stbi_image_free(data);}}//結(jié)束一系列操作之后,會return textureID; }函數(shù)本身應(yīng)該很熟悉了。它基本就是上一部分中立方體貼圖的代碼,只不過合并到了一個便于管理的函數(shù)中。
之后,在調(diào)用這個函數(shù)之前,我們需要將合適的紋理路徑按照立方體貼圖枚舉指定的順序加載到一個vector中。
std::vector<std::string> faces{"E:/OpenGl/textures/skybox.texture/skybox/right.jpg","E:/OpenGl/textures/skybox.texture/skybox/left.jpg","E:/OpenGl/textures/skybox.texture/skybox/top.jpg","E:/OpenGl/textures/skybox.texture/skybox/bottom.jpg","E:/OpenGl/textures/skybox.texture/skybox/front.jpg","E:/OpenGl/textures/skybox.texture/skybox/back.jpg"}; //直接存入根路徑即可;現(xiàn)在我們就將這個天空盒加載為一個立方體貼圖了,它的id是cubemapTexture。
顯示天空盒
由于天空盒是繪制在一個立方體上的,和其它物體一樣,我們需要另一個VAO、VBO以及新的一組頂點。你可以在這里找到它的頂點數(shù)據(jù)。
float skyboxVertices[] = {// positions -1.0f, 1.0f, -1.0f,-1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, 1.0f,-1.0f, -1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, 1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, -1.0f,1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,-1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, -1.0f, 1.0f,-1.0f, -1.0f, 1.0f,-1.0f, 1.0f, -1.0f,1.0f, 1.0f, -1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, 1.0f };用于貼圖3D立方體的立方體貼圖可以使用立方體的位置作為紋理坐標(biāo)來采樣。當(dāng)立方體處于原點(0, 0, 0)時,它的每一個位置向量都是從原點出發(fā)的方向向量。這個方向向量正是獲取立方體上特定位置的紋理值所需要的。正是因為這個,我們只需要提供位置向量而不用紋理坐標(biāo)了。
要渲染天空盒的話,我們需要一組新的著色器,它們都不是很復(fù)雜。因為我們只有一個頂點屬性,頂點著色器非常簡單:
#version 330 core layout (location = 0) in vec3 aPos;out vec3 TexCoords;uniform mat4 projection; uniform mat4 view;voidmain(){TexCoords = aPos;gl_Position = projection * view * vec4(aPos, 1.0); }注意,頂點著色器中很有意思的部分是,我們將輸入的位置向量作為輸出給片段著色器的紋理坐標(biāo)。片段著色器會將它作為輸入來采樣samplerCube:
#version 330 core out vec4 FragColor;in vec3 TexCoords;uniform samplerCube skybox;voidmain(){ FragColor = texture(skybox, TexCoords); }片段著色器非常直觀。我們將頂點屬性的位置向量作為紋理的方向向量,并使用它從立方體貼圖中采樣紋理值。
有了立方體貼圖紋理,渲染天空盒現(xiàn)在就非常簡單了,我們只需要綁定立方體貼圖紋理,skybox采樣器就會自動填充上天空盒立方體貼圖了。繪制天空盒時,我們需要將它變?yōu)閳鼍爸械牡谝粋€渲染的物體,并且禁用深度寫入。這樣子天空盒就會永遠(yuǎn)被繪制在其它物體的背后了。
glDepthMask(GL_FALSE); skyboxShader.use(); // ... 設(shè)置觀察和投影矩陣 glBindVertexArray(skyboxVAO); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); glDrawArrays(GL_TRIANGLES, 0, 36); glDepthMask(GL_TRUE); // ... 繪制剩下的場景如果你運行一下的話你就會發(fā)現(xiàn)出現(xiàn)了一些問題。我們希望天空盒是以玩家為中心的,這樣不論玩家移動了多遠(yuǎn),天空盒都不會變近,讓玩家產(chǎn)生周圍環(huán)境非常大的印象。然而,當(dāng)前的觀察矩陣【即我們的攝像機移動會改變與其的距離】會旋轉(zhuǎn)、縮放和位移來變換天空盒的所有位置,所以當(dāng)玩家移動的時候,立方體貼圖也會移動!我們希望移除觀察矩陣中的位移部分,讓移動不會影響天空盒的位置向量。
你可能還記得在基礎(chǔ)光照小節(jié)中,我們通過取4x4矩陣左上角的3x3矩陣來移除變換矩陣的位移部分。我們可以將觀察矩陣轉(zhuǎn)換為3x3矩陣(移除位移),再將其轉(zhuǎn)換回4x4矩陣,來達(dá)到類似的效果。
//glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));//下面是我代碼中的內(nèi)容,我沒有把攝像機分裝起來; glm::mat4 view = glm::mat4(glm::mat3(glm::LookAt(cameraPos,cameraFront+cameraPos,cameraUp)));這將移除任何的位移,但保留旋轉(zhuǎn)變換,讓玩家仍然能夠環(huán)顧場景。
【這里移除了觀察矩陣,使得我們不會再次因為移動來改變距離】;
有了天空盒,最終的效果就是一個看起來巨大的場景了。如果你在箱子周圍轉(zhuǎn)一轉(zhuǎn),你就能立刻感受到距離感,極大地提升了場景的真實度。
優(yōu)化
目前我們是首先渲染天空盒,之后再渲染場景中的其它物體。這樣子能夠工作,但不是非常高效。如果我們先渲染天空盒,我們就會對屏幕上的每一個像素運行一遍片段著色器,即便只有一小部分的天空盒最終是可見的。可以使用提前深度測試(Early Depth Testing)輕松丟棄掉的片段能夠節(jié)省我們很多寶貴的帶寬。
所以,我們將會最后渲染天空盒,以獲得輕微的性能提升。這樣子的話,深度緩沖就會填充滿所有物體的深度值了,我們只需要在提前深度測試通過的地方渲染天空盒的片段就可以了,很大程度上減少了片段著色器的調(diào)用。問題是,天空盒很可能會渲染在所有其他對象之上,因為它只是一個1x1x1的立方體(譯注:意味著距離攝像機的距離也只有1),會通過大部分的深度測試。不用深度測試來進行渲染不是解決方案,因為天空盒將會復(fù)寫場景中的其它物體。我們需要欺騙深度緩沖,讓它認(rèn)為天空盒有著最大的深度值1.0,只要它前面有一個物體,深度測試就會失敗。
在坐標(biāo)系統(tǒng)小節(jié)中我們說過,透視除法是在頂點著色器運行之后執(zhí)行的,將gl_Position的xyz坐標(biāo)除以w分量。我們又從深度測試小節(jié)中知道,相除結(jié)果的z分量等于頂點的深度值。使用這些信息,我們可以將輸出位置的z分量等于它的w分量,讓z分量永遠(yuǎn)等于1.0,這樣子的話,當(dāng)透視除法執(zhí)行之后,z分量會變?yōu)?/span>w / w = 1.0。
voidmain(){TexCoords = aPos;vec4 pos = projection * view * vec4(aPos, 1.0);gl_Position = pos.xyww; }最終的標(biāo)準(zhǔn)化設(shè)備坐標(biāo)將永遠(yuǎn)會有一個等于1.0的z值:最大的深度值。結(jié)果就是天空盒只會在沒有可見物體的地方渲染了(只有這樣才能通過深度測試,其它所有的東西都在天空盒前面)。
我們還要改變一下深度函數(shù),將它從默認(rèn)的GL_LESS改為GL_LEQUAL。深度緩沖將會填充上天空盒的1.0值,所以我們需要保證天空盒在值小于或等于深度緩沖而不是小于時通過深度測試。
【我們將天空盒的深度值設(shè)置為最大的1.0,因此我們只有將深度函數(shù)設(shè)置為GL_LEQUAL才可以使得小于等于1.0的物體被繪入,如果用的是GL_LESS僅會繪入深度小于1的物體,就沒有繪入天空盒;】
深度測試的相關(guān)內(nèi)容可以在這里看到:
https://learnopengl-cn.github.io/04%20Advanced%20OpenGL/01%20Depth%20testing/
以模型的人物為例
代碼運行的結(jié)果如上圖所示;
接下來是反射以及折射的效果:
反射
反射這個屬性表現(xiàn)為物體(或物體的一部分)反射它周圍環(huán)境,即根據(jù)觀察者的視角,物體的顏色或多或少等于它的環(huán)境。鏡子就是一個反射性物體:它會根據(jù)觀察者的視角反射它周圍的環(huán)境。
下面這張圖展示了我們?nèi)绾斡嬎惴瓷湎蛄?#xff0c;并如何使用這個向量來從立方體貼圖中采樣:
【聯(lián)想最開始的圖片,這里我們就可以通過反射得到的向量去對天空盒貼圖進行采樣】;
我們根據(jù)觀察方向向量IˉIˉ和物體的法向量Nˉ,來計算反射向量Rˉ。我們可以使用GLSL內(nèi)建的reflect函數(shù)來計算這個反射向量。最終的Rˉ向量將會作為索引/采樣立方體貼圖的方向向量,返回環(huán)境的顏色值。最終的結(jié)果是物體看起來反射了天空盒。
因為我們已經(jīng)在場景中配置好天空盒了,創(chuàng)建反射效果并不會很難。我們將會改變模型的片段著色器,讓其有反射性:
#version 330 core out vec4 FragColor;#define Max_texture_size 8struct Material {//這里創(chuàng)建兩個紋理單元;sampler2D texture_diffuse[Max_texture_size];sampler2D texture_specular[Max_texture_size]; int texture_diffuse_num;int texture_specular_num;//這兩個參數(shù)用于記錄兩種紋理的最終個數(shù),以便用于調(diào)用紋理單元;float shininess;//---------samplerCube skybox;//設(shè)置一個采樣器;//--------- }; //定向光的結(jié)構(gòu)體; struct DirLight{vec3 direction;//定向光的方向;vec3 ambient;vec3 diffuse;vec3 specular;//定向光的三種分量; }; //點光源的結(jié)構(gòu)體; struct PointLight{vec3 position;//點光源不用考慮光照方向;光照方向即為與片段的連線;float constant;float linear;float quadratic;vec3 ambient;vec3 diffuse;vec3 specular; };//聚光的結(jié)構(gòu)體; struct SpotLight{vec3 position;vec3 direction;float cutOff;float outerCutOff;float constant;float linear;float quadratic;vec3 ambient;vec3 diffuse;vec3 specular; }; //這里我設(shè)置了兩個光源; #define NR_POINT_LIGHTS 2in vec3 FragPos; //片段的插值的Normal以及FragColor; in vec3 Normal; in vec2 TexCoords;uniform vec3 viewPos; uniform Material material; uniform DirLight dirlight;//聲明一個定向光結(jié)構(gòu)體的變量; uniform PointLight pointlights[NR_POINT_LIGHTS];//創(chuàng)建一個包含2個元素的PointLight結(jié)構(gòu)體數(shù)組; uniform SpotLight spotlight;//聲明一個聚光的結(jié)構(gòu)體變量;vec3 CalcDirLight(DirLight light,vec3 normal,vec3 viewDir);//定向光; vec3 CalcPointLight(PointLight light,vec3 normal,vec3 fragPos,vec3 viewDir);//點光源; vec3 CalcSpotLight(SpotLight light,vec3 normal,vec3 fragPos,vec3 viewDir);//聚光; vec3 CalLightSum(vec3 lightAmbient, vec3 lightDiffuse, vec3 lightSpecular, float diff, float spec);//用于計算最終片段的顏色數(shù)值的函數(shù);//change; void main() {vec3 norm = normalize(Normal);//法線方向;vec3 viewDir = normalize(viewPos - FragPos);//視覺方向;//計算定向光;vec3 result = CalcDirLight(dirlight,norm,viewDir);//計算點光照;for(int i = 0; i < NR_POINT_LIGHTS; i++)result += CalcPointLight(pointlights[i],norm, FragPos, viewDir); //計算聚光;result += CalcSpotLight(spotlight, norm, FragPos, viewDir); //實現(xiàn)反射;//---------------------vec3 I = normalize(FragPos - viewPos);vec3 R = reflect(I,normalize(Normal));result += texture(material.skybox,R).rgb;//---------------------//實現(xiàn)折射;運用rafract函數(shù);//float ratio = 1.0/1.52;//vec3 I = normalize(FragPos - viewPos);//vec3 R = refract(I,normalize(Normal),ratio);//result += texture(material.skybox,R).rgb;//輸出最終片段;FragColor = vec4(result,1.0);//最終片段的顏色是三種光照的相加值; } //change; //一個用于計算定向光最后的顏色函數(shù);(返回的是一個vec3的向量); vec3 CalcDirLight(DirLight light,vec3 normal,vec3 viewDir)//函數(shù)傳入的是定向光的結(jié)構(gòu)體,以及片段已經(jīng)單位化的法線,還有viewDir向量; {vec3 lightDir = normalize(-light.direction);//漫反射著色;float diff = max(dot(normal, lightDir), 0.0);//鏡面著色;vec3 reflectDir = reflect(-lightDir,normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//進行合并;return CalLightSum(light.ambient, light.diffuse, light.specular, diff, spec);; }//change; //一個用于計算點光源最終輸出顏色的函數(shù); vec3 CalcPointLight(PointLight light,vec3 normal,vec3 fragPos,vec3 viewDir)//normal已經(jīng)單位化; {vec3 lightDir = normalize(light.position - fragPos);// 漫反射著色float diff = max(dot(normal, lightDir), 0.0);// 鏡面光著色vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// 衰減float distance = length(light.position - fragPos);float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); return CalLightSum(light.ambient, light.diffuse, light.specular, diff, spec)*attenuation; }//change; //一個用于計算聚光最終輸出的函數(shù); //傳入聚光結(jié)構(gòu)體數(shù)組,單位化后的法線,片段位置,以及viewDir視角方向; vec3 CalcSpotLight(SpotLight light,vec3 normal,vec3 fragPos,vec3 viewDir) {vec3 lightDir = normalize(light.position - fragPos);//光線方向;//漫反射著色;float diff = max(0.0,dot(lightDir,normal));//鏡面光著色;vec3 reflectDir = reflect(-lightDir,normal);float spec = pow(max(0.0,dot(reflectDir,viewDir)),material.shininess);//衰減;float distance = length(light.position - fragPos);float atten = 1.0 / (light.constant+distance * light.linear+light.quadratic*(distance*distance));//柔和;float theta = dot(lightDir,normalize(-light.direction));float epison = light.cutOff - light.outerCutOff;float intensity = clamp((theta - light.outerCutOff) / epison,0.0,1.0);//利用clamp將柔和限制在0到1之間;//進行整合;return CalLightSum(light.ambient, light.diffuse, light.specular, diff, spec)*atten*intensity; }//創(chuàng)建一個用來計算漫反射,環(huán)境光,以及高光三者顏色綜合的一個函數(shù); vec3 CalLightSum(vec3 lightAmbient, vec3 lightDiffuse, vec3 lightSpecular, float diff, float spec) {vec3 diffuse = vec3(0.0,0.0,0.0);for(int i = 0; i < material.texture_diffuse_num; i++){diffuse += texture(material.texture_diffuse[i], TexCoords).rgb;}vec3 ambient = lightAmbient * diffuse;diffuse = lightDiffuse * diff * diffuse;vec3 specular = vec3(0.0,0.0,0.0);for(int i = 0; i < material.texture_specular_num; i++){specular += texture(material.texture_specular[i], TexCoords).rgb;}specular = lightSpecular * spec * specular;return ambient + diffuse + specular; }下面是頂點著色器:
#version 330 core layout(location = 0) in vec3 aPos; layout(location = 1) in vec3 aNormal; layout(location = 2) in vec2 aTexCoords;uniform mat4 projection; uniform mat4 view; uniform mat4 model;out vec3 Normal; out vec3 FragPos;//輸出一個通過model矩陣轉(zhuǎn)換到世界空間的頂點; out vec2 TexCoords;void main() {gl_Position = projection*view*model*vec4(aPos,1.0);FragPos = vec3(model*vec4(aPos,1.0f));//片段的位置經(jīng)過模型空間的變化到世界空間;Normal = mat3(transpose(inverse(model)))*aNormal;TexCoords = aTexCoords;//傳入紋理坐標(biāo)到片段著色器; }我們現(xiàn)在使用了一個法向量,所以我們將再次使用法線矩陣(Normal Matrix)來變換它們。FragPos輸出向量是一個世界空間的位置向量。【僅用了model變換而沒有用view】頂點著色器的這個Position輸出將用來在片段著色器內(nèi)計算觀察方向向量。
在main.cpp中還要進行綁定;
// render the loaded model glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f)); model = glm::scale(model, glm::vec3(1.0f, 1.0f, 1.0f)); // it's a bit too big for our scene, so scale it down lightshader.setMat4("model", model); //------------------ glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); //------------------ OurModel.Draw(lightshader);//調(diào)用Draw進行繪制相關(guān)操作;結(jié)果:
折射
環(huán)境映射的另一種形式是折射,它和反射很相似。折射是光線由于傳播介質(zhì)的改變而產(chǎn)生的方向變化。在常見的類水表面上所產(chǎn)生的現(xiàn)象就是折射,光線不是直直地傳播,而是彎曲了一點。將你的半只胳膊伸進水里,觀察出來的就是這種效果。
折射是通過斯涅爾定律(Snell’s Law)來描述的,使用環(huán)境貼圖的話看起來像是這樣:
同樣,我們有一個觀察向量Iˉ,一個法向量Nˉ,而這次是折射向量Rˉ。可以看到,觀察向量的方向輕微彎曲了。彎折后的向量Rˉ將會用來從立方體貼圖中采樣。
折射可以使用GLSL的內(nèi)建refract函數(shù)來輕松實現(xiàn),它需要一個法向量、一個觀察方向和兩個材質(zhì)之間的折射率(Refractive Index)。
折射率決定了材質(zhì)中光線彎曲的程度,每個材質(zhì)都有自己的折射率。一些最常見的折射率可以在下表中找到:
材質(zhì) | 折射率 |
空氣 | 1.00 |
水 | 1.33 |
冰 | 1.309 |
玻璃 | 1.52 |
鉆石 | 2.42 |
我們使用這些折射率來計算光傳播的兩種材質(zhì)間的比值。在我們的例子中,光線/視線從空氣進入玻璃(如果我們假設(shè)模型是玻璃制的),所以比值為1.00/1.52=0.658。
我們已經(jīng)綁定了立方體貼圖,提供了頂點數(shù)據(jù)和法線,并設(shè)置了攝像機位置的uniform。唯一要修改的就是片段著色器:
//........for(int i = 0; i < NR_POINT_LIGHTS; i++)result += CalcPointLight(pointlights[i],norm, FragPos, viewDir); //計算聚光;result += CalcSpotLight(spotlight, norm, FragPos, viewDir); //實現(xiàn)反射;//---------------------//vec3 I = normalize(FragPos - viewPos);//vec3 R = reflect(I,normalize(Normal));//result += texture(material.skybox,R).rgb;//---------------------//實現(xiàn)折射;運用rafract函數(shù);float ratio = 1.0/1.52;vec3 I = normalize(FragPos - viewPos);vec3 R = refract(I,normalize(Normal),ratio);result += texture(material.skybox,R).rgb; //.......通過改變折射率,你可以創(chuàng)建完全不同的視覺效果。編譯程序并運行,我們可以看到一個類玻璃的物體:
好看捏!!;
下面是main.cpp和片段著色器的代碼:
main.cpp
#include <glad/glad.h> #include <GLFW/glfw3.h> #include <iostream> #include<E:\OpenGl\練習(xí)1.1\3.3.shader_class\shader s.h>//引入shader類頭文件; //以下三行為glm的頭文件代碼; #include <E:\OpenGl\glm\glm-master\glm\glm.hpp> #include <E:\OpenGl\glm\glm-master\glm\gtc\matrix_transform.hpp> #include <E:\OpenGl\glm\glm-master\glm\gtc\type_ptr.hpp>//#define STB_IMAGE_IMPLEMENTATION #include <E:/OpenGl/stb_image.h/stb-master/stb_image.h>//這兩行代碼加入了stb_image庫;#include"Model s.h"void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow* window); void mouse_callback(GLFWwindow* window, double xpos, double ypos); void scroll_back(GLFWwindow* window, double xoffset, double yoffset); unsigned int loadCubemap(std::vector<std::string> faces);//傳入六張圖片,返回一個紋理ID;//三個調(diào)整攝像機位置的全局變量; glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f); glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f); glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);float deltatime = 0.0f;//上一幀與這一幀的時間差; float lastime = 0.0f;//上一幀的時間;//用來存儲上一幀鼠標(biāo)的位置!,設(shè)置為屏幕中心; float lastX = 400.0; float lastY = 300.0;//仰俯角和偏航角; float pitch = 0.0f; float yaw = -90.0f;//從下往上;float fov = 45.0f;//視域角;glm::vec3 lightPos(1.2f, 1.0f, 2.0f);//聲明一個光源,表示光源在空間中的位置;int main() {//先進行初始化glfw;glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);//主版本設(shè)置為3;glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//次版本設(shè)置為3;glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);GLFWwindow* window = glfwCreateWindow(1000, 750, "MY OPENGL", NULL, NULL);if (window == NULL){std::cout << "Fail to create a window" << std::endl;glfwTerminate();//釋放資源;return -1;}glfwMakeContextCurrent(window);glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//創(chuàng)建完告訴將上下文設(shè)置為進程上下文;//以下兩步用于攝像機操作中的設(shè)置,由于是窗口的操作,因此放在此處!!;glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);//告訴GLFW隱藏光標(biāo)并捕捉他;glfwSetCursorPosCallback(window, mouse_callback);//此句代碼當(dāng)發(fā)生鼠標(biāo)移動時,就會調(diào)用mouse_callback函數(shù)改變兩個歐拉角,//進而改變cameraFront方向向量,進而可以實現(xiàn)3D旋轉(zhuǎn);//還要對視域角fov做出變化,可以進行放大縮小;glfwSetScrollCallback(window, scroll_back);//當(dāng)滾動鼠標(biāo)滾輪的時候,我們就可以通過調(diào)用該函數(shù)來改變fov,進而改變透視投影矩陣,//以此來進一步形成放大和縮小!!;//對glad進行初始化;if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Fail to initnite glad" << std::endl;return -1;}//引入著色器類,著色器被封裝到了class Shader里面;//這里要創(chuàng)建兩個著色器程序,分別用在物體和光源上面;//// light positionsfloat vertices[] = {-0.5f, -0.5f, -0.5f,0.5f, -0.5f, -0.5f,0.5f, 0.5f, -0.5f,0.5f, 0.5f, -0.5f,-0.5f, 0.5f, -0.5f,-0.5f, -0.5f, -0.5f,-0.5f, -0.5f, 0.5f,0.5f, -0.5f, 0.5f,0.5f, 0.5f, 0.5f,0.5f, 0.5f, 0.5f,-0.5f, 0.5f, 0.5f,-0.5f, -0.5f, 0.5f,-0.5f, 0.5f, 0.5f,-0.5f, 0.5f, -0.5f,-0.5f, -0.5f, -0.5f,-0.5f, -0.5f, -0.5f,-0.5f, -0.5f, 0.5f,-0.5f, 0.5f, 0.5f,0.5f, 0.5f, 0.5f,0.5f, 0.5f, -0.5f,0.5f, -0.5f, -0.5f,0.5f, -0.5f, -0.5f,0.5f, -0.5f, 0.5f,0.5f, 0.5f, 0.5f,-0.5f, -0.5f, -0.5f,0.5f, -0.5f, -0.5f,0.5f, -0.5f, 0.5f,0.5f, -0.5f, 0.5f,-0.5f, -0.5f, 0.5f,-0.5f, -0.5f, -0.5f,-0.5f, 0.5f, -0.5f,0.5f, 0.5f, -0.5f,0.5f, 0.5f, 0.5f,0.5f, 0.5f, 0.5f,-0.5f, 0.5f, 0.5f,-0.5f, 0.5f, -0.5f,};glEnable(GL_DEPTH_TEST);//啟用深度測試;//光源unsigned int lightVAO;unsigned int lightVBO;glGenVertexArrays(1, &lightVAO);glBindVertexArray(lightVAO);glGenBuffers(1, &lightVBO);glBindBuffer(GL_ARRAY_BUFFER, lightVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);//天空盒;//天空盒立方體頂點數(shù)據(jù);float skyboxVertices[] = {// positions -1.0f, 1.0f, -1.0f,-1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, 1.0f,-1.0f, -1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, -1.0f,-1.0f, 1.0f, 1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, -1.0f,1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,-1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, -1.0f, 1.0f,-1.0f, -1.0f, 1.0f,-1.0f, 1.0f, -1.0f,1.0f, 1.0f, -1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f, -1.0f,-1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, -1.0f,1.0f, -1.0f, -1.0f,-1.0f, -1.0f, 1.0f,1.0f, -1.0f, 1.0f};//創(chuàng)建天空盒的VAO,VBO;//skybox;VAO,VBO;//此處創(chuàng)建一個新的VAO,VBO用于存儲頂點數(shù)據(jù);unsigned int skyboxVAO, skyboxVBO;glGenVertexArrays(1, &skyboxVAO);glGenBuffers(1, &skyboxVBO);glBindVertexArray(skyboxVAO);glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//ready to be change;std::vector<std::string> faces{"E:/OpenGl/textures/skybox.texture/skybox/right.jpg","E:/OpenGl/textures/skybox.texture/skybox/left.jpg","E:/OpenGl/textures/skybox.texture/skybox/top.jpg","E:/OpenGl/textures/skybox.texture/skybox/bottom.jpg","E:/OpenGl/textures/skybox.texture/skybox/front.jpg","E:/OpenGl/textures/skybox.texture/skybox/back.jpg"};unsigned int cubemapTexture = loadCubemap(faces);//獲取到天空盒的ID;//下面要傳入兩個天空盒的著色器進行著色繪制;Shader lightshader("3.2.shader2.vs", "3.2.shader2.fs");Shader lightCubeshader("3.2.shader.light.vs", "3.2.shader.light.fs");//用于光源的著色器程序;//后面會繪制兩個光源;Shader skyboxshader("4.4.sky.shader.vs", "4.4.sky.shader.fs");//用于繪制天空盒的著色器程序;skyboxshader.useProgram();skyboxshader.setInt("skybox", 0);//---------------------------lightshader.useProgram();lightshader.setInt("material.skybox", 3);//---------------------------//模型導(dǎo)入:Model OurModel("E:/OpenGl/Model/Model1/nanosuit.obj");//光源位置;glm::vec3 pointLightPositions[] = {glm::vec3(-2.0f,6.0f,1.0f),glm::vec3(2.0f,15.0f,0.0f)};//準(zhǔn)備引擎:while (!glfwWindowShouldClose(window)){float currentFrame = static_cast<float>(glfwGetTime());deltatime = currentFrame - lastime;lastime = currentFrame;processInput(window);glClearColor(0.05f, 0.05f, 0.05f, 1.0f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// don't forget to enable shader before setting uniformslightshader.useProgram();//啟用著色器;lightshader.setFloat("material.shininess", 32.0f);//設(shè)置material中的選項;//人眼設(shè)置;//這里設(shè)置為攝像機的位置;lightshader.setVec3("viewPos", cameraPos);//定向光參數(shù)設(shè)置;lightshader.setVec3("dirlight.position", glm::vec3(-2.0f, -0.3f, -1.0f));lightshader.setVec3("dirlight.ambient", glm::vec3(0.2f, 0.2f, 0.2f));lightshader.setVec3("dirlight.specular", glm::vec3(1.0f, 1.0f, 1.0f));lightshader.setVec3("dirlight.diffuse", glm::vec3(0.5f));//點光源參數(shù)設(shè)置;下面有兩個電光源需要進行設(shè)置;//point1;lightshader.setVec3("pointlights[0].position", pointLightPositions[0]);lightshader.setVec3("pointlights[0].ambient", glm::vec3(0.2f, 0.2f, 0.2f));lightshader.setVec3("pointlights[0].specular", glm::vec3(50.0f, 50.0f, 50.0f));lightshader.setVec3("pointlights[0].diffuse", glm::vec3(0.5f, 0.5f, 0.5f));lightshader.setFloat("pointlights[0].constant", 1.0f);lightshader.setFloat("pointlights[0].linear", 0.09f);lightshader.setFloat("pointlights[0].quadratic", 0.032f);//point2;lightshader.setVec3("pointlights[1].position", pointLightPositions[1]);lightshader.setVec3("pointlights[1].ambient", glm::vec3(0.2f, 0.2f, 0.2f));lightshader.setVec3("pointlights[1].specular", glm::vec3(10.0f, 10.0f, 10.0f));lightshader.setVec3("pointlights[1].diffuse", glm::vec3(0.5f, 0.5f, 0.5f));lightshader.setFloat("pointlights[1].constant", 1.0f);lightshader.setFloat("pointlights[1].linear", 0.09f);lightshader.setFloat("pointlights[1].quadratic", 0.032f);//聚光參數(shù)設(shè)置;lightshader.setVec3("spotlight.position", cameraPos);lightshader.setVec3("spotlight.direction", cameraFront);lightshader.setVec3("spotlight.ambient", glm::vec3(0.2f, 0.2f, 0.2f));lightshader.setVec3("spotlight.specular", glm::vec3(1.0f, 1.0f, 1.0f));lightshader.setVec3("spotlight.diffuse", glm::vec3(0.5f, 0.5f, 0.5f));lightshader.setFloat("spotlight.constant", 1.0f);lightshader.setFloat("spotlight.linear", 0.09f);lightshader.setFloat("spotlight.quadratic", 0.032f);lightshader.setFloat("spotlight.outOff", glm::radians(12.5f));lightshader.setFloat("spotlight.outerCutOff", glm::radians(17.5f));// view/projection transformationsglm::mat4 projection = glm::perspective(glm::radians(fov), (float)800 / (float)600, 0.1f, 100.0f);glm::mat4 view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);lightshader.setMat4("projection", projection);lightshader.setMat4("view", view);// render the loaded modelglm::mat4 model = glm::mat4(1.0f);model = glm::translate(model, glm::vec3(0.0f, 0.0f, 0.0f)); model = glm::scale(model, glm::vec3(1.0f, 1.0f, 1.0f)); // it's a bit too big for our scene, so scale it downlightshader.setMat4("model", model);//------------------glActiveTexture(GL_TEXTURE3);glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);//------------------OurModel.Draw(lightshader);//調(diào)用Draw進行繪制相關(guān)操作;//下面是對燈源的操作,會在場景中畫出兩個燈光位置,燈光均為白色;lightCubeshader.useProgram();//啟用另外一個著色器;glBindVertexArray(lightVAO);lightCubeshader.setMat4("view", view);lightCubeshader.setMat4("projection", projection);for (unsigned int i = 0; i < 2; i++){model = glm::translate(glm::mat4(1.0f), pointLightPositions[i]);model = glm::rotate(model, glm::radians(180.0f), glm::vec3(0.0f, 1.0f, 0.0f));model = glm::scale(model, glm::vec3(0.25f, 0.25f, 0.25f));lightCubeshader.setMat4("model", model);glDrawArrays(GL_TRIANGLES, 0, 36);}// 最后畫天空盒//更改深度函數(shù),以便深度測試在值等于深度緩沖區(qū)的內(nèi)容時通過//注意此處深度測試是重點!!!!;glDepthFunc(GL_LEQUAL);skyboxshader.useProgram();view = glm::mat4(glm::mat3(view)); //從視圖矩陣中刪除平移skyboxshader.setMat4("view", view);skyboxshader.setMat4("projection", projection);// 天空盒g(shù)lBindVertexArray(skyboxVAO);glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);glDrawArrays(GL_TRIANGLES, 0, 36);glBindVertexArray(0);glDepthFunc(GL_LESS); // 將深度功能設(shè)置回默認(rèn)值glfwSwapBuffers(window);glfwPollEvents();}glfwTerminate();return 0; }void framebuffer_size_callback(GLFWwindow* window, int width, int height) {glViewport(0, 0, width, height); } void processInput(GLFWwindow* window) {if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)//如果按下的鍵為回車鍵;glfwSetWindowShouldClose(window, true);float cameraSpeed = 10.0f * deltatime;//移動速度;if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)cameraPos += cameraUp * cameraSpeed;if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)cameraPos -= cameraUp * cameraSpeed;if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)cameraPos -= cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp));if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)cameraPos += cameraSpeed * glm::normalize(glm::cross(cameraFront, cameraUp)); }bool firstMouse = true;void mouse_callback(GLFWwindow* window, double xpos, double ypos) {//計算鼠標(biāo)距上一幀的偏移量。//把偏移量添加到攝像機的俯仰角和偏航角中。//對偏航角和俯仰角進行最大和最小值的限制。//計算方向向量。if (firstMouse){lastX = xpos;lastY = ypos;firstMouse = false;//否則每一次都會進行循環(huán);}//1.計算鼠標(biāo)距上一幀的偏移量。float xoffset = xpos - lastX;float yoffset = lastY - ypos;lastX = xpos;lastY = ypos;//更新存儲的上一幀的值;float sensitivity = 0.1f;//設(shè)置靈敏度;xoffset *= sensitivity;yoffset *= sensitivity;//2.把偏移量添加到攝像機的俯仰角和偏航角中。pitch = pitch + yoffset;yaw = yaw + xoffset;//3.對偏航角和俯仰角進行最大和最小值的限制if (pitch > 89.0f)pitch = 89.0f;if (pitch < -89.0f)pitch = -89.0f;//計算方向向量。glm::vec3 direction;direction.x = cos(glm::radians(pitch)) * cos(glm::radians(yaw));direction.y = sin(glm::radians(pitch));direction.z = cos(glm::radians(pitch)) * sin(glm::radians(yaw));cameraFront = glm::normalize(direction); } void scroll_back(GLFWwindow* window, double xoffset, double yoffset) {//我們要把fov限制在1.0到45.0之間!!;if (fov >= 1.0f && fov <= 45.0f){fov -= yoffset;}if (fov >= 45.0f){fov = 45.0f;}if (fov <= 1.0f){fov = 1.0f;} }//skybox天空盒的生成函數(shù);//注意返回的是skybox的ID; unsigned int loadCubemap(std::vector<std::string> faces)//這里傳入的是一個包含了六個面的數(shù)組; {unsigned int textureID;//一個紋理ID;glGenTextures(1, &textureID);glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);//此處要綁定的對象是GL_TEXTURE_CUBE_MAP;注意已經(jīng)不是GL_TEXTURE_2D;//設(shè)置紋理選項;//此處用上GL_CLAMP_TO_EDGE的原因是因為正好處于兩個面之間的紋理坐標(biāo)可能不能擊中一個面(由于一些硬件限制),所以通過使用GL_CLAMP_TO_EDGE,OpenGL將在我們對兩個面之間采樣的時候,永遠(yuǎn)返回它們的邊界值。glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, 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_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER,GL_LINEAR);int width, height, nrchannels;for (unsigned int i = 0; i < faces.size(); i++)//對六個面分別進行紋理的加載;{unsigned char* data = stbi_load(faces[i].c_str(), &width, &height, &nrchannels, 0);//獲取對應(yīng)一個面的紋理數(shù)據(jù);if (data)//如果獲取到則進行生成;{//對六個面的處理一般為右左上下前后;glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);//生成紋理;//glGenerateMipmap(GL_TEXTURE_CUBE_MAP);stbi_image_free(data);//釋放數(shù)據(jù);}else{std::cout << "Cubemap texture failed to load at path:" << std::endl;stbi_image_free(data);}}//結(jié)束一系列操作之后,會return textureID; }片段著色器
#version 330 core out vec4 FragColor;#define Max_texture_size 8struct Material {//這里創(chuàng)建兩個紋理單元;sampler2D texture_diffuse[Max_texture_size];sampler2D texture_specular[Max_texture_size]; int texture_diffuse_num;int texture_specular_num;//這兩個參數(shù)用于記錄兩種紋理的最終個數(shù),以便用于調(diào)用紋理單元;float shininess;//---------samplerCube skybox;//設(shè)置一個采樣器;//--------- }; //定向光的結(jié)構(gòu)體; struct DirLight{vec3 direction;//定向光的方向;vec3 ambient;vec3 diffuse;vec3 specular;//定向光的三種分量; }; //點光源的結(jié)構(gòu)體; struct PointLight{vec3 position;//點光源不用考慮光照方向;光照方向即為與片段的連線;float constant;float linear;float quadratic;vec3 ambient;vec3 diffuse;vec3 specular; };//聚光的結(jié)構(gòu)體; struct SpotLight{vec3 position;vec3 direction;float cutOff;float outerCutOff;float constant;float linear;float quadratic;vec3 ambient;vec3 diffuse;vec3 specular; }; //這里我設(shè)置了兩個光源; #define NR_POINT_LIGHTS 2in vec3 FragPos; //片段的插值的Normal以及FragColor; in vec3 Normal; in vec2 TexCoords;uniform vec3 viewPos; uniform Material material; uniform DirLight dirlight;//聲明一個定向光結(jié)構(gòu)體的變量; uniform PointLight pointlights[NR_POINT_LIGHTS];//創(chuàng)建一個包含2個元素的PointLight結(jié)構(gòu)體數(shù)組; uniform SpotLight spotlight;//聲明一個聚光的結(jié)構(gòu)體變量;vec3 CalcDirLight(DirLight light,vec3 normal,vec3 viewDir);//定向光; vec3 CalcPointLight(PointLight light,vec3 normal,vec3 fragPos,vec3 viewDir);//點光源; vec3 CalcSpotLight(SpotLight light,vec3 normal,vec3 fragPos,vec3 viewDir);//聚光; vec3 CalLightSum(vec3 lightAmbient, vec3 lightDiffuse, vec3 lightSpecular, float diff, float spec);//用于計算最終片段的顏色數(shù)值的函數(shù);//change; void main() {vec3 norm = normalize(Normal);//法線方向;vec3 viewDir = normalize(viewPos - FragPos);//視覺方向;//計算定向光;vec3 result = CalcDirLight(dirlight,norm,viewDir);//計算點光照;for(int i = 0; i < NR_POINT_LIGHTS; i++)result += CalcPointLight(pointlights[i],norm, FragPos, viewDir); //計算聚光;result += CalcSpotLight(spotlight, norm, FragPos, viewDir); //實現(xiàn)反射;//---------------------//vec3 I = normalize(FragPos - viewPos);//vec3 R = reflect(I,normalize(Normal));//result += texture(material.skybox,R).rgb;//---------------------//實現(xiàn)折射;運用rafract函數(shù);float ratio = 1.0/1.52;vec3 I = normalize(FragPos - viewPos);vec3 R = refract(I,normalize(Normal),ratio);result += texture(material.skybox,R).rgb;//輸出最終片段;FragColor = vec4(result,1.0);//最終片段的顏色是三種光照的相加值; } //change; //一個用于計算定向光最后的顏色函數(shù);(返回的是一個vec3的向量); vec3 CalcDirLight(DirLight light,vec3 normal,vec3 viewDir)//函數(shù)傳入的是定向光的結(jié)構(gòu)體,以及片段已經(jīng)單位化的法線,還有viewDir向量; {vec3 lightDir = normalize(-light.direction);//漫反射著色;float diff = max(dot(normal, lightDir), 0.0);//鏡面著色;vec3 reflectDir = reflect(-lightDir,normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);//進行合并;return CalLightSum(light.ambient, light.diffuse, light.specular, diff, spec);; }//change; //一個用于計算點光源最終輸出顏色的函數(shù); vec3 CalcPointLight(PointLight light,vec3 normal,vec3 fragPos,vec3 viewDir)//normal已經(jīng)單位化; {vec3 lightDir = normalize(light.position - fragPos);// 漫反射著色float diff = max(dot(normal, lightDir), 0.0);// 鏡面光著色vec3 reflectDir = reflect(-lightDir, normal);float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);// 衰減float distance = length(light.position - fragPos);float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); return CalLightSum(light.ambient, light.diffuse, light.specular, diff, spec)*attenuation; }//change; //一個用于計算聚光最終輸出的函數(shù); //傳入聚光結(jié)構(gòu)體數(shù)組,單位化后的法線,片段位置,以及viewDir視角方向; vec3 CalcSpotLight(SpotLight light,vec3 normal,vec3 fragPos,vec3 viewDir) {vec3 lightDir = normalize(light.position - fragPos);//光線方向;//漫反射著色;float diff = max(0.0,dot(lightDir,normal));//鏡面光著色;vec3 reflectDir = reflect(-lightDir,normal);float spec = pow(max(0.0,dot(reflectDir,viewDir)),material.shininess);//衰減;float distance = length(light.position - fragPos);float atten = 1.0 / (light.constant+distance * light.linear+light.quadratic*(distance*distance));//柔和;float theta = dot(lightDir,normalize(-light.direction));float epison = light.cutOff - light.outerCutOff;float intensity = clamp((theta - light.outerCutOff) / epison,0.0,1.0);//利用clamp將柔和限制在0到1之間;//進行整合;return CalLightSum(light.ambient, light.diffuse, light.specular, diff, spec)*atten*intensity; }//創(chuàng)建一個用來計算漫反射,環(huán)境光,以及高光三者顏色綜合的一個函數(shù); vec3 CalLightSum(vec3 lightAmbient, vec3 lightDiffuse, vec3 lightSpecular, float diff, float spec) {vec3 diffuse = vec3(0.0,0.0,0.0);for(int i = 0; i < material.texture_diffuse_num; i++){diffuse += texture(material.texture_diffuse[i], TexCoords).rgb;}vec3 ambient = lightAmbient * diffuse;diffuse = lightDiffuse * diff * diffuse;vec3 specular = vec3(0.0,0.0,0.0);for(int i = 0; i < material.texture_specular_num; i++){specular += texture(material.texture_specular[i], TexCoords).rgb;}specular = lightSpecular * spec * specular;return ambient + diffuse + specular; }練習(xí)
嘗試在我們之前在模型加載小節(jié)中創(chuàng)建的模型加載器中引入反射貼圖。你可以在這里找到升級后有反射貼圖的納米裝模型。仍有幾點要注意的:
Assimp在大多數(shù)格式中都不太喜歡反射貼圖,所以我們需要欺騙一下它,將反射貼圖儲存為漫反射貼圖。你可以在加載材質(zhì)的時候?qū)?span id="ze8trgl8bvbq" class="kdocs-color" style="color:#C21C13;">反射貼圖的紋理類型設(shè)置為aiTextureType_AMBIENT。
由于模型加載器本身就已經(jīng)在著色器中占用了3個紋理單元了,你需要將天空盒綁定到第4個紋理單元上,因為我們要從同一個著色器中對天空盒采樣。
修改代碼如下:
struct Material {sampler2D texture_diffuse1;sampler2D texture_specular1;sampler2D texture_reflection1;samplerCube texture1;sampler2D texture_normal1;sampler2D texture_height1;float shininess; };然后在計算反射顏色時將反射貼圖顏色和天空盒顏色相乘,加在結(jié)果上
vec3 R = reflect(- viewDir, norm);vec3 reflectMap = vec3(texture(material.texture_reflection1, TexCoords));vec3 reflection = vec3(texture(material.texture1, R).rgb) * reflectMap * 2;那么讀取的時候怎么加載這個紋理呢?
Assimp在大多數(shù)格式中都不太喜歡反射貼圖,所以我們需要欺騙一下它,將反射貼圖儲存為漫反射貼圖。你可以在加載材質(zhì)的時候在model.h里面將反射貼圖的紋理類型設(shè)置為aiTextureType_AMBIENT,加入textures
// 1. diffuse mapsvector<Texture> diffuseMaps = loadMaterialTextures(material, aiTextureType_DIFFUSE, "texture_diffuse");textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());// 2. specular mapsvector<Texture> specularMaps = loadMaterialTextures(material, aiTextureType_SPECULAR, "texture_specular");textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());// 3. normal mapsstd::vector<Texture> normalMaps = loadMaterialTextures(material, aiTextureType_HEIGHT, "texture_normal");textures.insert(textures.end(), normalMaps.begin(), normalMaps.end());// 4. height mapsstd::vector<Texture> heightMaps = loadMaterialTextures(material, aiTextureType_AMBIENT, "texture_height");textures.insert(textures.end(), heightMaps.begin(), heightMaps.end());// 5. reflection mapsstd::vector<Texture> reflectionMaps = loadMaterialTextures(material, aiTextureType_AMBIENT, "texture_reflection");textures.insert(textures.end(), reflectionMaps.begin(), reflectionMaps.end());然后在mesh.h里面激活綁定反射貼圖和天空盒貼圖,由于模型加載器本身就已經(jīng)在著色器中占用了3個紋理單元了,我們需要將天空盒綁定到第4個紋理單元上,因為我們要從同一個著色器中對天空盒采樣。
shader.setInt("material.texture1", 3); glActiveTexture(GL_TEXTURE3); glBindTexture(GL_TEXTURE_CUBE_MAP, texture); if (name == "texture_diffuse")number = std::to_string(diffuseNr++); else if (name == "texture_specular")number = std::to_string(specularNr++); // transfer unsigned int to string else if (name == "texture_normal")number = std::to_string(normalNr++); // transfer unsigned int to string else if (name == "texture_height")number = std::to_string(heightNr++); // transfer unsigned int to string else if (name == "texture_reflection") number = std::to_string(reflectionNr++); // transfer unsigned int to string // now set the sampler to the correct texture unit shader.setInt(("material." + name + number).c_str(), i); // and finally bind the texture glBindTexture(GL_TEXTURE_2D, textures[i].id);結(jié)果:
總結(jié)
以上是生活随笔為你收集整理的LearnOpenGL->立方体贴图的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 嵌入式Linux MIPI接口LCD调试
- 下一篇: 蒲公英 · JELLY技术周刊 Vol.