【OpenGL】LearnOpenGL学习笔记17 - Cubemap、Skybox、环境映射(反射、折射)

上接:https://blog.csdn.net/weixin_44506615/article/details/150935025?spm=1001.2014.3001.5501
完整代码:https://gitee.com/Duo1J/learn-open-gl | https://github.com/Duo1J/LearnOpenGL

一、立方体贴图 (Cubemap)

立方体贴图就是一个包含了6张2D纹理的纹理,不同于2D纹理使用UV来采样,我们使用一个方向向量来对Cubemap进行采样,如下图所示 (图片来自于LearnOpenGL)
方向向量采样Cubemap
创建Cubemap

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

要绑定纹理数据,之前我们使用 glTexImage2D ,对于Cubemap我们则需要调用6次

// faces: Cubemap纹理路径数组
for (unsigned int i = 0; i < faces.size(); i++)
{const char* path = faces[i].c_str();unsigned char* data = stbi_load(path, &width, &height, &channel, 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);std::cout << "Load cubmap(" << GL_TEXTURE_CUBE_MAP_POSITIVE_X + i << "): " << path << " success, with: " << width << " height: " << height << " channel: " << channel << " ID: " << textureID << std::endl;}else{std::cout << "[Error] Failed to load cubemap: " << path << std::endl;}
}

这里第一位参数我们在2D纹理中传入的是 GL_TEXTURE_2D
对于Cubemap我们则需要通过这个参数来表明它是哪一个面的纹理,如下表所示

目标方向枚举值
GL_TEXTURE_CUBE_MAP_POSITIVE_X0x8515
GL_TEXTURE_CUBE_MAP_NEGATIVE_X0x8516
GL_TEXTURE_CUBE_MAP_POSITIVE_Y0x8517
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y0x8518
GL_TEXTURE_CUBE_MAP_POSITIVE_Z0x8519
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z0x851A

由于枚举值连续,所以我们在设置 faces 的时候边可以按 右左上下前后 的顺序传入

接下来我们来包装一个立方体纹理类来管理Cubemap
TextureCube.h 新建

#pragma once#include <iostream>
#include <vector>#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "stb_image.h"class TextureCube
{
public:explicit TextureCube(std::vector<std::string> faces);/*** 获取纹理ID*/unsigned int GetTextureID();private:/*** 纹理ID*/unsigned int textureID = 0;/*** 六面路径* 顺序 Right - Left - Top - Bottom - Front - Back*/std::vector<std::string> faces;/*** 加载*/void LoadCubemap();
};

TextureCube.cpp 新建

#include "TextureCube.h"TextureCube::TextureCube(std::vector<std::string> faces)
{this->faces = faces;LoadCubemap();
}void TextureCube::LoadCubemap()
{glGenTextures(1, &textureID);glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);stbi_set_flip_vertically_on_load(false);int width, height, channel;for (unsigned int i = 0; i < faces.size(); i++){const char* path = faces[i].c_str();unsigned char* data = stbi_load(path, &width, &height, &channel, 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);std::cout << "Load cubmap(" << GL_TEXTURE_CUBE_MAP_POSITIVE_X + i << "): " << path << " success, with: " << width << " height: " << height << " channel: " << channel << " ID: " << textureID << std::endl;}else{std::cout << "[Error] Failed to load cubemap: " << path << std::endl;}}stbi_set_flip_vertically_on_load(true);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);
}unsigned int TextureCube::GetTextureID()
{return textureID;
}

二、天空盒

接下来我们应用Cubemap来创建一个天空盒,天空盒就是一个包围场景的大立方体
首先我们可以在这里或是顶部git仓库中的 Resource/skybox 目录中获取到天空盒资源
天空盒

接着定义天空盒Cube的顶点数据以及天空盒纹理的路径
Main.cpp

// 天空盒纹理
std::vector<std::string> skyboxFaces
{"F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Right.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Left.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Top.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Bottom.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Front.jpg","F:/Scripts/Cpp/LearnOpenGL/learn-open-gl/Resource/skybox/T_Back.jpg"
};// 天空盒Cube顶点
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
};

然后编写天空盒的顶点和片段着色器
SkyboxVertex.glsl 新建
这里我们使用了pos.xyww来作为顶点着色器的输出,之后在透视除法时,z分量会w / w = 1,表示深度值为1,以便后续利用深度缓冲优化天空盒的绘制 (除此之外我们可以把天空盒放在第一个绘制,并禁用深度写入来达到一样的效果,但是性能会劣于以上方法)

