6计算机图形学原理及实践:基础方法(第3版)

纹理映射

表面细节

纹理坐标纹理滤波凹凸映射

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第6章 纹理映射(Texture Mapping)

一、导读

1.1 本章学习目标

纹理映射是为三维模型添加表面细节的核心技术。没有纹理的三维模型看起来像塑料玩具,而有了高质量的纹理,模型可以呈现出照片级的真实感——木头的纹理、砖墙的粗糙、金属的划痕、布料的编织图案,这些都是纹理映射的功劳。

本章的核心学习目标包括:

  • 理解纹理映射的基本原理:掌握将二维图像映射到三维表面的数学方法,理解纹理坐标(UV 坐标)的定义和生成。
  • 掌握纹理滤波技术:理解最近邻、双线性、三线和各向异性滤波的原理和效果差异,以及为什么远处物体会出现闪烁和摩尔纹。
  • 理解 Mipmap 技术:掌握多级纹理的原理和生成方法,理解它如何解决远处物体的走样问题并提高缓存命中率。
  • 掌握高级纹理技术:深入理解凹凸映射、法线贴图、视差映射和视差遮挡映射的原理和实现。
  • 了解 PBR 纹理工作流:理解金属度-粗糙度工作流中各纹理贴图的物理含义和制作方法。
  • 掌握纹理压缩和格式:了解 BC、ASTC、ETC2 等纹理压缩格式的特点和适用场景。

1.2 为什么本章如此重要

纹理映射是实时渲染中提升视觉质量最有效的手段。一个只有 1000 个三角形的模型,配上高分辨率的纹理贴图,看起来可能比 100000 个三角形但没有纹理的模型更加精细。这就是纹理映射的力量——它用极小的性能代价(主要是显存带宽)换取了巨大的视觉提升。

从技术角度看,纹理映射涉及图形学中多个核心问题:采样理论(如何正确地从连续纹理中采样)、信号处理(如何避免走样)、内存管理(如何高效地存储和访问纹理数据)。理解这些底层原理,你才能正确选择纹理参数、诊断纹理相关的渲染问题、优化纹理的内存和带宽使用。

在现代 PBR 渲染中,纹理不再仅仅是"贴一张图片"。一套完整的 PBR 材质可能包含 5-6 张纹理贴图(基础颜色、金属度、粗糙度、法线、环境光遮蔽、高度),每张都编码了不同的物理属性。理解纹理映射的原理,是掌握 PBR 工作流的前提。

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

纹理坐标是如何定义的?为什么使用 (u, v) 而不是 (x, y)?UV 展开的本质是什么问题?

当一个纹理像素(texel)对应多个屏幕像素时,或者一个屏幕像素对应多个纹理像素时,分别会出现什么问题?如何解决?

各向异性滤波为什么比三线性滤波效果好?它的性能开销在哪里?

法线贴图为什么用紫色(蓝紫色)存储法线?如何从法线贴图中恢复出实际的法线向量?

视差遮挡映射(POM)与视差映射有什么区别?它如何模拟真正的几何位移效果?

纹理压缩如何在有限的显存中存储高质量的纹理?有损压缩对渲染质量有什么影响?


二、核心概念详解

2.1 纹理映射基础

2.1.1 纹理坐标

纹理坐标(Texture Coordinates),也称为 UV 坐标,定义了三维模型表面上的点与二维纹理图像之间的对应关系。对于每个顶点,除了位置和法线,还需要指定纹理坐标 (u, v),其中 u 和 v 的范围通常是 [0, 1]。

纹理坐标系统:

纹理图像              模型表面
(0,1)───(1,1)        ┌─────┐
  │         │        │    / │
  │  纹理   │  映射   │   /  │
  │         │  ──→   │  /   │
(0,0)───(1,0)        └─────┘

u 轴对应水平方向,v 轴对应垂直方向
注意:图像坐标的 v 轴通常向下,而 OpenGL 纹理坐标的 v 轴向上
cpp
// 带纹理坐标的顶点结构
struct Vertex {
  glm::vec3 position;
  glm::vec3 normal;
  glm::vec2 texCoord;  // UV 坐标
};

// 立方体的纹理坐标
std::vector<Vertex> cubeVertices = {
  // 前面
  {{-0.5f, -0.5f,  0.5f}, {0, 0, 1}, {0.0f, 0.0f}},
  {{ 0.5f, -0.5f,  0.5f}, {0, 0, 1}, {1.0f, 0.0f}},
  {{ 0.5f,  0.5f,  0.5f}, {0, 0, 1}, {1.0f, 1.0f}},
  {{-0.5f,  0.5f,  0.5f}, {0, 0, 1}, {0.0f, 1.0f}},
  // ... 其他面类似
};

