OpenGL 入门 17:立方体贴图
立方体贴图(Cube Map)
立方体贴图是由“上下左右前后”6个2D纹理合并成的一张纹理。与2D纹理使用(u,v)坐标采样不同的是,立方体纹理使用一个方向向量进行采样。
1. 方向向量 长度不重要,重要的是 方向。
2. 采样时,会将向量的原点放到立方体的中心,把向量当做一条无线延长的射线,因此必将与立方体产生交点,根据交点即可确定采样哪个2D纹理以及采样坐标。
立方体纹理的采样方法:
把一个1x1x1的单位立方体中心放置原点(0,0,0),并给它应用立方体贴图。
在片元着色器中,把插值后的顶点位置坐标当做采样立方体贴图的方向向量。
(顶点位置坐标 - 原点 = 顶点位置向量)且因为原点为(0,0,0),顶点位置坐标与顶点位置向量形式上相等,因此可以直接当做进行采样的方向向量。
接下来对所有插值后的顶点执行相同的操作即可。
因此通过对一个单位立方体的渲染,就可完成对立方体纹理所有面的采样。
创建立方体贴图
与生成其他纹理类似,只不过此次要绑定到GL_TEXTURE_CUBE_MAP的纹理目标。
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
与创建2D纹理仅需调用glTexImage2D一次不同的,立方体纹理需要调用六次。需要为每个面使用特定的纹理目标(target)参数进行纹理绑定设定。
该纹理目标参数其实是枚举类型,其背后的int值时线性增加的。因此可以按顺序准备好纹理资源的路径放到一个叫做textures_faces的vector中,之后可通过for循环进行加载。
纹理目标 |
方位 |
GL_TEXTURE_CUBE_MAP_POSITIVE_X |
右 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_X |
左 |
GL_TEXTURE_CUBE_MAP_POSITIVE_Y |
上 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y |
下 |
GL_TEXTURE_CUBE_MAP_POSITIVE_Z |
后 |
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z |
前 |
int width, height, nrChannels;
unsigned char *data;
for(unsigned int i = 0; i < textures_faces.size(); i++)
{data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
}
之后再设定纹理的环绕和过滤方式。其中需要将环绕设定为GL_CLAMP_TO_EDGE,当在两个面之间采样时,永远返回它们之间的边界值。
完整代码:
将整个加载过程封装到一个loadCubemap函数中,逻辑清晰,方便理解。
vector<std::string> faces
{"right.jpg","left.jpg","top.jpg","bottom.jpg","front.jpg","back.jpg"
};
unsigned int cubemapTexture = loadCubemap(faces);unsigned int loadCubemap(vector<std::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++){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;
}
天空盒
玩过cf类似的3D游戏,就知道游戏的天空背景就是采用天空盒。在其中有两个很明显的特征:
背景永远覆盖在最底层,其他物体绘制在其之上。
无论玩家怎么移动,天空盒总是没有任何变化,给玩家产生周围环境非常大的印象。
特征一实现
方式一:关闭深度写入,首先绘制天空盒。打开深度写入,再绘制其他物体进行覆盖。
1. 方式一中,对于天空盒,我们其实可以想象有一个单位立方体套在摄像机头上,不管摄像机怎么移动,它总是跟随着它,而且我们看到的一直是立方体的内部。并且它的体积是比较小的,因此要是开启深度写入,其必然会遮挡住场景中的其他物体。所以关闭深度写入,我们先把天空盒绘制到颜色缓存区中,进行一次打底操作,然后打开深度写入,再绘制其他物体在内存缓冲区中进行覆盖。
2. 然而方有可能因为大量物体的遮挡,最后只呈现出一部分的天空盒,造成一定浪费,因此该方式并不高效。有没有一种方法能够最后绘制天空盒,从而避免对被遮挡住的部分进行绘制?
3. 为了减少对一些被遮挡片元的绘制,可以使用 提前深度测试(Early Depth Testing)。但是天空盒仍然是一个1x1x1的立方体,在深度测试中,它的深度值仍然小于绝大部分物体。考虑一下基本情况,其实我们想要天空盒置于最底层,也就是尽量不让其通过 提前深度测试,也就意味着它在ndc坐标的z值需要足够大,比如说是1.0,这样子只要其前面有任何物体都会被遮挡住。因此我们可以想办法修改它的z值,以达到我们的目的。
4. 透视除法是在顶点着色器之后运行的,因此我们可以采取一种欺骗的手段,在顶点着色器中将经过mvp矩阵计算过后顶点坐标的z分量修改为w分量。这样子就能在ndc坐标中让z值为1.0。
方式二:先绘制物体,再绘制天空盒。开启提前深度测试,在天空盒顶点着色器中,将透视除法前的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);gl_Position = pos.xyww;
}
片元着色器:
#version 330 core
out vec4 FragColor;in vec3 TexCoords;uniform samplerCube skybox;void main()
{ FragColor = texture(skybox, TexCoords);
}
特征二实现
可以想象有个套在摄像机上的天空盒立方体,无论摄像机怎么位移,该天空盒总是和它保持相对位置不变。但是其并不受摄像机的旋转操作影响。
方式一:在世界空间中,让天空盒的位置与摄像机的位置重合。因此需要在天空的模型变化中加上一个位移,即位移到摄像机的位置。
方式二:天空盒在没有任何模型变换时,就处于世界坐标的原点。观测变换可理解为一种逆变换,即把摄像机位移到世界原点,并把轴旋转至于世界坐标重合。通常来说,是需要对所有物体使用相同的观测变化。但是天空盒是个例外,我们是希望天空盒与摄像机的位置是重合的。因此对天空盒进行观测变换时,需要去除位移的部分,只保留旋转的部分即可。具体操作时保留观测矩阵的左上角3x3的部分并以4x4形式储存。
glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
环境映射
立方体纹理除了可以制作天空盒,还可以作用于物体之上,给予物体反射和折射的属性,这种使用环境立方体贴图的技术叫做环境映射(Environment Mapping)。
反射
镜子就是一个反射的物体,它会根据观察者的视角反射周围的环境。GLSL内建的reflect函数来计算这个反射向量。反射向量将作为采样立方体纹理的方向向量,返回环境的颜色值。
整个计算过程是在世界空间中完成的。
顶点着色器:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;out vec3 Normal;
out vec3 Position;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{Normal = mat3(transpose(inverse(model))) * aNormal;Position = vec3(model * vec4(aPos, 1.0));gl_Position = projection * view * model * vec4(aPos, 1.0);
}
片元着色器:
#version 330 core
out vec4 FragColor;in vec3 Normal;
in vec3 Position;uniform vec3 cameraPos;
uniform samplerCube skybox;void main()
{ vec3 I = normalize(Position - cameraPos);vec3 R = reflect(I, normalize(Normal));FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
折射
水就是一个折射的例子。这是由于不同介质之间的折射率不同所导致的。GLSL的内建refract函数来实现了折射效果,除了法向量和观察方向,我们还需指定两个材质之间的折射率。
材质 |
折射率 |
空气 |
1.00 |
水 |
1.33 |
冰 |
1.309 |
玻璃 |
1.52 |
钻石 |
2.42 |
片元着色器需要修改的地方:
void main()
{ float ratio = 1.00 / 1.52;vec3 I = normalize(Position - cameraPos);vec3 R = refract(I, normalize(Normal), ratio);FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
动态环境贴图
之前使用的是静态的图像来组成天空盒,效果已十分不错,可惜它并没有场景中的其他物体。因此在实现一些反射或折射效果时,并不是那么真实。
因此我们可以采用上一节内容 OpenGL 入门 16:帧缓冲 中的技术。在渲染循环中,使用帧缓冲为一个物体在六个方向分别创建场景纹理并存储到一个立方体贴图中。这样使用这个动态生成的立方体贴图,就可以生成更真实的,包含了其他物体的反射和折射效果。这就是动态环境映射。
虽然它的效果很好,但是它的性能开销十分之大。
OpenGL 入门 17:立方体贴图相关推荐
- 【OpenGL ES】立方体贴图(6张图)
1 前言 本文通过一个立方体贴图的例子,讲解三维纹理贴图的应用,案例中使用 6 张不同的图片给立方体贴图,图片如下: 本文涉及到的知识点主要包含:三维绘图.MVP 矩阵变换.纹理贴图,读者如果对 Op ...
- OpenGL 核心技术之立方体贴图
笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D ...
- OPenGL笔记--给立方体贴图(纹理)
文章目录 一.前置知识 二.效果展示. 三.完整代码 附.给立方体每个面渲染不同的纹理 一.前置知识 经过前面的学习,我们已经知道了立方体怎么创建了,接下来学习怎么给立方体贴图: 为了将纹理正确的映射 ...
- OpenGL进阶之立方体贴图
参考: https://learnopenglcn.github.io/04%20Advanced%20OpenGL/06%20Cubemaps/ 立方体贴图就是一个包含了6个2D纹理的纹理,每个2D ...
- OpenGL Cube Map立方体贴图的实例
OpenGL Shadow Mapping阴影贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include "vermilion.h" ...
- 使用OpenGL 立方体贴图
openGL系列文章目录 文章目录 openGL系列文章目录 前言 一.OpenGL 立方体贴图 二.使用步骤 1.代码 2.着色器程序 运行结果 注意 源码下载 参考 前言 对于室外3D 场景,通常 ...
- 第三十七章 立方体贴图总结
立方体贴图:将多个纹理组合起来映射到一张纹理上的一种纹理类型. 一个立方体贴图时包含了6个2D纹理的纹理,每个2D纹理都组成了立方体的一个面,相当于是一个有纹理的立方体. 创建立方体贴图: 首先需要生 ...
- OpenGL cubemap 立方体贴图实例
OpenGL cubemap 立方体贴图 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> #include ...
- OpenGL ES 3. 天空盒 立方体贴图
大家好,接下来将为大家介绍OpenGL ES 3. 天空盒 立方体贴图. OpenGL ES 立方体贴图本质上还是纹理映射,是一种 3D 纹理映射.立方体贴图所使的纹理称为立方图纹理,它是由 6 个单 ...
最新文章
- 设计模式 之美 -- 简单工厂模式
- 【论文解读】一种基于时间卷积网络的知识驱动股票趋势预测方法
- java泛型怎么用反射生成_Java 之 使用反射生成并操作对象
- 记录一次maven依赖成功导入,但找不到相关包的IDEA臭bug
- IntelliJ IDEA使用技巧——关于版本控制(上)
- 用Linux编写C语言程序
- python第二版答案第六章_Python语言程序设计基础(第2版) 课后题 第六章
- 客服系统源代码下载-h5手机端在线客服代码-在线聊天系统源代码(前端vue开发,后台go语言开发)
- 如何在pe安装深度linux系统,深度系统(Deepin Linux)U盘安装教程
- 毕业论文排版(六)-三线表
- 2021年中国上牌和驾驶员数量分析:新注册登记机动车3674万辆 新领证驾驶人2750万人[图]
- mysql 创建utf-8数据库_mysql 创建utf-8数据库
- 2019最新activiti6.0工作流搭建平台
- 关于java面试被虐的痛苦经历,你有体验过吗?
- 【mac】【转发】Mac系统升级后,按大小写键没反应了,切换大小写的灯不亮了
- mysql在线编辑器
- bad SQL grammer []; nested exception is java.sql.SQLSyntaxErrorException:ORA-00918:未明确定义列
- L2-019 悄悄关注 (25分)
- 计算机专业这么多课程怎么学?
- MQTT协议及安全详解
热门文章
- 题9.5:有10个学生,每个学生的数据包括学号、姓名、3门课程的成绩,从键盘输人10个 学生数据,要求输出3门课程总平均成绩,以及最高分的学生的数据(包括学号、姓 名、3门课程成绩、平均分数)。
- python socket connect 阻塞_python 网络编程(socketserver,阻塞,其他方法)
- Python fitter包:拟合数据样本的分布
- 编程程软件测试学院3周年 为你破解入职大厂的终极奥秘
- 华润数科控股有限公司正式成立;DEKRA德凯预计2021年营业额同比增长9%至35亿欧元 | 全球TMT...
- 软件测试工资一般多少 即使测试刚入行,起步月薪也会在8k-9k
- 服务名无效。 请键入 NET HELPMSG 2185 以获得更多的帮助。
- Excel Application对象应用大全
- 三位一体自荐信计算机专业,三位一体自荐信写法和范文
- 有一个棋盘,有64个方格,在第一个方格里面放1粒芝麻重量是0.00001kg,第二个里面放2粒,第三个里面放4,棋盘上放的所有芝麻的重量。