LearnGL - 05.2 - Texture - 实现类似2D UI流光动画


LearnGL - 学习笔记目录

本人才疏学浅,如有什么错误,望不吝指出。

上一篇:LearnGL - 05.1 - Texture Wrap Mode,了解到可以使用纹理坐标来滚动动画。

这一篇:我们使用纹理坐标滚动动画 + 多纹理,实现类似 2D UI 流光动画


实现过程与思路的参考可以查看我之前写的:Unity Shader - 实现类似UI遮罩流光

(个人觉得 Unity 中的 ShaderLab 结合了 GLSL 与 HLSL 的有点,还是比 OpenGL GLSL有好很多的,而且还柔和了多种渲染状态相关的设置在 ShaderLab 里,提升了渲染状态设置的内聚性,便于理解与维护)

思路

  • 准备流光图
  • 准备流光遮罩图
  • 主要显示纹理

流光图(我用GIMP随便画的,还是PS好用)
在这里插入图片描述

流光遮罩图
在这里插入图片描述

主纹理
在这里插入图片描述

着色器

// jave.lin - tex_2d_ui_flash_light.vert - 测试实现 2D UI 流光的顶点着色器
#version 450 compatibility
attribute vec3 vPos;
attribute vec2 vUV;
varying vec2 fUV;
void main() {
	gl_Position = vec4(vPos, 1.0);
	fUV = vUV;
}

// jave.lin - tex_2d_ui_flash_light.frag - 测试实现 2D UI 流光的片段着色器
#version 450 compatibility
varying vec2 fUV;					// uv 坐标

uniform sampler2D main_tex;			// 主纹理
uniform sampler2D mask_tex;			// 遮罩纹理
uniform sampler2D flash_light_tex;	// 闪光/流光纹理

uniform float time;					// 时间(秒)用于动画

void main() {
	vec3 mainCol 	= texture(main_tex, fUV).rgb;
	float mask 		= texture(mask_tex, fUV).r;
	vec4 flashCol 	= texture(flash_light_tex, fUV + vec2(-time, 0));
	flashCol *= flashCol.a * mask;
	mainCol 		= mainCol + flashCol.rgb;
	gl_FragColor 	= vec4(mainCol, 1.0);
}

创建三个纹理对象

	glCreateTextures(GL_TEXTURE_2D, 3, texture);					// 创建 3 个纹理对象

绑定到纹理单元

glActiveTexture(GL_TEXTURE0);									// 激活第 0 索引纹理单元
glBindTextureUnit(0, texture[0]);								// 纹理单元 0 绑定:主纹理
glActiveTexture(GL_TEXTURE1);									// 激活第 1 索引纹理单元
glBindTextureUnit(1, texture[1]);								// 纹理单元 0 绑定:遮罩纹理
glActiveTexture(GL_TEXTURE2);									// 激活第 2 索引纹理单元
glBindTextureUnit(2, texture[2]);								// 纹理单元 0 绑定:闪光/流光纹理

加载三个纹理对象

loadTexture(texture[0], "my_tex.png");							// 加载纹理对象0:主纹理
loadTexture(texture[1], "my_tex_flash_mask.jpg");				// 加载纹理对象1:遮罩纹理
loadTexture(texture[2], "flash.png");							// 加载纹理对象2:闪光/流光纹理

设置采样器采样对应的纹理单元

shaderProgram->setInt("main_tex", 0);					// 主			纹理设置采样器采样 0 索引纹理单元
shaderProgram->setInt("mask_tex", 1);					// 遮罩			纹理设置采样器采样 1 索引纹理单元
shaderProgram->setInt("flash_light_tex", 2);			// 闪光/流光	纹理设置采样器采样 2 索引纹理单元

传入 time 时间控制 uv 纹理坐标动画

shaderProgram->setFloat("time", (float)glfwGetTime());	// 测试用就直接用字符串了,方便一些

运行效果

在这里插入图片描述

完整代码

