(自用)learnOpenGL学习总结-高级OpenGL-立方体贴图

2024-01-29 22:28

本文主要是介绍(自用)learnOpenGL学习总结-高级OpenGL-立方体贴图,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ok终于来到了立方体贴图了,在这里面我们可以加入好看的天空包围盒,这样的画我们的背景就不再是黑色的了!

首先,立方体贴图和前面的sampler2D贴图一样,不过是6个2D组成的立方体而已。

那么为什么要把6个组合在一起呢?立方体贴图可以通过一个方向向量来进行索引(或者说采样)。什么意思?

我们类比一下,之前在一个2D面上我们通过uv纹理坐标来找到对应的纹理值对吧。这里也一样,不过是通过一个方向向量来获得。

 假设我们有一个立方体、然后又有一个立方体贴图,只要立方体中心在原点,我就可以通过立方体表面的位置来确定对应的方向向量从而确定对应的立方体纹理坐标。

创建立方体贴图

和前面创建贴图一样。

unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

不过因为有6个面,所以需要贴6次。

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);
}

这里我们有一个叫做textures_faces的vector,它包含了立方体贴图所需的所有纹理路径,并以表中的顺序排列。这将为当前绑定的立方体贴图中的每个面生成一个纹理。

然后还需要设置环绕和过滤方式

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_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);

可以看到这里出现了个R坐标,其实就是纹理的第三个维度,和位置的z一样。

最后在片段着色器中要使用一个samplerCube就行

天空盒

我们现在下载一个天空盒到我们的项目中。

 加载天空盒

和之前把图片加载到gpu一样,现在是加载一个cubemap。

unsigned int LoadCubeMapToGPU(std::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;};

然后需要存一个skybox的图片vector

float skyboxVertices[] = {// positions          -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
};std::vector<std::string> faces
{"right.jpg","left.jpg","top.jpg","bottom.jpg","front.jpg","back.jpg"
};

然后需要给这cube给一个VAO VBO

	unsigned int cubemapTexture = LoadCubeMapToGPU(faces);unsigned int skyboxVAO, skyboxVBO;glGenBuffers(1, &skyboxVBO);glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);glGenVertexArrays(1, &skyboxVAO);glBindVertexArray(skyboxVAO);glEnableVertexAttribArray(0);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

最后写一个skybox的shader就好。

#version 330 corelayout(location = 0) in vec3 aPos;out vec3 TexCoords;uniform mat4 projMat;
uniform mat4 viewMat;void main(){gl_Position = projMat*viewMat*vec4(aPos,1.0f);TexCoords = aPos;
}#version 330 core
out vec4 FragColor;
in vec3 TexCoords;uniform samplerCube skybox;void main(){FragColor =	 texture(skybox,TexCoords);
}

可以看到在顶点着色器里面我们把坐标信息作为纹理信息传给了片段着色器,这是因为cubemap的纹理坐标就是一个向量,所以我们直接把原点在世界中心的立方体的各点坐标当作纹理向量给片段着色器就好。

最后是loop中,我们要记住,画天空盒,我们得先画,而且画的时候需要把深度测试关了,这样的画,之后的物体就是画在背景之前了。

loop中这么写。

//第一阶段
...
//clear screen
...
//skybox
glDepthMask(GL_FALSE);
skyboxShader->use();iewMat = camera.GetViewMatrix();
projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);
glUniformMatrix4fv(glGetUniformLocation(skyboxShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));
glUniformMatrix4fv(glGetUniformLocation(skyboxShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthMask(GL_TRUE);
...
//木块 机器人 其他

不过结果不对,我们希望天空盒是以玩家为中心的,这样不论玩家移动了多远,天空盒都不会变近,让玩家产生周围环境非常大的印象。然而,当前的观察矩阵会旋转、缩放和位移来变换天空盒的所有位置,所以当玩家移动的时候,立方体贴图也会移动!我们希望移除观察矩阵中的位移部分,让移动不会影响天空盒的位置向量。

在基础光照小节中,我们通过取4x4矩阵左上角的3x3矩阵来移除变换矩阵的位移部分。我们可以将观察矩阵转换为3x3矩阵(移除位移),再将其转换回4x4矩阵,来达到类似的效果。

修改viewMat

glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));

