计算机图形学 您所在的位置:网站首页 纹理绘制和顶点着色的区别 计算机图形学

计算机图形学

2024-07-09 17:39| 来源: 网络整理| 查看: 265

计算机图形学 | 实验九:纹理贴图和天空盒 计算机图形学 | 实验九:纹理贴图和天空盒实验概述顶点数据立方体顶点数据天空盒顶点数组 纹理载入创建纹理纹理读取纹理绑定 使用纹理立方体着色器顶点着色器片元着色器 天空盒着色器顶点着色器片元着色器 立方体贴图和平面纹理对比着色器中的区别定义和设置上的区别纹理定义资源载入 天空盒位置的控制 总结

华中科技大学《计算机图形学》课程

MOOC地址:计算机图形学(HUST)

计算机图形学 | 实验九:纹理贴图和天空盒 实验概述

这次实验我们主要学习如何绘制带有平面纹理的立方体,以及运用立方体贴图实现的天空盒。

实验要求:

平面纹理(之前实验的基础上,在立方体贴上纹理)立方体贴图(天空盒,背景的天空盒采用立方体贴图)

实验最终的实现效果:

在这里插入图片描述

顶点数据 立方体顶点数据

这是立方体顶点数组,可以看到前三个float量是我们熟悉的顶点坐标位置,后面两个float量是顶点所对应的纹理UV坐标,通过这个UV坐标,我们可以控制将纹理上的哪一部分贴在三角形片元上。