// jave.lin
#include"glad/glad.h"
#include"GLFW/glfw3.h"
//#include"linmath.h"
// 把linmath.h 放在 iostream 之前include会有错误,所以放到iostream 后include就好了
// 而这个错误正式 xkeycheck.h 文件内 #error 提示的,所以可以使用 #define _XKEYCHECK_H 这个头文件的引用标记宏
// 就可以避免对 xkeycheck.h 头文件的 include 了。
#include<iostream>
#include"linmath.h"
#include"shader.h"
// 使用 stb_image.h 的加载库
// github 源码:https://github.com/nothings/stb/blob/master/stb_image.h
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
// 将之前的打印版本信息代码包含一下
#include"print_gl_version_info.h"

GLfloat vertices[] = {
	// x,	y,	  z
	// 直接放4个顶点
	-0.5f, -0.5f, 0.0f,						// 第0个顶点,左下角
	 0.5f, -0.5f, 0.0f,						// 第1个顶点,右下角
	 0.5f,  0.5f, 0.0f,						// 第2个顶点,右上角
	-0.5f,  0.5f, 0.0f,						// 第3个顶点,左上角
};

GLfloat uvs[] = {								// 顶点的 uv 坐标
	0.0f, 0.0f,									// 左下角
	1.0f, 0.0f,									// 右下角
	1.0f, 1.0f,									// 右上角
	0.0f, 1.0f,									// 左上角
};

GLuint indices[] = {							// 注意索引从0开始!通过索引缓存来指定 图元 组成 用的 顶点有哪些
	0, 1, 3,									// 放置顶点的索引,第一个三角形
	1, 2, 3										// 放置顶点的索引,第二个三角形
};

// 定义:获取 Shader 目录的回调函数原型
typedef char* (__stdcall* GetShaderPathCallback)(char*, const char*);
GetShaderPathCallback g_GetShaderPathCallback = NULL;
// 定义:获取 Pic 目录的回调函数原型
typedef char* (__stdcall* GetPicturePathCallback)(char*, const char*);
GetPicturePathCallback g_GetPicturePathCallback = NULL;

static void error_callback(int error, const char* description) {
	fprintf(stderr, "ErrorCode : %d(0x%08x), Error: %s\n", error, error, description);
}

static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { // 当键盘按键ESCAPE按下时,设置该window为:需要关闭
	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
		glfwSetWindowShouldClose(window, GLFW_TRUE);
}

// 时候开启检测GL的错误
#define CHECK_GL_ERROR

#ifdef CHECK_GL_ERROR
// 检测如果有GL的错误,则提示并退出程序
#define checkGLError() \
{\
	GLenum errorCode = glGetError(); \
	if (errorCode != 0) { \
		std::cout << "Line:" << __LINE__ << " "; \
		std::cout << std::dec; \
		std::cout << "glError : " << errorCode; \
		std::cout << std::hex; \
		std::cout << "(0x" << errorCode << ")" << std::endl; \
		exit(EXIT_FAILURE); \
	}\
}
#else
#define checkGLError() 
#endif