优化

 在刚刚的过程中,是先画天空盒,在这中间关闭了深度测试。是因为我们想让其他物体画在天空盒上。不过这样不过高效,是因为在画天空盒的时候会对每个像素都跑一次片段着色器。

所以我们要最后渲染天空盒。但是怎么做呢?提前深度测试可以做到,我们先渲染其他东西,然后再提前深度测试通过的地方渲染天空盒就行了。

不过要知道的我们的天空盒只有1x1x1,其他东西很容易就在天空盒上,所以我们需要欺骗深度缓冲,或者说让他认为天空盒的深度值有最大,要让天空盒最深。

在坐标系统小节中我们说过,透视除法是在顶点着色器运行之后执行的,将gl_Position的xyz坐标除以w分量。我们又从深度测试小节中知道,相除结果的z分量等于顶点的深度值。使用这些信息,我们可以将输出位置的z分量等于它的w分量,让z分量永远等于1.0,这样子的话,当透视除法执行之后,z分量会变为w / w = 1.0

修改顶点着色器

void main(){vec4 pos = projMat*viewMat*vec4(aPos,1.0);gl_Position = pos.xyww;TexCoords = aPos;
}

修改main中的顺序,把天空盒放在其他物体之后,但是是再透明物体之前。

我们还要改变一下深度函数,将它从默认的GL_LESS改为GL_LEQUAL。

深度缓冲将会填充上天空盒的1.0值,所以我们需要保证天空盒在值小于或等于深度缓冲而不是小于时通过深度测试。

结果和之前一样

环境映射

现在我们可以通过这个环境纹理来给物体一些反射折射的属性了

反射

 这里灰色向量是我们的观察方向向量、红色是法向方向,绿色是反射方向,可以通过reflect()来获得,之后把这个绿色的向量作为天空盒的纹理方向坐标就可以获得立方体贴图了。

所以重新写一个反射天空盒的箱子着色器。

#version 330 corelayout(location = 0) in vec3 aPos;
layout(location =1) in vec3 aNormal;out vec3 Position;
out vec3 Normal;uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;void main(){Normal = mat3(transpose(inverse(modelMat)))*aNormal;Position = vec3(modelMat* vec4(aPos,1.0f));gl_Position = projMat * viewMat * modelMat*vec4(aPos,1.0f);
}#version 330 corein vec3 Position;
in vec3 Normal;out vec4 FragColor;uniform vec3 cameraPos;
uniform samplerCube skybox;void main(){vec3 viewDir = normalize(Position-cameraPos);vec3 refDir = reflect(viewDir,normalize(Normal));FragColor = vec4(texture(skybox,refDir).rgb,1.0f);
}

这里注意看下,因为片段着色器需要法向量信息,所以需要通过顶点着色器传个normal过去,但是这里是需要经过model变换的,也不能直接变换。所以需要通过

Normal = mat3(transpose(inverse(model))) * aNormal;

这样才能得到正确的法向量。

然后在main中 建立新的shader,绑定VAOVBO,之后再loop中画一个这样的cube,记得要把camera的position传进去。

//反射天空盒cubeskyboxReflectShader->use();modelMat = glm::translate(glm::mat4(1.0f), glm::vec3(0, 0, 5.0f));viewMat = camera.GetViewMatrix();projMat = projMat = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);glUniformMatrix4fv(glGetUniformLocation(skyboxReflectShader->ID, "modelMat"), 1, GL_FALSE, glm::value_ptr(modelMat));glUniformMatrix4fv(glGetUniformLocation(skyboxReflectShader->ID, "viewMat"), 1, GL_FALSE, glm::value_ptr(viewMat));glUniformMatrix4fv(glGetUniformLocation(skyboxReflectShader->ID, "projMat"), 1, GL_FALSE, glm::value_ptr(projMat));glUniform3f(glGetUniformLocation(skyboxReflectShader->ID, "cameraPos"), camera.Position.x, camera.Position.y, camera.Position.z);glBindVertexArray(skyBoxReflectionVAO);glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);glDrawArrays(GL_TRIANGLES, 0, 36);

