✠OpenGL-9-天空和背景
Posted itzyjr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了✠OpenGL-9-天空和背景相关的知识,希望对你有一定的参考价值。
天空盒
或
天空穹顶
提供了有效且相对简单的方法,用来生成令人信服的地平线景观。
天空盒
- 如何为地平线制作纹理?
立方体有 6 个面,我们需要为这些面都添加纹理。
一种方法是使用 6 个图像文件和 6 个纹理单元。
另一种常见(且高效)的方式则是使用一个包含 6 个面的纹理的图像。
使用纹理立方体贴图为立方体添加纹理需要指定适当的纹理坐标。下图展示了纹理坐
标的分布,这些坐标接着会分配给立方体的每个顶点。
- 如何让天空盒看起来“距离很远”?
通过使用以下两个技巧,可以使天空盒显得巨大(从而感觉距离很远):
(a)禁用深度测试并先渲染天空盒(在渲染场景中的其他对象时重新启用深度测试);
(b)天空盒随相机移动(如果相机需要移动)。
通过在禁用深度测试的情况下先绘制天空盒,深度缓冲器的值仍将全设为 1.0(即最远
距离)。因此,场景中的所有其他对象将被完全渲染,即天空盒不会阻挡任何其他对象。这
样,无论天空盒的实际大小如何,会使天空盒的各面的位置看起来比其他物体都更远。而
实际的天空盒立方体本身可以非常小,只要它在相机移动时随相机一起移动即可。下图展示了从天空盒内部查看简单的场景。
场景中可见的天空盒部分是立方体贴图的最右侧部分。这是因为摄像机处于默认方向,面向−Z 方向,因此正在观察天空盒立方体的背面(如“立方体贴图纹理坐标”图所示)。另请注意,立方体贴图的背面在场景中渲染时会呈水平反转状态;这是因为立方体贴图的“背面”部分已经折叠在相机周围,因此看起来是经过侧向翻转的(如文章顶部两图所示)。 - 如何构建纹理立方体贴图?
从图稿或照片构建纹理立方体贴图图像时,需要注意避免在立方体面交汇点处的“接缝”,并创建正确的透视图,才能让天空盒看起来逼真且无畸变。有许多工具可以辅助达成这一目标: Terragen、 Autodesk 3Ds Max、 Blender 和 Adobe Photoshop 都有用于构建或处理立方体贴图的工具。同时,还有许多网站提供各种现成的立方体地图。
天空穹顶
建立地平线效果的另一种方法是使用天空穹顶
。除了使用带纹理的球体(或半球体)代替带纹理的立方体,其基本思路与天空盒相同。与天空盒相同,我们首先渲染天空穹顶(禁用深度测试),并将摄像机保持在天空穹顶的中心位置(下图中的天空穹顶纹理是使用Terragen这个工具制作的)。
天空穹顶相比天空盒有自己的优势。例如,它们不易受到畸变和接缝的影响(尽管在纹理图像中必须考虑极点处的球形畸变)。
天空穹顶的缺点之一则是球体或穹顶模型比立方体模型更复杂,天空穹顶有更多的顶点,其数量取决于期望的精度。
当使用天空穹顶呈现室外场景时,通常与地平面或某种地形相结合。当使用天空穹顶呈现宇宙中的场景(例如星空)时,使用下图所示的球体通常更为实际(为了清晰地使球体可视化,球体表面添加了一道虚线)。
实现天空盒
尽管天空穹顶有许多优点,天空盒仍然更为常见。 OpenGL 对天空盒的支持也更好,在进行环境贴图时更方便(本章后面会介绍)。出于这些原因,我们将专注于天空盒的实现。天空盒有两种实现方法:从头开始构建一个简单的天空盒;或使用 OpenGL 中的立方体贴图工具。它们有各自的优点。
从头开始构建天空盒
这里,我们将看到如何简单地启用和禁用深度测试(只需要一行代码)。场景中仅包含一个带纹理的环面。
//所有变量声明,构造函数和 init()与之前相同
. . .
void display(GLFWwindow* window, double currentTime) {
// 清除颜色缓冲区和深度缓冲区,并像之前一样创建投影视图矩阵和摄像机视图矩阵
. . .
vMat = glm::translate(glm::mat4(1.0f), glm::vec3(-cameraX, -cameraY, -cameraZ));
glUseProgram(renderingProgram);
// 准备首先绘制天空盒。 M 矩阵将天空盒放置在摄像机位置
// 注:mMat当然不是负相机位置,因为相机在世界空间中位置就是(cameraX,cameraY,cameraZ)
mMat = glm::translate(glm::mat4(1.0f), glm::vec3(cameraX, cameraY, cameraZ));
// 构建 MODEL-VIEW 矩阵
mvMat = vMat * mMat;
// 如前,将 MV 和 PROJ 矩阵放入统一变量
. . .
// 设置包含顶点的缓冲区
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);
// 立方体缠绕顺序是顺时针的,但我们从内部查看,因此使用逆时针缠绕顺序:GL_CCW
glFrontFace(GL_CCW);
// 在[没有深度测试的情况下]绘制天空盒
glDisable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);
glEnable(GL_DEPTH_TEST);
//现在像之前一样绘制场景中的对象
. . .
glDrawElements( . . . ); //和之前的场景中的对象一样
}
void setupVertices(void) {
// cube_vertices 定义与之前相同
// 天空盒的立方体纹理坐标,如图“立方体贴图纹理坐标”所示
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 // 上面左上角
};
//像往常一样为立方体和场景对象设置缓冲区
}
//用于加载着色器、纹理、 main()等的模块,如前
给的天空盒图片如下:
程序运行效果如下:
天空盒容易受到图像畸变和接缝的影响。 接缝指两个纹理图像接触的地方(比
如沿着立方体的边缘)有时出现的可见线条。下图展示了一个图像上半部分出现接缝的示例,它是运行上面程序时出现的伪影。为了避免接缝,需要仔细构建立方体贴图图像,并分配精确的纹理坐标。有一些工具可以用来沿图像边缘减少接缝(例如GNU Image Manipulation Program)。
使用OpenGL立方体贴图
构建天空盒的另一种方法是使用 OpenGL纹理立方体贴图
。 OpenGL 立方体贴图比我们在上面看到的简单方法稍微复杂一点。但是,使用 OpenGL 立方体贴图有自己的优点,例如减少接缝以及支持环境贴图。
OpenGL 纹理立方体贴图类似于稍后将要研究的 3D 纹理,它们都使用 3 个纹理坐标访问——通常标记为(s, t, r)。
OpenGL 纹理立方体贴图其中的图像以纹理图像的【左上角】为(0, 0, 0)。
上面程序通过读入单个图像来为立方体贴图添加纹理,而这里将用loadCubeMap()函数读入6个单独的立方体面图像文件。
在这里,SOIL2用于实例化和加载OpenGL立方体贴图也非常方便。
在使用OpenGL立方体贴图时,无须垂直翻转纹理,OpenGL会自动处理。
init()函数现在包含一个函数调用以启用 GL_TEXTURE_CUBE_MAP_SEAMLESS, 它告诉 OpenGL 尝试混合立方体相邻的边以减少或消除接缝。
在 display()中,立方体的顶点像以前一样沿管线向下发送,但这次不需要发送立方体的纹理坐标。我们将会看到, OpenGL 纹理立方体贴图通常使用立方体的顶点位置作为其纹理坐标。之后禁用深度测试并绘制立方体。然后为场景的其余部分重新启用深度测试。
完成后的 OpenGL 纹理立方体贴图使用了 int 类型的标识符进行引用。与阴影贴图时一样,通过将纹理包裹模式设置为“夹紧到边缘”,可以减少沿边框的伪影。在这种情况下,它还可以帮助进一步缩小接缝。请注意,这里需要为 3 个纹理坐标 s、 t 和 r 都设置纹理包裹模式。
在片段着色器中使用名为 samplerCube
的特殊类型的采样器访问纹理。在纹理立方体贴图中,从采样器返回的值是沿着方向向量(s, t, r)从原点“看到”的纹素。因此,我们通常可以简单地使用传入的插值顶点位置作为纹理坐标。在顶点着色器中,我们将立方体顶点位置分配到输出纹理坐标属性中,以便在它们到达片段着色器时进行插值。另外需要注意,在顶点着色器中,我们将传入的视图矩阵转换为 3× 3,然后再转换回 4× 4。这个“技巧”有效地移除了平移分量,同时保留了旋转。这样,就将立方体贴图固定在了摄像机位置,同时仍允许合成相机“环顾四周”。
分析矩阵中值的存储 及 mat4转mat3原理
如下分别为平移矩阵、缩放矩阵和绕轴旋转的变换矩阵:
(
平
移
变
换
)
(
X
+
T
x
Y
+
T
y
Z
+
T
z
1
)
=
[
1
0
0
T
x
0
1
0
T
y
0
0
1
T
z
0
0
0
1
]
×
(
X
Y
Z
1
)
(
缩
放
变
换
)
(
X
∗
S
x
Y
∗
S
y
Z
∗
S
z
1
)
=
[
S
x
0
0
0
0
S
y
0
0
0
0
S
z
0
0
0
0
1
]
×
(
X
Y
Z
1
)
(
绕
X
轴
旋
转
)
(
X
′
Y
′
Z
′
1
)
=
[
1
0
0
0
0
c
o
s
θ
−
s
i
n
θ
0
0
s
i
n
θ
c
o
s
θ
0
0
0
0
1
]
×
(
X
Y
Z
1
)
(
绕
Y
轴
旋
转
)
(
X
′
Y
′
Z
′
1
)
=
[
c
o
s
θ
0
s
i
n
θ
0
0
1
0
0
−
s
i
n
θ
0
c
o
s
θ
0
0
0
0
1
]
×
(
X
Y
Z
1
)
(
绕
Z
轴
旋
转
)
(
X
′
Y
′
Z
′
1
)
=
[
c
o
s
θ
−
s
i
n
θ
0
0
s
i
n
θ
c
o
s
θ
0
0
0
0
1
0
0
0
0
1
]
×
(
X
Y
Z
1
)
(平移变换) \\left( \\begin{array} { l } { X+T_x } \\\\ { Y+T_y } \\\\ { Z+T_z } \\\\ { 1 } \\end{array} \\right) = \\left[ \\begin{array} { l l l l } { 1 } & { 0 } & { 0 } & { T_x } \\\\ { 0 } & { 1 } & { 0 } & { T_y } \\\\ { 0 } & { 0 } & { 1 } & { T_z } \\\\ { 0 } & { 0 } & { 0 } & { 1 } \\end{array} \\right] \\times \\left( \\begin{array} { l } { X } \\\\ { Y } \\\\ { Z } \\\\ { 1 } \\end{array} \\right)\\\\(缩放变换) \\left( \\begin{array} { l } { X*S_x } \\\\ { Y*S_y } \\\\ { Z*S_z } \\\\ { 1 } \\end{array} \\right) = \\left[ \\begin{array} { l l l l } { S_x } & { 0 } & { 0 } & { 0 } \\\\ { 0 } & { S_y } & { 0 } & { 0 } \\\\ { 0 } & { 0 } & { S_z } & { 0 } \\\\ { 0 } & { 0 } & { 0 } & { 1 } \\end{array} \\right] \\times \\left( \\begin{array} { l } { X } \\\\ { Y } \\\\ { Z } \\\\ { 1 } \\end{array} \\right)\\\\(绕X轴旋转) \\left( \\begin{array} { l } { X' } \\\\ { Y' } \\\\ { Z' } \\\\ { 1 } \\end{array} \\right) = \\left[ \\begin{array} { l l l l } { 1 } & { 0 } & { 0 } & { 0 } \\\\ { 0 } & { cosθ } & { -sinθ } & { 0 } \\\\ { 0 } & { sinθ } & { cosθ } & { 0 } \\\\ { 0 } & { 0 } & { 0 } & { 1 } \\end{array} \\right] \\times \\left( \\begin{array} { l } { X } \\\\ { Y } \\\\ { Z } \\\\ { 1 } \\end{array} \\right)\\\\(绕Y轴旋转) \\left( \\begin{array} { l } { X' } \\\\ { Y' } \\\\ { Z' } \\\\ { 1 } \\end{array} \\right) = \\left[ \\begin{array} { l l l l } { cosθ } & { 0 } & { sinθ } & { 0 } \\\\ { 0 } & { 1 } & { 0 } & { 0 } \\\\ { -sinθ } & { 0 } & { cosθ } & { 0 } \\\\ { 0 } & { 0 } & { 0 } & { 1 } \\end{array} \\right] \\times \\left( \\begin{array} { l } { X } \\\\ { Y } \\\\ { Z } \\\\ { 1 } \\end{array} \\right)\\\\(绕Z轴旋转) \\left( \\begin{array} { l } { X' } \\\\ { Y' } \\\\ { Z' } \\\\ { 1 } \\end{array} \\right) = \\left[ \\begin{array} { l l l l } { cosθ } & { -sinθ } & { 0 } & { 0 } \\\\ { sinθ } & { cosθ } & { 0 } & { 0 } \\\\ { 0 } & { 0 } & { 1 } & { 0 } \\\\ { 0 } & { 0 } & { 0 } & { 1 } \\end{array} \\right] \\times \\left( \\begin{array} { l } { X } \\\\ { Y } \\\\ { Z } \\\\ { 1 } \\end{array} \\right)
(平移变换)⎝⎜⎜⎛X+TxY+TyZ+Tz1⎠⎟⎟⎞=⎣⎢⎢⎡100001000010TxTyTz1⎦⎥⎥⎤×⎝⎜⎜⎛Unity3D 灵巧小知识点☀️ | Unity 中 怎样切换 天空盒 背景