#version 330 corelayout (location = 0) in vec3 aPos;out vec3 Direction;uniform mat4 projection;
uniform mat4 view;void main()
{Direction = aPos;vec4 pos = projection * view * vec4(aPos, 1.0);gl_Position = pos.xyww;
}

SkyboxFragment.glsl 新建
直接用方向向量对天空盒Cubemap进行采样

#version 330 coreout vec4 FragColor;in vec3 Direction;uniform samplerCube skybox;void main()
{FragColor = texture(skybox, Direction);
}

接着准备绘制天空盒
Main.cpp

// 天空盒缓冲
unsigned int skyboxVAO, skyboxVBO;
glGenVertexArrays(1, &skyboxVAO);
glGenBuffers(1, &skyboxVBO);
glBindVertexArray(skyboxVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);// 天空盒Cubemap
TextureCube skyboxCubemap(skyboxFaces);// 天空盒Shader
Shader skyboxShader("Shader/SkyboxVertex.glsl", "Shader/SkyboxFragment.glsl");// 主循环
// 绘制不透明物体// 绘制天空盒
// 注意要设置为小于等于,因为天空盒深度为1
glDepthFunc(GL_LEQUAL);
skyboxShader.Use();
// 天空盒是固定的,去掉位移
skyboxShader.SetMat4("view", glm::mat4(glm::mat3(view)));
skyboxShader.SetMat4("projection", projection);
skyboxShader.SetInt("skybox", 0);
glBindVertexArray(skyboxVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxCubemap.GetTextureID());
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glDepthFunc(GL_LESS);// 绘制半透明物体
// 屏幕后处理

编译运行,顺利的话可以看见以下图像
天空盒

三、环境映射

我们将周围的环境映射到了Cubemap上,除了天空盒还可以做更多需要依赖环境信息的消息,例如反射 (Reflection)折射 (Refraction)

反射 (Reflect)

我们可以通过计算 反射后的方向R 来对Cubemap进行采样,从而获得物体表面的反射颜色,如下图所示 (图片来自于LearnOpenGL)
反射采样Cubemap

首先编写Shader,我们复用并修改背包的顶点着色器,并新增反射专用的片段着色器
Main.cpp

Shader reflectShader("Shader/VertexShader.glsl", "Shader/ReflectFragment.glsl");

VertexShader.glsl

#version 330 corelayout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;out vec3 FragPos;
out vec3 Normal;
out vec2 TexCoords;
// 新增顶点世界空间位置
out vec3 Position;uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;void main()
{gl_Position = projection * view * model * vec4(aPos, 1.0);FragPos = vec3(model * vec4(aPos, 1.0));Normal = mat3(transpose(inverse(model))) * aNormal;TexCoords = aTexCoords;// model乘以aPos得到世界空间位置Position = vec3(model * vec4(aPos, 1.0));
}

ReflectFragment.glsl 新增

#version 330 coreout vec4 FragColor;in vec3 Normal;
in vec3 Position;uniform vec3 cameraPos;
uniform samplerCube skybox;void main()
{vec3 I = normalize(Position - cameraPos);// 计算视线反射后的向量Rvec3 R = reflect(I, normalize(Normal));// 采样skyboxFragColor = vec4(texture(skybox, R).rgb, 1.0);
}

接下来使用新创建的着色器进行绘制

// 绘制背包之后// 反射
modelMatrix = glm::translate(modelMatrix, glm::vec3(-5.0f, 0.0f, 0.0f));
reflectShader.Use();
reflectShader.SetMat4("view", view);
reflectShader.SetMat4("projection", projection);
reflectShader.SetMat4("model", modelMatrix);
reflectShader.SetVec3("cameraPos", camera.transform.position);
reflectShader.SetInt("skybox", 0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skyboxCubemap.GetTextureID());
model.Draw(shader);

编译运行,顺利的话可以看见如下图像,一个镜子一样的背包
反射

折射 (Refract)

和反射的实现方式类似,我们可以通过计算视线折射后的方向来对Cubemap进行采样,如下图所示 (图片来自于LearnOpenGL)
折射
计算折射同样可以使用GLSL提供的 refract 函数来实现,最后一位参数我们需要传入折射率,以下是一些常见材质的折射率

材质折射率
空气1.00
1.33
1.309
玻璃1.52
钻石2.42

这里我们使用玻璃的折射率来渲染一个玻璃背包,只考虑从空气进入玻璃发生的折射,那么比值 ratio = 1.00 / 1.52 = 0.658

RefractFragment.glsl 新建

