✠opengl-9-天空和背景(代码片段)

itzyjr itzyjr     2022-12-11     463

关键词:


天空盒天空穹顶提供了有效且相对简单的方法,用来生成令人信服的地平线景观。

天空盒
  • 如何为地平线制作纹理?
    立方体有 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( \\beginarray l X+T_x \\\\ Y+T_y \\\\ Z+T_z \\\\ 1 \\endarray \\right) = \\left[ \\beginarray l l l l 1 & 0 & 0 & T_x \\\\ 0 & 1 & 0 & T_y \\\\ 0 & 0 & 1 & T_z \\\\ 0 & 0 & 0 & 1 \\endarray \\right] \\times \\left( \\beginarray l X \\\\ Y \\\\ Z \\\\ 1 \\endarray \\right)\\\\(缩放变换) \\left( \\beginarray l X*S_x \\\\ Y*S_y \\\\ Z*S_z \\\\ 1 \\endarray \\right) = \\left[ \\beginarray l l l l S_x & 0 & 0 & 0 \\\\ 0 & S_y & 0 & 0 \\\\ 0 & 0 & S_z & 0 \\\\ 0 & 0 & 0 & 1 \\endarray \\right] \\times \\left( \\beginarray l X \\\\ Y \\\\ Z \\\\ 1 \\endarray \\right)\\\\(绕X轴旋转) \\left( \\beginarray l X' \\\\ Y' \\\\ Z' \\\\ 1 \\endarray \\right) = \\left[ \\beginarray l l l l 1 & 0 & 0 & 0 \\\\ 0 & cosθ & -sinθ & 0 \\\\ 0 & sinθ & cosθ & 0 \\\\ 0 & 0 & 0 & 1 \\endarray \\right] \\times \\left( \\beginarray l X \\\\ Y \\\\ Z \\\\ 1 \\endarray \\right)\\\\(绕Y轴旋转) \\left( \\beginarray l X' \\\\ Y' \\\\ Z' \\\\ 1 \\endarray \\right) = \\left[ \\beginarray l l l l cosθ & 0 & sinθ & 0 \\\\ 0 & 1 & 0 & 0 \\\\ -sinθ & 0 & cosθ & 0 \\\\ 0 & 0 & 0 & 1 \\endarray \\right] \\times \\left( \\beginarray l X \\\\ Y \\\\ Z \\\\ 1 \\endarray \\right)\\\\(绕Z轴旋转) \\left( \\beginarray l X' \\\\ Y' \\\\ Z' \\\\ 1 \\endarray \\right) = \\left[ \\beginarray l l l l cosθ & -sinθ & 0 & 0 \\\\ sinθ & cosθ & 0 & 0 \\\\ 0 & 0 & 1 & 0 \\\\ 0 & 0 & 0 & 1 \\endarray \\right] \\times \\left( \\beginarray l X \\\\ Y \\\\ Z \\\\ 1 \\endarray \\right) ()X+TxY+TyZ+Tz1=100001000010TxTyTz1×查看详情

p1195口袋的天空(代码片段)

...背景小杉坐在教室里,透过口袋一样的窗户看口袋一样的天空。有很多云飘在那里,看起来很漂亮,小杉想摘下那样美的几朵云,做成棉花糖。题目描述给你云朵的个数N,再给你M个关系,表示哪些云朵可以连在一起。现在小杉... 查看详情

inavflight之mspdji协议天空端请求报文(代码片段)

iNavFlight之MSPDJI协议天空端请求报文1.报文格式(请求)2.报文标志(flag)3.报文命令(cmd)4.参考资料MSPDJI协议是用于DJI天空端与飞控端之间的通信协议,其工作模式符合C/S经典设计。这里我们重点介绍下天空端请求报文格式和命令。... 查看详情

小a和uim之大逃离(代码片段)

...道道闪电,一阵阵雷声。刹那间,狂风大作,乌云布满了天空,紧接着豆大的雨点从天空中打落下来,只见前方出现了一个披头散发、青面獠牙的怪物,低沉着声音说:“呵呵,既然你们来到这,只能活下来一个!”。小a和他... 查看详情

sh天空漏斗(代码片段)

查看详情

p1373小a和uim之大逃离(代码片段)

...道道闪电,一阵阵雷声。刹那间,狂风大作,乌云布满了天空,紧接着豆大的雨点从天空中打落下来,只见前方出现了一个披头散发、青面獠牙的怪物,低沉着声音说:“呵呵,既然你们来到这,只能活下来一个!”。小a... 查看详情

[p1373]小a和uim之大逃离(代码片段)

...道道闪电,一阵阵雷声。刹那间,狂风大作,乌云布满了天空,紧接着豆大的雨点从天空中打落下来,只见前方出现了一个披头散发、青面獠牙的怪物,低沉着声音说:“呵呵,既然你们来到这,只能活下来一个!”。小a... 查看详情

猿创征文|程序猿乘风破浪pythonpygame原创小游戏源码+解析(代码片段)

...别为:    (一)游戏背景        1、天空背景。用多条蓝色横线点缀的白色天空,使用Python代码让天空背景从右到左不停的移动,从而制造猴子向右移动的假象。天空背景背景精灵代码:classBackgroun... 查看详情

猿创征文|程序猿乘风破浪pythonpygame原创小游戏源码+解析(代码片段)

...别为:    (一)游戏背景        1、天空背景。用多条蓝色横线点缀的白色天空,使用Python代码让天空背景从右到左不停的移动,从而制造猴子向右移动的假象。天空背景背景精灵代码:classBackgroun... 查看详情

luogup1195口袋的天空(代码片段)

...题是学长推荐做的(我点名要水题/*超喜欢题面嗷漂亮的天空上有漂亮的云彩,漂亮的孩子做着漂亮的梦(而且立刻联想到小云彩嗷嗷嗷 qwq*/无视以上废话,看题其实和最小生成树的模版题没什么区别(悄咪咪表示某谷测试... 查看详情

小功能⭐️unity动态更换天空盒旋转天空盒(代码片段)

文章目录🟥Unity动态更换天空盒1️⃣方法12️⃣方法2🟧旋转天空盒🟥Unity动态更换天空盒1️⃣方法11、在摄像头上添加SkyBox组件放到其他地方不管用。2、创建SkyBox类型的的材质球。放入即可。3、通过代码,你便... 查看详情

观影目录(代码片段)

x记忆大师 青春派(从居然承认他失恋的那时候,《我的天空》作为背景音乐响起的时候,) 前任3  x  查看详情

opencv-实现天空变换(图像分割)(代码片段)

...作者获得授权,非商业转载请注明出处实现原理   天空变换是图像分割的一种应用,把图像中的天空与非天空区分割开,结合掩膜将天空更改为其他图像。如何较优地实现天空变换,与识别证件照类似,难... 查看详情

cesium之天空盒对应方位(代码片段)

下文讲解一下关于Cesium的天空盒具体方位。天空盒对应图一个立方体展开图,相当于一个站在negz的位置,背对电脑屏幕,对应关系如下negz→downposx→rightnegx→leftposy→backposz→upnegy→front分享一个天空盒网址www.custommapmakers.o... 查看详情

天空卫士c++一面(技术面61min)(代码片段)

天空卫士C++一面(技术面、61min)面试官问你现在是在做毕设吗?这学期在干什么呢?自我介绍~项目是老师带着做的还是怎么一回事呢?你是哪里人呢?看你得的奖挺多的你们班有多少人进实验室C跟... 查看详情

天空卫士c++一面(技术面61min)(代码片段)

天空卫士C++一面(技术面、61min)面试官问你现在是在做毕设吗?这学期在干什么呢?自我介绍~项目是老师带着做的还是怎么一回事呢?你是哪里人呢?看你得的奖挺多的你们班有多少人进实验室C跟... 查看详情

androidopengles2.0(十七)——球形天空盒vr效果实现(代码片段)

在3D游戏中通常都会用到天空盒,在3D引擎中也一般会存在天空盒组件,让开发者可以直接使用。那么天空盒是什么?天空盒又是如何实现的呢?本篇博客主要介绍如何在Android中利用OpenGLES绘制一个天空盒,并... 查看详情

unity3d灵巧小知识点☀️|unity中使用代码切换天空盒(代码片段)

Unity小科普老规矩,先介绍一下Unity的科普小知识:Unity是实时3D互动内容创作和运营平台。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助Unity将创意变成现实。Unity平台提供一整套完善的软件解... 查看详情