2.1.2 纹理加载与绑定

cpp
// 使用 stb_image 加载纹理
#include <stb_image.h>

GLuint loadTexture(const char* path) {
  GLuint textureID;
  glGenTextures(1, &textureID);

  int width, height, nrChannels;
  stbi_set_flip_vertically_on_load(true); // OpenGL v 轴向上
  unsigned char* data = stbi_load(path, &width, &height, &nrChannels, 0);

  if (data) {
    GLenum format = GL_RGB;
    if (nrChannels == 1) format = GL_RED;
    else if (nrChannels == 3) format = GL_RGB;
    else if (nrChannels == 4) format = GL_RGBA;

    glBindTexture(GL_TEXTURE_2D, textureID);
    glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
                 format, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);

    // 设置纹理参数
    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_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  } else {
    std::cerr << "Failed to load texture: " << path << std::endl;
  }

  stbi_image_free(data);
  return textureID;
}

2.1.3 纹理环绕模式

当纹理坐标超出 [0, 1] 范围时,需要定义如何处理:

cpp
// GL_REPEAT:重复纹理(默认)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
// 效果:纹理在表面上平铺

// GL_MIRRORED_REPEAT:镜像重复
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
// 效果:每次重复时镜像翻转

// GL_CLAMP_TO_EDGE:拉伸边缘像素
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
// 效果:边缘像素延伸到边界

// GL_CLAMP_TO_BORDER:使用边框颜色
float borderColor[] = {1.0f, 0.0f, 0.0f, 1.0f};
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);

2.2 纹理滤波

2.2.1 放大滤波(Magnification)

当一个纹理像素(texel)小于一个屏幕像素时,需要决定用哪个 texel 的颜色。

最近邻滤波(GL_NEAREST):选择距离纹理坐标最近的 texel。速度快但会产生块状效果。

双线性滤波(GL_LINEAR):对最近的 4 个 texel 进行加权平均。效果好但稍慢。

cpp
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// 或
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
最近邻 vs 双线性:

纹理坐标落在 4 个 texel 之间:
┌────┬────┐
│ A  │ B  │
├────✕────┤  ← 纹理坐标位置
│ C  │ D  │
└────┴────┘

最近邻:选择最近的 texel(如 A)
双线性:先水平插值再垂直插值
  top = lerp(A, B, fx)
  bot = lerp(C, D, fx)
  result = lerp(top, bot, fy)

2.2.2 缩小滤波(Minification)

当一个纹理像素大于一个屏幕像素时(物体远离相机),一个屏幕像素覆盖了多个 texel,直接采样会导致走样(闪烁、摩尔纹)。

问题:假设一个 1024×1024 的纹理映射到屏幕上只有 10×10 像素的区域。每个屏幕像素覆盖了约 100 个 texel,但标准采样只取其中 1 个或 4 个,大量纹理信息被丢弃,导致严重的走样。

Mipmap 解决方案:预计算一系列缩小版本的纹理,每级宽高减半。

Mipmap 层级:
Level 0: 1024 × 1024  (原始纹理)
Level 1: 512  × 512
Level 2: 256  × 256
Level 3: 128  × 128
Level 4: 64   × 64
Level 5: 32   × 32
Level 6: 16   × 16
Level 7: 8    × 8
Level 8: 4    × 4
Level 9: 2    × 2
Level 10: 1   × 1

总显存 = 原始纹理 × (1 + 1/4 + 1/16 + ...) ≈ 原始纹理 × 4/3
cpp
// 生成 Mipmap
glGenerateMipmap(GL_TEXTURE_2D);

// 使用 Mipmap 的滤波模式
// GL_NEAREST_MIPMAP_NEAREST:最近 Mipmap 级 + 最近邻
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                GL_NEAREST_MIPMAP_NEAREST);
// GL_LINEAR_MIPMAP_NEAREST:最近 Mipmap 级 + 双线性
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                GL_LINEAR_MIPMAP_NEAREST);
// GL_NEAREST_MIPMAP_LINEAR:相邻两级各最近邻 + 级间线性插值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                GL_NEAREST_MIPMAP_LINEAR);
// GL_LINEAR_MIPMAP_LINEAR:相邻两级各双线性 + 级间线性插值(最好)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                GL_LINEAR_MIPMAP_LINEAR);

2.2.3 各向异性滤波

三线性滤波假设屏幕像素在纹理上的投影是正方形,但实际上当表面倾斜时,投影可能是细长的矩形。各向异性滤波(Anisotropic Filtering)考虑了这种非均匀的投影形状。