static void loadTexture(GLuint &texture, const char* textureFileName) {
	// loading texture here...
// 加载纹理需要用的图片数据
	char img_path[MAX_PATH];
	//g_GetPicturePathCallback(img_path, "\\Noise\\image1.png");				// 获取图片目录
	g_GetPicturePathCallback(img_path, textureFileName);				// 获取图片目录
	int img_w, img_h, img_channels;
	stbi_set_flip_vertically_on_load(1);							// 也可以在加载前设置加载时翻转的变量
	unsigned char* img_data = stbi_load(img_path, &img_w, &img_h, &img_channels, 4); // 加载图片数据,返回确定宽、高、通道数量、每个分量要多少字节
	if (img_data == NULL) {											// 如果加载图片失败
		std::cout << "Loading Image File : " << img_path << " FAILURE : " << stbi_failure_reason() << std::endl;
		exit(EXIT_FAILURE);
	}
	//stbi__vertical_flip(img_data, img_w, img_h, 4);					// 如果不设置前面stbi_set_flip_vertically_on_load(1),也可以在这手动去翻转,因为图片坐标与纹理坐标的Y轴增量方向不同,所以需要翻转垂直方向的行数数据

	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);	// 使用 OpenGL 4.5+ 的 API 会更清晰:glTextureParameteri,因为这个是更具 target 类型,与当前 bind 的 纹理对象来确定设置那个纹理对象的,从可读性来说 4.5+ 版本的可读性高很多
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTextureParameteri(texture, GL_TEXTURE_WRAP_S, GL_REPEAT);		// 设置 texture 纹理对象的 GL_TEXTURE_WRAP_S 参数,就是设置 uv 中的水平 u 坐标超出0~1范围后的数值环绕方式
	glTextureParameteri(texture, GL_TEXTURE_WRAP_T, GL_REPEAT);		// 设置 texture 纹理对象的 GL_TEXTURE_WRAP_T 参数,就是设置 uv 中的水平 v 坐标超出0~1范围后的数值环绕方式
	glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR);	// 设置 texture 紋理对象的 GL_TEXTURE_MIN_FILTER 在像素缩小时的滤波方式
	glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	// 设置 texture 紋理对象的 GL_TEXTURE_MAG_FILTER 在像素放大时的滤波方式
	checkGLError();

	glTextureStorage2D(												// 设置 texture 纹理对象的内部格式
		texture,													// 要设置的 texture 纹理对象
		1,															// mipmaps 的层数,只要1层 mipmaps 即可,至少要有1层,否则有错误。需要需要多层 mipmaps ,可以指定多层
		GL_RGBA8,													// 内部数据格式
		img_w,														// 图像的宽
		img_h														// 图像的高
	);
	checkGLError();

	glTextureSubImage2D(											// 给 texture 纹理对象设置对应 mipmap 层级的数据
		texture,													// 要设置的 texture 纹理对象
		0,															// mipmaps 的层级索引,从0开始,mipmaps 的
		0, 0,														// 要从 x,y 偏移多少开始,不要偏移所以都填0
		img_w, img_h,												// 要填入的行、列尺寸的像素数量
		GL_RGBA, GL_UNSIGNED_BYTE,									// 外部格式,指定要包含的分量数量 和 分量类型
		img_data													// 外部图片数据
	);
	checkGLError();													// GET_IMG_DATA_TYPE 2 时会有错误
	//glGenerateTextureMipmap(texture);// opengl 4.5 API,生成指定纹理对象的mipmaps
	//checkGLError();

	// when loading complete.
	// free image data here
	stbi_image_free(img_data);										// 纹理已经上传到了显存,内存中的数据可以删除了
}

