對于室外3D場景,通??梢酝ㄟ^在地平線上創(chuàng)造一些逼真的效果,來增強其真實感。當我們極目遠眺,目光越過附近的建筑和森林,我們習慣于看到遠處的大型物體,例如:云、群山或太陽(或夜 空中的星星和月亮)。但是,將這些對象作為單個模型添加到場景中可能會產生高到無法承受的性能成本。天空盒或天空穹頂提供了有效 且相對簡單的方法,用來生成令人信服的地平線景觀。
9.1 天空盒
天空盒的概念非常巧妙而又簡單:
(1)實例化一個立方體對象;
(2)將立方體的紋理設置為所需的環(huán)境;
(3)將立方體圍繞相機放置。
我們已經知道如何完成以上這些步驟。但還有少量其他細節(jié)需要注意。
如何為地平線制作紋理?
立方體有6個面,我們需要為這些面都添加紋理。一種方法是使用6個圖像文件和6個紋理單元。另一種常見(且高效)的方式則是使用一個包含6個面的紋理的圖像,如圖9.1所示。
上例中的紋理立方體貼圖,僅用一個紋理單元,就可以為6個面添加紋理的圖像。立方體貼圖的6個部分對應于立方體的頂部、底部、正面、背面和兩側。當貼圖“包裹”在立方體周圍時,對于立方體內的相機而言,它扮演了地平線的角色,如圖9.2所示。
使用紋理立方體貼圖為立方體添加紋理需要指定適當?shù)募y理坐標。圖9.3展示了紋理坐標的分布,這些坐標接著會分配給立方體的每個頂點。
如何讓天空盒看起來“距離很遠”?
構建天空盒的另一個重要因素是確保紋理的表現(xiàn)看起來像是遠處的地平線。首先,人們可能會認為這需要構建巨大的天空盒。然而,事實證明這并不可取,因為巨大的天空盒會拉伸和扭曲紋理。相反,通過使用以下兩個技巧,可以使天空盒顯得巨大(從而感覺距離很遠):
(a)禁用深度測試并先渲染天空盒(在渲染場景中的其他對象時重新啟用深度測試);
(b)天空盒隨相機移動(如果相機需要移動)。
通過在禁用深度測試的情況下先繪制天空盒,深度緩沖器的值仍將全設為1.0(即最遠距離)。因此,場景中的所有其他對象將被完全渲染,即天空盒不會阻擋任何其他對象。這樣,無論天空盒的實際大小如何,會使天空盒的各面的位置看起來比其他物體都更遠。而實際的天空盒立方體本身可以非常小,只要它在相機移動時隨相機一起移動即可。圖9.4展示了從天空盒內部查看簡單的場景(實際上只有一個磚紋理環(huán)面)。
這里我們得益于對圖9.4與之前圖9.2和圖9.3的關系的仔細研究。 注意,場景中可見的天空盒部分是立方體貼圖的最右側部分。這是因為攝像機處于默認方向,面向?Z方向,因此正在觀察天空盒立方體的背面(如圖9.3所示)。另請注意,立方體貼圖的背面在場景中渲染時會呈水平反轉狀態(tài);這是因為立方體貼圖的“背面”部分已經折疊在相機周圍,因此看起來是經過側向翻轉的,如圖9.2所示。
如何構建紋理立方體貼圖?
從圖稿或照片構建紋理立方體貼圖圖像時,需要注意避免在立方體面交匯點處的“接縫”,并創(chuàng)建正確的透視圖,才能讓天空盒看起來逼真且無畸變。有許多工具可以輔助達成這一目標:Terragen、 Autodesk 3Ds Max、Blender和Adobe Photoshop都有用于構建或處理立方體貼圖的工具。同時,還有許多網站提供各種現(xiàn)成的立方體地圖,既有付費的,也有免費的。
9.2 天空穹頂
建立地平線效果的另一種方法是使用天空穹頂。除了使用帶紋理的球體(或半球體)代替帶紋理的立方體,其基本思路與天空盒相同。與天空盒相同,我們首先渲染天空穹頂(禁用深度測試),并將攝像機保持在天空穹頂?shù)闹行奈恢茫▓D9.5中的天空穹頂紋理是使用 Terragen [TE16]制作的)。
天空穹頂相比天空盒有自己的優(yōu)勢。例如,它們不易受到畸變和接縫的影響(盡管在紋理圖像中必須考慮極點處的球形畸變)。而天空穹頂?shù)娜秉c之一則是球體或穹頂模型比立方體模型更復雜,天空穹頂有更多的頂點,其數(shù)量取決于期望的精度。
當使用天空穹頂呈現(xiàn)室外場景時,通常與地平面或某種地形相結合。當使用天空穹頂呈現(xiàn)宇宙中的場景(例如星空)時,使用圖9.6所示的球體通常更為實際(為了清晰地使球體可視化,球體表面添加了一道虛線)。
9.3 實現(xiàn)天空盒
盡管天空穹頂有許多優(yōu)點,天空盒仍然更為常見。OpenGL對天空盒的支持也更好,在進行環(huán)境貼圖時更方便(本章后面會介紹)。出于這些原因,我們將專注于天空盒的實現(xiàn)。
天空盒有兩種實現(xiàn)方法:從頭開始構建一個簡單的天空盒;或使用OpenGL中的立方體貼圖工具。它們有各自的優(yōu)點,因此我們下面都會進行介紹。
9.3.1 從頭開始構建天空盒
我們已經涵蓋了構建簡單天空盒所需的幾乎所有內容。第4章介紹了立方體模型;分配紋理坐標已經在本章前面圖9.3中進行了展示;使用SOIL2庫讀取紋理以及在3D空間中放置對象也都已經在之前的章節(jié)進行過講解。這里,我們將看到如何簡單地啟用和禁用深度測試(只需要一行代碼)。
程序9.1展示了簡單天空盒的代碼結構,場景中僅包含一個帶紋理的環(huán)面。紋理坐標分配和啟用/禁用深度測試的調用已突出顯示。
程序9.1 簡單的天空盒
標準紋理著色器現(xiàn)在用于場景中的所有對象,包括立方體貼圖:
vertShader.glsl
#version 430
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 tex_coord;
out vec2 tc;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding = 0) uniform sampler2D s;
void main(void)
{
tc = tex_coord;
gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
}
fragShader.glsl
#version 430
in vec2 tc;
out vec4 fragColor;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding = 0) uniform sampler2D s;
void main(void)
{
fragColor = texture(s,tc);
}
main.cpp
#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <SOIL2\soil2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "Torus.h"
#include "Utils.h"
using namespace std;
float toRadians(float degrees) { return (degrees * 2.0f * 3.14159f) / 360.0f; }
#define numVAOs 1
#define numVBOs 5
float cameraX, cameraY, cameraZ;
float torLocX, torLocY, torLocZ;
GLuint renderingProgram;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint brickTexture, skyboxTexture;
float rotAmt = 0.0f;
// variable allocation for display
GLuint mvLoc, projLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat;
Torus myTorus(0.5f, 0.2f, 48);
int numTorusVertices, numTorusIndices;
void setupVertices(void) {
// cube_vertices定義與之前相同
// 天空盒的立方體紋理坐標,如圖9.3所示
float cubeVertexPositions[108] =
{ -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
};
float cubeTextureCoord[72] =
{ 1.00f, 0.66f, 1.00f, 0.33f, 0.75f, 0.33f, // 背面右下角
0.75f, 0.33f, 0.75f, 0.66f, 1.00f, 0.66f, // 背面左上角
0.75f, 0.33f, 0.50f, 0.33f, 0.75f, 0.66f, // 右面右下角
0.50f, 0.33f, 0.50f, 0.66f, 0.75f, 0.66f, // 右面左上角
0.50f, 0.33f, 0.25f, 0.33f, 0.50f, 0.66f, // 正面右下角
0.25f, 0.33f, 0.25f, 0.66f, 0.50f, 0.66f, // 正面左上角
0.25f, 0.33f, 0.00f, 0.33f, 0.25f, 0.66f, // 左面右下角
0.00f, 0.33f, 0.00f, 0.66f, 0.25f, 0.66f, // 左面左上角
0.25f, 0.33f, 0.50f, 0.33f, 0.50f, 0.00f, // 下面右下角
0.50f, 0.00f, 0.25f, 0.00f, 0.25f, 0.33f, // 下面左上角
0.25f, 1.00f, 0.50f, 1.00f, 0.50f, 0.66f, // 上面右下角
0.50f, 0.66f, 0.25f, 0.66f, 0.25f, 1.00f // 上面左上角
};
//像往常一樣為立方體和場景對象設置緩沖區(qū)
numTorusVertices = myTorus.getNumVertices();
numTorusIndices = myTorus.getNumIndices();
std::vector<int> ind = myTorus.getIndices();
std::vector<glm::vec3> vert = myTorus.getVertices();
std::vector<glm::vec2> tex = myTorus.getTexCoords();
std::vector<glm::vec3> norm = myTorus.getNormals();
std::vector<float> pvalues;
std::vector<float> tvalues;
std::vector<float> nvalues;
for (int i = 0; i < numTorusVertices; i++) {
pvalues.push_back(vert[i].x);
pvalues.push_back(vert[i].y);
pvalues.push_back(vert[i].z);
tvalues.push_back(tex[i].s);
tvalues.push_back(tex[i].t);
nvalues.push_back(norm[i].x);
nvalues.push_back(norm[i].y);
nvalues.push_back(norm[i].z);
}
glGenVertexArrays(1, vao);
glBindVertexArray(vao[0]);
glGenBuffers(numVBOs, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertexPositions) * 4, cubeVertexPositions, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeTextureCoord) * 4, cubeTextureCoord, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glBufferData(GL_ARRAY_BUFFER, pvalues.size() * 4, &pvalues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);
glBufferData(GL_ARRAY_BUFFER, tvalues.size() * 4, &tvalues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[4]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ind.size() * 4, &ind[0], GL_STATIC_DRAW);
}
void init(GLFWwindow* window) {
renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
glfwGetFramebufferSize(window, &width, &height);
aspect = (float)width / (float)height;
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
setupVertices();
brickTexture = Utils::loadTexture("brick1.jpg");
skyboxTexture = Utils::loadTexture("alien.jpg");
torLocX = 0.0f; torLocY = -0.75f; torLocZ = 0.0f;
cameraX = 0.0f; cameraY = 0.0f; cameraZ = 5.0f;
}
void display(GLFWwindow* window, double currentTime) {
// 清除顏色緩沖區(qū)和深度緩沖區(qū),并像之前一樣創(chuàng)建投影視圖矩陣和攝像機視圖矩陣
glClear(GL_DEPTH_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT);
vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
// 準備首先繪制天空盒。M矩陣將天空盒放置在攝像機位置
glUseProgram(renderingProgram);
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cameraX, cameraY, cameraZ));
// 構建MODEL-VIEW矩陣
mvMat = vMat * mMat;
// 如前,將MV和PROJ矩陣放入統(tǒng)一變量
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
// 設置包含頂點的緩沖區(qū)
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
//激活天空盒紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, skyboxTexture);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW); // 立方體纏繞順序是順時針的,但我們從內部查 看,因此使用逆時針纏繞順序GL_CCW
glDisable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);// 在沒有深度測試的情況下 繪制天空盒
glEnable(GL_DEPTH_TEST);
//現(xiàn)在像之前一樣繪制場景中的對象
glUseProgram(renderingProgram);
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(torLocX, torLocY, torLocZ));
mMat = glm::rotate(mMat, toRadians(15.0f), glm::vec3(1.0f, 0.0f, 0.0f));
mvMat = vMat * mMat;
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vbo[3]);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, brickTexture);
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glDisable(GL_LEQUAL);
glDrawArrays(GL_TRIANGLES, 0, 36);
glEnable(GL_DEPTH_TEST);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[4]);
glDrawElements(GL_TRIANGLES, numTorusIndices, GL_UNSIGNED_INT, 0);
}
void window_size_callback(GLFWwindow* win, int newWidth, int newHeight) {
aspect = (float)newWidth / (float)newHeight;
glViewport(0, 0, newWidth, newHeight);
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
}
int main(void) {
if (!glfwInit()) { exit(EXIT_FAILURE); }
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
GLFWwindow* window = glfwCreateWindow(800, 800, "Chapter9 - program1", NULL, NULL);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }
glfwSwapInterval(1);
glfwSetWindowSizeCallback(window, window_size_callback);
init(window);
while (!glfwWindowShouldClose(window)) {
display(window, glfwGetTime());
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
如前所述,天空盒容易受到圖像畸變和接縫的影響。接縫指兩個紋理圖像接觸的地方(比如沿著立方體的邊緣)有時出現(xiàn)的可見線條。圖9.8展示了一個圖像上半部分出現(xiàn)接縫的示例,它是運行程序 9.1時出現(xiàn)的偽影。為了避免接縫,需要仔細構建立方體貼圖圖像,并分配精確的紋理坐標。有一些工具可以用來沿圖像邊緣減少接縫(例 如[GI16]),不過這個主題超出了本書的范圍。
9.3.2 使用OpenGL立方體貼圖
構建天空盒的另一種方法是使用OpenGL紋理立方體貼圖。OpenGL 立方體貼圖比我們在上一節(jié)中看到的簡單方法稍微復雜一點。但是,使用OpenGL立方體貼圖有自己的優(yōu)點,例如減少接縫以及支持環(huán)境貼圖。
OpenGL紋理立方體貼圖類似于稍后將要研究的3D紋理,它們都使用3個紋理坐標訪問——通常標記為(s,t.r)
——而不是我們目前為止用到的兩個。OpenGL紋理立方體貼圖的另一個特性是,其中的圖像以紋理圖像的左上角(而不是通常的左下角)作為紋理坐標(0,0,0),這通常是混亂產生的源頭。
程序9.1中展示的方法通過讀入單個圖像來為立方體貼圖添加紋理,而程序9.2中展示的loadCubeMap()
函數(shù)則讀入6個單獨的立方體面圖像文件。正如我們在第5章中所學的,有許多方法可以讀取紋理圖像,我們選擇使用SOIL2庫。在這里,SOIL2用于實例化和加載OpenGL 立方體貼圖也非常方便。我們先找到需要讀入的文件,然后調用 SOIL_load_OGL_cubemap()
,其參數(shù)包括6個圖像文件和一些其他參數(shù),類似于我們在第5章中看到的SOIL_load_OGL_texture()
。在使用 OpenGL立方體貼圖時,無須垂直翻轉紋理,OpenGL會自動進行處理,注意,loadCubeMap()
函數(shù)放在“Utils.cpp”文件中。
init()函數(shù)現(xiàn)在包含一個函數(shù)調用以啟用 GL_TEXTURE_CUBE_MAP_SEAMLESS
,它告訴OpenGL嘗試混合立方體相鄰 的邊以減少或消除接縫。在display()
中,立方體的頂點像以前一樣沿管線向下發(fā)送,但這次不需要發(fā)送立方體的紋理坐標。我們將會看到,OpenGL紋理立方體貼圖通常使用立方體的頂點位置作為其紋理坐標。之后禁用深度測試并繪制立方體。然后為場景的其余部分重新啟用深度測試。
完成后的OpenGL紋理立方體貼圖使用了int類型的標識符進行引用。與陰影貼圖時一樣,通過將紋理包裹模式設置為“夾緊到邊緣”,可以減少沿邊框的偽影。在這種情況下,它還可以幫助進一步縮小接縫。請注意,這里需要為3個紋理坐標s、t和r都設置紋理包裹模式。
在片段著色器中使用名為samplerCube
的特殊類型的采樣器訪問紋理。在紋理立方體貼圖中,從采樣器返回的值是沿著方向向量(s,t,r)從原點“看到”的紋素。因此,我們通??梢院唵蔚厥褂脗魅氲牟逯淀旤c位置作為紋理坐標。在頂點著色器中,我們將立方體頂點位置分配到輸出紋理坐標屬性中,以便在它們到達片段著色器時進行插值。
另外需要注意,在頂點著色器中,我們將傳入的視圖矩陣轉換為 3×3,然后再轉換回4×4。這個“技巧”有效地移除了平移分量,同時保留了旋轉(回想一下,平移值在轉換矩陣的第四列中)。這樣,就將立方體貼圖固定在了攝像機位置,同時仍允許合成相機“環(huán)顧四周”。
程序9.2 OpenGL立方體貼圖天空盒
vertShader.glsl
#version 430
layout (location = 0) in vec3 position;
layout (location = 1) in vec2 tex_coord;
out vec2 tc;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding = 0) uniform sampler2D s;
void main(void)
{
tc = tex_coord;
gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
}
vertCShader.glsl
#version 430
layout (location = 0) in vec3 position;
out vec3 tc;
uniform mat4 v_matrix;
uniform mat4 p_matrix;
layout (binding = 0) uniform samplerCube samp;
void main(void)
{
tc = position;// 紋理坐標就是頂點坐標
mat4 v3_matrix = mat4(mat3(v_matrix));// 從視圖矩陣中刪除平移
gl_Position = p_matrix * v3_matrix * vec4(position,1.0);
}
fragShader.glsl
#version 430
in vec2 tc;
out vec4 fragColor;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
layout (binding = 0) uniform sampler2D s;
void main(void)
{
fragColor = texture(s,tc);
}
fragCShader.glsl
#version 430
in vec3 tc;
out vec4 fragColor;
uniform mat4 v_matrix;
uniform mat4 p_matrix;
layout (binding = 0) uniform samplerCube samp;
void main(void)
{
fragColor = texture(samp,tc);
}
main.cpp
#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <SOIL2\soil2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "Torus.h"
#include "Utils.h"
using namespace std;
float toRadians(float degrees) { return (degrees * 2.0f * 3.14159f) / 360.0f; }
#define numVAOs 1
#define numVBOs 4
float cameraX, cameraY, cameraZ;
float torLocX, torLocY, torLocZ;
GLuint renderingProgram, renderingProgramCubeMap;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint brickTexture, skyboxTexture;
float rotAmt = 0.0f;
// variable allocation for display
GLuint vLoc, mvLoc, projLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat;
Torus myTorus(0.8f, 0.4f, 48);
int numTorusVertices, numTorusIndices;
void setupVertices(void) {
float cubeVertexPositions[108] =
{ -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
};
numTorusVertices = myTorus.getNumVertices();
numTorusIndices = myTorus.getNumIndices();
std::vector<int> ind = myTorus.getIndices();
std::vector<glm::vec3> vert = myTorus.getVertices();
std::vector<glm::vec2> tex = myTorus.getTexCoords();
std::vector<glm::vec3> norm = myTorus.getNormals();
std::vector<float> pvalues;
std::vector<float> tvalues;
std::vector<float> nvalues;
for (int i = 0; i < numTorusVertices; i++) {
pvalues.push_back(vert[i].x);
pvalues.push_back(vert[i].y);
pvalues.push_back(vert[i].z);
tvalues.push_back(tex[i].s);
tvalues.push_back(tex[i].t);
nvalues.push_back(norm[i].x);
nvalues.push_back(norm[i].y);
nvalues.push_back(norm[i].z);
}
glGenVertexArrays(1, vao);
glBindVertexArray(vao[0]);
glGenBuffers(numVBOs, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertexPositions), cubeVertexPositions, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, pvalues.size() * 4, &pvalues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glBufferData(GL_ARRAY_BUFFER, tvalues.size() * 4, &tvalues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ind.size() * 4, &ind[0], GL_STATIC_DRAW);
}
void init(GLFWwindow* window) {
renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
renderingProgramCubeMap = Utils::createShaderProgram("vertCShader.glsl", "fragCShader.glsl");//注意這里
glfwGetFramebufferSize(window, &width, &height);
aspect = (float)width / (float)height;
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
setupVertices();
brickTexture = Utils::loadTexture("brick1.jpg");// 場景中的環(huán)面
skyboxTexture = Utils::loadCubeMap("cubeMap");// 包含天空盒紋理的文件夾
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
torLocX = 0.0f; torLocY = 0.0f; torLocZ = 0.0f;
cameraX = 0.0f; cameraY = 0.0f; cameraZ = 5.0f;
}
void display(GLFWwindow* window, double currentTime) {
glClear(GL_DEPTH_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT);
vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
// draw cube map
// 準備首先繪制天空盒—注意,現(xiàn)在它的渲染程序不同了
glUseProgram(renderingProgramCubeMap);
vLoc = glGetUniformLocation(renderingProgramCubeMap, "v_matrix");
glUniformMatrix4fv(vLoc, 1, GL_FALSE, glm::value_ptr(vMat));
projLoc = glGetUniformLocation(renderingProgramCubeMap, "p_matrix");
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
// 初始化立方體的頂點緩沖區(qū)(這里不再需要紋理坐標緩沖區(qū))
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
// 激活立方體貼圖紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
// 禁用深度測試,之后繪制立方體貼圖
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW); // cube is CW, but we are viewing the inside
glDisable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);
glEnable(GL_DEPTH_TEST);
// draw scene (in this case it is just a torus)
glUseProgram(renderingProgram);
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(torLocX, torLocY, torLocZ));
mMat = glm::rotate(mMat, toRadians(35.0f), glm::vec3(1.0f, 0.0f, 0.0f));
mvMat = vMat * mMat;
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, brickTexture);
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glDisable(GL_LEQUAL);
glDrawArrays(GL_TRIANGLES, 0, 36);
glEnable(GL_DEPTH_TEST);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glDrawElements(GL_TRIANGLES, numTorusIndices, GL_UNSIGNED_INT, 0);
}
void window_size_callback(GLFWwindow* win, int newWidth, int newHeight) {
aspect = (float)newWidth / (float)newHeight;
glViewport(0, 0, newWidth, newHeight);
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
}
int main(void) {
if (!glfwInit()) { exit(EXIT_FAILURE); }
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
GLFWwindow* window = glfwCreateWindow(800, 800, "Chapter9 - program2", NULL, NULL);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }
glfwSwapInterval(1);
glfwSetWindowSizeCallback(window, window_size_callback);
init(window);
while (!glfwWindowShouldClose(window)) {
display(window, glfwGetTime());
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
9.4 環(huán)境貼圖
在照明和材質章節(jié)中,我們考慮了物體的“光澤”。然而,我們從未對非常閃亮的物體進行建模,例如鏡子或鉻制品。這些物體在有小范圍鏡面高光的同時,還能夠反射出周圍物體的鏡像。當我們看向這些物品時,我們會看到房間里的其他東西,有時甚至會看到我們自己的倒影。ADS照明模型并沒有提供模擬這種效果的方法。
不過,紋理立方體貼圖提供了一種相對簡單的方法來模擬(至少部分模擬)反射表面。其訣竅是使用立方體貼圖來構造反射對象本身。[1]如果想要做得看起來真實,則需要找我們從物體上看到的周圍環(huán)境所對應的紋理坐標。
圖9.9展示了使用視圖向量和法向量組合計算反射向量的策略,之后,該反射向量會用來從立方體貼圖中查找紋素。因此,反射向量可用來直接訪問紋理立方體貼圖。當立方體貼圖用于上述功能時,稱其為環(huán)境貼圖。
我們在之前研究Blinn-Phong照明時計算過反射向量。除了我們現(xiàn)在使用反射向量從紋理貼圖中查找值,這里的反射向量概念和之前類似。這種技術稱為環(huán)境貼圖或反射貼圖。如果使用我們描述的第二種方法(在9.3.2小節(jié)中,使用OpenGL GL_TEXTURE_CUBE_MAP)實現(xiàn)立方體貼圖,那么OpenGL可以使用與之前為立方體添加紋理相同的方法來進行環(huán)境貼圖查找。我們使用視圖向量和曲面法向量計算視圖向量對應的離開對象表面的反射向量。然后可以使用反射向量直接對紋理立方體貼圖圖像進行采樣。查找過程由OpenGL samplerCube輔助實現(xiàn);
回憶上一節(jié)中,samplerCube使用視圖方向向量索引。因此,反射向量非常適用于查找所需的紋素。
實現(xiàn)環(huán)境貼圖需要添加相對少量的代碼。程序9.3展示了 display()函數(shù)和init()函數(shù)以及相關著色器中的更改,以使用環(huán)境貼圖渲染“反射”環(huán)面。所有更改都已經高亮顯示。值得注意的是,如果使用了Blinn-Phong照明,那么很多需要添加的代碼可能已經存在了。真正新的代碼部分在片段著色器中[在main()函數(shù)中]。
乍一看程序9.3中突出顯示的代碼好像并不是新代碼。實際上,在我們研究照明的時候,已經看到過幾乎相同的代碼。然而,在當前情況下,法向量和反射向量用于完全不同的目的。在之前的代碼中,它們用于實現(xiàn)ADS照明模型。而在這里,它們用于計算環(huán)境貼圖的紋理坐標。因此,我們將部分代碼高亮,以便讀者可以更輕松地追蹤法向量和反射向量計算的使用,以實現(xiàn)這一新目的。
渲染的結果會顯示使用了環(huán)境貼圖的“鉻制”環(huán)面,如圖9.10所示(見彩插)。
程序9.3 環(huán)境貼圖
vertShader.glsl
#version 430
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 normal;
out vec3 vNormal;
out vec3 vVertPos;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 normalMat;
layout (binding = 0) uniform samplerCube t;
void main(void)
{
vVertPos = (mv_matrix * vec4(position,1.0)).xyz;
vNormal = (normalMat * vec4(normal,1.0)).xyz;
gl_Position = proj_matrix * mv_matrix * vec4(position,1.0);
}
vertCShader.glsl
#version 430
layout (location = 0) in vec3 position;
out vec3 tc;
uniform mat4 v_matrix;
uniform mat4 p_matrix;
layout (binding = 0) uniform samplerCube samp;
void main(void)
{
tc = position;
mat4 v3_matrix = mat4(mat3(v_matrix));
gl_Position = p_matrix * v3_matrix * vec4(position,1.0);
}
fragShader.glsl
#version 430
in vec3 vNormal;
in vec3 vVertPos;
out vec4 fragColor;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
uniform mat4 normalMat;
layout (binding = 0) uniform samplerCube t;
void main(void)
{
vec3 r = -reflect(normalize(-vVertPos), normalize(vNormal));
fragColor = texture(t,r);
}
fragCShader.glsl
#version 430
in vec3 tc;
out vec4 fragColor;
uniform mat4 v_matrix;
uniform mat4 p_matrix;
layout (binding = 0) uniform samplerCube samp;
void main(void)
{
fragColor = texture(samp,tc);
}
main.cpp
#include <GL\glew.h>
#include <GLFW\glfw3.h>
#include <SOIL2\soil2.h>
#include <string>
#include <iostream>
#include <fstream>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "Torus.h"
#include "Utils.h"
using namespace std;
float toRadians(float degrees) { return (degrees * 2.0f * 3.14159f) / 360.0f; }
#define numVAOs 1
#define numVBOs 4
Utils util = Utils();
float cameraX, cameraY, cameraZ;
float torLocX, torLocY, torLocZ;
GLuint renderingProgram, renderingProgramCubeMap;
GLuint vao[numVAOs];
GLuint vbo[numVBOs];
GLuint skyboxTexture;
float rotAmt = 0.0f;
// variable allocation for display
GLuint vLoc, mvLoc, projLoc, nLoc;
int width, height;
float aspect;
glm::mat4 pMat, vMat, mMat, mvMat, invTrMat;
Torus myTorus(0.8f, 0.4f, 48);
int numTorusVertices, numTorusIndices;
void setupVertices(void) {
float cubeVertexPositions[108] =
{ -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
};
numTorusVertices = myTorus.getNumVertices();
numTorusIndices = myTorus.getNumIndices();
std::vector<int> ind = myTorus.getIndices();
std::vector<glm::vec3> vert = myTorus.getVertices();
std::vector<glm::vec2> tex = myTorus.getTexCoords();
std::vector<glm::vec3> norm = myTorus.getNormals();
std::vector<float> pvalues;
std::vector<float> tvalues;
std::vector<float> nvalues;
for (int i = 0; i < numTorusVertices; i++) {
pvalues.push_back(vert[i].x);
pvalues.push_back(vert[i].y);
pvalues.push_back(vert[i].z);
tvalues.push_back(tex[i].s);
tvalues.push_back(tex[i].t);
nvalues.push_back(norm[i].x);
nvalues.push_back(norm[i].y);
nvalues.push_back(norm[i].z);
}
glGenVertexArrays(1, vao);
glBindVertexArray(vao[0]);
glGenBuffers(numVBOs, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertexPositions), cubeVertexPositions, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glBufferData(GL_ARRAY_BUFFER, pvalues.size() * 4, &pvalues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glBufferData(GL_ARRAY_BUFFER, nvalues.size() * 4, &nvalues[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, ind.size() * 4, &ind[0], GL_STATIC_DRAW);
}
void init(GLFWwindow* window) {
renderingProgram = Utils::createShaderProgram("vertShader.glsl", "fragShader.glsl");
renderingProgramCubeMap = Utils::createShaderProgram("vertCShader.glsl", "fragCShader.glsl");
glfwGetFramebufferSize(window, &width, &height);
aspect = (float)width / (float)height;
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
setupVertices();
skyboxTexture = Utils::loadCubeMap("cubeMap"); // expects a folder name
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
torLocX = 0.0f; torLocY = 0.0f; torLocZ = 0.0f;
cameraX = 0.0f; cameraY = 0.0f; cameraZ = 5.0f;
}
void display(GLFWwindow* window, double currentTime) {
glClear(GL_DEPTH_BUFFER_BIT);
glClear(GL_COLOR_BUFFER_BIT);
vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
// draw cube map
glUseProgram(renderingProgramCubeMap);
vLoc = glGetUniformLocation(renderingProgramCubeMap, "v_matrix");
glUniformMatrix4fv(vLoc, 1, GL_FALSE, glm::value_ptr(vMat));
projLoc = glGetUniformLocation(renderingProgramCubeMap, "p_matrix");
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW); // cube is CW, but we are viewing the inside
glDisable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);
glEnable(GL_DEPTH_TEST);
// draw scene (in this case it is just a torus)
glUseProgram(renderingProgram);
// 矩陣變換的統(tǒng)一變量位置,包括法向量的變換
mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix");
projLoc = glGetUniformLocation(renderingProgram, "proj_matrix");
nLoc = glGetUniformLocation(renderingProgram, "normalMat");
// 構建MODEL矩陣,如前
rotAmt += 0.01f;
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(torLocX, torLocY, torLocZ));
mMat = glm::rotate(mMat, rotAmt, glm::vec3(1.0f, 0.0f, 0.0f));
mvMat = vMat * mMat;
// 構建MODEL-VIEW矩陣,如前
invTrMat = glm::transpose(glm::inverse(mvMat));
// 法向量變換現(xiàn)在在統(tǒng)一變量中
glUniformMatrix4fv(mvLoc, 1, GL_FALSE, glm::value_ptr(mvMat));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(pMat));
glUniformMatrix4fv(nLoc, 1, GL_FALSE, glm::value_ptr(invTrMat));
// 激活環(huán)面頂點緩沖區(qū),如前
glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
// 我們需要激活環(huán)面法向量緩沖區(qū)
glBindBuffer(GL_ARRAY_BUFFER, vbo[2]);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
// 環(huán)面紋理現(xiàn)在是立方體貼圖
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
// 繪制環(huán)面的過程未做更改
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_CULL_FACE);
glFrontFace(GL_CCW);
glDepthFunc(GL_LEQUAL);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[3]);
glDrawElements(GL_TRIANGLES, numTorusIndices, GL_UNSIGNED_INT, 0);
}
void window_size_callback(GLFWwindow* win, int newWidth, int newHeight) {
aspect = (float)newWidth / (float)newHeight;
glViewport(0, 0, newWidth, newHeight);
pMat = glm::perspective(1.0472f, aspect, 0.1f, 1000.0f);
}
int main(void) {
if (!glfwInit()) { exit(EXIT_FAILURE); }
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
GLFWwindow* window = glfwCreateWindow(800, 800, "Chapter9 - program2", NULL, NULL);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK) { exit(EXIT_FAILURE); }
glfwSwapInterval(1);
glfwSetWindowSizeCallback(window, window_size_callback);
init(window);
while (!glfwWindowShouldClose(window)) {
display(window, glfwGetTime());
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
雖然該場景需要兩組著色器——一組用于立方體貼圖,另一組用于環(huán)面——但是程序9.3中僅展示了用于繪制環(huán)面的著色器。這是因為用于渲染立方體貼圖的著色器與程序9.2中的相同。通過對程序9.2的修改得到程序9.3的過程,總結如下。
在init()函數(shù)中:
創(chuàng)建環(huán)面的法向量緩沖區(qū)[實際上在setupVertices()中完成,由 init()調用];不再需要環(huán)面的紋理坐標緩沖區(qū)。
在display()函數(shù)中:
創(chuàng)建用于變換法向量的矩陣(在第7章中稱為“norm_matrix”)并將其連接到關聯(lián)的統(tǒng)一變量;
激活環(huán)面法向量緩沖區(qū); 激活紋理立方體貼圖為環(huán)面的紋理(而非之前的“磚”紋理)。
在頂點著色器中:將法向量和norm_matrix相加;輸出變換的頂點和法向量以備計算反射向量,與在照明和陰影章
節(jié)中所做的相似。
在片段著色器中:
以與照明章節(jié)中相似的方式計算反射向量;從紋理(現(xiàn)在是立方體貼圖)檢索輸出顏色,使用反射向量而非紋理坐標進行查找。
圖9.10中顯示的渲染結果是一個很好的例子,展示了通過簡單的技巧能夠實現(xiàn)強大的幻覺。通過在對象上簡單地繪制背景,我們使對象看起來有“金屬質感”,而根本沒有進行ADS材質建模。即使沒有任何ADS照明被整合到場景中,這種技巧也能讓人感覺光從物體反射出來。在這個例子中,我們甚至會感到在環(huán)面的左下方似乎有一個鏡面高光,因為立方體貼圖中包括太陽在水中反射的倒影。
補充說明
正如我們在第5章中第一次研究紋理時的情況一樣,使用SOIL2使得構建立方體貼圖和為立方體貼圖添加紋理變得容易。同時它也可能會有一些副作用,即阻擋用戶學習一些有用的OpenGL細節(jié)內容。當然,用戶也可以在沒有SOIL2的情況下實例化并加載OpenGL立方體貼圖。雖然該主題超出了本書的范圍,但基本步驟如下:
(1)使用C++工具讀取6個圖像文件(它們必須是正方形);
(2)使用glGenTextures()為立方體貼圖創(chuàng)建紋理及其整型引用;
(3)調用glBindTexture(),指定紋理的ID和 GL_TEXTURE_CUBE_MAP;
(4)使用glTexStorage2D()指定立方體貼圖的存儲需求;
(5)調用glTexImage2D()或glTexSubImage2D()將圖像分配給立方體的各個面。
更多有關在沒有SOIL2的情況下創(chuàng)建OpenGL立方體貼圖的詳細信息,請瀏覽互聯(lián)網上的一些相關教程[dV14], [GE16]。
如本章所述,環(huán)境貼圖的主要限制之一是它只能構建反射立方體貼圖內容的對象。在場景中渲染的其他對象并不會出現(xiàn)在使用貼圖模擬反射的對象中。這種限制是否可以接受取決于場景的性質。如果場 景中存在必須出現(xiàn)在鏡面或鉻制對象中的對象,則必須使用其他方法。一種常見的方法是使用模板緩沖區(qū)(在第8章中有提到),許多網 絡教程(例如[OV12]、[NE14]和[GR16])中都有描述,不過它超出了本書的范圍。
我們沒有介紹天空穹頂?shù)膶崿F(xiàn),雖然它們在某些方面可以說比天空盒更簡單,并且不易受到失真的影響,甚至用它實現(xiàn)環(huán)境貼圖也更簡單——至少在數(shù)學上——但OpenGL對立方體貼圖的支持常常使得天空盒更加實用。
在書后面部分涵蓋的主題中,天空盒和天幕在概念上可以說是最簡單的。然而,讓它們看起來令人信服可能會耗費大量時間。我們只簡要介紹了可能出現(xiàn)的一些問題(例如接縫),但根據使用的紋理圖像文件,可能會出現(xiàn)其他問題,需要額外修復。尤其是在動畫場景中 或相機可以通過交互進行移動時。
我們還大致介紹了如何生成可用且令人信服的紋理立方體貼圖圖像。這方面有許多優(yōu)秀的工具,其中最受歡迎的是Terragen [TE16]。文章來源:http://www.zghlxwxcb.cn/news/detail-483345.html
本章中的所有立方體貼圖均由作者使用Terragen制作(圖9.6中的星域圖除外)。文章來源地址http://www.zghlxwxcb.cn/news/detail-483345.html
到了這里,關于計算機圖形學與opengl C++版 學習筆記 第9章 天空和背景的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!