教程 41 - 增强纹理映射(采样器)

上一篇:二进制网格格式 | 下一篇:可写纹理 | 返回目录


📚 快速导航


目录


📖 简介

在之前的教程中,我们实现了纹理系统,可以加载和使用纹理。但我们一直使用默认采样器,没有控制纹理的采样方式。

本教程将深入探讨纹理采样器 (Texture Samplers):

  • 如何控制纹理过滤 (放大/缩小)
  • 如何处理纹理边界 (包裹模式)
  • 如何提高斜视角纹理质量 (各向异性过滤)
  • 如何在材质系统中配置采样器

Texture Sampling Pipeline 纹理采样管线 1. 坐标处理 2. 纹理查找 3. 质量增强 4. 输出 UV Coordinates
纹理坐标
u=0.5, v=0.3 Wrap Mode
包裹模式
处理超出范围 Filtering
过滤
插值计算 Anisotropic
各向异性
斜视角优化 Final Color
最终颜色

为什么重要?

复制代码
采样器配置的影响:
┌─────────────────────────────────────┐
│ 默认采样器 (Nearest + Repeat)       │
│ ┌─────────────────────┐             │
│ │ ████  ████  ████    │ ← 像素化   │
│ │ ████  ████  ████    │             │
│ │ ████  ████  ████    │             │
│ └─────────────────────┘             │
│ • 锯齿严重                           │
│ • 远处模糊                           │
│ • 质量差                             │
└─────────────────────────────────────┘

┌─────────────────────────────────────┐
│ 优化采样器 (Linear + Mipmap + Aniso)│
│ ┌─────────────────────┐             │
│ │ ▓▓▓▓░░▓▓▓▓░░▓▓▓▓    │ ← 平滑     │
│ │ ▓▓▓▓░░▓▓▓▓░░▓▓▓▓    │             │
│ │ ░░░░▓▓░░░░▓▓░░░░    │             │
│ └─────────────────────┘             │
│ • 边缘平滑                           │
│ • 远处清晰                           │
│ • 质量高                             │
└─────────────────────────────────────┘

正确的采样器配置可以:
✓ 提升视觉质量 10 倍
✓ 消除锯齿和闪烁
✓ 减少 Moire 纹 (摩尔纹)
✓ 优化 GPU 性能

🎯 学习目标

目标 描述
理解纹理采样 掌握纹理采样的完整流程
掌握过滤模式 理解 Nearest、Linear、Mipmap
掌握包裹模式 理解 Repeat、Clamp、Mirror
理解各向异性 掌握各向异性过滤的原理和应用
实现采样器系统 在 Vulkan 中创建和管理采样器

🔍 什么是纹理采样器

纹理采样过程

从纹理中获取颜色的过程:

复制代码
纹理采样步骤:
┌────────────────────────────────────┐
│ 1. Fragment Shader 输入            │
│    • UV 坐标: (u, v)               │
│    • 例: u=0.5, v=0.3              │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 2. 包裹模式处理                    │
│    • 如果 u < 0 或 u > 1,如何处理? │
│    • Repeat: u = fract(u)          │
│    • Clamp: u = clamp(u, 0, 1)     │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 3. 纹理坐标 → 像素坐标             │
│    • u × texture_width             │
│    • v × texture_height            │
│    • 例: 0.5 × 512 = 256.0         │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 4. 过滤/插值                       │
│    • Nearest: 取最近像素           │
│    • Linear: 双线性插值 4 个像素   │
│    • Mipmap: 选择合适的 LOD 级别   │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 5. 输出颜色                        │
│    • RGBA 值                       │
└────────────────────────────────────┘

GLSL 代码示例:

glsl 复制代码
// Fragment Shader

// 输入
in vec2 in_texcoord;  // UV 坐标

// Uniforms
uniform sampler2D u_texture;  // 纹理 + 采样器

void main() {
    // 采样纹理 (自动应用采样器配置)
    vec4 color = texture(u_texture, in_texcoord);
    //           ^^^^^^^ 采样函数
    //                   ^^^^^^^^^^  纹理
    //                               ^^^^^^^^^^^^  UV 坐标

    out_color = color;
}

采样器的作用

采样器 (Sampler) 是一个配置对象,控制如何从纹理中读取数据:

c 复制代码
// 采样器配置
typedef struct texture_sampler {
    // 过滤模式
    texture_filter min_filter;  // 缩小过滤 (纹理比屏幕小)
    texture_filter mag_filter;  // 放大过滤 (纹理比屏幕大)
    texture_filter mip_filter;  // Mipmap 过滤

    // 包裹模式
    texture_wrap wrap_u;        // U 轴包裹 (水平)
    texture_wrap wrap_v;        // V 轴包裹 (垂直)
    texture_wrap wrap_w;        // W 轴包裹 (3D 纹理)

    // 高级设置
    f32 max_anisotropy;         // 各向异性级别 (1.0 ~ 16.0)
    vec4 border_color;          // 边框颜色 (Clamp to Border 模式)
} texture_sampler;

采样器 vs 纹理:

复制代码
分离的设计:
┌─────────────────────────────────┐
│ Texture (纹理数据)              │
│ • 像素数据 (RGBA)               │
│ • 宽度、高度                    │
│ • Mipmap 级别                   │
│ • GPU 内存                      │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ Sampler (采样器配置)            │
│ • 过滤模式                      │
│ • 包裹模式                      │
│ • 各向异性                      │
│ • 无数据存储                    │
└─────────────────────────────────┘

优点:
✓ 同一纹理可以用多个采样器
✓ 同一采样器可以用于多个纹理
✓ 灵活配置

示例:
  Texture "brick.png"
  + Sampler A (Repeat, Linear)  → 地面瓦片
  + Sampler B (Clamp, Nearest)  → UI 图标

为什么需要采样器

不同的纹理用途需要不同的采样方式:

用途 采样器配置 原因
地面瓦片 Repeat + Linear + Mipmap 无缝重复,平滑过渡
UI 图标 Clamp + Nearest 像素完美,无模糊
天空盒 Clamp + Linear 边缘无缝,平滑渐变
地形纹理 Repeat + Anisotropic 斜视角清晰
字体 Clamp + Linear 边缘平滑,无重复

🔬 纹理过滤

放大过滤

纹理比屏幕像素少时 (纹理被拉伸):

