OpenGL 纹理和贴图

纹理和贴图:

  • 可以为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像。但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多的颜色。这将会产生很多额外开销,因为每个模型都会需求更多的顶点,每个顶点又需求一个颜色属性。
  • 纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节
  • 纹理也可以被用来储存大量的数据,这些数据可以发送到着色器上
  • 为了将纹理映射到 三角形上,需要指定纹理坐标,纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的左下角到右上角
  • 纹理坐标轴s、t(如果是使用3D纹理那么还有一个r)它们和空间坐标系x、y、z是等价的

纹理环绕方式:(纹理坐标设置在范围之外所采取的措施)

环绕方式描述
GL_REPEAT对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER超出的坐标为用户指定的边缘颜色。

纹理过滤:

  • GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式,OpenGL会选择中心点最接近纹理坐标的那个像素
  • GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色

参考链接:
https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/
https://www.cnblogs.com/zhxmdefj/p/11255134.html


纹理单元:

先了解一下为什么sampler2D变量是个uniform,我们却不用glUniform给它赋值?

首先要知道,如果我们使用glUniform1i,给纹理采样器分配的就是一个位置值(有了位置我们能在一个片段着色器中设置多个纹理),一个纹理的位置值通常称为一个纹理单元(Texture Unit),由于一个纹理的默认纹理单元是0,它是默认的激活纹理单元,所以我们前面就没有给它分配

纹理单元的主要目的是让我们在着色器中可以使用多个的纹理,通过把纹理单元赋值给采样器,只要我们首先激活对应的纹理单元,我们可以一次绑定多个纹理

就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元:

激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元,纹理单元GL_TEXTURE0默认总是被激活,所以我们在前面的例子里当我们使用glBindTexture的时候,无需激活任何纹理单元

OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15,它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用

我们仍然需要编辑片段着色器来接收另一个采样器


准备两张图片:

wallhh

图形加载库stb_image.h:https://blog.csdn.net/y_dd6011/article/details/116703348?spm=1001.2014.3001.5501

着色器类的头文件shader.h:https://blog.csdn.net/y_dd6011/article/details/116701837?spm=1001.2014.3001.5501


加载与创建Texture的过程:

  • 编写着色器程序
  • 定义纹理坐标:indices[]
  • 定义纹理对象:glGenTextures(1, &texture)
  • 加载图片stbi_load(“wall.jpg”, &width, &height, &nrChannels, 0)
  • 释放图像内存:stbi_image_free(data)
  • 设置纹理单元:ourShader.setInt(“ourTexture1”, 1);
  • 在渲染循环中,激活纹理

效果图:
image-20210512163733056
源码:

// shader.vs
// -------------------------
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    TexCoord = aTexCoord;
}

// shader.fs
// -------------------------
#version 330 core
out vec4 FragColor;

in vec2 TexCoord;
uniform sampler2D ourTexture;

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

// main.cpp
// ---------------------------
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "shader.h"
#include "stb_image.h"

