1 纹理(Texture)
纹理是一个2D图片(也有1D和3D的纹理),可以用来添加物体的细节;可以理解为贴纸。纹理的意义在于 可以让物体非常精细而不用指定额外的顶点。
纹理坐标在x和y轴上,范围为0到1之间(前提是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),终于(1, 1),即从纹理图片的左下角到右上角。下图 展示了把纹理坐标映射到三角形上的过程。如下所示:
这里为三角形指定了3个纹理坐标点。我们希望 三角形的左下角对应纹理的左下角,即 三角形左下角顶点的纹理坐标设置为(0, 0);以此类推,三角形的上方顶点的纹理坐标设置为(0.5, 1.0);右下方的顶点纹理坐标设置为(1, 0)。我们只要给顶点着色器传递这三个纹理坐标就可以了。纹理坐标也是数组,如下所示:
GLfloat texCoords[] = {
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 三角形顶点
};
纹理采样过程 可以采用几种不同的插值方式。
2 纹理环绕方式
OpenGL提供了几种环绕方式,如下:
- GL_REPEAT :对纹理的默认行为。重复纹理图像。
- GL_MIRRORED_REPEAT :和GL_REPEAT一样,但每次重复图片是镜像放置的。
- GL_CLAMP_TO_EDGE :纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
- GL_CLAMP_TO_BORDER :超出的坐标为用户指定的边缘颜色。
使用时用glTexParameteri函数来设置,函数如下:
/*
目的:设置纹理环绕方式
第一个参数:指定了纹理目标;我们使用的是2D纹理,因此纹理目标是GL_TEXTURE_2D。
第二个参数:需要我们指定设置的选项与应用的纹理轴。我们打算配置的是WRAP选项,并且指定S和T轴。
(说明:3D纹理的轴 s、t、r 分别对应 x、y、z)
第三个参数:需要我们传递一个环绕方式,在这个例子中OpenGL会给当前激活的纹理设定纹理环绕方式为GL_MIRRORED_REPEAT。
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
当然,若最后参数选GL_CLAMP_TO_BORDER,那就还需要使用glTexParametefv,以GL_TEXTURE_BORDER_COLOR作为它的选项,传递一个颜色值,代码如下:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
3 纹理过滤方式
3.1 纹理过滤方式
纹理过滤有很多个选项,这里只谈论GL_NEAREST和GL_LINEAR。如下:
- GL_NEAREST(Nearest Neighbor Filtering,邻近过滤):OpenGL默认的纹理过滤方式。当设置为GL_NEAREST时,OpenGL会选择中心点最接近纹理坐标的那个像素。该过滤方式产生了颗粒状的图案,我们能够清晰看到组成纹理的像素。
- GL_LINEAR((Bi)linear Filtering,线性过滤):基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。该过滤方式能够产生更平滑的图案,很难看出单个的纹理像素。
这里也是使用glTexParameteri函数来设置过滤方式,如下所示:
//缩小时设置为 GL_NEAREST 过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//放大时设置为 GL_LINEAR 过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
3.2 多级渐远纹理(mipmap)
多级渐远纹理是目前应用最为广泛的纹理映射(map)技术之一。它是实现 “实物(图片)看起来近大远小,近处清晰远处模糊”的效果。实现原理是:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体距离的那个。一般来说,由于距离远解析度不高也不会被用户注意到。效果如下:
多级渐远纹理的设置同纹理过滤一致,多级渐远纹理的过滤方式有如下几种:
- GL_NEAREST_MIPMAP_NEAREST:使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样。
- GL_LINEAR_MIPMAP_NEAREST:使用最邻近的多级渐远纹理级别,并使用线性插值进行采样。
- GL_NEAREST_MIPMAP_LINEAR:在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样。
- GL_LINEAR_MIPMAP_LINEAR:在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样。
这里也是使用glTexParameteri函数来设置多级渐远纹理,如下所示:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
注意:多级渐远纹理主要是使用在纹理被缩小的情况下的。一般不会在放大时使用。
4 使用纹理
4.1 加载 & 创建纹理
使用纹理之前是要把它们加载到我们的应用中,一般是使用 支持多种流行格式的图像加载库,这里介绍其中的一些库:
@1 SOIL(Simple OpenGL Image Library),调用方式为:
int width, height;
unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB);
@2 STB(Sean T. Barrett)库,调用方式为:
int texture_face_width, texture_face_height, texture_face_nrChannels;
stbi_load(FileSystem::getPath("resources/textures/toy_box_disp.png").c_str(), &texture_face_width, &texture_face_height, &texture_face_nrChannels, 0);
当然,类似的库有很多,但实际上这里最重要的目的是把图片转换为字节流。
4.2 生成纹理
生成纹理流程如下:
//@1 创建纹理
GLuint texture;
//@2 绑定纹理数量和ID
glGenTextures(1, &texture);
//@3 绑定纹理
glBindTexture(GL_TEXTURE_2D, texture);
//@4 为当前绑定的纹理对象设置环绕、过滤方式
//...
//@5 加载并生成image数组数据
//说明:这里也可以使用STB库 或者其他库来读取image
int width, height;
unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB);
//...
/*@6 目的:使用图片数据生成纹理
第一个参数:指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理。
第二个参数:为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。填0表示基本级别。
第三个参数:告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有RGB值,因此我们也把纹理储存为RGB值。
第四/五个参数:设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
第六个参数设为0即可(属于历史遗留问题)。
第七/八个参数:定义了源图格式和数据类型。
第九个参数:真正的图像数据数组。
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
//@7 自动生成纹理和多级渐远纹理
glGenerateMipmap(GL_TEXTURE_2D);
//@8 释放图像的内存并解绑纹理对象
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);
4.3 应用纹理
说明:主要解读代码中如何应用的纹理
4.3.1 顶点数据及处理流程
在应用纹理时 我们需要告知OpenGL如何采样纹理,所以我们将纹理坐标也纳入顶点数据中,如下所示:
GLfloat 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 // 左上
};
同时新的顶点格式也跟着变化,从之前的
变化为:
即:不止有颜色数据,还有纹理数据,相关代码为:
glVertexAttribPointer(2, 2, GL_FLOAT,GL_FALSE, 8 * sizeof(GLfloat), \
(GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(2);
说明:此时两个顶点属性的步长参数为8 * sizeof(GLfloat)。
4.3.2 着色器变化和处理流程
这里 输入上 添加了 纹理这一属性,顶点着色器代码为:
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;
out vec3 ourColor;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(position, 1.0f);
ourColor = color;
TexCoord = texCoord;
}
接下来在 这里多了一个采样器片段着色器变化为:
#version 330 core
in vec3 ourColor;
in vec2 TexCoord;
out vec4 color;
uniform sampler2D ourTexture;
void main()
{
/*目的:把一个纹理添加到片段着色器中
第一个参数:纹理采样器,
第二个参数:对应的纹理坐标。
*/
color = texture(ourTexture, TexCoord);
}
4.3.2 绘制处理流程
最后的绑定和绘制 关键代码如下:
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
4.4 纹理单元
纹理单元的目的是让我们在着色器中可以使用多个纹理。同时在使用前 我们需要激活对应的纹理单元。就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元,如下所示:
glActiveTexture(GL_TEXTURE0); //在绑定纹理之前先激活纹理单元,GL_TEXTURE0默认是激活的
glBindTexture(GL_TEXTURE_2D, texture);
接下来,如果想使用多个纹理,只需变更之前的片段着色器,流程如下:
#version 330 core
...
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
void main()
{
/*目的:混合纹理,输出新纹理
前两个参数:纹理1,纹理2
最后参数:混合比例
*/
color = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
}
渲染流程为:
//纹理1相关操作
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);
//纹理2相关操作
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);
//渲染
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
该系列文章主要参考openGL官网 和学习 learnopenGL官网 进行知识体系的梳理和重构,重在形成自己对openGL知识的理解和知识体系。
- 参考相关链接总站:LearnOpenGL-CN
- 参考OpenGL官网:About The Khronos Group - The Khronos Group Inc
- 同时也参考了网上其他内容知识 进而 进行整合。