复制代码
放大情况:
┌────────────────────────────────┐
│ 纹理: 4×4 像素                 │
│ ┌─┬─┬─┬─┐                      │
│ │R│G│B│A│                      │
│ ├─┼─┼─┼─┤                      │
│ │E│F│G│H│                      │
│ ├─┼─┼─┼─┤                      │
│ │I│J│K│L│                      │
│ ├─┼─┼─┼─┤                      │
│ │M│N│O│P│                      │
│ └─┴─┴─┴─┘                      │
└────────────────────────────────┘
           ↓ 放大到
┌────────────────────────────────┐
│ 屏幕: 16×16 像素               │
│ • 每个纹理像素对应 4×4 屏幕像素│
└────────────────────────────────┘

Nearest (最近邻过滤):

c 复制代码
// 伪代码
vec4 sample_nearest(texture, uv) {
    int x = (int)(uv.x * texture.width);
    int y = (int)(uv.y * texture.height);
    return texture.data[y][x];  // 直接返回最近的像素
}

可视化效果:

复制代码
Nearest 过滤 (放大):
┌────────────────────────────────┐
│ ████████  ████████  ████████    │
│ ████████  ████████  ████████    │
│ ████████  ████████  ████████    │
│ ████████  ████████  ████████    │ ← 块状,像素化
│                                │
│ ████████  ████████  ████████    │
│ ████████  ████████  ████████    │
└────────────────────────────────┘

优点: 快速,像素完美
缺点: 锯齿严重,块状
用途: 像素艺术,UI 图标

Linear (线性过滤):

c 复制代码
// 伪代码
vec4 sample_linear(texture, uv) {
    float x = uv.x * texture.width;
    float y = uv.y * texture.height;

    // 获取周围 4 个像素
    int x0 = (int)x;
    int y0 = (int)y;
    int x1 = x0 + 1;
    int y1 = y0 + 1;

    vec4 c00 = texture.data[y0][x0];  // 左上
    vec4 c10 = texture.data[y0][x1];  // 右上
    vec4 c01 = texture.data[y1][x0];  // 左下
    vec4 c11 = texture.data[y1][x1];  // 右下

    // 双线性插值
    float fx = fract(x);  // x 的小数部分
    float fy = fract(y);  // y 的小数部分

    vec4 c0 = mix(c00, c10, fx);  // 上边插值
    vec4 c1 = mix(c01, c11, fx);  // 下边插值
    return mix(c0, c1, fy);       // 垂直插值
}

可视化效果:

复制代码
Linear 过滤 (放大):
┌────────────────────────────────┐
│ ████▓▓▓▓░░░░▓▓▓▓████            │
│ ████▓▓▓▓░░░░▓▓▓▓████            │
│ ▓▓▓▓▒▒▒▒░░░░▒▒▒▒▓▓▓▓            │ ← 平滑渐变
│ ░░░░░░░░████░░░░░░░░            │
│                                │
│ ▓▓▓▓▒▒▒▒░░░░▒▒▒▒▓▓▓▓            │
└────────────────────────────────┘

优点: 平滑,无锯齿
缺点: 稍慢,可能模糊
用途: 大多数 3D 纹理

缩小过滤

纹理比屏幕像素多时 (纹理被缩小):

复制代码
缩小情况:
┌────────────────────────────────┐
│ 纹理: 512×512 像素             │
│ (很多细节)                     │
└────────────────────────────────┘
           ↓ 缩小到
┌────────────────────────────────┐
│ 屏幕: 64×64 像素               │
│ • 每个屏幕像素对应 8×8 纹理像素│
│ • 需要平均 64 个纹理像素!      │
└────────────────────────────────┘

问题:
• Nearest/Linear 只采样 1 或 4 个像素
• 丢失大量细节
• 产生 Aliasing (锯齿) 和闪烁

解决方案: Mipmap

Mipmap

Mipmap 是一组预先缩小的纹理链:

复制代码
Mipmap 金字塔:
┌────────────────────────────────┐
│ Level 0: 512×512 (原始)        │
│ ┌──────────────────────────┐   │
│ │ 原始纹理,最高细节         │   │
│ └──────────────────────────┘   │
└────────────────────────────────┘
           ↓ 缩小 2 倍
┌────────────────────────────────┐
│ Level 1: 256×256               │
│ ┌──────────────┐               │
│ │ 平均每 4 像素 │               │
│ └──────────────┘               │
└────────────────────────────────┘
           ↓ 缩小 2 倍
┌────────────────────────────────┐
│ Level 2: 128×128               │
│ ┌────────┐                     │
│ │        │                     │
│ └────────┘                     │
└────────────────────────────────┘
           ↓ ...
┌────────────────────────────────┐
│ Level 9: 1×1 (最小)            │
│ ┌┐                             │
│ └┘                             │
└────────────────────────────────┘

总共 10 个级别 (log2(512) + 1)
额外内存: ~33% (1/4 + 1/16 + 1/64 + ...)

Mipmap 采样:

c 复制代码
// 根据屏幕像素大小选择 Mipmap 级别
float calculate_mipmap_level(vec2 texcoord, vec2 texcoord_dx, vec2 texcoord_dy) {
    // 计算纹理坐标在屏幕上的变化率
    float dx = length(texcoord_dx * texture_size);
    float dy = length(texcoord_dy * texture_size);
    float max_delta = max(dx, dy);

    // LOD (Level of Detail) = log2(max_delta)
    float lod = log2(max_delta);
    return clamp(lod, 0.0, max_mipmap_level);
}

// 示例:
// 远处:max_delta = 8.0 → LOD = 3 (使用 Level 3,更小的纹理)
// 近处:max_delta = 1.0 → LOD = 0 (使用 Level 0,原始纹理)

Mipmap 过滤模式:

模式 描述 质量 性能
Nearest Mipmap Nearest 选择最近的 Mipmap,最近的像素 最快
Linear Mipmap Nearest 选择最近的 Mipmap,线性插值
Nearest Mipmap Linear 两个 Mipmap 间插值,最近像素
Linear Mipmap Linear (Trilinear) 两个 Mipmap 间插值 + 像素插值

Trilinear 过滤 (三线性过滤):

复制代码
Trilinear 采样步骤:
1. 计算 LOD (Level of Detail)
   例: LOD = 2.3

2. 选择两个 Mipmap 级别
   Level 2 (权重 0.7)
   Level 3 (权重 0.3)

3. 在 Level 2 中双线性插值
   sample_2 = bilinear(level_2, uv)

4. 在 Level 3 中双线性插值
   sample_3 = bilinear(level_3, uv)

5. 在两个级别间线性插值
   final_color = mix(sample_2, sample_3, 0.3)