// 定义回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// 宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
	// 初始化和配置 glfw
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	// 创建glfw窗口
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Hello world", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// 使用glad加载OpenGL函数指针
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to inittalize GLAD" << std::endl;
		return -1;
	}

	// 确定视口(Viewport)的大小
	glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// 设置两个必须的着色器:顶点和片段着色器
	Shader ourShader("shader.vs", "shader.fs");

	// 定义正方形坐标
	float vertices[] = {
		//  位置            // 纹理坐标
		 0.5f,  0.5f, 0.0f, 1.0f, 1.0f,   // 右上
		 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,   // 右下
		-0.5f, -0.5f, 0.0f, 0.0f, 0.0f,   // 左下
		-0.5f,  0.5f, 0.0f, 0.0f, 1.0f    // 左上
	};

	unsigned int indices[] = {
	   0, 1, 3, // first triangle
	   1, 2, 3  // second triangle
	};

	// 设置顶点缓冲对象(Vertex Buffer Objects, VBO)管理内存(坐标点数据)与 VAO
	unsigned int VAO, VBO, EBO;
	glGenVertexArrays(1, &VAO);  // 使用glGenVertexArrays函数和一个缓冲ID生成一个VAO对象
	glGenBuffers(1, &VBO);       // 使用glGenBuffers函数和一个缓冲ID生成一个VBO对象
	glGenBuffers(1, &EBO);

	// 绑定: 先VAO 再VBO
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	// 位置属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);  // 使用glEnableVertexAttribArray,以顶点属性位置值作为参数,启用顶点属性,顶点属性默认是禁用的
	// 纹理属性
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);

	// 生成纹理
	unsigned int texture;
	// glGenTextures先输入要生成纹理的数量,然后把它们储存在第二个参数的`unsigned int`数组中
	glGenTextures(1, &texture);
	// 绑定
	glBindTexture(GL_TEXTURE_2D, texture);
	// 为当前绑定的纹理对象设置环绕、过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	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);
	// 加载并生成纹理
	int width, height, nrChannels;
	unsigned char* data = stbi_load("wall.jpg", &width, &height, &nrChannels, 0);
	if (data)
	{
		// 生成纹理
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		// 注:不需要手动更改我们在片段着色器定义的uniform sampler2D ourTexture,它会自动把纹理赋值给片段着色器的采样器ourTexture

		// 为当前绑定的纹理自动生成所有需要的多级渐远纹理
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		std::cout << "Failed to load texture" << std::endl;
	}
	// 释放图像的内存
	stbi_image_free(data);

	// 开启渲染循环(Render Loop)
	while (!glfwWindowShouldClose(window))
	{
		// 输入控制:
		processInput(window);

		// 渲染指令
		// 修改背景颜色
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		// 绘图
		ourShader.use();                        // 激活着色器
		glActiveTexture(GL_TEXTURE0);           // 激活纹理单元
		glBindTexture(GL_TEXTURE_2D, texture);  // 绑定纹理
		glBindVertexArray(VAO);                 // 启动VAO
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

		// 检查调用事件,并交换缓冲
		glfwPollEvents();
		glfwSwapBuffers(window);
	}

	// 回收资源
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);

	glfwTerminate();
	return 0;
}

// 窗口改变回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height);
}
// 窗口按键输入
void processInput(GLFWwindow* window)
{
	// 按下esc按键,退出程序
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

加载两张纹理,效果图:
image-20210513171257002
源码:

// shader.vs
// --------------------
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    TexCoord = aTexCoord;
}

// shader.fs
// --------------------
#version 330 core
out vec4 FragColor;

in vec2 TexCoord;
uniform sampler2D ourTexture0;
uniform sampler2D ourTexture1;

void main()
{
    FragColor = mix(texture(ourTexture0, TexCoord), texture(ourTexture1, TexCoord), 0.4f);
}

// main.cpp
// --------------------
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "shader.h"
#include "stb_image.h"


// 定义回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

