OpenGL learning records (4)

References:
https://learnopengl.com/
https://learnopengl-cn.github.io/

What we're going to do this time is map the two textures overlay onto the image. In order to facilitate the reading of texture data, you can use the library file provided here , and use the following statement to read the image data after importing:

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

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

Using the texture requires the use of uv values. Next, you need to adjust the vertex shader to accept vertex coordinates as a vertex attribute, and pass the coordinates to the fragment shader. Now the vertex shader will receive position, color, and uv information, and output vertex color and uv information:

//顶点着色器编码
#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;
}													

The fragment shader receives vertex color and uv information, and declares two sampler2D textures, which are used to achieve the superimposed effect of two images. When calculating the color, use texture to sample the texture color, and use the mix function to mix the sampling results of the two images:

//片元着色器编码
#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);	
}	

For this reason, the information of all vertices should be modified accordingly, and the corresponding texture coordinates should be added:

//顶点数据
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    // 左上
};

And in addition to telling OpenGL to parse the position color attribute of the vertex, it also needs to parse the uv attribute. Because of the addition of the uv two-dimensional attribute, the size of the "interval between consecutive vertex attribute groups" has also changed to 8, of which Position information accounts for 3, color information accounts for 3, and texture coordinate information accounts for 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);

The current vertex information structure is shown in the figure below:
insert image description here
OpenGL guarantees at least 16 texture units for you to use, that is to say, you can activate from GL_TEXTURE0 to GL_TEXTRUE15. We need two textures, so use two texture units to ensure that there is no conflict and the unit number is less than 16. After the preparation is completed, what needs to be done in the main function is to bind the texture to the respective texture unit, which can be done using glActiveTexture and glBindTexture, and then read the texture, as mentioned before. You also need to tell OpenGL which texture unit each shader sampler belongs to, implement it using glUniform1i, and finally draw it. In addition, since OpenGL requires that the 0.0 coordinate of the y-axis is at the bottom of the picture, that is, the origin is at the lower left corner, the picture may be inverted when outputting. You can add the stbi_set_flip_vertically_on_load(true) statement before loading any image to flip the y-axis. . The annotated main file code is as follows:

#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;
}

Running produces the following results:
insert image description here

Guess you like

Origin blog.csdn.net/weixin_47260762/article/details/128196535