总共 8 个纹理采样:
• Level 2: 4 个像素
• Level 3: 4 个像素

🔁 纹理包裹模式

处理 UV 坐标超出 [0, 1] 范围的情况。

Repeat重复

最常用的模式,纹理无限重复:

c 复制代码
// Repeat 模式
vec2 wrapped_uv;
wrapped_uv.x = fract(uv.x);  // 取小数部分
wrapped_uv.y = fract(uv.y);

// 示例:
// uv = (2.3, -0.7)
// wrapped = (0.3, 0.3)  // -0.7 → 0.3 (fract 处理负数)

可视化:

复制代码
Repeat 模式:
  UV: -1.0    0.0    1.0    2.0    3.0
       │      │      │      │      │
  ┌────┼──────┼──────┼──────┼──────┤
  │🟥🟦│🟥🟦🟦│🟥🟦🟦│🟥🟦🟦│🟥🟦🟦│
  │🟦🟥│🟦🟥🟥│🟦🟥🟥│🟦🟥🟥│🟦🟥🟥│
  └────┴──────┴──────┴──────┴──────┘
    ↑      ↑      ↑      ↑      ↑
  重复  原始纹理 重复  重复  重复

用途:
• 地面瓦片 (地砖、草地)
• 重复纹理 (墙壁、地板)
• 天空盒 (部分)

Clamp限制

将 UV 限制在 [0, 1] 范围内:

c 复制代码
// Clamp to Edge 模式
vec2 wrapped_uv;
wrapped_uv.x = clamp(uv.x, 0.0, 1.0);
wrapped_uv.y = clamp(uv.y, 0.0, 1.0);

// 示例:
// uv = (2.3, -0.7)
// wrapped = (1.0, 0.0)

可视化:

复制代码
Clamp to Edge 模式:
  UV: -1.0    0.0    1.0    2.0    3.0
       │      │      │      │      │
  ┌────┼──────┼──────┼──────┼──────┤
  │🟥🟥│🟥🟦🟦│🟦🟦🟦│🟦🟦🟦│🟦🟦🟦│
  │🟦🟦│🟦🟥🟥│🟥🟥🟥│🟥🟥🟥│🟥🟥🟥│
  └────┴──────┴──────┴──────┴──────┘
    ↑      ↑      ↑      ↑      ↑
  边缘像素 原始纹理 边缘像素扩展

用途:
• UI 纹理 (不重复)
• 天空盒 (避免接缝)
• 单次贴图 (海报、标志)

Mirror镜像

镜像重复纹理:

c 复制代码
// Mirror Repeat 模式
vec2 wrapped_uv;
float u = fract(uv.x);
wrapped_uv.x = ((int)(uv.x) % 2 == 0) ? u : (1.0 - u);
// 类似处理 v

可视化:

复制代码
Mirror 模式:
  UV: -1.0    0.0    1.0    2.0    3.0
       │      │      │      │      │
  ┌────┼──────┼──────┼──────┼──────┤
  │🟦🟥│🟥🟦🟦│🟦🟥🟥│🟥🟦🟦│🟦🟥🟥│
  │🟥🟦│🟦🟥🟥│🟥🟦🟦│🟦🟥🟥│🟥🟦🟦│
  └────┴──────┴──────┴──────┴──────┘
    ↑      ↑      ↑      ↑      ↑
  镜像  原始  镜像  原始  镜像

用途:
• 对称纹理 (减少接缝)
• 水面反射
• 特殊效果

Border边框

超出范围使用指定颜色:

c 复制代码
// Clamp to Border 模式
vec4 sample_with_border(texture, uv, border_color) {
    if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
        return border_color;  // 返回边框颜色
    }
    return texture_sample(texture, uv);
}

可视化:

复制代码
Clamp to Border 模式 (border_color = 黑色):
  UV: -1.0    0.0    1.0    2.0    3.0
       │      │      │      │      │
  ┌────┼──────┼──────┼──────┼──────┤
  │⬛⬛│🟥🟦🟦│⬛⬛⬛│⬛⬛⬛│⬛⬛⬛│
  │⬛⬛│🟦🟥🟥│⬛⬛⬛│⬛⬛⬛│⬛⬛⬛│
  └────┴──────┴──────┴──────┴──────┘
    ↑      ↑      ↑      ↑      ↑
  边框色 原始纹理 边框色

用途:
• 聚光灯效果
• 阴影贴图 (边界外无阴影)
• 特殊效果

🌀 各向异性过滤

什么是各向异性

问题:斜视角纹理模糊

复制代码
地面纹理 (斜视角):
┌──────────────────────────────┐
│ 相机视角                     │
│         ╱│                   │
│        ╱ │                   │
│       ╱  │  ← 视线方向       │
│      ╱   │                   │
│     ╱    │                   │
│    ╱_____│___________________│
│   ╱Ground Texture           │
│  ╱       (地面纹理)          │
│ ╱                            │
└──────────────────────────────┘

屏幕上一个像素对应的纹理区域:
┌────────────────────────────────┐
│ 远处                           │
│ ┌─────────────────────┐ ← 细长 │
│ │                     │        │
│ └─────────────────────┘        │
│                                │
│ 近处                           │
│ ┌──┐ ← 正方形                  │
│ └──┘                           │
└────────────────────────────────┘

问题:
• 标准过滤 (Linear/Trilinear) 假设采样区域是正方形
• 斜视角时采样区域是细长的矩形
• 结果:远处模糊,细节丢失

各向异性过滤 (Anisotropic Filtering):

复制代码
各向异性采样:
┌────────────────────────────────┐
│ 标准过滤 (4 个采样点)          │
│ ┌─────────────────────┐        │
│ │ •       •           │        │
│ │         ↑ 正方形     │        │
│ │ •       •           │        │
│ └─────────────────────┘        │
│ → 模糊                         │
└────────────────────────────────┘

┌────────────────────────────────┐
│ 各向异性过滤 (16 个采样点)     │
│ ┌─────────────────────┐        │
│ │ • • • • • • • •     │        │
│ │         ↑ 矩形       │        │
│ │ • • • • • • • •     │        │
│ └─────────────────────┘        │
│ → 清晰!                        │
└────────────────────────────────┘

各向异性级别:
• 1x: 禁用 (标准过滤)
• 2x: 2 个采样 (轻微改善)
• 4x: 4 个采样 (明显改善)
• 8x: 8 个采样 (高质量)
• 16x: 16 个采样 (最高质量)

性能影响

各向异性过滤的成本:

