【OpenGL学习笔记⑤】——纹理变换【glm配置+两张图片交替渐变变换 + 纹理平移 + 实现雪花飘落】
关键字:OpenGL、纹理变换、GLM环境配置、两张图片交替变换、图片渐变变换、纹理平移、雪花飘落
🍻 国庆节快乐!
有趣的正方形 ☁️
上一篇文章地址链接:【OpenGL学习笔记④】——纹理贴图【绘制木板 + 纹理环绕】.
下一篇文章地址链接:【OpenGL学习笔记⑥】——3D变换【旋转的正方体 ⭐实现地月系统⭐ 旋转+平移+缩放】.
OpenGL总学习目录: https://blog.csdn.net/Wang_Dou_Dou_/article/details/121240714.
零、成果预览图:
● 实现思路:纹理环绕 + 两张纹理(图片)交替变换 + 纹理平移 【注:纹理环绕在上一篇文章中已经写了】
● 刚做出来还是挺开心的 😀,挺好看的。那接下来让我们共同来学习,并将其实现吧!
● 首先,大家先保存这两张图片,待会要用:
第一张雪花图→ ,第二张雪花图→
● 两张雪花图的放置地方,需要和 .cpp 源代码文件在同一个目录下。如下图所示:
一、纹理单元
● 其实在上一篇文章 【OpenGL学习笔记④】——纹理贴图【绘制木板 + 纹理环绕】中的 “八、绘制纹理” 中就已经写了部分关于 “纹理单元” 的相关知识。
● 纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。
● 新的片元着色器的代码如下:
#version 330 core
// in vec3 ourColor;
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D ourTexture_1; // 负责纹理一的采样器
uniform sampler2D ourTexture_2; // 负责纹理二的采样器
uniform float time;
void main()
{
FragColor = mix(texture(ourTexture_1, TexCoord), texture(ourTexture_2, TexCoord), time); // 混合重叠
}
◆ 说明一:GLSL 内建的 mix()
函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回 80% 的第一个输入颜色和 20% 的第二个输入颜色,即返回两个纹理的混合色。
◆ 说明二:mix()
函数的第 3 个参数设置为time
是 后面要实现交替变换而设置的一个 变化范围为[0.0 , 1.0]的 float 类型的变量。
● OpenGL 有 16 个纹理单元供我们使用,也就是说我们可以激活从 GL_TEXTURE0 到 GL_TEXTRUE15 。当我们需要循环一些纹理单元的时候会很有用。
● 通过使用 glUniform1i()
设置每个采样器的方式告诉 OpenGL 每个着色器采样器属于哪个纹理单元。我们只需要设置一次即可,所以这个会放在渲染循环(即在 “draw loop” )之前:
/* 设置 uniform 变量 */
ourShader.Use(); // 不要忘记在设置 uniform 变量之前激活着色器程序!
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture_1"), 0); // 告诉名为“ourTexture_1”的着色器采样器(关键字为Sampler2D) 属于纹理单元0
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture_2"), 1); // 告诉名为“ourTexture_2”的着色器采样器(关键字为Sampler2D) 属于纹理单元1
● 为了使用第二个纹理(以及第一个),我们必须改变一点渲染流程【在主函数中】。即先用glActiveTexture()
激活相应的纹理单元,然后再用glBindTexture()
绑定纹理对到相应的纹理目标上:
glActiveTexture(GL_TEXTURE0); // 激活 0 号纹理单元。最多 16 个通道
glBindTexture(GL_TEXTURE_2D, texture_1); // 绑定这个纹理到当前激活的纹理目标
glActiveTexture(GL_TEXTURE1); // 激活 1 号纹理单元。最多 16 个通道
glBindTexture(GL_TEXTURE_2D, texture_2); // 绑定这个纹理到当前激活的纹理目标
二、纹理平移(附glm配置)
● 在实现纹理平移之前,需要的一个额外的库(GLM)。GLM 是 OpenGL Mathematics 的缩写,它是一个只有头文件的库,也就是说我们只需包含对应的头文件就行了,不用链接和编译。我推荐一个安装和配置的网站:OpenGL GLM 环境配置
● 所需要的多数功能可以在下面3个头文件中找到:
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
● 在主函数中,我们先用定义一个mat4
类型的变量 transform_1
,初始化为一个 4×4 的单位矩阵。
glm::mat4 firsrMatrix_1 = glm::mat4(1.0f); // 初始化为单位矩阵
● 下一步是创建一个变换矩阵,我们是把单位矩阵和一个位移向量传递给glm::translate()
函数来完成这个工作的。
♦ 说明一:得到的向量应该是(firsrMatrix_1.x + 0.0f, firsrMatrix_1.y + 1.0f - cnt*1.0/N, firsrMatrix_1.z + 0.0f),也就是(原始矩阵的 x x x + x x x方向上的偏量,原始矩阵的 y y y + y y y方向上的偏量,原始矩阵的 z z z + z z z方向上的偏量)。原理图如下:
♦ 说明二:代码如下【在主函数中】
glm::mat4 transform_1 = glm::translate(firsrMatrix_1, glm::vec3(0.0f, 1.0f - cnt*1.0/N, 0.0f));
♦ 注:代码中的 cnt 是计数器变量,随时间的变化而改变。N 是常量(用于放缓“下雪速度”)。
● 然后在顶点着色器中用变换矩阵乘以固定的位置向量就能获得最后需要的四个参数的行向量,原理图如下:
● 顶点着色器的代码如下:
#version 330 core
layout (location = 0) in vec3 position; // 坐标变量的顶点属性位置值为 1
layout(location = 1) in vec3 color; // 颜色变量的顶点属性位置值为 1
layout(location = 2) in vec2 textureCoords; // 纹理坐标只有两个浮点数 (s,t), 属性位置值为 2
out vec2 TexCoord;
// out vec3 ourColor;
uniform mat4 transform_1; // 一个分号忘了加, 差点要了我的命....
void main()
{
// ourColor = color;
gl_Position = transform_1 * vec4(position, 1.0); // transform_1 就是那个变换矩阵
TexCoord = vec2(textureCoords.x, 1-textureCoords.y);
}
♦ 注意:因为,矩阵相乘顺序不能乱变,若写成 “gl_Position = vec4(position, 1.0) * transform_1;”,你将看到 “神奇” 的翻转下雪场景。
● 做完以上工作后,还需要做一些关于 glm 的相关设置:
// get matrix's uniform location and set matrix
unsigned int transformLoc = glGetUniformLocation(ourShader.Program, "transform_1");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform_1));
♦ 说明:
① 首先找到名为“transform_1”的uniform属性的变量的地址,然后用以 Matrix4fv为后缀的 glUniform()
函数把矩阵数据发送给着色器。
② 第一个参数:它是 uniform 的位置值。
③ 第二个参数:告诉 OpenGL 我们将要发送多少个矩阵,这里是1。
④ 第三个参数:询问我们我们是否希望对我们的矩阵进行置换。GLM 的默认布局是列主序,所以不需要置换,我们填 GL_FALSE。
⑤ 第四个参数:真正的矩阵数据。但是需要用 GLM 的自带的函数value_ptr()
来转换一下。
三、随时间变化的变换机制
● 这部分代码因人而异,只靠找好 “循环点” 就行,我实现过程的伪代码如下:
...
... // 前面的纹理生成等代码
int cnt = 0; // 计数器
int flag = 1; // 纹理切换标志
int N = 2000; // N 越大,位移/交替的速度都会变慢
/* draw loop 画图循环 */
while (!glfwWindowShouldClose(window_1))
{
/* 视口 + 时间 */
...
/* 渲染 + 清除颜色缓冲 */
...
/* 激活 + 绑定纹理 */
...
/* 每一次循环引起的反应 */
cnt = cnt + 1; // 计数器加1
if( cnt >= 2*N )
cnt = 0; // 设置重复循环
if( cnt == 0 || cnt == N )
flag = -flag; // 设置纹理循环交替标志
/* 平移变换的实现 */
glm::mat4 firsrMatrix_1 = glm::mat4(1.0f); // 先初始化一个单位矩阵
glm::mat4 transform_1 = glm::translate(firsrMatrix_1, glm::vec3(0.0f, 1.0f - cnt*1.0/N, 0.0f));
/* 交替变换的实现 */
int vertexColorLocation = glGetUniformLocation(ourShader.Program, "time"); // 找到 “time” 的索引
if( flag == 1 )
glUniform1f(vertexColorLocation, cnt*1.0/N ); // 从纹理1 逐渐交替到 纹理2
else
glUniform1f(vertexColorLocation, 2.0 - cnt*1.0/N ); // 从纹理2 逐渐交替到 纹理1
// get matrix's uniform location and set matrix
...
/* 绘制图形 */
...
}
四、完整代码(主函数)
● 头文件 Shader.h 依旧沿用第③篇的代码⭐着色器【GLSL Uniform 彩色三角形 变色正方形】⭐。主函数如下:
/* 引入相应的库 */
#include <iostream>
using namespace std;
#define GLEW_STATIC
#include"Shader.h"
#include"glew-2.2.0\include\GL\glew.h" // 注:这一部分要根据个人情况进行设定
#include"glfw-3.3.4.bin.WIN32\include\GLFW\glfw3.h"
#include"SOIL2\include\stb_image.h"
#include"SOIL2\include\SOIL2.h"
#include"glm\glm.hpp"
#include"glm\gtc\matrix_transform.hpp"
#include"glm\gtc\type_ptr.hpp"
/* 编写各顶点位置 */
GLfloat vertices_1[] =
{
//position // color // texture_1 coords(纹理坐标)
1.0f, 3.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 2.0f, // 右上顶点 编号0
1.0f, -3.0, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, -1.0f, // 右下顶点 编号1
-1.0f, -3.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, // 左下顶点 编号2
-1.0f, 3.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f // 左上顶点 编号3
// 这里纹理坐标的范围超过 (0, 0) 到 (1, 1) 的部分会被进行 “纹理环绕” 处理!!!
};
/* 四个顶点的连接信息给出来 */
GLuint indices_1[] =
{
0, 1, 3, // 序号为 0、1、3 的顶点组合成一个三角形
1, 2, 3 // 序号为 1、2、3 的顶点组合成一个三角形
};
const GLint WIDTH = 600, HEIGHT = 600;
int main()
{
glfwInit();
GLFWwindow* window_1 = glfwCreateWindow(WIDTH, HEIGHT, "Learn OpenGL Transform test", nullptr, nullptr);
int screenWidth_1, screenHeight_1;
glfwGetFramebufferSize(window_1, &screenWidth_1, &screenHeight_1);
cout << "screenWidth_1 = " << screenWidth_1 << ", screenHeight = " << screenHeight_1 << endl;
glfwMakeContextCurrent(window_1);
glewInit();
/* 将我们自己设置的着色器文本传进来 */
Shader ourShader = Shader("shader_v.txt", "shader_f.txt"); // 相对路径
/* 设置顶点缓冲对象(VBO) + 设置顶点数组对象(VAO) + 索引缓冲对象(EBO) */
GLuint VAO, VBO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_1), vertices_1, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices_1), indices_1, GL_STATIC_DRAW);
/* 设置链接顶点属性 */
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0); // 通道 0 打开
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (GLvoid*)(3*sizeof(GLfloat)));
glEnableVertexAttribArray(1); // 通道 1 打开
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (GLvoid*)(6*sizeof(GLfloat)));
glEnableVertexAttribArray(2); // 通道 2 打开
/* 读取纹理 */
int width, height;
unsigned char* image_1 = SOIL_load_image("T_snow1.png", &width, &height, 0, SOIL_LOAD_RGBA); // 获取图片
cout << "width = " << width << ", height = " << height <<endl;
unsigned char* image_2 = SOIL_load_image("T_snow2.png", &width, &height, 0, SOIL_LOAD_RGBA);
cout << "width = " << width << ", height = " << height <<endl;
/* 生成纹理——雪花图1 */
GLuint texture_1;
glGenTextures(1, &texture_1); // 生成纹理的数量为 1
glBindTexture(GL_TEXTURE_2D, texture_1); // 绑定在 GL_TEXTURE_2D 上
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_1);
glGenerateMipmap(GL_TEXTURE_2D); // 多层渐进纹理
/* 生成纹理——雪花图2 */
GLuint texture_2;
glGenTextures(1, &texture_2);
glBindTexture(GL_TEXTURE_2D, texture_2);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_2);
glGenerateMipmap(GL_TEXTURE_2D); // 多层渐进纹理
/* 释放图像内存 */
SOIL_free_image_data(image_1);
SOIL_free_image_data(image_2);
/* 纹理环绕方式 */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // S 坐标
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // T 坐标
/* 纹理过滤 */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/* 设置 uniform 变量 */
ourShader.Use(); // don't forget to activate/use the shader before setting uniforms!
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture_1"), 0);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture_2"), 1);
int cnt = 0; // 计数器
int flag = 1; // 纹理切换标志
int N = 2000; // N 越大,位移/交替的速度都会变慢
/* draw loop 画图循环 */
while (!glfwWindowShouldClose(window_1))
{
/* 视口 + 时间 */
glViewport(0, 0, screenWidth_1, screenHeight_1);
glfwPollEvents();
/* 渲染 + 清除颜色缓冲 */
glClearColor(0.5f, 0.8f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
/* 激活 + 绑定纹理 */
glActiveTexture(GL_TEXTURE0); // 激活 0 号纹理单元。最多 16 个通道
glBindTexture(GL_TEXTURE_2D, texture_1); // 绑定这个纹理到当前激活的纹理目标
glActiveTexture(GL_TEXTURE1); // 激活 1 号纹理单元。最多 16 个通道
glBindTexture(GL_TEXTURE_2D, texture_2); // 绑定这个纹理到当前激活的纹理目标
/* 每一次循环引起的反应 */
cnt = cnt + 1; // 计数器加1
if( cnt >= 2*N )
cnt = 0; // 设置重复循环
if( cnt == 0 || cnt == N )
flag = -flag; // 设置纹理循环交替标志
/* 平移变换的实现 */
glm::mat4 firsrMatrix_1 = glm::mat4(1.0f); // 初始化为单位矩阵
glm::mat4 transform_1 = glm::translate(firsrMatrix_1, glm::vec3(0.0f, 1.0f - cnt*1.0/N, 0.0f));
/* 交替变换的实现 */
int vertexColorLocation = glGetUniformLocation(ourShader.Program, "time"); // 找到 “time” 的索引
if( flag == 1 )
glUniform1f(vertexColorLocation, cnt*1.0/N ); // 从纹理1 逐渐交替到 纹理2
else
glUniform1f(vertexColorLocation, 2.0 - cnt*1.0/N ); // 从纹理2 逐渐交替到 纹理1
// get matrix's uniform location and set matrix
unsigned int transformLoc = glGetUniformLocation(ourShader.Program, "transform_1");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(transform_1));
/* 绘制图形 */
//ourShader.Use();
glBindVertexArray(VAO); // 绑定 VAO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); // 绑定 EBO
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); // 画两个三角形 从第0个顶点开始 一共画6次
glBindVertexArray(0); // 解绑定 VAO
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); // 解绑定 EBO
glBindTexture(GL_TEXTURE_2D, 0); // 解绑定 纹理
/* 交换缓冲 */
glfwSwapBuffers(window_1);
}
/* 释放资源 */
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate(); // 结束
return 0;
}
● 运行结果:
五、参考附录:
[1] 《LearnOpenGL CN —— 纹理》
链接: https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/.
[2] 《LearnOpenGL CN —— 变换》
链接: https://learnopengl-cn.github.io/01%20Getting%20started/07%20Transformations/.
[3] 《OpenGL GLM 环境配置》
链接: https://www.pianshen.com/article/130332661/.
上一篇文章地址链接:【OpenGL学习笔记④】——纹理贴图【绘制木板 + 纹理环绕】.
下一篇文章地址链接:【OpenGL学习笔记⑥】——3D变换【旋转的正方体 ⭐实现地月系统⭐ 旋转+平移+缩放】.
OpenGL总学习目录: https://blog.csdn.net/Wang_Dou_Dou_/article/details/121240714.
🍻 国庆节快乐!
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)