学习OpenGL——第五天

42 阅读5分钟

纹理详解与应用


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超出部分使用用户指定的边缘颜色

image.png


2.2 纹理过滤

纹理坐标为浮点值,OpenGL需要确定如何将纹理像素(Texel)映射到纹理坐标。

  • GL_NEAREST(邻近过滤):选择最接近纹理坐标中心点的像素。

image.png

  • GL_LINEAR(线性过滤):对纹理坐标周围像素进行插值,获得混合色。

image.png

放大与缩小时的过滤设置:

可分别为纹理放大(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会根据物体与观察者距离,自动选择合适分辨率的纹理,提升渲染性能和视觉效果。

image.png

自动生成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) 允许同时激活和绑定多个纹理。

使用流程:

  1. 激活纹理单元
  2. 绑定纹理到该单元
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);