#version 330 coreout vec4 FragColor;in vec3 Normal;
in vec3 Position;uniform vec3 cameraPos;
uniform samplerCube skybox;void main()
{float ratio = 1 / 1.52;vec3 I = normalize(Position - cameraPos);vec3 R = refract(I, normalize(Normal), ratio);FragColor = vec4(texture(skybox, R).rgb, 1.0);
}

Main.cpp

// 折射
Shader refractShader("Shader/VertexShader.glsl", "Shader/RefractFragment.glsl");// 绘制背包之后// 折射
modelMatrix = glm::translate(modelMatrix, glm::vec3(-5.0f, 0.0f, 0.0f));
refractShader.Use();
refractShader.SetMat4("view", view);
refractShader.SetMat4("projection", projection);
refractShader.SetMat4("model", modelMatrix);
refractShader.SetVec3("cameraPos", camera.transform.position);
refractShader.SetInt("skybox", 0);
model.Draw(shader);

编译运行,顺利的话可以看见以下图像,一个玻璃背包
折射

完整代码可在顶部git仓库中找到
下接:https://blog.csdn.net/weixin_44506615/article/details/151043459?spm=1001.2014.3001.5502

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/94927.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94927.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十七章 ESP32S3 SW_PWM 实验

本章将介绍使用 ESP32-S3 LED 控制器(LEDC)。 LEDC 主要用于控制 LED&#xff0c;也可产生PWM信号用于其他设备的控制。该控制器有 8 路通道&#xff0c;可以产生独立的波形&#xff0c;驱动 RGB LED 等设备。 LED PWM 控制器可在无需 CPU 干预的情况下自动改变占空比&#xff…

Flink CDC如何保障数据的一致性

Flink CDC如何保障数据的一致性 前言 在大规模流处理中&#xff0c;故障是无可避免的。机器会宕机&#xff0c;网络会抖动。一个可靠的流处理引擎不仅要能高效地处理数据&#xff0c;更要在遇到这些故障时&#xff0c;保证计算结果的正确性。Apache Flink 正是因其强大的容错机…

Spring Boot 定时任务入门

1. 概述 在产品的色彩斑斓的黑的需求中&#xff0c;有存在一类需求&#xff0c;是需要去定时执行的&#xff0c;此时就需要使用到定时任务。例如说&#xff0c;每分钟扫描超时支付的订单&#xff0c;每小时清理一次日志文件&#xff0c;每天统计前一天的数据并生成报表&#x…

学习:uniapp全栈微信小程序vue3后台(6)

26.实现描述评分标签的双向数据绑定 /pages/wallpaper/picadd Array.prototype.splice() splice() 方法就地移除或者替换已存在的元素和/或添加新的元素。 二次确认 展现 确认标签 删除标签 温故知新&#xff1a; 标签&#xff1a; 关闭标签 27.uni-data-select调用云端分类…

Azure Marketplace 和 Microsoft AppSource的区别

微软的商业应用生态中&#xff0c;Azure Marketplace 和 Microsoft AppSource 是微软并行的两个主要“应用市场”&#xff08;Marketplace&#xff09;&#xff0c;它们共同构成了微软的“商业市场”&#xff08;Commercial Marketplace&#xff09;计划&#xff0c;但服务的目…

完整实验命令解析:从集群搭建到负载均衡配置(2)

一、环境准备与基础网络配置1.1 节点角色与网络规划节点角色主机名所属网段IP 地址网关核心功能Web 服务器web110.1.8.0/2410.1.8.1110.1.8.10&#xff08;后期调整为 10.1.8.20&#xff09;部署 Nginx/HTTPD&#xff0c;提供 Web 服务Web 服务器web210.1.8.0/2410.1.8.1210.1.…

uniapp H5禁止微信浏览器长按出菜单,只针对图片

一、问题描述 如图&#xff1a;uni-image>img,img {pointer-events: none;-webkit-pointer-events: none;-ms-pointer-events: none;-moz-pointer-events: none; }uni-image::before {content: ;position: absolute;top: 0;bottom: 0;left: 0;right: 0;background: transpa…

【机器学习】 15 Gaussian processes

本章目录 15 Gaussian processes 515 15.1 Introduction 515 15.2 GPs for regression 516 15.2.1 Predictions using noise-free observations 517 15.2.2 Predictions using noisy observations 518 15.2.3 Effect of the kernel parameters 519 15.2.4 Estimating the kern…

Vue加载速度优化,verder.js和element.js加载速度慢解决方法

1. 使用CDN 这里把常用的vue、vuex、elementui、echarts、axios都引入成cdn的方式 1、在index.html引入CDN 找到public/index.html在上方引入下边的cdn。 [!NOTE] 引入script的时候&#xff0c;一定要把vue.js放到最上边&#xff0c;最先引入&#xff0c;不然后边的js加载会…