// 宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
	// 初始化和配置 glfw
	glfwInit();
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	// 创建glfw窗口
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Hello world", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "Failed to create GLFW window" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// 使用glad加载OpenGL函数指针
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "Failed to inittalize GLAD" << std::endl;
		return -1;
	}

	// 确定视口(Viewport)的大小
	glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// 设置两个必须的着色器:顶点和片段着色器
	Shader ourShader("shader.vs", "shader.fs");

	// 定义正方形坐标
	float vertices[] = {
		//  位置            // 纹理坐标
		 0.5f,  0.5f, 0.0f, 1.0f, 1.0f,   // 右上
		 0.5f, -0.5f, 0.0f, 1.0f, 0.0f,   // 右下
		-0.5f, -0.5f, 0.0f, 0.0f, 0.0f,   // 左下
		-0.5f,  0.5f, 0.0f, 0.0f, 1.0f    // 左上
	};

	unsigned int indices[] = {
	   0, 1, 3, // first triangle
	   1, 2, 3  // second triangle
	};

	// 设置顶点缓冲对象(Vertex Buffer Objects, VBO)管理内存(坐标点数据)与 VAO
	unsigned int VAO, VBO, EBO;
	glGenVertexArrays(1, &VAO);  // 使用glGenVertexArrays函数和一个缓冲ID生成一个VAO对象
	glGenBuffers(1, &VBO);       // 使用glGenBuffers函数和一个缓冲ID生成一个VBO对象
	glGenBuffers(1, &EBO);

	// 绑定: 先VAO 再VBO
	glBindVertexArray(VAO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	// 位置属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);  // 使用glEnableVertexAttribArray,以顶点属性位置值作为参数,启用顶点属性,顶点属性默认是禁用的
	// 纹理属性
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
	glEnableVertexAttribArray(1);

	// 生成纹理
	unsigned int texture0, texture1;
	stbi_set_flip_vertically_on_load(true); // 防止图片倒置
	// glGenTextures先输入要生成纹理的数量,然后把它们储存在第二个参数的`unsigned int`数组中
	glGenTextures(1, &texture0);
	// 绑定
	glBindTexture(GL_TEXTURE_2D, texture0);
	// 为当前绑定的纹理对象设置环绕、过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	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);
	// 加载并生成纹理
	int width, height, nrChannels;
	unsigned char* data = stbi_load("wall.jpg", &width, &height, &nrChannels, 0);
	if (data)
	{
		// 生成纹理
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		// 注:不需要手动更改我们在片段着色器定义的uniform sampler2D ourTexture,它会自动把纹理赋值给片段着色器的采样器ourTexture

		// 为当前绑定的纹理自动生成所有需要的多级渐远纹理
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		std::cout << "Failed to load texture" << std::endl;
	}
	// 释放图像的内存
	stbi_image_free(data);

	glGenTextures(1, &texture1);
	// 绑定
	glBindTexture(GL_TEXTURE_2D, texture1);
	// 为当前绑定的纹理对象设置环绕、过滤方式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	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);
	data = stbi_load("hh.png", &width, &height, &nrChannels, 0);
	if (data)
	{
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
		glGenerateMipmap(GL_TEXTURE_2D);
	}
	else
	{
		std::cout << "Failed to load texture" << std::endl;
	}
	// 释放图像的内存
	stbi_image_free(data);

	ourShader.use();
	// 我们还要通过使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。我们只需要设置一次即可,所以这个会放在渲染循环的前面:
	glUniform1i(glGetUniformLocation(ourShader.ID, "ourTexture0"), 0); // 手动设置
	ourShader.setInt("ourTexture1", 1); // 或者使用着色器类设置

	// 开启渲染循环(Render Loop)
	while (!glfwWindowShouldClose(window))
	{
		// 输入控制:
		processInput(window);

		// 渲染指令
		// 修改背景颜色
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);

		// 绘图
		ourShader.use();
		glActiveTexture(GL_TEXTURE0);           // 激活纹理单元
		glBindTexture(GL_TEXTURE_2D, texture0);  // 绑定纹理
		glActiveTexture(GL_TEXTURE1);           // 激活纹理单元
		glBindTexture(GL_TEXTURE_2D, texture1);  // 绑定纹理

		glBindVertexArray(VAO);                 // 启动VAO
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

		// 检查调用事件,并交换缓冲
		glfwPollEvents();
		glfwSwapBuffers(window);
	}

	// 回收资源
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);

	glfwTerminate();
	return 0;
}

// 窗口改变回调函数
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
	glViewport(0, 0, width, height);
}
// 窗口按键输入
void processInput(GLFWwindow* window)
{
	// 按下esc按键,退出程序
	if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
		glfwSetWindowShouldClose(window, true);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值