✠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 中 怎样切换 天空盒 背景

使用 Kotlin 更改片段中的按钮背景

005-unity3d 添加背景音乐音效 以及 天空盒子

炫酷 CSS 背景效果的 10 个代码片段

C4D如何制作纯白色背景,而且地面要有反射,忘不吝赐教,谢谢

AlertDialog 更改片段中的背景颜色 [重复]