【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
英文地址,請注明出處:http://en.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…