OpenGL学习记录(四)

参考资料:
https://learnopengl.com/
https://learnopengl-cn.github.io/

这次要做的是将两张纹理叠加映射到图像上。为了方便读取纹理数据,可以用 这里 提供的库文件,导入后使用如下语句进行图片数据的读取:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

int width, height, nrChannel;
unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannel, 0);

使用纹理需要使用uv值,接下来需要调整顶点着色器使其能够接受顶点坐标为一个顶点属性,并把坐标传给片元着色器。现在顶点着色器将接收位置、颜色、uv三种信息,输出顶点颜色和uv信息:

//顶点着色器编码
#version 330 core										
layout(location = 0) in vec3 aPos;						
layout(location = 1) in vec3 aColor;		
layout(location = 2) in vec2 aTexCoord;	

out vec4 vertexColor;						  
out vec2 TexCoord;

void main() {
    
    											
	gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);    
	vertexColor = vec4(aColor, 1.0f);	
	TexCoord = aTexCoord;
}													

片元着色器则接收顶点颜色和uv信息,并声明两张sampler2D纹理,用于实现两张图片叠加效果。计算颜色时,使用texture采样纹理颜色,使用mix函数将两张图片的采样结果混合:

//片元着色器编码
#version 330 core				
in vec4 vertexColor;	
in vec2 TexCoord;

uniform sampler2D ourTexture;
uniform sampler2D ourFace;

out vec4 FragColor;

void main() {
    
    					
	FragColor = mix(texture(ourTexture, TexCoord), texture(ourFace, TexCoord), 0.3);	
}	

为此,所有顶点的信息也要对应修改,添加相应的纹理坐标:

//顶点数据
float vertices[] = {
    
    
	//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
		 0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
		 0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
		-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
		-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
};

以及除了告诉OpenGL解析顶点的位置颜色属性外,也要解析uv属性,也因添加了uv这个二维属性,“在连续的顶点属性组之间的间隔”这一项大小也变为了8,其中位置信息占3,颜色信息占3,纹理坐标信息占2:

//位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); //告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)。参数1:要配置的顶点属性(layout(location = 0),故0),参数2:指定顶点属性的大小(vec3,故3),参数3:指定数据的类型,参数4:是否希望数据被标准化,参数5:在连续的顶点属性组之间的间隔,参数6:表示位置数据在缓冲中起始位置的偏移量(Offset)。
glEnableVertexAttribArray(0); //以顶点属性位置值作为参数,启用顶点属性,由于前面声明了layout(location = 0),故为0

//颜色属性
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);

//UV属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

现在的顶点信息结构如下图所示:
在这里插入图片描述
OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。我们需要两张贴图,因此使用两个纹理单元,保证不冲突和单元号小于16就行。准备完成后,在main函数中需要做的是,绑定纹理到各自的纹理单元,可使用glActiveTexture和glBindTexture来完成,然后读取纹理,之前已提到。还需告诉OpenGL每个着色器采样器属于哪个纹理单元,使用glUniform1i实现,最后绘制。另外,由于OpenGL要求y轴0.0坐标是在图片的底部的,也就是原点在左下角,输出时有可能会出现图片颠倒问题,可在加载任何图像前加入stbi_set_flip_vertically_on_load(true)语句对y轴进行翻转。带注释的主文件代码如下:

#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "Shader.h"

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

//顶点数据
float vertices[] = {
    
    
	//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -
		 0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // 右上
		 0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // 右下
		-0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // 左下
		-0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // 左上
};

//顶点索引
unsigned int indices[] = {
    
    
	0, 1, 2,   //第一个三角形使用的顶点
	2, 3, 0    //第二个三角形使用的顶点
};


//检查输入函数
void processInput(GLFWwindow* window)
{
    
    
	//按下ESC键时,将WindowShouldClose设为true,循环绘制将停止
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
	{
    
    
		glfwSetWindowShouldClose(window, true);
	}
}

//视口改变时的回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    
    
	glViewport(0, 0, width, height); //OpenGL渲染窗口的尺寸大小
}