记得再画之前绑定一下天空盒的材质。

这是怎么少一个面呢,是因为面剔除的原因,这里的顶点坐标没有按照逆时针的顺序排列。

修改一下,在画的时候把面剔除关了。

 

OK了 。

如果直接另外画一个机器人模型,但是用这里的reflectShader的话

 这看起来非常棒,但在现实中大部分的模型都不具有完全反射性。我们可以引入反射贴图(Reflection Map),来给模型更多的细节。与漫反射和镜面光贴图一样,反射贴图也是可以采样的纹理图像,它决定这片段的反射性。通过使用反射贴图,我们可以知道模型的哪些部分该以什么强度显示反射。在本节的练习中,将由你来为我们之前创建的模型加载器中引入反射贴图,显著提升纳米装模型的细节。

折射

折射通过refract函数实现。需要一个法向量,一个观察方向,两个材质之间的折射率。

现在修改下片段着色器就好。

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);
}

 它不太能显示折射的效果,现在看起来只是有点像一个放大镜。对纳米装使用相同的着色器却能够展现出了我们期待的效果:一个类玻璃的物体。

你可以想象出有了光照、反射、折射和顶点移动的正确组合,你可以创建出非常漂亮的水。注意,如果要想获得物理上精确的结果,我们还需要在光线离开物体的时候再次折射,现在我们使用的只是单面折射(Single-side Refraction),但它对大部分场合都是没问题的。

动态环境贴图

现在我们使用的都是静态图像的组合来作为天空盒,看起来很不错,但它没有在场景中包括可移动的物体。我们一直都没有注意到这一点,因为我们只使用了一个物体。如果我们有一个镜子一样的物体,周围还有多个物体,镜子中可见的只有天空盒,看起来就像它是场景中唯一一个物体一样。

通过使用帧缓冲,我们能够为物体的6个不同角度创建出场景的纹理,并在每个渲染迭代中将它们储存到一个立方体贴图中。之后我们就可以使用这个(动态生成的)立方体贴图来创建出更真实的,包含其它物体的,反射和折射表面了。这就叫做动态环境映射(Dynamic Environment Mapping),因为我们动态创建了物体周围的立方体贴图,并将其用作环境贴图。

虽然它看起来很棒,但它有一个很大的缺点:我们需要为使用环境贴图的物体渲染场景6次,这是对程序是非常大的性能开销。现代的程序通常会尽可能使用天空盒,并在可能的时候使用预编译的立方体贴图,只要它们能产生一点动态环境贴图的效果。虽然动态环境贴图是一个很棒的技术,但是要想在不降低性能的情况下让它工作还是需要非常多的技巧的。

这篇关于(自用)learnOpenGL学习总结-高级OpenGL-立方体贴图的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/658359

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

git使用的说明总结

Git使用说明 下载安装(下载地址) macOS: Git - Downloading macOS Windows: Git - Downloading Windows Linux/Unix: Git (git-scm.com) 创建新仓库 本地创建新仓库:创建新文件夹,进入文件夹目录,执行指令 git init ,用以创建新的git 克隆仓库 执行指令用以创建一个本地仓库的

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

二分最大匹配总结

HDU 2444  黑白染色 ,二分图判定 const int maxn = 208 ;vector<int> g[maxn] ;int n ;bool vis[maxn] ;int match[maxn] ;;int color[maxn] ;int setcolor(int u , int c){color[u] = c ;for(vector<int>::iter