49.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--集成网关--Refit跨服务调用

Refit是一个用于.NET平台的REST库,它可以将REST API转换为实时类型安全的接口。通过Refit,我们可以轻松实现微服务之间的跨服务调用,使服务间通信变得更加简单和类型安全。本文将介绍如何在我们的项目中使用Refit来实现微服务间的通信。 一、什么是Refit Refit是一个强大的REST…

日志ELK、ELFK、EFK

一.ELK架构Elasticsearch Logstash Kibana 数据库日志处理日志显示1.logstash的使用&#xff08;1&#xff09;input&#xff1a;输入&#xff08;2&#xff09;filter&#xff1a;处理&#xff08;3&#xff09;output&#xff1a;输出2.ELFK架构Filebeat-->Elasticsearc…

【CUDA进阶】MMA分析Bank Conflict与Swizzle(下)

目录前言1. bank conflict 分析2. 通过 padding 解决 bank conflict3. mma 搭配 wmma 实现矩阵乘法计算3.1 代码实现3.2 补充&#xff1a;stmatrix_sync 函数分析3.3 补充&#xff1a;__shfl_sync 函数详解4. swizzle 原理讲解5. swizzle 实现思路讲解结语下载链接参考前言 学习…

天气查询系统

项目要求 项目知识点 问题与解决 代码分部 结果展示 项目要求 1.显示天气预报系统界面 2.系统可以通过选择城市配置获取不同城市天气信息 3.查看实时的天气信息 &#xff08;当前温度、最高温度、最低温度、当前湿度、最高湿度、最低湿度、风向、风力、风级等信息&#x…

三重积分的对称性

文章目录前言柱面球面前言 规律作息 柱面 太牛了。我完全看不懂。实际上就类似于极坐标系。 球面 看到这么多东西&#xff0c;我真害怕。今天是 8.30 &#xff0c;不管 9.10 有没有复习完概率的强化&#xff0c;我都一定要开始套卷&#xff0c;还有专业课的复习。ϕ\phiϕ…

深入理解 RabbitMQ:从底层原理到实战落地的全维度指南

引言&#xff1a; 本文总字数&#xff1a;约 18500 字预计阅读时间&#xff1a;45 分钟 为什么我们需要 RabbitMQ&#xff1f; 在当今分布式系统架构中&#xff0c;消息队列已成为不可或缺的核心组件。想象一下&#xff0c;当你在电商平台下单时&#xff0c;系统需要处理库存…

宽带有丢包,重传高的情况怎么优化

宽带丢包和重传率高是一个非常影响网络体验的常见问题。它会导致游戏卡顿、视频通话模糊、网页加载慢等。别担心&#xff0c;我们可以按照从易到难的顺序&#xff0c;系统地排查和优化。请遵循以下步骤&#xff1a;第一步&#xff1a;基础排查&#xff08;自己动手&#xff0c;…

Kotlin 协程之Channel 的高阶应用

前言 了解了 Channel 的基础概念和基本使用 后&#xff0c;我们再来看一看 Channel 的特性以及高阶应用。 Channel 是"热流" 热流概念 Channel 是热流&#xff08;Hot Stream&#xff09;&#xff0c;具备以下特性&#xff1a; 数据的生产和消费是两套独立的流程 …

PostgreSQL表空间(Tablespace)作用(管理数据库对象的存储位置)(pg_default、pg_global)

文章目录**1. 灵活的数据存储管理**- **逻辑与物理分离**&#xff1a;表空间为数据库对象&#xff08;如表、索引&#xff09;提供了一个逻辑名称与物理存储路径的映射。用户无需直接操作底层文件路径&#xff0c;只需通过表空间名称管理数据。- **多数据库共享表空间**&#x…

Ansible 核心运维场景落地:YUM 仓库、SSH 公钥、固定 IP 配置技巧

1&#xff1a;如何一次性验证所有主机能否被 Ansible 访问&#xff1f; 答&#xff1a;使用临时命令&#xff1a;ansible all -m ansible.builtin.ping或验证 sudo 是否正常&#xff1a;ansible all -m ansible.builtin.ping --become -K2&#xff1a;如何用 Ansible 统一配置…

rman导致的报错ORA-27037: unable to obtain file status

有套3节点的11204集群环境&#xff0c;在db2上配置了rman备份&#xff0c;今天例行检查时发现db1和db3上不定期有报错&#xff0c;报错如下&#xff1a;Control file backup creation failed:failure to open backup target file /u01/app/oracle/product/11.2.0/db_1/dbs/snap…