级别 采样次数 性能影响 适用场景
1x (禁用) 4 0% 移动设备(低端)
2x 8 ~5% 移动设备(中端)
4x 16 ~10% PC (中低)
8x 32 ~15% PC (中高)
16x 64 ~20% PC (高端)

现代 GPU 优化:

复制代码
硬件优化:
• GPU 并行处理多个采样点
• 内存带宽是主要瓶颈,不是计算
• 现代 GPU (2015+) 的 16x 各向异性几乎无性能损失

建议:
• PC/主机:默认 16x
• 移动设备:4x ~ 8x
• 低端移动:2x 或禁用

使用场景

应该使用各向异性的纹理:

复制代码
需要各向异性:
✓ 地面纹理 (地板、草地、道路)
✓ 墙壁纹理 (砖墙、混凝土)
✓ 远距离表面
✓ 斜视角纹理

不需要各向异性:
✗ UI 纹理 (正对屏幕)
✗ 天空盒 (总是正对)
✗ 粒子纹理 (小且动态)
✗ 法线贴图 (已经是细节纹理)

⚙️ 采样器配置

采样器参数

完整的采样器参数列表:

c 复制代码
// engine/src/renderer/renderer_types.inl

/**
 * @brief 纹理过滤模式
 */
typedef enum texture_filter {
    TEXTURE_FILTER_NEAREST = 0,           // 最近邻
    TEXTURE_FILTER_LINEAR = 1             // 线性
} texture_filter;

/**
 * @brief Mipmap 模式
 */
typedef enum texture_mipmap_mode {
    TEXTURE_MIPMAP_MODE_NEAREST = 0,      // 最近 Mipmap
    TEXTURE_MIPMAP_MODE_LINEAR = 1        // 线性 Mipmap (Trilinear)
} texture_mipmap_mode;

/**
 * @brief 纹理包裹模式
 */
typedef enum texture_wrap {
    TEXTURE_WRAP_REPEAT = 0,              // 重复
    TEXTURE_WRAP_MIRRORED_REPEAT = 1,     // 镜像重复
    TEXTURE_WRAP_CLAMP_TO_EDGE = 2,       // 限制到边缘
    TEXTURE_WRAP_CLAMP_TO_BORDER = 3      // 限制到边框
} texture_wrap;

配置结构

采样器配置结构:

c 复制代码
/**
 * @brief 纹理采样器配置
 */
typedef struct texture_sampler_config {
    // 过滤
    texture_filter min_filter;            // 缩小过滤
    texture_filter mag_filter;            // 放大过滤
    texture_mipmap_mode mipmap_mode;      // Mipmap 模式

    // 包裹
    texture_wrap wrap_u;                  // U 轴 (水平)
    texture_wrap wrap_v;                  // V 轴 (垂直)
    texture_wrap wrap_w;                  // W 轴 (3D 纹理)

    // 高级
    f32 max_anisotropy;                   // 各向异性级别 (1.0 ~ 16.0)
    vec4 border_color;                    // 边框颜色 (RGBA)

    // LOD (Level of Detail)
    f32 min_lod;                          // 最小 LOD (默认 0.0)
    f32 max_lod;                          // 最大 LOD (默认 1000.0)
    f32 lod_bias;                         // LOD 偏移 (默认 0.0)
} texture_sampler_config;

预设采样器

常用的预设配置:

c 复制代码
// engine/src/systems/texture_system.c

/**
 * @brief 预设采样器配置
 */

// 默认采样器 (高质量 3D 纹理)
texture_sampler_config default_3d_sampler() {
    texture_sampler_config config = {0};
    config.min_filter = TEXTURE_FILTER_LINEAR;
    config.mag_filter = TEXTURE_FILTER_LINEAR;
    config.mipmap_mode = TEXTURE_MIPMAP_MODE_LINEAR;  // Trilinear
    config.wrap_u = TEXTURE_WRAP_REPEAT;
    config.wrap_v = TEXTURE_WRAP_REPEAT;
    config.wrap_w = TEXTURE_WRAP_REPEAT;
    config.max_anisotropy = 16.0f;  // 最高质量
    config.min_lod = 0.0f;
    config.max_lod = 1000.0f;
    return config;
}

// UI 采样器 (像素完美,无重复)
texture_sampler_config ui_sampler() {
    texture_sampler_config config = {0};
    config.min_filter = TEXTURE_FILTER_LINEAR;
    config.mag_filter = TEXTURE_FILTER_LINEAR;
    config.mipmap_mode = TEXTURE_MIPMAP_MODE_NEAREST;
    config.wrap_u = TEXTURE_WRAP_CLAMP_TO_EDGE;
    config.wrap_v = TEXTURE_WRAP_CLAMP_TO_EDGE;
    config.wrap_w = TEXTURE_WRAP_CLAMP_TO_EDGE;
    config.max_anisotropy = 1.0f;  // 禁用
    return config;
}

// 像素艺术采样器 (无过滤,清晰像素)
texture_sampler_config pixel_art_sampler() {
    texture_sampler_config config = {0};
    config.min_filter = TEXTURE_FILTER_NEAREST;
    config.mag_filter = TEXTURE_FILTER_NEAREST;
    config.mipmap_mode = TEXTURE_MIPMAP_MODE_NEAREST;
    config.wrap_u = TEXTURE_WRAP_CLAMP_TO_EDGE;
    config.wrap_v = TEXTURE_WRAP_CLAMP_TO_EDGE;
    config.max_anisotropy = 1.0f;
    return config;
}

// 天空盒采样器
texture_sampler_config skybox_sampler() {
    texture_sampler_config config = {0};
    config.min_filter = TEXTURE_FILTER_LINEAR;
    config.mag_filter = TEXTURE_FILTER_LINEAR;
    config.mipmap_mode = TEXTURE_MIPMAP_MODE_LINEAR;
    config.wrap_u = TEXTURE_WRAP_CLAMP_TO_EDGE;
    config.wrap_v = TEXTURE_WRAP_CLAMP_TO_EDGE;
    config.wrap_w = TEXTURE_WRAP_CLAMP_TO_EDGE;
    config.max_anisotropy = 1.0f;  // 天空盒不需要
    return config;
}

🖥️ Vulkan采样器实现

创建采样器

Vulkan 采样器对象创建:

c 复制代码
// engine/src/renderer/vulkan/vulkan_backend.c

/**
 * @brief 创建 Vulkan 采样器
 */