cpp
// 启用各向异性滤波
float maxAniso;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &maxAniso);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, maxAniso);
// 通常设为 16,表示沿最长轴最多采样 16 个 texel
各向异性滤波示意:

三线性滤波采样区域:     各向异性滤波采样区域:
    ┌───┐                    ┌───────────────┐
    │   │                    │               │
    └───┘                    └───────────────┘
    正方形采样               沿倾斜方向拉长采样
    倾斜表面模糊             倾斜表面清晰

2.3 高级纹理技术

2.3.1 凹凸映射(Bump Mapping)

凹凸映射通过扰动表面法线来模拟微观几何细节,而不改变实际的几何形状。原始的凹凸映射使用高度图,通过计算高度图的梯度来扰动法线。

glsl
// 简单凹凸映射
float height = texture(heightMap, texCoord).r;
float scale = 0.05;

// 通过有限差分计算高度梯度
float hL = texture(heightMap, texCoord - vec2(texOffset, 0)).r;
float hR = texture(heightMap, texCoord + vec2(texOffset, 0)).r;
float hD = texture(heightMap, texCoord - vec2(0, texOffset)).r;
float hU = texture(heightMap, texCoord + vec2(0, texOffset)).r;

// 梯度 → 法线扰动
vec3 bumpNormal = normalize(vec3(
  (hL - hR) * scale,
  (hD - hU) * scale,
  1.0
));

2.3.2 法线贴图(Normal Mapping)

法线贴图是凹凸映射的改进版本。它直接在纹理中存储法线方向,而不是高度值。每个像素的 RGB 值编码了该点的法线方向 (Nx, Ny, Nz),范围从 [0,1] 映射到 [-1,1]。

法线贴图的紫色外观:
默认法线方向 (0, 0, 1) 映射为 RGB (0.5, 0.5, 1.0)
→ 看起来是蓝紫色
平坦区域的法线贴图就是蓝紫色的
glsl
// 法线贴图采样和变换
vec3 sampleNormalMap(sampler2D normalMap, vec2 texCoord,
                     vec3 normal, vec3 tangent, vec3 bitangent) {
  // 从纹理采样法线([0,1] → [-1,1])
  vec3 tangentNormal = texture(normalMap, texCoord).rgb * 2.0 - 1.0;

  // 构建 TBN 矩阵(从切线空间到世界空间)
  mat3 TBN = mat3(
    normalize(tangent),
    normalize(bitangent),
    normalize(normal)
  );

  // 将法线从切线空间变换到世界空间
  return normalize(TBN * tangentNormal);
}

// 使用偏导数自动计算切线和副切线
vec3 getNormalFromMap(sampler2D normalMap, vec3 worldPos,
                      vec3 normal, vec2 texCoord) {
  vec3 tangentNormal = texture(normalMap, texCoord).xyz * 2.0 - 1.0;

  vec3 Q1 = dFdx(worldPos);
  vec3 Q2 = dFdy(worldPos);
  vec2 st1 = dFdx(texCoord);
  vec2 st2 = dFdy(texCoord);

  vec3 N = normalize(normal);
  vec3 T = normalize(Q1 * st2.t - Q2 * st1.t);
  vec3 B = normalize(cross(N, T));

  return normalize(T * tangentNormal.x +
                   B * tangentNormal.y +
                   N * tangentNormal.z);
}

2.3.3 视差映射(Parallax Mapping)

视差映射在法线贴图的基础上,根据观察角度和高度信息偏移纹理坐标,产生真正的视差效果——从不同角度看,表面细节的位置会变化。

glsl
// 视差映射
vec2 parallaxMapping(vec2 texCoord, vec3 viewDir) {
  float height = texture(heightMap, texCoord).r;
  vec2 p = viewDir.xy / viewDir.z * (height * heightScale);
  return texCoord - p;
}

// 使用视差偏移后的纹理坐标进行法线贴图和颜色采样
vec2 newTexCoord = parallaxMapping(vTexCoord, viewDir);
vec3 normal = sampleNormalMap(normalMap, newTexCoord);
vec3 color = texture(diffuseMap, newTexCoord).rgb;

2.3.4 视差遮挡映射(Parallax Occlusion Mapping)

视差遮挡映射是视差映射的高级版本,通过沿视线方向步进搜索正确的深度交叉点,实现更精确的视差效果,甚至可以模拟遮挡(高处的细节遮挡低处的细节)。