int main() {
	glfwSetErrorCallback(error_callback); // 安装glfw内部错误时的回调

	if (!glfwInit()) { // 初始化glfw
		std::cout << "glfwInit FAILURE" << std::endl; // 初始化失败
		exit(EXIT_FAILURE);
	}
	// 设置最低的openGL 版本,major:主版本号,minor:次版本号
	// openGl 太低版本的话是不支持CORE Profile模式的
	// 会报错:ErrorCode: 65540(0x00010004), Error : Context profiles are only defined for OpenGL version 3.2 and above
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
	// 根据上面的错误提示,至少使用3.2才行,这里我们使用4.5
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
	//glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);

	// core profile 下运行有问题,不显示任何内容,但不会报错。
	// 着色器编译、着色器程序链接都没有错误日志信息。
	// 很有可能是因为我参考的学习网站使用的API相对比较老,使用的是3.3的。
	//glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	// 所以这里我们不设置 major, minor的版本,默认使用本计算机能用的最高版本
	// 使用 compatibility profile 就有内容出现了。

	int width = 600;
	int height = 600;

	// 使用glfw创建窗体
	GLFWwindow* window = glfwCreateWindow(width, height, "jave.lin - Learning OpenGL - 05_02_Texture_2D_UI_FlashLight", NULL, NULL);
	if (window == NULL) {
		std::cout << "Failed to create GLFW window" << std::endl; // 构建窗体失败
		glfwTerminate();
		exit(EXIT_FAILURE);
	}
	glfwMakeContextCurrent(window);
	glfwSetKeyCallback(window, key_callback); // 安装glfw内部键盘按键的回调

	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { // 装载OpenGL的C函数库
		std::cout << "Failed to initialize OpenGL context" << std::endl; // 装载报错
		glfwTerminate();
		exit(EXIT_FAILURE);
	}

	// 打印版本信息
	print_infos(window);

	// 打印支持最大的顶点支持的数量
	int nrAttributes;
	glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
	std::cout << "Maximum number of vertex attributes supported : " << nrAttributes << std::endl;

	// 打印着色器支持最大的纹理图像单元的数量
	int maxTexUnit;
	glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTexUnit);
	std::cout << "Maximun number of texture image units : " << maxTexUnit << std::endl;

	// 打印着色器支持最大的所有组合的纹理图像单元的数量
	int maxCombinedTexUnit;
	glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxCombinedTexUnit);
	std::cout << "Maximun number of Combined texture image units : " << maxCombinedTexUnit << std::endl;

	GLint vpos_location, vuv_location;
	GLuint vertex_buffer[2], index_buffer;
	GLuint vertex_array_object;
	GLuint texture[3];
	GLuint pixelBufObject;
	GLint success, infoLogLen;

	// 用 lambda 设置,获取 pic 目录的回调,后面在封装
	g_GetPicturePathCallback = [](char* receiveBuff, const char* file)->char* {
		char buf[MAX_PATH];
		sprintf_s(buf, "..\\..\\Dependencies\\Pic\\%s", file);
		strcpy_s(receiveBuff, MAX_PATH, buf);
		return receiveBuff;
	};
	// 用 lambda 设置,获取 shader 目录的回调,后面在封装
	g_GetShaderPathCallback = [](char* receiveBuff, const char* file)->char* {
		char buf[MAX_PATH];
		sprintf_s(buf, "..\\..\\Dependencies\\Shaders\\%s", file);
		strcpy_s(receiveBuff, MAX_PATH, buf);
		return receiveBuff;
	};

	ShaderProgram* shaderProgram = new ShaderProgram;

	// shader program init 5 - 根据shader源码的相对路径(变量),加载deps下的shader
	char vs_path[MAX_PATH], fs_path[MAX_PATH];
	g_GetShaderPathCallback(vs_path, "TestingTexture\\tex_2d_ui_flash_light.vert");
	g_GetShaderPathCallback(fs_path, "TestingTexture\\tex_2d_ui_flash_light.frag");
	if (!shaderProgram->initByPath(vs_path, fs_path)) {
		std::cout << "ShaderProgram init Error: " << shaderProgram->errorLog() << std::endl; // 输出shader program错误
		exit(EXIT_FAILURE);
	}

	glCreateTextures(GL_TEXTURE_2D, 3, texture);					// 创建 3 个纹理对象
	
	glActiveTexture(GL_TEXTURE0);									// 激活第 0 索引纹理单元
	glBindTextureUnit(0, texture[0]);								// 纹理单元 0 绑定:主纹理
	glActiveTexture(GL_TEXTURE1);									// 激活第 1 索引纹理单元
	glBindTextureUnit(1, texture[1]);								// 纹理单元 0 绑定:遮罩纹理
	glActiveTexture(GL_TEXTURE2);									// 激活第 2 索引纹理单元
	glBindTextureUnit(2, texture[2]);								// 纹理单元 0 绑定:闪光/流光纹理
	checkGLError();

	loadTexture(texture[0], "my_tex.png");							// 加载纹理对象0:主纹理
	loadTexture(texture[1], "my_tex_flash_mask.jpg");				// 加载纹理对象1:遮罩纹理
	loadTexture(texture[2], "flash.png");							// 加载纹理对象2:闪光/流光纹理
	checkGLError();

	vpos_location = shaderProgram->getAttributeLoc("vPos");						// 获取 顶点着色器中的顶点 attribute 属性的 location
	vuv_location = shaderProgram->getAttributeLoc("vUV");						// 获取 顶点着色器中的顶点 attribute 属性的 location

	glGenVertexArrays(1, &vertex_array_object);						// 生成1个 VAO

	glGenBuffers(2, vertex_buffer);									// 创建2个 VBO,这里我们因为有一个一样的顶点坐标,一个一样的顶点UV
	glGenBuffers(1, &index_buffer);									// 创建1个 EBO,因为两个 Quad 的顶点索引顺序都是一样的
	//
	// === VAO[0] ===
	//
	glBindVertexArray(vertex_array_object);						// 绑定 VAO[0],那么之后的 vbo, ebo,的绑定指针都是指向该 VAO 中的,还有顶点格式(规范)都会保存在该 VAO

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 绑定 VBO[0]
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 设置 VBO 坐标数据

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]);				// 绑定 VBO[1]
	glBufferData(GL_ARRAY_BUFFER, sizeof(uvs), uvs, GL_STATIC_DRAW); // 设置 VBO uv数据

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);			// 绑定 EBO
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); // 设置 EBO 数据

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[0]);				// 绑定 VBO[0],因为后面要设置该 VBO 的坐标格式
	glVertexAttribPointer(vpos_location, 3, GL_FLOAT, GL_FALSE,		// 设置 顶点属性 vPos 格式
		sizeof(GLfloat) * 3, (GLvoid*)0);
	glEnableVertexAttribArray(vpos_location);						// 启用 顶点缓存 location 位置的属性

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer[1]);				// 绑定 VBO[1],因为后面要设置该 VBO 的uv格式
	glVertexAttribPointer(vuv_location, 2, GL_FLOAT, GL_FALSE,		// 设置 顶点属性 vUV 格式
		sizeof(GLfloat) * 2, (GLvoid*)0);
	glEnableVertexAttribArray(vuv_location);						// 启用 顶点缓存 location uv的属性

	//glEnable(GL_CULL_FACE);										// 开启面向剔除
	//glCullFace(GL_BACK);										// 设置剔除背面
	GLboolean cf = glIsEnabled(GL_CULL_FACE);					// 查看是否启用面向剔除
	std::cout << "cull face enabled : " << (cf ? "true" : "false") << std::endl;

	//glFrontFace(GL_CW);											// 顺时针
	//glFrontFace(GL_CCW);										// 逆时针(默认的)ClockWise
	GLint facing;
	glGetIntegerv(GL_FRONT_FACE, &facing);						// 获取正面的顺逆时针 : CW(ClockWise - 顺时针), CCW(Counter ClockWise - 逆时针)
	std::cout << "facing : " << (facing == GL_CW ? "CW" : "CCW") << std::endl;

	while (!glfwWindowShouldClose(window)) {					// 检测是否需要关闭窗体

		glfwGetFramebufferSize(window, &width, &height);		// 获取窗口大小

		glViewport(0, 0, width, height);						// 设置Viewport
		glClearColor(0.1f, 0.2f, 0.1f, 0.f);					// 设置清理颜色缓存时,填充颜色值
		glClear(GL_COLOR_BUFFER_BIT);							// 清理颜色缓存

		shaderProgram->use();									// 使用此着色器程序
		shaderProgram->setInt("main_tex", 0);					// 主			纹理设置采样器采样 0 索引纹理单元
		shaderProgram->setInt("mask_tex", 1);					// 遮罩			纹理设置采样器采样 1 索引纹理单元
		shaderProgram->setInt("flash_light_tex", 2);			// 闪光/流光	纹理设置采样器采样 2 索引纹理单元
		shaderProgram->setFloat("time", (float)glfwGetTime());	// 测试用就直接用字符串了,方便一些

		glBindVertexArray(vertex_array_object);				// 先绘制 VAO[0] 的 VBO,EBO,VAF,ENABLED
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (GLvoid*)0); // 参数1:绘制三角图元;参数2:取6个索引来绘制三角图元(每个三角图元需要3个,所以可以画两个三角图元);参数3:将 GL_ELEMENT_ARRAY_BUFFER 每个元素视为 uint 类型;参数4:设置索引缓存的字节偏移量。也可以设置为另一个 缓存数据的指针,即:使用另一个数据。

		glfwSwapBuffers(window);								// swap buffer, from backbuffer to front buffer
		glfwPollEvents();										// 处理其他的系统消息
	}

	glDeleteBuffers(1, &pixelBufObject);						// 测试删除 BO
	glDeleteBuffers(2, vertex_buffer);							// 测试删除 VBO
	glDeleteBuffers(1, &index_buffer);							// 测试删除 EBO
	glDeleteBuffers(1, &vertex_array_object);					// 测试删除 VAO
	glDeleteTextures(3, texture);								// 删除纹理对象 TO
	delete shaderProgram;										// 销毁 shader program

	checkGLError();												// 最后再看看GL还有什么错误

	glfwDestroyWindow(window);									// 销毁之前创建的window对象
	glfwTerminate();											// 清理glfw之前申请的资源

	//checkGLError();												// 最后再看看GL还有什么错误,从这句一直报错可以看出:glfw 的销毁与退出是有问题的

	return 0;
} // int main() {
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值