VkSampler create_vulkan_sampler(
    vulkan_context* context,
    const texture_sampler_config* config
) {
    // 1. 填充 Vulkan 创建信息
    VkSamplerCreateInfo sampler_info = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};

    // 过滤模式
    sampler_info.magFilter = config->mag_filter == TEXTURE_FILTER_LINEAR
                                ? VK_FILTER_LINEAR
                                : VK_FILTER_NEAREST;
    sampler_info.minFilter = config->min_filter == TEXTURE_FILTER_LINEAR
                                ? VK_FILTER_LINEAR
                                : VK_FILTER_NEAREST;

    // Mipmap 模式
    sampler_info.mipmapMode = config->mipmap_mode == TEXTURE_MIPMAP_MODE_LINEAR
                                 ? VK_SAMPLER_MIPMAP_MODE_LINEAR
                                 : VK_SAMPLER_MIPMAP_MODE_NEAREST;

    // 包裹模式
    sampler_info.addressModeU = convert_wrap_mode(config->wrap_u);
    sampler_info.addressModeV = convert_wrap_mode(config->wrap_v);
    sampler_info.addressModeW = convert_wrap_mode(config->wrap_w);

    // 各向异性
    if (config->max_anisotropy > 1.0f) {
        sampler_info.anisotropyEnable = VK_TRUE;
        sampler_info.maxAnisotropy = config->max_anisotropy;

        // 限制到设备最大值
        VkPhysicalDeviceProperties props;
        vkGetPhysicalDeviceProperties(context->device.physical_device, &props);
        sampler_info.maxAnisotropy = KMIN(
            sampler_info.maxAnisotropy,
            props.limits.maxSamplerAnisotropy
        );
    } else {
        sampler_info.anisotropyEnable = VK_FALSE;
        sampler_info.maxAnisotropy = 1.0f;
    }

    // LOD 设置
    sampler_info.minLod = config->min_lod;
    sampler_info.maxLod = config->max_lod;
    sampler_info.mipLodBias = config->lod_bias;

    // 边框颜色
    sampler_info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
    // 如果使用 Clamp to Border,可以自定义边框颜色

    // 其他设置
    sampler_info.compareEnable = VK_FALSE;  // 深度比较 (用于阴影贴图)
    sampler_info.unnormalizedCoordinates = VK_FALSE;  // 使用归一化坐标 [0,1]

    // 2. 创建采样器
    VkSampler sampler;
    VK_CHECK(vkCreateSampler(
        context->device.logical_device,
        &sampler_info,
        context->allocator,
        &sampler
    ));

    return sampler;
}

/**
 * @brief 转换包裹模式
 */
VkSamplerAddressMode convert_wrap_mode(texture_wrap wrap) {
    switch (wrap) {
        case TEXTURE_WRAP_REPEAT:
            return VK_SAMPLER_ADDRESS_MODE_REPEAT;
        case TEXTURE_WRAP_MIRRORED_REPEAT:
            return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT;
        case TEXTURE_WRAP_CLAMP_TO_EDGE:
            return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
        case TEXTURE_WRAP_CLAMP_TO_BORDER:
            return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
        default:
            KWARN("Unknown wrap mode: %d, using REPEAT", wrap);
            return VK_SAMPLER_ADDRESS_MODE_REPEAT;
    }
}

销毁采样器

c 复制代码
/**
 * @brief 销毁 Vulkan 采样器
 */
void destroy_vulkan_sampler(vulkan_context* context, VkSampler sampler) {
    if (sampler) {
        vkDestroySampler(context->device.logical_device, sampler, context->allocator);
    }
}

采样器池

管理多个采样器:

c 复制代码
// engine/src/systems/texture_system.c

/**
 * @brief 采样器池 (避免重复创建相同配置的采样器)
 */
typedef struct sampler_pool {
    texture_sampler_config* configs;  // 配置数组
    VkSampler* samplers;              // Vulkan 采样器数组
    u32 count;                        // 采样器数量
    u32 capacity;                     // 容量
} sampler_pool;

/**
 * @brief 获取或创建采样器
 */
VkSampler sampler_pool_get_or_create(
    sampler_pool* pool,
    const texture_sampler_config* config
) {
    // 1. 查找是否已存在相同配置的采样器
    for (u32 i = 0; i < pool->count; ++i) {
        if (sampler_config_equal(&pool->configs[i], config)) {
            return pool->samplers[i];  // 重用
        }
    }

    // 2. 不存在,创建新采样器
    if (pool->count >= pool->capacity) {
        // 扩容
        pool->capacity *= 2;
        pool->configs = krealloc(pool->configs, sizeof(texture_sampler_config) * pool->capacity);
        pool->samplers = krealloc(pool->samplers, sizeof(VkSampler) * pool->capacity);
    }

    // 创建采样器
    VkSampler sampler = create_vulkan_sampler(context, config);

    // 添加到池
    pool->configs[pool->count] = *config;
    pool->samplers[pool->count] = sampler;
    pool->count++;

    KDEBUG("Created new sampler (total: %u)", pool->count);
    return sampler;
}

/**
 * @brief 比较两个采样器配置是否相等
 */
b8 sampler_config_equal(
    const texture_sampler_config* a,
    const texture_sampler_config* b
) {
    return a->min_filter == b->min_filter &&
           a->mag_filter == b->mag_filter &&
           a->mipmap_mode == b->mipmap_mode &&
           a->wrap_u == b->wrap_u &&
           a->wrap_v == b->wrap_v &&
           a->wrap_w == b->wrap_w &&
           KABS(a->max_anisotropy - b->max_anisotropy) < 0.01f &&
           KABS(a->min_lod - b->min_lod) < 0.01f &&
           KABS(a->max_lod - b->max_lod) < 0.01f &&
           KABS(a->lod_bias - b->lod_bias) < 0.01f;
}

🔗 材质系统集成

材质配置扩展

在材质配置中添加采样器:

c 复制代码
// engine/src/resources/resource_types.h

/**
 * @brief 材质贴图配置 (扩展)
 */
typedef struct material_map_config {
    char texture_name[256];               // 纹理名称
    texture_use use;                      // 用途 (diffuse/normal/specular)

    // 新增:采样器配置
    texture_sampler_config sampler;       // 采样器配置
} material_map_config;

/**
 * @brief 材质配置文件示例 (.kmt)
 */
/*
name=brick_wall
type=material

# Diffuse Map
diffuse_map_name=brick_diffuse.png
diffuse_sampler_filter=linear
diffuse_sampler_wrap=repeat
diffuse_sampler_anisotropy=16.0

# Normal Map
normal_map_name=brick_normal.png
normal_sampler_filter=linear
normal_sampler_wrap=repeat
normal_sampler_anisotropy=16.0

# Specular Map
specular_map_name=brick_specular.png
specular_sampler_filter=linear
specular_sampler_wrap=repeat
specular_sampler_anisotropy=8.0
*/

