OpenGL学习笔记(3) 纹理

关于纹理

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

项目的代码

learnOpenGL里的图解

OpenGL学习笔记(3) 纹理

纹理环绕

纹理坐标的范围是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) 纹理

过滤模式

可以看到环绕方式测试的图片是有一丢丢模糊的(其实是用错过滤模式了),这是因为用了线性过滤模式(Linear),使用线性过滤模式采样的时候,每个像素的颜色会和周围的颜色进行混合算出一个插值,这个插值近似于这些像素

而采用 邻近过滤(Nearest) 的话,那么像素的颜色就是采样器采到的颜色,如果分辨率小的图片贴到大的物体上的话,就会出现颗粒状的图案,但使用线性过滤模式的话这些颗粒就会变得比较平滑

测试一下

OpenGL学习笔记(3) 纹理

是不是可以很明显地看出Linear方式采样的珂朵莉的眼神比较温柔啊,这就是经过平滑处理过的图片。在这里顺便说下unity的三种过滤模式

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

测试代码

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

//加载纹理
template <typename S1 = std::string>
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<std::string>(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) 纹理

珂朵莉好可爱~

上一篇:[PCL]ApproximateVoxelGrid


下一篇:PHP MySQL Where 子句 之Where