对于室外3D场景,通常可以通过在地平线上创造一些逼真的效果,来增强其真实感。当我们极目远眺,目光越过附近的建筑和森林,我们习惯于看到远处的大型物体,例如:云、群山或太阳(或夜 空中的星星和月亮)。但是,将这些对象作为单个模型添加到场景中可能会产生高到无法承受的性能成本。天空盒或天空穹顶提供了有效 且相对简单的方法,用来生成令人信服的地平线景观。
9.1 天空盒
天空盒的概念非常巧妙而又简单:
(1)实例化一个立方体对象;
(2)将立方体的纹理设置为所需的环境;
(3)将立方体围绕相机放置。
我们已经知道如何完成以上这些步骤。但还有少量其他细节需要注意。
如何为地平线制作纹理?
立方体有6个面,我们需要为这些面都添加纹理。一种方法是使用6个图像文件和6个纹理单元。另一种常见(且高效)的方式则是使用一个包含6个面的纹理的图像,如图9.1所示。
上例中的纹理立方体贴图,仅用一个纹理单元,就可以为6个面添加纹理的图像。立方体贴图的6个部分对应于立方体的顶部、底部、正面、背面和两侧。当贴图“包裹”在立方体周围时,对于立方体内的相机而言,它扮演了地平线的角色,如图9.2所示。
使用纹理立方体贴图为立方体添加纹理需要指定适当的纹理坐标。图9.3展示了纹理坐标的分布,这些坐标接着会分配给立方体的每个顶点。
如何让天空盒看起来“距离很远”?
构建天空盒的另一个重要因素是确保纹理的表现看起来像是远处的地平线。首先,人们可能会认为这需要构建巨大的天空盒。然而,事实证明这并不可取,因为巨大的天空盒会拉伸和扭曲纹理。相反,通过使用以下两个技巧,可以使天空盒显得巨大(从而感觉距离很远):
(a)禁用深度测试并先渲染天空盒(在渲染场景中的其他对象时重新启用深度测试);
(b)天空盒随相机移动(如果相机需要移动)。
通过在禁用深度测试的情况下先绘制天空盒,深度缓冲器的值仍将全设为1.0(即最远距离)。因此,场景中的所有其他对象将被完全渲染,即天空盒不会阻挡任何其他对象。这样,无论天空盒的实际大小如何,会使天空盒的各面的位置看起来比其他物体都更远。而实际的天空盒立方体本身可以非常小,只要它在相机移动时随相机一起移动即可。图9.4展示了从天空盒内部查看简单的场景(实际上只有一个砖纹理环面)。
这里我们得益于对图9.4与之前图9.2和图9.3的关系的仔细研究。 注意,场景中可见的天空盒部分是立方体贴图的最右侧部分。这是因为摄像机处于默认方向,面向−Z方向,因此正在观察天空盒立方体的背面(如图9.3所示)。另请注意,立方体贴图的背面在场景中渲染时会呈水平反转状态;这是因为立方体贴图的“背面”部分已经折叠在相机周围,因此看起来是经过侧向翻转的,如图9.2所示。
如何构建纹理立方体贴图?
从图稿或照片构建纹理立方体贴图图像时,需要注意避免在立方体面交汇点处的“接缝”,并创建正确的透视图,才能让天空盒看起来逼真且无畸变。有许多工具可以辅助达成这一目标:Terragen、 Autodesk 3Ds Max、Blender和Adobe Photoshop都有用于构建或处理立方体贴图的工具。同时,还有许多网站提供各种现成的立方体地图,既有付费的,也有免费的。
9.2 天空穹顶
建立地平线效果的另一种方法是使用天空穹顶。除了使用带纹理的球体(或半球体)代替带纹理的立方体,其基本思路与天空盒相同。与天空盒相同,我们首先渲染天空穹顶(禁用深度测试),并将摄像机保持在天空穹顶的中心位置(图9.5中的天空穹顶纹理是使用 Terragen [TE16]制作的)。
天空穹顶相比天空盒有自己的优势。例如,它们不易受到畸变和接缝的影响(尽管在纹理图像中必须考虑极点处的球形畸变)。而天空穹顶的缺点之一则是球体或穹顶模型比立方体模型更复杂,天空穹顶有更多的顶点,其数量取决于期望的精度。
当使用天空穹顶呈现室外场景时,通常与地平面或某种地形相结合。当使用天空穹顶呈现宇宙中的场景(例如星空)时,使用图9.6所示的球体通常更为实际(为了清晰地使球体可视化,球体表面添加了一道虚线)。
9.3 实现天空盒
尽管天空穹顶有许多优点,天空盒仍然更为常见。OpenGL对天空盒的支持也更好,在进行环境贴图时更方便(本章后面会介绍)。出于这些原因,我们将专注于天空盒的实现。
天空盒有两种实现方法:从头开始构建一个简单的天空盒;或使用OpenGL中的立方体贴图工具。它们有各自的优点,因此我们下面都会进行介绍。
9.3.1 从头开始构建天空盒
我们已经涵盖了构建简单天空盒所需的几乎所有内容。第4章介绍了立方体模型;分配纹理坐标已经在本章前面图9.3中进行了展示;使用SOIL2库读取纹理以及在3D空间中放置对象也都已经在之前的章节进行过讲解。这里,我们将看到如何简单地启用和禁用深度测试(只需要一行代码)。
程序9.1展示了简单天空盒的代码结构,场景中仅包含一个带纹理的环面。纹理坐标分配和启用/禁用深度测试的调用已突出显示。
程序9.1 简单的天空盒
标准纹理着色器现在用于场景中的所有对象,包括立方体贴图:
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 // 上面左上角
};
//像往常一样为立方体和场景对象设置缓冲区
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) {
// 清除颜色缓冲区和深度缓冲区,并像之前一样创建投影视图矩阵和摄像机视图矩阵
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矩阵放入统一变量
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));
// 设置包含顶点的缓冲区
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);
//现在像之前一样绘制场景中的对象
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);
}
如前所述,天空盒容易受到图像畸变和接缝的影响。接缝指两个纹理图像接触的地方(比如沿着立方体的边缘)有时出现的可见线条。图9.8展示了一个图像上半部分出现接缝的示例,它是运行程序 9.1时出现的伪影。为了避免接缝,需要仔细构建立方体贴图图像,并分配精确的纹理坐标。有一些工具可以用来沿图像边缘减少接缝(例 如[GI16]),不过这个主题超出了本书的范围。
9.3.2 使用OpenGL立方体贴图
构建天空盒的另一种方法是使用OpenGL纹理立方体贴图。OpenGL 立方体贴图比我们在上一节中看到的简单方法稍微复杂一点。但是,使用OpenGL立方体贴图有自己的优点,例如减少接缝以及支持环境贴图。
OpenGL纹理立方体贴图类似于稍后将要研究的3D纹理,它们都使用3个纹理坐标访问——通常标记为(s,t.r)
——而不是我们目前为止用到的两个。OpenGL纹理立方体贴图的另一个特性是,其中的图像以纹理图像的左上角(而不是通常的左下角)作为纹理坐标(0,0,0),这通常是混乱产生的源头。
程序9.1中展示的方法通过读入单个图像来为立方体贴图添加纹理,而程序9.2中展示的loadCubeMap()
函数则读入6个单独的立方体面图像文件。正如我们在第5章中所学的,有许多方法可以读取纹理图像,我们选择使用SOIL2库。在这里,SOIL2用于实例化和加载OpenGL 立方体贴图也非常方便。我们先找到需要读入的文件,然后调用 SOIL_load_OGL_cubemap()
,其参数包括6个图像文件和一些其他参数,类似于我们在第5章中看到的SOIL_load_OGL_texture()
。在使用 OpenGL立方体贴图时,无须垂直翻转纹理,OpenGL会自动进行处理,注意,loadCubeMap()
函数放在“Utils.cpp”文件中。
init()函数现在包含一个函数调用以启用 GL_TEXTURE_CUBE_MAP_SEAMLESS
,它告诉OpenGL尝试混合立方体相邻 的边以减少或消除接缝。在display()
中,立方体的顶点像以前一样沿管线向下发送,但这次不需要发送立方体的纹理坐标。我们将会看到,OpenGL纹理立方体贴图通常使用立方体的顶点位置作为其纹理坐标。之后禁用深度测试并绘制立方体。然后为场景的其余部分重新启用深度测试。
完成后的OpenGL纹理立方体贴图使用了int类型的标识符进行引用。与阴影贴图时一样,通过将纹理包裹模式设置为“夹紧到边缘”,可以减少沿边框的伪影。在这种情况下,它还可以帮助进一步缩小接缝。请注意,这里需要为3个纹理坐标s、t和r都设置纹理包裹模式。
在片段着色器中使用名为samplerCube
的特殊类型的采样器访问纹理。在纹理立方体贴图中,从采样器返回的值是沿着方向向量(s,t,r)从原点“看到”的纹素。因此,我们通常可以简单地使用传入的插值顶点位置作为纹理坐标。在顶点着色器中,我们将立方体顶点位置分配到输出纹理坐标属性中,以便在它们到达片段着色器时进行插值。
另外需要注意,在顶点着色器中,我们将传入的视图矩阵转换为 3×3,然后再转换回4×4。这个“技巧”有效地移除了平移分量,同时保留了旋转(回想一下,平移值在转换矩阵的第四列中)。这样,就将立方体贴图固定在了摄像机位置,同时仍允许合成相机“环顾四周”。
程序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");// 场景中的环面
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
// 准备首先绘制天空盒—注意,现在它的渲染程序不同了
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);
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 环境贴图
在照明和材质章节中,我们考虑了物体的“光泽”。然而,我们从未对非常闪亮的物体进行建模,例如镜子或铬制品。这些物体在有小范围镜面高光的同时,还能够反射出周围物体的镜像。当我们看向这些物品时,我们会看到房间里的其他东西,有时甚至会看到我们自己的倒影。ADS照明模型并没有提供模拟这种效果的方法。
不过,纹理立方体贴图提供了一种相对简单的方法来模拟(至少部分模拟)反射表面。其诀窍是使用立方体贴图来构造反射对象本身。[1]如果想要做得看起来真实,则需要找我们从物体上看到的周围环境所对应的纹理坐标。
图9.9展示了使用视图向量和法向量组合计算反射向量的策略,之后,该反射向量会用来从立方体贴图中查找纹素。因此,反射向量可用来直接访问纹理立方体贴图。当立方体贴图用于上述功能时,称其为环境贴图。
我们在之前研究Blinn-Phong照明时计算过反射向量。除了我们现在使用反射向量从纹理贴图中查找值,这里的反射向量概念和之前类似。这种技术称为环境贴图或反射贴图。如果使用我们描述的第二种方法(在9.3.2小节中,使用OpenGL GL_TEXTURE_CUBE_MAP)实现立方体贴图,那么OpenGL可以使用与之前为立方体添加纹理相同的方法来进行环境贴图查找。我们使用视图向量和曲面法向量计算视图向量对应的离开对象表面的反射向量。然后可以使用反射向量直接对纹理立方体贴图图像进行采样。查找过程由OpenGL samplerCube辅助实现;
回忆上一节中,samplerCube使用视图方向向量索引。因此,反射向量非常适用于查找所需的纹素。
实现环境贴图需要添加相对少量的代码。程序9.3展示了 display()函数和init()函数以及相关着色器中的更改,以使用环境贴图渲染“反射”环面。所有更改都已经高亮显示。值得注意的是,如果使用了Blinn-Phong照明,那么很多需要添加的代码可能已经存在了。真正新的代码部分在片段着色器中[在main()函数中]。
乍一看程序9.3中突出显示的代码好像并不是新代码。实际上,在我们研究照明的时候,已经看到过几乎相同的代码。然而,在当前情况下,法向量和反射向量用于完全不同的目的。在之前的代码中,它们用于实现ADS照明模型。而在这里,它们用于计算环境贴图的纹理坐标。因此,我们将部分代码高亮,以便读者可以更轻松地追踪法向量和反射向量计算的使用,以实现这一新目的。
渲染的结果会显示使用了环境贴图的“铬制”环面,如图9.10所示(见彩插)。
程序9.3 环境贴图
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);
// 矩阵变换的统一变量位置,包括法向量的变换
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));
// 法向量变换现在在统一变量中
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));
// 激活环面顶点缓冲区,如前
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, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(1);
// 环面纹理现在是立方体贴图
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxTexture);
// 绘制环面的过程未做更改
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);
}
虽然该场景需要两组着色器——一组用于立方体贴图,另一组用于环面——但是程序9.3中仅展示了用于绘制环面的着色器。这是因为用于渲染立方体贴图的着色器与程序9.2中的相同。通过对程序9.2的修改得到程序9.3的过程,总结如下。
在init()函数中:
创建环面的法向量缓冲区[实际上在setupVertices()中完成,由 init()调用];不再需要环面的纹理坐标缓冲区。
在display()函数中:
创建用于变换法向量的矩阵(在第7章中称为“norm_matrix”)并将其连接到关联的统一变量;
激活环面法向量缓冲区; 激活纹理立方体贴图为环面的纹理(而非之前的“砖”纹理)。
在顶点着色器中:将法向量和norm_matrix相加;输出变换的顶点和法向量以备计算反射向量,与在照明和阴影章
节中所做的相似。
在片段着色器中:
以与照明章节中相似的方式计算反射向量;从纹理(现在是立方体贴图)检索输出颜色,使用反射向量而非纹理坐标进行查找。
图9.10中显示的渲染结果是一个很好的例子,展示了通过简单的技巧能够实现强大的幻觉。通过在对象上简单地绘制背景,我们使对象看起来有“金属质感”,而根本没有进行ADS材质建模。即使没有任何ADS照明被整合到场景中,这种技巧也能让人感觉光从物体反射出来。在这个例子中,我们甚至会感到在环面的左下方似乎有一个镜面高光,因为立方体贴图中包括太阳在水中反射的倒影。
补充说明
正如我们在第5章中第一次研究纹理时的情况一样,使用SOIL2使得构建立方体贴图和为立方体贴图添加纹理变得容易。同时它也可能会有一些副作用,即阻挡用户学习一些有用的OpenGL细节内容。当然,用户也可以在没有SOIL2的情况下实例化并加载OpenGL立方体贴图。虽然该主题超出了本书的范围,但基本步骤如下:
(1)使用C++工具读取6个图像文件(它们必须是正方形);
(2)使用glGenTextures()为立方体贴图创建纹理及其整型引用;
(3)调用glBindTexture(),指定纹理的ID和 GL_TEXTURE_CUBE_MAP;
(4)使用glTexStorage2D()指定立方体贴图的存储需求;
(5)调用glTexImage2D()或glTexSubImage2D()将图像分配给立方体的各个面。
更多有关在没有SOIL2的情况下创建OpenGL立方体贴图的详细信息,请浏览互联网上的一些相关教程[dV14], [GE16]。
如本章所述,环境贴图的主要限制之一是它只能构建反射立方体贴图内容的对象。在场景中渲染的其他对象并不会出现在使用贴图模拟反射的对象中。这种限制是否可以接受取决于场景的性质。如果场 景中存在必须出现在镜面或铬制对象中的对象,则必须使用其他方法。一种常见的方法是使用模板缓冲区(在第8章中有提到),许多网 络教程(例如[OV12]、[NE14]和[GR16])中都有描述,不过它超出了本书的范围。
我们没有介绍天空穹顶的实现,虽然它们在某些方面可以说比天空盒更简单,并且不易受到失真的影响,甚至用它实现环境贴图也更简单——至少在数学上——但OpenGL对立方体贴图的支持常常使得天空盒更加实用。
在书后面部分涵盖的主题中,天空盒和天幕在概念上可以说是最简单的。然而,让它们看起来令人信服可能会耗费大量时间。我们只简要介绍了可能出现的一些问题(例如接缝),但根据使用的纹理图像文件,可能会出现其他问题,需要额外修复。尤其是在动画场景中 或相机可以通过交互进行移动时。
我们还大致介绍了如何生成可用且令人信服的纹理立方体贴图图像。这方面有许多优秀的工具,其中最受欢迎的是Terragen [TE16]。文章来源:https://www.toymoban.com/news/detail-483345.html
本章中的所有立方体贴图均由作者使用Terragen制作(图9.6中的星域图除外)。文章来源地址https://www.toymoban.com/news/detail-483345.html
到了这里,关于计算机图形学与opengl C++版 学习笔记 第9章 天空和背景的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!