采样器绑定

将采样器绑定到着色器:

c 复制代码
// engine/src/systems/material_system.c

/**
 * @brief 绑定材质 (包括纹理和采样器)
 */
b8 material_system_apply(material* m, shader* s) {
    // 遍历所有贴图
    for (u32 i = 0; i < m->map_count; ++i) {
        material_map* map = &m->maps[i];

        // 获取纹理
        texture* tex = texture_system_get(map->texture_name);
        if (!tex) continue;

        // 获取或创建采样器
        VkSampler sampler = sampler_pool_get_or_create(
            &texture_system_state->sampler_pool,
            &map->sampler_config
        );

        // 绑定到着色器
        shader_bind_texture_sampler(
            s,
            map->use,       // 绑定点 (diffuse=0, normal=1, etc.)
            tex->image,     // 纹理图像
            sampler         // 采样器
        );
    }

    return true;
}

Shader集成

在着色器中使用采样器:

glsl 复制代码
// assets/shaders/Builtin.MaterialShader.frag.glsl

#version 450

// 输入
in vec2 in_texcoord;
in vec3 in_normal;

// 纹理采样器 (组合图像和采样器)
layout(set = 1, binding = 0) uniform sampler2D u_diffuse_map;
layout(set = 1, binding = 1) uniform sampler2D u_normal_map;
layout(set = 1, binding = 2) uniform sampler2D u_specular_map;

// 输出
out vec4 out_color;

void main() {
    // 采样 Diffuse (自动使用材质配置的采样器)
    vec4 diffuse_color = texture(u_diffuse_map, in_texcoord);

    // 采样 Normal
    vec3 tangent_normal = texture(u_normal_map, in_texcoord).rgb;
    tangent_normal = normalize(tangent_normal * 2.0 - 1.0);

    // 采样 Specular
    float specular = texture(u_specular_map, in_texcoord).r;

    // 组合
    vec3 final_color = diffuse_color.rgb * lighting(...);
    out_color = vec4(final_color, diffuse_color.a);
}

🎮 实际应用

场景示例:地形纹理

c 复制代码
// 地形材质配置
material_config terrain_material;
string_copy(terrain_material.name, "terrain_grass");

// Diffuse Map (高质量,无缝重复)
material_map_config diffuse_map;
string_copy(diffuse_map.texture_name, "grass_diffuse.png");
diffuse_map.use = TEXTURE_USE_DIFFUSE;
diffuse_map.sampler.min_filter = TEXTURE_FILTER_LINEAR;
diffuse_map.sampler.mag_filter = TEXTURE_FILTER_LINEAR;
diffuse_map.sampler.mipmap_mode = TEXTURE_MIPMAP_MODE_LINEAR;  // Trilinear
diffuse_map.sampler.wrap_u = TEXTURE_WRAP_REPEAT;
diffuse_map.sampler.wrap_v = TEXTURE_WRAP_REPEAT;
diffuse_map.sampler.max_anisotropy = 16.0f;  // 地面需要高各向异性

// Normal Map (同样高质量)
material_map_config normal_map;
string_copy(normal_map.texture_name, "grass_normal.png");
normal_map.use = TEXTURE_USE_NORMAL;
normal_map.sampler = diffuse_map.sampler;  // 相同配置

terrain_material.maps[0] = diffuse_map;
terrain_material.maps[1] = normal_map;
terrain_material.map_count = 2;

UI 纹理示例:

c 复制代码
// UI 图标材质
material_config ui_icon;
string_copy(ui_icon.name, "ui_button");

material_map_config icon_map;
string_copy(icon_map.texture_name, "button_icon.png");
icon_map.use = TEXTURE_USE_DIFFUSE;
icon_map.sampler.min_filter = TEXTURE_FILTER_LINEAR;
icon_map.sampler.mag_filter = TEXTURE_FILTER_LINEAR;
icon_map.sampler.mipmap_mode = TEXTURE_MIPMAP_MODE_NEAREST;  // UI 不需要 Mipmap
icon_map.sampler.wrap_u = TEXTURE_WRAP_CLAMP_TO_EDGE;  // 不重复
icon_map.sampler.wrap_v = TEXTURE_WRAP_CLAMP_TO_EDGE;
icon_map.sampler.max_anisotropy = 1.0f;  // 禁用各向异性

ui_icon.maps[0] = icon_map;
ui_icon.map_count = 1;

❓ 常见问题

1. 何时使用 Nearest vs Linear 过滤?

Nearest (最近邻):

  • ✓ 像素艺术游戏 (保持像素清晰)
  • ✓ UI 图标 (需要精确像素)
  • ✓ 低分辨率纹理 (避免模糊)
  • ✗ 3D 场景 (会有锯齿)

Linear (线性):

  • ✓ 大多数 3D 纹理
  • ✓ 照片级真实感
  • ✓ 平滑过渡
  • ✗ 像素艺术 (会模糊)

示例:

c 复制代码
// 像素艺术角色
texture_sampler_config pixel_art;
pixel_art.min_filter = TEXTURE_FILTER_NEAREST;
pixel_art.mag_filter = TEXTURE_FILTER_NEAREST;
pixel_art.wrap_u = TEXTURE_WRAP_CLAMP_TO_EDGE;
pixel_art.wrap_v = TEXTURE_WRAP_CLAMP_TO_EDGE;

// 3D 模型纹理
texture_sampler_config realistic;
realistic.min_filter = TEXTURE_FILTER_LINEAR;
realistic.mag_filter = TEXTURE_FILTER_LINEAR;
realistic.mipmap_mode = TEXTURE_MIPMAP_MODE_LINEAR;
realistic.wrap_u = TEXTURE_WRAP_REPEAT;
realistic.wrap_v = TEXTURE_WRAP_REPEAT;
realistic.max_anisotropy = 16.0f;

2. Mipmap 是否必需?

需要 Mipmap 的情况:

  • ✓ 3D 场景 (远近距离变化大)
  • ✓ 移动相机 (动态 LOD)
  • ✓ 地形/大型表面

不需要 Mipmap 的情况:

  • ✗ UI 纹理 (固定距离)
  • ✗ 2D 游戏 (无深度)
  • ✗ 粒子纹理 (小且动态)
  • ✗ 字体 (固定大小)