glsl
// 视差遮挡映射
vec2 parallaxOcclusionMapping(vec2 texCoord, vec3 viewDir) {
  const int minLayers = 10;
  const int maxLayers = 50;
  float numLayers = mix(maxLayers, minLayers,
                        abs(dot(vec3(0, 0, 1), viewDir)));

  float layerDepth = 1.0 / numLayers;
  float currentLayerDepth = 0.0;
  vec2 p = viewDir.xy / viewDir.z * heightScale;
  vec2 deltaTexCoords = p / numLayers;

  vec2 currentTexCoords = texCoord;
  float currentDepthMapValue = texture(heightMap, currentTexCoords).r;

  while (currentLayerDepth < currentDepthMapValue) {
    currentTexCoords -= deltaTexCoords;
    currentDepthMapValue = texture(heightMap, currentTexCoords).r;
    currentLayerDepth += layerDepth;
  }

  // 线性插值精确化
  vec2 prevTexCoords = currentTexCoords + deltaTexCoords;
  float afterDepth = currentDepthMapValue - currentLayerDepth;
  float beforeDepth = texture(heightMap, prevTexCoords).r -
                      currentLayerDepth + layerDepth;
  float weight = afterDepth / (afterDepth - beforeDepth);
  return mix(currentTexCoords, prevTexCoords, weight);
}

2.4 PBR 纹理工作流

2.4.1 纹理贴图类型

现代 PBR 渲染使用多种纹理贴图来描述材质属性:

贴图类型通道含义典型值范围
Albedo(基础颜色)RGB表面颜色sRGB 空间
Metallic(金属度)R金属/非金属0 或 1
Roughness(粗糙度)G表面粗糙程度0-1
Normal(法线)RGB表面法线方向[-1,1]
AO(环境光遮蔽)R自遮蔽因子0-1
Height(高度)R位移量0-1
cpp
// PBR 材质结构
struct PBRMaterial {
  GLuint albedoMap;
  GLuint normalMap;
  GLuint metallicMap;
  GLuint roughnessMap;
  GLuint aoMap;
  GLuint heightMap;

  void bind(GLuint shader) const {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, albedoMap);
    glUniform1i(glGetUniformLocation(shader, "albedoMap"), 0);

    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, normalMap);
    glUniform1i(glGetUniformLocation(shader, "normalMap"), 1);

    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, metallicMap);
    glUniform1i(glGetUniformLocation(shader, "metallicMap"), 2);

    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, roughnessMap);
    glUniform1i(glGetUniformLocation(shader, "roughnessMap"), 3);

    glActiveTexture(GL_TEXTURE4);
    glBindTexture(GL_TEXTURE_2D, aoMap);
    glUniform1i(glGetUniformLocation(shader, "aoMap"), 4);

    glActiveTexture(GL_TEXTURE5);
    glBindTexture(GL_TEXTURE_2D, heightMap);
    glUniform1i(glGetUniformLocation(shader, "heightMap"), 5);
  }
};

2.4.2 通道打包

为了节省显存和带宽,通常将多个灰度贴图打包到一张纹理的不同通道中:

常见的通道打包方式:

ORM 贴图:
  R 通道 → AO(环境光遮蔽)
  G 通道 → Roughness(粗糙度)
  B 通道 → Metallic(金属度)

好处:3 张贴图合并为 1 张,节省 2/3 的纹理采样和显存
glsl
// ORM 贴图采样
vec3 orm = texture(ormMap, texCoord).rgb;
float ao = orm.r;
float roughness = orm.g;
float metallic = orm.b;

2.5 纹理压缩

2.5.1 为什么需要纹理压缩

纹理占用大量显存。一张 4K(4096×4096)的 RGBA 纹理占用 64MB 显存。一个现代游戏场景可能使用数百张纹理,总显存可达数 GB。纹理压缩可以显著减少显存占用和带宽需求。

2.5.2 常见压缩格式

格式平台压缩比特点
BC1/DXT1PC6:1RGB,无 Alpha
BC3/DXT5PC4:1RGBA
BC5PC3:1双通道(法线贴图)
BC7PC3:1高质量 RGB/RGBA
ASTC移动可变高质量,灵活块大小
ETC2移动可变OpenGL ES 3.0 标准
cpp
// 使用压缩纹理
// BC7 压缩纹理加载
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_BPTC_UNORM,
                       width, height, 0, dataSize, compressedData);

// ASTC 压缩纹理
glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_ASTC_8x8,
                       width, height, 0, dataSize, compressedData);

三、实战案例

3.1 完整纹理映射场景

