OpenGL学习笔记(3) 纹理

关于纹理

一般游戏里的物体不一定都是纯色的物体,物体上面会有一些图片贴在上面,比如墙壁,箱子,地板,可以看到砖头、木板和大理石组成的图片,要把图片贴到计算机里的几何图形的话,就要把图片的颜色采样贴到几何图形上,采样是计算机经常干的工作,计算机要处理自然中的数据就需要对数据进行采样,比如说对声音采样就是采集声音的频率和频幅,分别代表声音的音色和声量,当然,采集到的是一个模拟量,然而计算机无法处理模拟量,所以需要将模拟量量化数字量,也就是二进制数进行存储,同理,颜色也是一样,现代计算机中颜色一般以RGB的形式,分别代表Red,Green,Blud三种颜色在这个颜色中的占比,一般使用3个字节存储,也就是说颜色的数量可以达到2^24种,已经足够用了,想要把采集到的颜色贴到物体上的话,我们需要指定顶点的纹理坐标,告诉着色器要从纹理的哪个点开始采样, 纹理坐标的范围是0到1
项目的代码
learnOpenGL里的图解
OpenGL学习笔记(3) 纹理_第1张图片

纹理环绕

纹理坐标的范围是0到1,假如超出这个范围的话,在OpenGL里会有几种方式来贴图,这些方式叫做纹理环绕方式

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

在我的工程里有用来测试的函数SurroundTest,把环绕方式和过滤模式封装成枚举方便调试

//环绕模式
enum class SurroundMode
{
	Repeat = GL_REPEAT,						//重复纹理图像
	MirroredRepeat = GL_MIRRORED_REPEAT,	//镜像重复纹理图像
	ClampToEdge = GL_CLAMP_TO_EDGE,			//将边缘拉伸
	ClampToBoreder = GL_CLAMP_TO_BORDER		//超出的部分为指定边缘颜色
};
//过滤方式
enum class FilteringMode
{
	Nearest = GL_NEAREST,
	Linear = GL_LINEAR,
};

将纹理坐标的分量值设置到[0,1]之外,进行测试

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

测试结果
OpenGL学习笔记(3) 纹理_第2张图片

过滤模式

可以看到环绕方式测试的图片是有一丢丢模糊的(其实是用错过滤模式了),这是因为用了线性过滤模式(Linear),使用线性过滤模式采样的时候,每个像素的颜色会和周围的颜色进行混合算出一个插值,这个插值近似于这些像素
而采用 邻近过滤(Nearest) 的话,那么像素的颜色就是采样器采到的颜色,如果分辨率小的图片贴到大的物体上的话,就会出现颗粒状的图案,但使用线性过滤模式的话这些颗粒就会变得比较平滑
测试一下
OpenGL学习笔记(3) 纹理_第3张图片
是不是可以很明显地看出Linear方式采样的珂朵莉的眼神比较温柔啊,这就是经过平滑处理过的图片。在这里顺便说下unity的三种过滤模式

  • Point 点像素过滤,纹理像素会在附近变为块状。
  • Bilinear 双线性过滤,平均纹理样本
  • Trilinear 三线性过滤,平均纹理样本,同时也在多级渐远纹理级别之间混合

测试代码

首先是读取图片用的代码,对 learnOpenGL 里的代码进行了一次封装,针对不同位深度的图片进行处理,位深度就是图片记录每个像素点的位数,24位表示3个float,也就是RGB值,32位则是4个float,即RGBA,读取图片用了官方介绍的stb_image

//加载纹理
template 
void LoadTexture(unsigned int &texture, S1&& pic)
{
	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;
	stbi_set_flip_vertically_on_load(true);
	unsigned char *data = stbi_load((TEXTURE_PATH + std::forward(pic)).c_str(), &width, &height, &nrChannels, 0);
	std::cout << "nrChannels = " << nrChannels << endl;
	if (data)
	{
		//位深度为24,3个通道(jpg
		if (nrChannels == 3)
			glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
		//位深度为32,4个通道(png
		else if (nrChannels == 4)
			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);
	data = nullptr;
}

着色器代码
顶点着色器
vertex_3.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;
}

位置0是顶点的坐标,位置1是输入的纹理坐标,要通过顶点着色器将纹理坐标输出到片段着色器
片段着色器
fragment_3.fs

#version 330 core
out vec4 FragColor;

in vec2 TexCoord;

uniform sampler2D ourTexture;

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

TexCoord是从顶点着色器接收的纹理坐标,使用texture函数来采样,第一个参数是采样器(Sampler),第二个参数是纹理坐标,着色器输出过滤后的颜色值FragColor
接下来是就是正常的测试代码了

void NormalTest()
{
	float vertices[] = {
		//     ---- 位置 ----     - 纹理坐标 -
			 0.9f,  0.9f, 0.0f,  1.0f, 1.0f,   // 右上
			 0.9f, -0.9f, 0.0f,  1.0f, 0.0f,   // 右下
			-0.9f, -0.9f, 0.0f,  0.0f, 0.0f,   // 左下
			-0.9f,  0.9f, 0.0f,  0.0f, 1.0f    // 左上
	};
	//索引
	unsigned int indices[] = {
		0,1,3,
		1,2,3
	};
	//编译着色器
	Shader ourShader("vertex_3.vs", "fragment_3.fs");
	ourShader.use();//glUseProgram(shaderProgram);
	unsigned int VAO, VBO, EBO;

	//顶点数组
	glGenVertexArrays(1, &VAO);
	glBindVertexArray(VAO);

	//绑定顶点数组缓存
	glGenBuffers(1, &VBO);
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	//索引缓存
	glGenBuffers(1, &EBO);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

	//生成纹理
	unsigned int texture1;
	LoadTexture(texture1, std::move("picture4.png"));	//加载纹理
	glBindTexture(GL_TEXTURE_2D, texture1);//绑定纹理

	// 位置属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);
	// 纹理属性
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));	//最后一个参数是数据的起点
	glEnableVertexAttribArray(1);


	while (!glfwWindowShouldClose(glWindow))
	{
		glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
		glClear(GL_COLOR_BUFFER_BIT);
		//draw
		glBindVertexArray(VAO);
		glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		glBindVertexArray(0);
		glfwPollEvents();
		glfwSwapBuffers(glWindow);
	}
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteBuffers(1, &EBO);
	glfwTerminate();
}

测试结果

OpenGL学习笔记(3) 纹理_第4张图片
珂朵莉好可爱~

你可能感兴趣的:(游戏开发)