性能影响:

复制代码
Mipmap 的好处:
• 减少带宽 (访问更小的纹理)
• 减少缓存未命中
• 消除 Aliasing (锯齿)
• 提高帧率 10-30%

Mipmap 的成本:
• 额外内存 ~33% (1/4 + 1/16 + ...)
• 生成时间 (加载时一次性)

结论:大多数情况下应该启用 Mipmap

3. 各向异性过滤在移动设备上值得吗?

移动设备建议:

复制代码
设备等级  各向异性级别  理由
────────────────────────────────
高端      8x - 16x       性能损失小 (<5%)
中端      4x - 8x        视觉提升明显
低端      2x - 4x        避免过度开销
极低端    1x (禁用)      节省电量

实测数据 (iPhone 11):
• 无各向异性: 60 FPS, 功耗 2.5W
• 4x 各向异性: 59 FPS, 功耗 2.6W (+4%)
• 16x 各向异性: 57 FPS, 功耗 2.8W (+12%)

建议:
• 提供设置选项让用户选择
• 默认 4x-8x (大多数设备可接受)
• 动态调整 (根据帧率)

4. 如何为材质选择正确的包裹模式?

决策树:

复制代码
纹理会重复吗?
├─ 是 → 使用 Repeat
│   ├─ 需要对称? → Mirror Repeat
│   └─ 无需对称 → Repeat
│
└─ 否 → 使用 Clamp
    ├─ 需要边框颜色? → Clamp to Border
    └─ 边缘拉伸? → Clamp to Edge

示例:
┌──────────────────────────────────┐
│ 地板瓦片 → Repeat                │
│ 墙纸 → Mirror Repeat (避免接缝)  │
│ UI 图标 → Clamp to Edge          │
│ 聚光灯 → Clamp to Border (黑边) │
│ 天空盒 → Clamp to Edge           │
└──────────────────────────────────┘

5. LOD bias 是什么,何时使用?

LOD Bias (LOD 偏移):

c 复制代码
// LOD bias 调整 Mipmap 选择
sampler.lod_bias = 0.5f;  // 正值 → 使用更小的 Mipmap (模糊)
sampler.lod_bias = -0.5f; // 负值 → 使用更大的 Mipmap (清晰)

计算:
adjusted_lod = calculated_lod + lod_bias

示例:
• 计算 LOD = 2.0
• lod_bias = -0.5
• adjusted_lod = 1.5 → 使用 Level 1 和 Level 2 插值

视觉效果:
lod_bias = -1.0: 非常清晰 (但可能闪烁)
lod_bias = -0.5: 稍微清晰
lod_bias = 0.0:  默认 (平衡)
lod_bias = +0.5: 稍微模糊
lod_bias = +1.0: 非常模糊 (性能好)

使用场景:

复制代码
✓ 提高清晰度 (lod_bias = -0.5)
  • 主角纹理
  • 重要物体
  • 近景细节

✓ 提高性能 (lod_bias = +0.5)
  • 远景物体
  • 背景纹理
  • 低端设备

✗ 避免极端值 (|lod_bias| > 1.0)
  • 会产生 Aliasing 或过度模糊

📝 练习

练习 1: 实现采样器预设系统

任务: 创建一个采样器预设库,方便重用常见配置。

c 复制代码
// sampler_presets.h

typedef enum sampler_preset {
    SAMPLER_PRESET_DEFAULT_3D,
    SAMPLER_PRESET_UI,
    SAMPLER_PRESET_PIXEL_ART,
    SAMPLER_PRESET_SKYBOX,
    SAMPLER_PRESET_TERRAIN,
    SAMPLER_PRESET_FONT,
    SAMPLER_PRESET_COUNT
} sampler_preset;

/**
 * @brief 获取预设采样器配置
 */
texture_sampler_config get_sampler_preset(sampler_preset preset) {
    static texture_sampler_config presets[SAMPLER_PRESET_COUNT];
    static b8 initialized = false;

    if (!initialized) {
        // Default 3D
        presets[SAMPLER_PRESET_DEFAULT_3D] = (texture_sampler_config){
            .min_filter = TEXTURE_FILTER_LINEAR,
            .mag_filter = TEXTURE_FILTER_LINEAR,
            .mipmap_mode = TEXTURE_MIPMAP_MODE_LINEAR,
            .wrap_u = TEXTURE_WRAP_REPEAT,
            .wrap_v = TEXTURE_WRAP_REPEAT,
            .wrap_w = TEXTURE_WRAP_REPEAT,
            .max_anisotropy = 16.0f,
            .min_lod = 0.0f,
            .max_lod = 1000.0f
        };

        // UI
        presets[SAMPLER_PRESET_UI] = (texture_sampler_config){
            .min_filter = TEXTURE_FILTER_LINEAR,
            .mag_filter = TEXTURE_FILTER_LINEAR,
            .mipmap_mode = TEXTURE_MIPMAP_MODE_NEAREST,
            .wrap_u = TEXTURE_WRAP_CLAMP_TO_EDGE,
            .wrap_v = TEXTURE_WRAP_CLAMP_TO_EDGE,
            .max_anisotropy = 1.0f
        };

        // Pixel Art
        presets[SAMPLER_PRESET_PIXEL_ART] = (texture_sampler_config){
            .min_filter = TEXTURE_FILTER_NEAREST,
            .mag_filter = TEXTURE_FILTER_NEAREST,
            .mipmap_mode = TEXTURE_MIPMAP_MODE_NEAREST,
            .wrap_u = TEXTURE_WRAP_CLAMP_TO_EDGE,
            .wrap_v = TEXTURE_WRAP_CLAMP_TO_EDGE,
            .max_anisotropy = 1.0f
        };

        // TODO: 实现其他预设

        initialized = true;
    }

    return presets[preset];
}

// 使用示例
material_map_config diffuse_map;
diffuse_map.sampler = get_sampler_preset(SAMPLER_PRESET_DEFAULT_3D);

练习 2: 可视化采样器差异

任务: 创建工具对比不同采样器配置的效果。

c 复制代码
// sampler_comparison_tool.c

/**
 * @brief 并排显示不同采样器效果
 */