int main() 
{
    
    
	glfwInit(); //初始化GLFW
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); //告诉GLFW要使用OpenGL的版本号
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); //主版本号、次版本号都为3,即3.3版本
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); //告诉GLFW使用核心模式(Core-profile)

	//打开 GLFW Window
	GLFWwindow* window = glfwCreateWindow(1600, 1200, "My OpenGL Game", nullptr, nullptr);
	if (window == nullptr) //若窗口创建失败,打印错误信息,终止GLFW并return -1
	{
    
    
		printf("Open window failed.");
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window); //创建OpenGL上下文
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); //用户改变窗口大小的时候,视口调用回调函数

	//初始化GLEW
	glewExperimental = true;
	if (glewInit() != GLEW_OK) //若GLEW初始化失败,打印错误信息并终止GLFW窗口
	{
    
    
		printf("Init GLEW failed.");
		glfwTerminate();
		return -1;
	}


	Shader* myShader = new Shader("vertexSource.txt", "fragmentSource.txt");

	//创建VAO(顶点数组对象)
	unsigned int VAO;
	glGenVertexArrays(1, &VAO); //生成一个顶点数组对象
	glBindVertexArray(VAO); //绑定VAO

	//创建VBO(顶点缓冲对象)
	unsigned int VBO;
	glGenBuffers(1, &VBO); //生成缓冲区对象。第一个参数是要生成的缓冲对象的数量,第二个是要输入用来存储缓冲对象名称的数组,由于只需创建一个VBO,因此不需要用数组形式
	glBindBuffer(GL_ARRAY_BUFFER, VBO); //把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上,GL_ARRAY_BUFFER是一种顶点缓冲对象的缓冲类型。OpenGL允许同时绑定多个缓冲,只要它们是不同的缓冲类型。
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); //把定义的数据复制到当前绑定缓冲的函数。参数1:目标缓冲类型,参数2:指定传输数据的大小(以字节为单位),参数3:我们希望发送的实际数据,参数4:指定显卡如何管理给定的数据,GL_STATIC_DRAW表示数据不会或几乎不会改变。

	//创建EBO(元素缓冲对象/索引缓冲对象)
	unsigned int EBO;
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);


	//位置属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); //告诉OpenGL该如何解析顶点数据(应用到逐个顶点属性上)。参数1:要配置的顶点属性(layout(location = 0),故0),参数2:指定顶点属性的大小(vec3,故3),参数3:指定数据的类型,参数4:是否希望数据被标准化,参数5:在连续的顶点属性组之间的间隔,参数6:表示位置数据在缓冲中起始位置的偏移量(Offset)。
	glEnableVertexAttribArray(0); //以顶点属性位置值作为参数,启用顶点属性,由于前面声明了layout(location = 0),故为0

	//颜色属性
	glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);

	//UV属性
	glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
	glEnableVertexAttribArray(2);

	//使用纹理单元0绑定TexBufferA
	unsigned int TexBufferA;
	glGenTextures(1, &TexBufferA);
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, TexBufferA);

	int width, height, nrChannel;
	stbi_set_flip_vertically_on_load(true); //翻转图像

	//加载并生成第一张纹理
	unsigned char* data = stbi_load("container.jpg", &width, &height, &nrChannel, 0);
	if (data)
	{
    
    
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
    
    
		std::cout << "图片加载失败" << std::endl;
	}
	stbi_image_free(data); //释放

	//使用纹理单元3绑定TexBufferB
	unsigned int TexBufferB;
	glGenTextures(1, &TexBufferB);
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, TexBufferB);

	//加载并生成第二张纹理
	unsigned char* data2 = stbi_load("awesomeface1.png", &width, &height, &nrChannel, 0);
	if (data2)
	{
    
    
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
    
    
		std::cout << "图片加载失败" << std::endl;
	}
	stbi_image_free(data2);


	//让程序在手动关闭之前不断绘制图像
	while (!glfwWindowShouldClose(window))
	{
    
    
		//检测输入
		processInput(window);

		//渲染指令
		glClearColor(0.f, 0.5f, 0.5f, 1.0f); //设置清空屏幕所用的颜色
		glClear(GL_COLOR_BUFFER_BIT); //清空屏幕的颜色缓冲区

		//绑定纹理到对应的纹理单元
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, TexBufferA);
		glActiveTexture(GL_TEXTURE3);
		glBindTexture(GL_TEXTURE_2D, TexBufferB);

		glBindVertexArray(VAO); //绘制物体的时候就拿出相应的VAO,绑定它
		glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); //绑定EBO
		

		myShader->use();

		//告诉OpenGL每个着色器采样器属于哪个纹理单元
		glUniform1i(glGetUniformLocation(myShader->ID, "ourTexture"), 0); 
		glUniform1i(glGetUniformLocation(myShader->ID, "ourFace"), 3);

		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); //绘制,参数1:绘制模式(三角形),参数2:绘制顶点数(两个三角形6个顶点),参数3:索引的类型,参数4:指定EBO中的偏移量。


		//检查并调用事件,交换缓冲区
		glfwSwapBuffers(window); //交换颜色缓冲区,前缓冲区保存最终输出的图像,后缓冲区负责绘制渲染指令,当渲染指令执行完毕后,交换前后缓冲区,使完整图像呈现出来,避免逐像素绘制图案时的割裂感
		glfwPollEvents(); //检查触发事件,如键盘输入、鼠标移动等
	}


	glfwTerminate(); //关闭GLFW并退出
	return 0;
}

运行可生成以下结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_47260762/article/details/128196535
今日推荐