const float vertices[] = { //立方体数组 -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f }; 天空盒顶点数组

紧接着我们看到天空盒的顶点数组。我们可以发现,天空盒的顶点数组是没有之前的纹理坐标的。

float skybox_vertices[] = { //天空盒顶点数组 -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f };

因为我们使用的是立方体贴图,天空盒的顶点坐标即可对应它纹理坐标。我们可以直接将顶点坐标作为立方体贴图的纹理坐标。

纹理载入

我们用平面纹理包裹住之前实验实现的立方体,用立方体贴图包裹住天空盒,即可得到我们最后需要的结果。

为了实现纹理贴图,我们需要进行几步操作:首先进行纹理定义和设置,然后进行纹理资源载入,生成多级纹理,在使用前进行纹理绑定,以及最后在着色器中进行采样。

在这里插入图片描述

创建纹理

首先我们定义一个纹理,绑定在GL_TEXTURE_2D上,然后就开始设置它的纹理属性,最后四行中,前两行是用来设置纹理的环绕方式。后两行则是设置纹理的过滤方式。设置这些纹理的属性有很多参数,下面我们就来介绍一下这里的参数有哪些,分别都是什么作用。

GLuint texture1; glGenTextures(1, &texture1); glBindTexture(GL_TEXTURE_2D, texture1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

上面的代码中,glTexParameteri函数是负责设置纹理的属性。

其中第四、第五行代码:

GL_TEXTURE_WRAP_S表示在s轴上纹理的环绕方式;GL_TEXTURE_WRAP_T表示在T轴上纹理的环绕方式,这里s和t等价于平面纹理图片的x轴和y轴;GL_REPEAT是表示纹理重复出现,它也是在不设置的情况下默认环绕方式。GL_MIRRORED_REPEAT也是重复图片,但是他表示以镜像方式重复出现;GL_CLAMP_TO_EDGE表示纹理坐标会被约束在0-1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。这种环绕方式通常会在我们设置纹理坐标超过0-1的范围时被使用到。

其中第六第七行代码:

GL_TEXTURE_MIN_FILTER是设置纹理在缩小时的过滤方式GL_TEXTURE_MAG_FILTER是设置纹理在放大时的过滤方式。

过滤方式我们主要使用的有两种,一种是GL_NEAREST即线性过滤,这种过滤方法会产生颗粒状的图案,但是也能更清晰的看到组成纹理的像素。GL_LINEAR即临近过滤,它能够产生更平滑的图案,但是也有更真实的输出。

纹理读取

接下来我们学习如何进行资源载入,即把图片读入内存,最终绑定到着色器。我们使用#include 中的_stbi_load,根据路径读取纹理图片,并读取纹理的宽高和通道数,将纹理数据存入data数组中,最后判断是否读取成功,若失败,则报错,若成功,则进行下一步操作。部分代码如下:

//加载纹理 int width, height, nrchannels;//纹理长宽,通道数 stbi_set_flip_vertically_on_load(true); unsigned char *data = stbi_load("res/texture/CG_Sprite.jpg", &width, &height, &nrchannels, 0); if (data) { //生成纹理 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); } else std::cout FragColor = texture(texture1,TexCoord); } 天空盒着色器 顶点着色器

接着我们看到天空盒的着色器具体跟立方体的着色器到底有哪些不同。

天空盒的顶点着色器将顶点坐标直接作为纹理坐标传入片元着色器,可以发现平面纹理的纹理坐标是vec2类型,而立方体的纹理坐标则是vec3类型,并且pvm矩阵也发生了变化,这个我们后面再提,还有我们可以看到的是我们把z变量替换成了w,这是为了使天空盒永远在所有物体之后,作为背景,使其深度值最大,不遮挡任何物体。

#version 330 core layout (location = 0) in vec3 aPos; out vec3 TexCoords; uniform mat4 projection; uniform mat4 view; void main() { TexCoords = aPos; vec4 pos = projection * view * vec4(aPos, 1.0); //这是因为天空盒会在之后覆盖所有的场景中其他物体。我们需要耍个花招让深度缓冲相信天空盒的深度缓冲有着最大深度值1.0,如此只要有个物体存在深度测试就会失败,看似物体就在它前面了 //透视除法(perspective division)是在顶点着色器运行之后执行的,把gl_Position的xyz坐标除以w元素。我们从深度测试教程了解到除法结果的z元素等于顶点的深度值。利用这个信息,我们可以把输出位置的z元素设置为它的w元素,这样就会导致z元素等于1.0了,因为,当透视除法应用后,它的z元素转换为w/w = 1.0: gl_Position = pos.xyww; } 片元着色器

天空盒的片元着色器也与立方体的片元着色器有些不同,它传入的是samplerCube类型的立方体贴图,且纹理坐标也是vec3类型,但是最终还是通过texture进行采样。

#version 330 core out vec4 FragColor; in vec3 TexCoords; uniform samplerCube skybox; void main() { FragColor = texture(skybox, TexCoords); } 立方体贴图和平面纹理对比 着色器中的区别

我们刚才这是对立方体和天空盒着色器的区别进行了分析,发现天空盒着色器传入的纹理是samplerCube类型,那我们怎样向着色器传入samplerCube类型的纹理呢?它与立方体纹理的区别又在那里呢?

立方体贴图的六个面,每个面都对应一个纹理,且有其相应的顺序。有人可能会觉得为什么不能贴六个纹理而非要用立方体贴图呢?那是因为立方体贴图有其独特的属性,我们有时候可以直接使用方向向量对立方体贴图进行索引和采样。

我们可以设想一下,有一束光线从立方体的中心向任意一个方向射出,我们知道了射出光线的方向向量,即可在在立方体贴图上找到对应的纹理坐标,获取到相应的颜色值,通过这种方法我们可以实现光的折射和反射,通过出射光和入射光的方向找到对应的颜色值。

定义和设置上的区别

我们之前讲了立方体贴图和平面纹理在着色器中的区别,那他们在别的方面还有什么区别吗?因为立方体贴图包括六个相关的平面纹理,所以他们在纹理的定义和设置和资源载入上还是有一些区别的。

纹理定义

首先设置纹理属性值,首先是定义纹理id时,我们将GLuint换成了unsigned int,这两种方式其实是完全等价的,所以使用的时候我们可以根据自己的需要来,接着就是绑定纹理,因为是立方体纹理,所以之前的绑定在GL_TEXTURE_2D变成了绑定在GL_TEXTURE_CUBE_MAP,其他属性的设置基本相同,但是需要注意的是,由于以及是立体纹理而不仅仅是平面纹理了,所以他的环绕方式也多出了一个R轴的环绕方式,对应的是现实中的z轴。

unsigned int load_cubemap(std::vector faces) { unsigned int textureID; glGenTextures(1, &textureID); glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); int width, height, nrchannels; for (unsigned int i = 0; i glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); stbi_image_free(data); } else { std::cout stbi_set_flip_vertically_on_load(false); unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrchannels, 0); if (data) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); stbi_image_free(data); } else { std::cout


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有