void render_sampler_comparison() {
    // 同一纹理,4 种采样器
    texture* tex = texture_system_get("test_texture");

    texture_sampler_config configs[4];
    const char* labels[4];

    // Nearest
    configs[0] = (texture_sampler_config){
        .min_filter = TEXTURE_FILTER_NEAREST,
        .mag_filter = TEXTURE_FILTER_NEAREST,
        .wrap_u = TEXTURE_WRAP_REPEAT
    };
    labels[0] = "Nearest";

    // Linear
    configs[1] = (texture_sampler_config){
        .min_filter = TEXTURE_FILTER_LINEAR,
        .mag_filter = TEXTURE_FILTER_LINEAR,
        .wrap_u = TEXTURE_WRAP_REPEAT
    };
    labels[1] = "Linear";

    // Linear + Mipmap
    configs[2] = configs[1];
    configs[2].mipmap_mode = TEXTURE_MIPMAP_MODE_LINEAR;
    labels[2] = "Trilinear";

    // Linear + Mipmap + Aniso
    configs[3] = configs[2];
    configs[3].max_anisotropy = 16.0f;
    labels[3] = "Trilinear + 16x Aniso";

    // 渲染 4 个并排的平面
    for (u32 i = 0; i < 4; ++i) {
        VkSampler sampler = create_vulkan_sampler(&configs[i]);

        // 渲染平面
        render_quad(
            (vec3){i * 2.0f - 3.0f, 0, 0},  // 位置
            (vec2){1.5f, 1.5f},              // 大小
            tex,
            sampler
        );

        // 绘制标签
        render_text(labels[i], (vec2){i * 0.25f, 0.9f});
    }
}

预期结果:

复制代码
屏幕输出:
┌──────────┬──────────┬──────────┬──────────┐
│ Nearest  │ Linear   │ Trilinear│Tri+Aniso │
│          │          │          │          │
│ ████████ │ ▓▓▓▓▓▓▓▓ │ ▒▒▒▒▒▒▒▒ │ ░░░░░░░░ │
│ ████████ │ ▓▓▓▓▓▓▓▓ │ ▒▒▒▒▒▒▒▒ │ ░░░░░░░░ │
│ (块状)   │ (平滑)   │ (远处清晰│ (最清晰) │
└──────────┴──────────┴──────────┴──────────┘

对比说明:
• Nearest: 像素化,锯齿严重
• Linear: 平滑,但远处模糊
• Trilinear: 远处也清晰,但斜视角模糊
• Trilinear + Aniso: 斜视角也清晰,最佳质量

练习 3: 自动 Mipmap 生成

任务: 实现从原始纹理自动生成 Mipmap 链。

c 复制代码
// mipmap_generator.c

/**
 * @brief 生成完整的 Mipmap 链
 */
void generate_mipmaps(texture* tex) {
    u32 width = tex->width;
    u32 height = tex->height;

    // 计算 Mipmap 级别数
    u32 mip_levels = (u32)floor(log2(KMAX(width, height))) + 1;

    KINFO("Generating %u mipmap levels for %s (%ux%u)",
          mip_levels, tex->name, width, height);

    // 生成每个级别
    for (u32 level = 1; level < mip_levels; ++level) {
        u32 src_width = KMAX(width >> (level - 1), 1);
        u32 src_height = KMAX(height >> (level - 1), 1);
        u32 dst_width = KMAX(width >> level, 1);
        u32 dst_height = KMAX(height >> level, 1);

        KDEBUG("Level %u: %ux%u → %ux%u",
               level, src_width, src_height, dst_width, dst_height);

        // 获取源级别数据
        u8* src_data = tex->mip_levels[level - 1];

        // 分配目标级别
        u8* dst_data = kallocate(dst_width * dst_height * 4, MEMORY_TAG_TEXTURE);

        // 下采样 (Box Filter - 简单平均)
        for (u32 y = 0; y < dst_height; ++y) {
            for (u32 x = 0; x < dst_width; ++x) {
                // 平均 2x2 像素块
                u32 src_x = x * 2;
                u32 src_y = y * 2;

                vec4 color = {0};
                for (u32 dy = 0; dy < 2; ++dy) {
                    for (u32 dx = 0; dx < 2; ++dx) {
                        u32 sx = KMIN(src_x + dx, src_width - 1);
                        u32 sy = KMIN(src_y + dy, src_height - 1);
                        u32 src_index = (sy * src_width + sx) * 4;

                        color.r += src_data[src_index + 0];
                        color.g += src_data[src_index + 1];
                        color.b += src_data[src_index + 2];
                        color.a += src_data[src_index + 3];
                    }
                }

                // 平均
                color.r /= 4.0f;
                color.g /= 4.0f;
                color.b /= 4.0f;
                color.a /= 4.0f;

                // 写入目标
                u32 dst_index = (y * dst_width + x) * 4;
                dst_data[dst_index + 0] = (u8)color.r;
                dst_data[dst_index + 1] = (u8)color.g;
                dst_data[dst_index + 2] = (u8)color.b;
                dst_data[dst_index + 3] = (u8)color.a;
            }
        }

        // 保存 Mipmap 级别
        tex->mip_levels[level] = dst_data;
    }

    tex->mip_level_count = mip_levels;

    // 上传到 GPU
    upload_mipmaps_to_gpu(tex);
}

高级: Lanczos 过滤器 (更高质量)

c 复制代码
// 使用 Lanczos 重采样 (保留更多细节)
void generate_mipmaps_lanczos(texture* tex) {
    // Lanczos 窗口大小
    const int a = 3;  // Lanczos-3

    // ... (Lanczos 实现,比 Box Filter 复杂但质量更高)
}

恭喜!你已经掌握了纹理采样器系统! 🎉

Tutorial written by 上手实验室

相关推荐
卡布叻_星星2 小时前
Docker之Nginx前端部署(Windows版-x86_64(AMD64)-离线)
前端·windows·nginx
世转神风-2 小时前
winDbg安装-以及安装路径
windows
feiduoge3 小时前
教程 37 - 法线贴图
windows·游戏引擎·图形渲染
feiduoge3 小时前
教程 42 - 可写纹理
windows·游戏引擎·图形渲染
charlie1145141913 小时前
深入解构:MSVC 调试机制与 Visual Studio 调试器原理
c++·ide·windows·学习·visual studio·调试·现代c++
武藤一雄3 小时前
[奇淫巧技] WPF篇 (长期更新)
windows·microsoft·c#·.net·wpf
qq_428639614 小时前
虚幻基础:mod制作流程
游戏引擎·虚幻
BoBoZz194 小时前
IterativeClosestPoints icp配准矩阵
python·vtk·图形渲染·图形处理
月明长歌5 小时前
【码道初阶】【Leetcode94&144&145】二叉树的前中后序遍历(非递归版):显式调用栈的优雅实现
java·数据结构·windows·算法·leetcode·二叉树