烟台网站建设加盟,新乡手机网站建设服务,网站排名技巧,iis网站建设计算机图形学 | 实验九#xff1a;纹理贴图和天空盒 计算机图形学 | 实验九#xff1a;纹理贴图和天空盒实验概述顶点数据立方体顶点数据天空盒顶点数组 纹理载入创建纹理纹理读取纹理绑定 使用纹理立方体着色器顶点着色器片元着色器 天空盒着色器顶点着色器片元着色器 立方体… 计算机图形学 | 实验九纹理贴图和天空盒 计算机图形学 | 实验九纹理贴图和天空盒实验概述顶点数据立方体顶点数据天空盒顶点数组 纹理载入创建纹理纹理读取纹理绑定 使用纹理立方体着色器顶点着色器片元着色器 天空盒着色器顶点着色器片元着色器 立方体贴图和平面纹理对比着色器中的区别定义和设置上的区别纹理定义资源载入 天空盒位置的控制 总结 华中科技大学《计算机图形学》课程
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 stb/stb_image.h中的_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);
}
elsestd::cout Failed to load texture std::endl;在读取纹理之后我们还要将data中的纹理数据存入GL_TEXTURE_2D存入的时候我们需要同时输入纹理的宽高接着我们使用glGenerateMipmap(GL_TEXTURE_2D)生成多级渐远纹理。
纹理绑定
由于可能出现多个纹理所以我们每次使用纹理绘制之前最好进行一次绑定操作。glActiveTexture(GL_TEXTURE0)为选择GL_TEXTUERE0纹理单元将要要使用的纹理绑定到GL_TEXTURE0纹理单元中计算机有很多纹理单元GL_TEXTURE1 GL_TEXTURE0 GL_TEXTURE2 GL_TEXTURE3 GL_TEXTURE4使用时每个纹理最好对一个纹理单元。
接着我们使用glBindTexture(GL_TEXTURE_2D, texture1);去绑定纹理绑定后即可进行绘制。
glActiveTexture(GL_TEXTURE0); //绑定纹理
glBindTexture(GL_TEXTURE_2D, texture1);...glDrawArrays(GL_TRIANGLES, 0, 36);//绘制使用纹理
后面介绍如何在着色器中使用纹理需要注意的是立方体和天空盒使用纹理的方式是不一样的我们先看看着色器中如何使用平面纹理。
立方体着色器
顶点着色器
立方体的顶点着色器负责的纹理方面的功能不多只是将顶点对应的纹理坐标传入片元着色器。
#version 330 core
layout (location 0)
in vec3 aPos;
layout (location 1) in vec2 aTexCoord;
out vec2 TexCoord;
...
void main()
{gl_Position projection * view * model * vec4(aPos, 1.0); TexCoordvec2(aTexCoord.x,aTexCoord.y);
}片元着色器
片元着色器则是根据传入的sampler2D类型的纹理和纹理对应坐标使用texture进行采样获取当前片元的颜色值。
在这里插入代码片#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
uniform sampler2D texture1;
void main()
{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.0gl_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::vectorstd::string faces)
{unsigned int textureID;glGenTextures(1, textureID);glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);int width, height, nrchannels;for (unsigned int i 0; i faces.size(); i){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 Cubemap texture failed to load at path: faces[i] std::endl;stbi_image_free(data);}}glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);return textureID;
}资源载入
资源载入时我们也需要一次性载入6张纹理一般我们使用for循环载入循环六次同时每次载入纹理存储的地址也有所不同不同的纹理存储在不同的地址不过这些地址都是相邻的所以每次只需要加i即可。
unsigned int load_cubemap(std::vectorstd::string faces)
{unsigned int textureID;glGenTextures(1, textureID);glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);int width, height, nrchannels;for (unsigned int i 0; i faces.size(); i){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 Cubemap texture failed to load at path: faces[i] std::endl;stbi_image_free(data);}}glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);return textureID;
}
天空盒位置的控制
那么我们该如何摆放天空盒呢首先天空盒是一个包裹着相机的大盒子它不需要进行世界坐标系的模型变换其次相机不管如何移动应该始终在天空盒的中心点那么我们如何取消天空盒着色器中view矩阵的位移而保存相机视角变换之类的操作呢
相机view矩阵主要包含了位移和旋转操作如图4.3.1所示第一行公式为位移矩阵如何发挥其功能而位移部分则是在TxTyTz中体现 TxTyTz为位移量我们只需要把view矩阵从4x4先转换为3x3矩阵去掉位移部分再转换回4x4矩阵则可完全去掉位移量并保持旋转效果。 体现在程序中则是如下操作先转换成mat3矩阵再转换成mat4矩阵。
//去除相机位移
view glm::mat4(glm::mat3(glm::lookAt(camera_position, camera_position camera_front, camera_up))); 总结
以上为本次实验的要点解析下面是实验结果演示。 运行之后我们移动视角可以发现不管我们怎样移动相机的位置天空盒相对于摄像机的位置始终保持不变。