纹理到底是什么?

预计阅读时间: 6 分钟
学习资料

本文主要参考:LearnOpenGL CN - 纹理

1. 基础概念

1.1 什么是纹理?

定义

纹理是一个2D/3D图像,可以用来为3D模型添加细节、颜色或者表面特征,而不需要增加额外的几何复杂度。

1.2 为什么需要纹理?

原因
  • 当3D渲染时,对于每一个顶点,如果使用纯色渲染,那么渲染出来的模型会非常单调,且失真严重。

  • 如果使用顶点属性来设置颜色,那么每个顶点都需要设置颜色,这样会导致渲染出来的模型非常复杂,体积巨大。

  • 使用纹理可以则可以把每一个顶点对应到纹理图片的每一个像素点,从而使渲染出来的模型更加逼真。

  • 并且纹理是静态的,不需要在渲染时进行计算颜色值,所以可以提高渲染效率。

  • 图片作为储存介质可以使用压缩算法,从而减少内存占用。

  • 除了颜色,纹理还可以存储其它信息,比如法线、粗糙度、金属度等。

1.3 纹理的类型

常见纹理类型
  1. 颜色纹理(Diffuse Map)

    • 最基础的纹理类型
    • 存储物体表面的颜色信息
  2. 法线纹理(Normal Map)

    • 存储表面法线信息
    • 用于模拟表面细节,而不增加实际几何体
  3. 高度纹理(Height Map)

    • 存储表面高度信息
    • 用于视差贴图等效果
  4. 环境遮蔽纹理(Ambient Occlusion Map)

    • 存储环境光遮蔽信息
    • 增强物体细节的明暗对比
  5. 金属度/粗糙度纹理(Metallic/Roughness Map)

    • 用于PBR渲染
    • 控制表面的物理特性

1.4 纹理坐标映射

纹理坐标系统
  • 纹理坐标(UV坐标)的范围是 (0,0)(1,1)
  • U轴对应水平方向,V轴对应垂直方向
  • 原点 (0,0) 位于纹理的左下角
  • 需要为每个顶点指定对应的UV坐标
  • 渲染时会对顶点之间的片段进行UV插值计算

::: warning 注意事项

  1. UV坐标和顶点坐标是独立的两个系统
  2. UV坐标的精度对纹理显示效果有重要影响
  3. 错误的UV映射可能导致纹理扭曲或撕裂 :::

2. 纹理环绕方式

2.1 环绕模式说明

::: info 环绕模式 当纹理坐标超出 [0,1] 范围时,OpenGL提供以下处理方式:

  • GL_REPEAT: 重复纹理图像
  • GL_MIRRORED_REPEAT: 镜像重复纹理图像
  • GL_CLAMP_TO_EDGE: 拉伸纹理边缘
  • GL_CLAMP_TO_BORDER: 使用指定的边界颜色 :::

2.2 设置环绕方式

1// 在OpenGL中设置纹理环绕方式
2glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
3glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
4
5// 在Three.js中设置纹理环绕方式
6texture.wrapS = THREE.RepeatWrapping;
7texture.wrapT = THREE.RepeatWrapping;

3. 纹理过滤

纹理过滤定义了纹理在放大或缩小时的采样方式。

3.1 线性过滤 (GL_LINEAR)

::: note 原理

  • 基于纹理坐标附近的纹理像素计算插值
  • 距离采样点越近的像素权重越大
  • 最终颜色是周围像素的加权平均
  • 效果:更平滑的视觉效果,但可能会有轻微的模糊 :::

3.2 邻近过滤 (GL_NEAREST)

::: note 原理

  • 选择最接近采样坐标的像素点
  • 直接使用该像素的颜色值
  • 效果:保留像素边界,适合像素风格游戏
  • 在放大时可能会产生明显的锯齿感 :::

3.3 多级渐远纹理过滤

::: tip Mipmap过滤选项

  • GL_NEAREST_MIPMAP_NEAREST: 使用最邻近的mipmap级别,并执行邻近过滤
  • GL_LINEAR_MIPMAP_NEAREST: 使用最邻近的mipmap级别,并执行线性过滤
  • GL_NEAREST_MIPMAP_LINEAR: 在两个最邻近的mipmap之间线性插值,并执行邻近过滤
  • GL_LINEAR_MIPMAP_LINEAR: 在两个最邻近的mipmap之间线性插值,并执行线性过滤 :::

4. 纹理单元

4.1 多纹理使用

::: info 基本概念

  • OpenGL至少支持16个纹理单元(GLTEXTURE0 到 GLTEXTURE15)
  • 每个纹理单元可以绑定一个纹理对象
  • 在着色器中通过 sampler2D 采样器访问纹理 :::

4.2 在着色器中使用多个纹理

1// 顶点着色器
2attribute vec2 aTexCoord;
3varying vec2 vTexCoord;
4
5void main() {
6    vTexCoord = aTexCoord;
7    // ... 其他代码
8}
9
10// 片段着色器
11uniform sampler2D uDiffuseMap;    // 颜色纹理
12uniform sampler2D uNormalMap;      // 法线纹理
13uniform sampler2D uSpecularMap;    // 高光纹理
14varying vec2 vTexCoord;
15
16void main() {
17    // 采样不同的纹理
18    vec4 diffuseColor = texture2D(uDiffuseMap, vTexCoord);
19    vec4 normalColor = texture2D(uNormalMap, vTexCoord);
20    vec4 specularColor = texture2D(uSpecularMap, vTexCoord);
21    
22    // 混合纹理效果
23    gl_FragColor = mix(diffuseColor, specularColor, normalColor.r);
24}