纹理详解与应用
1. 纹理基础
纹理是二维(2D)图片(也有1D和3D纹理),用于为3D物体添加丰富的细节。可以将纹理想象为一张绘有砖块的纸,贴合在3D房子的表面,让房子看起来有砖墙外观。利用纹理可以在不增加模型顶点的情况下,极大提升物体的精细度。
纹理映射(Mapping)
要将纹理映射到三角形上,需要为三角形每个顶点指定对应纹理的哪一部分。每个顶点有一个纹理坐标(Texture Coordinate),用于标明从纹理图像的哪个部分采样颜色。其它片段则通过片段插值获得纹理坐标。
- 纹理坐标范围:(0, 0)(左下角)到 (1, 1)(右上角)
- 采样(Sampling):使用纹理坐标获取纹理颜色
2. glTexParameter* 函数
glTexParameter*函数族用于设置纹理的参数,控制采样、过滤、环绕等行为,决定GPU如何处理纹理图像,包括缩放、边界处理和Mipmap生成。
函数原型:
void glTexParameterf(GLenum target, GLenum pname, GLfloat param);
void glTexParameterfv(GLenum target, GLenum pname, const GLfloat *param);
- target:纹理目标类型
- pname:参数名称
- param/params:参数值(类型和含义依赖于pname)
2.1 纹理环绕方式
| 环绕方式 | 描述 |
| GL_REPEAT | 默认行为,重复纹理图像 |
| GL_MIRRORED_REPEAT | 镜像重复纹理图像 |
| GL_CLAMP_TO_EDGE | 超出部分重复边缘像素,产生边缘拉伸效果 |
| GL_CLAMP_TO_BORDER | 超出部分使用用户指定的边缘颜色 |
2.2 纹理过滤
纹理坐标为浮点值,OpenGL需要确定如何将纹理像素(Texel)映射到纹理坐标。
- GL_NEAREST(邻近过滤):选择最接近纹理坐标中心点的像素。
- GL_LINEAR(线性过滤):对纹理坐标周围像素进行插值,获得混合色。
放大与缩小时的过滤设置:
可分别为纹理放大(Magnify)和缩小(Minify)指定过滤方式:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
2.3 多级渐远纹理(Mipmap)
多级渐远纹理是一系列尺寸递减为1/2的纹理图像。OpenGL会根据物体与观察者距离,自动选择合适分辨率的纹理,提升渲染性能和视觉效果。
自动生成Mipmap:
void glGenerateMipmap(GLenum target);
- target:纹理目标类型(如GL_TEXTURE_2D等)
多级渐远纹理的过滤方式:
| 过滤方式 | 描述 |
| GL_NEAREST_MIPMAP_NEAREST | 最近的Mipmap级别 + 邻近插值 |
| GL_LINEAR_MIPMAP_NEAREST | 最近的Mipmap级别 + 线性插值 |
| GL_NEAREST_MIPMAP_LINEAR | 两个Mipmap级别之间线性插值 + 邻近插值 |
| GL_LINEAR_MIPMAP_LINEAR | 两个Mipmap级别之间线性插值 + 线性插值 |
注意: 多级渐远纹理通常用于纹理缩小时。为放大过滤设置多级渐远纹理选项会导致GL_INVALID_ENUM错误。
3. 加载与创建纹理
3.1 stb_image.h
stb_image.h 是广泛使用的单头文件图像加载库,支持多种流行格式,易于集成。
使用方式:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
- 通过定义 STB_IMAGE_IMPLEMENTATION,头文件会包含函数定义源码。
加载图片:
unsigned char *stbi_load(char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
3.2 生成纹理
- 使用 glGenTextures 生成纹理对象
- 绑定纹理对象
- 设置参数(环绕、过滤方式)
- 加载图片数据
- 生成纹理和Mipmap
示例代码:
unsigned int texture;
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("container.jpg", &width, &height, &nrChannels, 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 << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
建议:生成纹理及Mipmap后应及时释放图片内存。
4. 应用纹理
GLSL内建 采样器(Sampler) 类型,用于在片段着色器中采样纹理,如 sampler2D、sampler3D。
片段着色器示例:
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main() {
FragColor = texture(ourTexture, TexCoord);
}
- texture 函数采样纹理颜色,自动应用之前设置的参数。
混合顶点颜色与纹理颜色:
FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);
5. 纹理单元
使用 glUniform1i 可为采样器分配位置值,实现片段着色器中使用多个纹理。纹理单元(Texture Unit) 允许同时激活和绑定多个纹理。
使用流程:
- 激活纹理单元
- 绑定纹理到该单元
glActiveTexture(GL_TEXTURE0); // 激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture); // 绑定纹理
- 默认 GL_TEXTURE0 被激活,绑定时无需手动激活。
小结:
- glActiveTexture 选择插槽
- glBindTexture 放入纹理
- 二者配合可管理多个纹理
OpenGL保证至少有16个纹理单元(GL_TEXTURE0 至 GL_TEXTURE15),可通过 GL_TEXTURE0 + i 方式访问。
常见问题:纹理上下颠倒
你可能注意到纹理上下颠倒了!这是因为OpenGL要求y轴0.0坐标在图片底部,而图片的y轴0.0通常在顶部。幸运的是,stb_image.h 能在图像加载时帮助翻转y轴,只需在加载任何图像前加入以下语句:
stbi_set_flip_vertically_on_load(true);