cpp
// 纹理化的 3D 场景
class TexturedScene {
public:
  void init() {
    // 加载 PBR 材质
    woodMaterial.albedoMap = loadTexture("textures/wood_albedo.png");
    woodMaterial.normalMap = loadTexture("textures/wood_normal.png");
    woodMaterial.ormMap = loadTexture("textures/wood_orm.png");

    metalMaterial.albedoMap = loadTexture("textures/metal_albedo.png");
    metalMaterial.normalMap = loadTexture("textures/metal_normal.png");
    metalMaterial.ormMap = loadTexture("textures/metal_orm.png");

    // 创建立方体网格
    setupCubeMesh();
  }

  void render(const glm::mat4& view, const glm::mat4& projection) {
    glUseProgram(pbrShader);

    // 设置光源
    glUniform3fv(glGetUniformLocation(pbrShader, "lights[0].position"),
                 1, glm::value_ptr(glm::vec3(10.0f, 10.0f, 10.0f)));
    glUniform3fv(glGetUniformLocation(pbrShader, "lights[0].color"),
                 1, glm::value_ptr(glm::vec3(300.0f)));

    glUniform3fv(glGetUniformLocation(pbrShader, "camPos"),
                 1, glm::value_ptr(cameraPos));

    // 渲染木纹立方体
    glm::mat4 model1 = glm::translate(glm::mat4(1.0f),
                                       glm::vec3(-2.0f, 0.0f, 0.0f));
    glUniformMatrix4fv(glGetUniformLocation(pbrShader, "model"),
                       1, GL_FALSE, glm::value_ptr(model1));
    woodMaterial.bind(pbrShader);
    drawCube();

    // 渲染金属立方体
    glm::mat4 model2 = glm::translate(glm::mat4(1.0f),
                                       glm::vec3(2.0f, 0.0f, 0.0f));
    glUniformMatrix4fv(glGetUniformLocation(pbrShader, "model"),
                       1, GL_FALSE, glm::value_ptr(model2));
    metalMaterial.bind(pbrShader);
    drawCube();
  }

private:
  PBRMaterial woodMaterial;
  PBRMaterial metalMaterial;
  GLuint cubeVAO;
  GLuint pbrShader;
  glm::vec3 cameraPos = glm::vec3(0.0f, 2.0f, 6.0f);
};

3.2 纹理滤波效果对比

cpp
// 纹理滤波对比工具
class TextureFilterComparison {
public:
  void render() {
    // 左半屏:最近邻滤波
    glViewport(0, 0, screenWidth / 2, screenHeight);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    renderTexturedQuad();

    // 右半屏:三线性滤波
    glViewport(screenWidth / 2, 0, screenWidth / 2, screenHeight);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                    GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    renderTexturedQuad();

    // 可选:各向异性滤波
    // glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, 16.0f);
  }
};

四、本章小结

本章全面讲解了纹理映射技术从基础到高级的完整知识:

纹理坐标定义了三维表面与二维纹理图像之间的对应关系,UV 展开是将三维表面映射到二维平面的过程。

纹理滤波解决采样问题。双线性滤波处理放大,Mipmap + 三线性滤波处理缩小,各向异性滤波处理倾斜表面。

Mipmap 通过预计算多级缩略纹理,有效解决了远处物体的走样问题,同时提高了缓存命中率。

法线贴图通过在片元级别扰动法线来模拟表面细节,是性价比最高的细节增强技术。

视差映射通过偏移纹理坐标产生真正的视差效果,视差遮挡映射进一步支持了自遮挡效果。

PBR 纹理工作流使用多张贴图描述材质的物理属性,通道打包技术有效节省了显存和带宽。

纹理压缩在保持可接受质量的前提下大幅减少显存占用,是移动平台和大场景渲染的必备技术。


五、思考与练习

思考题

为什么法线贴图中平坦区域的颜色是蓝紫色(约 RGB 128, 128, 255)?如果一个区域的法线完全指向右侧,它的颜色是什么?

Mipmap 的总显存开销是原始纹理的多少倍?请推导这个结果。

各向异性滤波在什么场景下效果最明显?为什么它的性能开销比三线性滤波大?

视差遮挡映射中的"步进数"对效果有什么影响?步进数太少会出现什么问题?如何自适应选择步进数?

为什么金属材质的 albedo 贴图通常是彩色的,而非金属的 albedo 贴图通常是灰度或低饱和度的?

编程练习

纹理滤波器对比:实现一个程序,同时显示最近邻、双线性、三线性各向异性四种滤波模式的效果,支持通过按键切换。

法线贴图:加载一个低多边形模型和配套的法线贴图,实现法线贴图光照。对比有无法线贴图时的视觉效果。

视差映射:实现视差遮挡映射,在一个简单的四边形平面上模拟砖墙效果。调整高度缩放参数观察效果变化。


6/8纹理映射
0%