教程 42 - 可写纹理

2025博客之星年度评选已开启 10w+人浏览 1.5k人参与

上一篇:增强纹理映射(采样器) | 下一篇:渲染目标和可配置渲染通道 | 返回目录


📚 快速导航


目录

📖 简介

到目前为止,我们使用的纹理都是只读的:从文件加载,然后在着色器中采样。但在现代图形编程中,我们经常需要动态生成修改纹理:

  • 渲染场景到纹理 (Render to Texture)
  • 阴影贴图 (Shadow Maps)
  • 后处理效果 (Post-processing)
  • 动态环境贴图 (Dynamic Cubemaps)
  • 程序化纹理 (Procedural Textures)

本教程将实现可写纹理 (Writeable Textures),支持 GPU 渲染和 CPU 写入。

Use Cases 使用场景
Creation Methods 创建方式
Texture Types 纹理类型
Shadow Maps
阴影贴图
Post-Processing
后处理
Mirrors/Portals
镜子/传送门
Dynamic Textures
动态纹理
Load from File
文件加载
PNG, JPG, etc.
Procedural
程序化
噪声,渐变等
Render Target
渲染目标
GPU 渲染
Read-Only Texture
只读纹理
从文件加载
Writeable Texture
可写纹理
可被 GPU/CPU 写入

核心概念:

纹理读写流程:
┌─────────────────────────────────┐
│ 只读纹理 (传统)                 │
│                                 │
│ File → Load → GPU Memory        │
│              (Read-Only)        │
│                ↓                │
│         Fragment Shader         │
│         texture(sampler, uv)    │
│                ↓                │
│         Screen Output           │
└─────────────────────────────────┘

┌─────────────────────────────────┐
│ 可写纹理 (新增)                 │
│                                 │
│ GPU Render → Texture            │
│     (Framebuffer)  ↓            │
│              GPU Memory         │
│            (Read-Write)         │
│                ↓                │
│         Fragment Shader         │
│         texture(sampler, uv)    │
│                ↓                │
│         Screen Output           │
│                                 │
│ CPU Write → Texture             │
│   (Direct)      ↓               │
│              GPU Memory         │
└─────────────────────────────────┘

关键差异:
• 只读: GPU 只能读取
• 可写: GPU 可以渲染到纹理,CPU 可以写入

🎯 学习目标

目标描述
理解可写纹理掌握可写纹理与只读纹理的区别
掌握纹理用途标志理解 Vulkan 的纹理用途 (Usage Flags)
实现渲染到纹理创建 Framebuffer 并渲染到纹理
程序化纹理从 CPU 动态生成纹理
应用实际场景实现阴影贴图、镜子等效果

📝 什么是可写纹理

只读纹理vs可写纹理

只读纹理 (Read-Only Texture):

// 传统纹理创建 (只读)
texture* diffuse_tex = texture_system_acquire("brick_diffuse.png", true);

// Vulkan 创建信息
VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT |           // 仅用于采样
                   VK_IMAGE_USAGE_TRANSFER_DST_BIT;       // 可从 CPU 传输

// 特点:
// ✓ 内存占用小
// ✓ 性能最优
// ✗ 不能作为渲染目标
// ✗ 不能动态修改

可写纹理 (Writeable Texture):

// 可写纹理创建
texture_create_info create_info = {0};
create_info.width = 1024;
create_info.height = 1024;
create_info.format = TEXTURE_FORMAT_RGBA8;
create_info.usage = TEXTURE_USAGE_SAMPLED |               // 可采样
                    TEXTURE_USAGE_COLOR_ATTACHMENT |      // 可作为颜色附件
                    TEXTURE_USAGE_TRANSFER_DST;           // 可从 CPU 传输

texture* render_tex = texture_system_create(&create_info);

// Vulkan 创建信息
VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT |           // 用于采样
                   VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |  // 作为颜色附件
                   VK_IMAGE_USAGE_TRANSFER_DST_BIT;       // 从 CPU 传输

// 特点:
// ✓ 可作为渲染目标
// ✓ 可动态修改
// ✓ 支持多种用途
// ✗ 内存占用稍大
// ✗ 性能稍低 (通常可忽略)

使用场景

可写纹理的典型应用:

1. 阴影贴图 (Shadow Mapping)
┌──────────────────────────────────┐
│ Pass 1: 从光源视角渲染           │
│ ┌────────────────┐               │
│ │ Shadow Map     │ ← 可写纹理    │
│ │ (Depth)        │               │
│ └────────────────┘               │
└──────────────┬───────────────────┘
               │
               ▼
┌──────────────────────────────────┐
│ Pass 2: 从相机视角渲染           │
│ • 采样 Shadow Map               │
│ • 计算阴影                       │
└──────────────────────────────────┘

2. 后处理 (Post-Processing)
┌──────────────────────────────────┐
│ Pass 1: 渲染场景到纹理           │
│ ┌────────────────┐               │
│ │ Scene Texture  │ ← 可写纹理    │
│ │ (Color)        │               │
│ └────────────────┘               │
└──────────────┬───────────────────┘
               │
               ▼
┌──────────────────────────────────┐
│ Pass 2: 全屏 Quad + 效果         │
│ • 采样 Scene Texture            │
│ • 应用模糊/调色/HDR 等          │
└──────────────────────────────────┘

3. 镜子/传送门 (Mirrors/Portals)
┌──────────────────────────────────┐
│ Pass 1: 从镜子视角渲染           │
│ ┌────────────────┐               │
│ │ Mirror Texture │ ← 可写纹理    │
│ │ (Color)        │               │
│ └────────────────┘               │
└──────────────┬───────────────────┘
               │
               ▼
┌──────────────────────────────────┐
│ Pass 2: 渲染场景                 │
│ • 镜子表面采样 Mirror Texture   │
│ • 显示反射                       │
└──────────────────────────────────┘

4. 动态天空盒 (Dynamic Cubemaps)
┌──────────────────────────────────┐
│ 每帧/定期更新                    │
│ • 从物体中心渲染 6 个面          │
│ • 写入 Cubemap 纹理              │
│ • 用于环境反射                   │
└──────────────────────────────────┘

5. 程序化纹理 (Procedural Textures)
┌──────────────────────────────────┐
│ CPU 生成                         │
│ • Perlin 噪声                    │
│ • 渐变                           │
│ • 动画纹理                       │
│ • 写入纹理数据                   │
└──────────────────────────────────┘

渲染到纹理流程

完整的 RTT (Render to Texture) 流程:

Render to Texture 流程:
┌────────────────────────────────────┐
│ 1. 创建可写纹理                    │
│    texture_create_info info;       │
│    info.usage = COLOR_ATTACHMENT;  │
│    texture* rt = create(&info);    │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 2. 创建 Framebuffer                │
│    framebuffer_create_info fb_info;│
│    fb_info.attachments[0] = rt;    │
│    framebuffer* fb = create(&fb);  │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 3. 开始渲染通道                    │
│    begin_renderpass(fb);           │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 4. 绘制物体                        │
│    draw_mesh(cube);                │
│    draw_mesh(sphere);              │
│    ...                             │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 5. 结束渲染通道                    │
│    end_renderpass();               │
│    → 纹理现在包含渲染结果          │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 6. 使用渲染的纹理                  │
│    bind_texture(rt);               │
│    draw_fullscreen_quad();         │
│    → 显示到屏幕或进一步处理        │
└────────────────────────────────────┘

🏷️ 纹理用途标志

常见用途组合

定义纹理如何被使用:

// engine/src/renderer/renderer_types.inl

/**
 * @brief 纹理用途标志 (可组合)
 */
typedef enum texture_usage {
    TEXTURE_USAGE_UNKNOWN = 0x00,

    // 基本用途
    TEXTURE_USAGE_SAMPLED = 0x01,              // 在着色器中采样
    TEXTURE_USAGE_TRANSFER_SRC = 0x02,         // 可作为传输源
    TEXTURE_USAGE_TRANSFER_DST = 0x04,         // 可作为传输目标

    // 渲染附件
    TEXTURE_USAGE_COLOR_ATTACHMENT = 0x08,     // 颜色附件 (渲染目标)
    TEXTURE_USAGE_DEPTH_ATTACHMENT = 0x10,     // 深度附件
    TEXTURE_USAGE_STENCIL_ATTACHMENT = 0x20,   // 模板附件

    // 高级用途
    TEXTURE_USAGE_STORAGE = 0x40,              // 存储图像 (Compute Shader)
    TEXTURE_USAGE_TRANSIENT = 0x80             // 临时附件 (不保存到内存)
} texture_usage;

常见用途组合

典型的用途标志组合:

场景用途标志组合说明
普通贴图SAMPLED | TRANSFER_DST从文件加载,仅读取
渲染目标SAMPLED | COLOR_ATTACHMENT可渲染,可采样
阴影贴图SAMPLED | DEPTH_ATTACHMENT深度渲染,可采样
后处理缓冲SAMPLED | COLOR_ATTACHMENT | TRANSFER_SRC渲染+采样+读回
动态纹理SAMPLED | TRANSFER_DSTCPU 写入,GPU 采样
Compute 输出SAMPLED | STORAGECompute Shader 写入

代码示例:

// 1. 普通 Diffuse 贴图 (只读)
texture_create_info diffuse_info = {0};
diffuse_info.width = 1024;
diffuse_info.height = 1024;
diffuse_info.format = TEXTURE_FORMAT_RGBA8;
diffuse_info.usage = TEXTURE_USAGE_SAMPLED |           // 着色器采样
                     TEXTURE_USAGE_TRANSFER_DST;       // CPU 上传
texture* diffuse = texture_system_create(&diffuse_info);

// 2. 渲染目标 (可读可写)
texture_create_info rt_info = {0};
rt_info.width = 1920;
rt_info.height = 1080;
rt_info.format = TEXTURE_FORMAT_RGBA8;
rt_info.usage = TEXTURE_USAGE_SAMPLED |               // 后续采样
                TEXTURE_USAGE_COLOR_ATTACHMENT;       // 作为渲染目标
texture* render_target = texture_system_create(&rt_info);

// 3. 阴影贴图 (深度)
texture_create_info shadow_info = {0};
shadow_info.width = 2048;
shadow_info.height = 2048;
shadow_info.format = TEXTURE_FORMAT_DEPTH32F;         // 深度格式
shadow_info.usage = TEXTURE_USAGE_SAMPLED |           // 着色器采样
                    TEXTURE_USAGE_DEPTH_ATTACHMENT;   // 深度附件
texture* shadow_map = texture_system_create(&shadow_info);

// 4. 程序化纹理 (CPU 写入)
texture_create_info procedural_info = {0};
procedural_info.width = 512;
procedural_info.height = 512;
procedural_info.format = TEXTURE_FORMAT_RGBA8;
procedural_info.usage = TEXTURE_USAGE_SAMPLED |       // 采样
                        TEXTURE_USAGE_TRANSFER_DST;   // CPU 写入
texture* procedural = texture_system_create(&procedural_info);

// 写入数据
u8* pixel_data = generate_noise_texture(512, 512);
texture_write_data(procedural, 0, pixel_data, 512 * 512 * 4);

Vulkan映射

纹理用途到 Vulkan 标志的映射:

// engine/src/renderer/vulkan/vulkan_backend.c

/**
 * @brief 转换纹理用途到 Vulkan 标志
 */
VkImageUsageFlags convert_texture_usage(texture_usage usage) {
    VkImageUsageFlags vk_usage = 0;

    if (usage & TEXTURE_USAGE_SAMPLED) {
        vk_usage |= VK_IMAGE_USAGE_SAMPLED_BIT;
    }
    if (usage & TEXTURE_USAGE_TRANSFER_SRC) {
        vk_usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
    }
    if (usage & TEXTURE_USAGE_TRANSFER_DST) {
        vk_usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    }
    if (usage & TEXTURE_USAGE_COLOR_ATTACHMENT) {
        vk_usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    }
    if (usage & TEXTURE_USAGE_DEPTH_ATTACHMENT) {
        vk_usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
    }
    if (usage & TEXTURE_USAGE_STORAGE) {
        vk_usage |= VK_IMAGE_USAGE_STORAGE_BIT;
    }
    if (usage & TEXTURE_USAGE_TRANSIENT) {
        vk_usage |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT;
    }

    return vk_usage;
}

性能影响

不同用途的性能考虑:

内存占用:
┌────────────────────────────────────┐
│ 用途标志              │ 内存开销  │
├────────────────────────────────────┤
│ SAMPLED only          │ 1.0x (基准)│
│ + COLOR_ATTACHMENT    │ 1.0x      │
│ + DEPTH_ATTACHMENT    │ 1.0x      │
│ + STORAGE             │ 1.0-1.2x  │
│ + TRANSIENT           │ 0.0x (!)  │
└────────────────────────────────────┘

注意:
• TRANSIENT 纹理不分配实际内存
• 仅用于 Renderpass 内部,不保存结果
• 用于 MSAA 解析、临时深度缓冲等

访问速度:
┌────────────────────────────────────┐
│ 用途                  │ 访问速度  │
├────────────────────────────────────┤
│ SAMPLED (read-only)   │ 最快      │
│ COLOR_ATTACHMENT      │ 快        │
│ DEPTH_ATTACHMENT      │ 快        │
│ STORAGE (read-write)  │ 中等      │
│ 频繁 CPU-GPU 传输     │ 慢        │
└────────────────────────────────────┘

最佳实践:
✓ 只指定需要的用途标志
✓ 避免过度使用 STORAGE (仅 Compute Shader 需要)
✓ 使用 TRANSIENT 优化临时附件
✗ 不要所有纹理都设为可写

🛠️ 纹理创建模式

从文件加载

传统方式(只读纹理):

/**
 * @brief 从文件加载纹理 (只读)
 */
texture* texture_system_acquire(const char* name, b8 auto_release) {
    // 1. 检查是否已加载
    texture* existing = find_texture(name);
    if (existing) {
        existing->reference_count++;
        return existing;
    }

    // 2. 加载图像数据
    resource image_resource;
    if (!resource_system_load(name, RESOURCE_TYPE_IMAGE, &image_resource)) {
        KERROR("Failed to load texture: %s", name);
        return NULL;
    }

    image_resource_data* image_data = (image_resource_data*)image_resource.data;

    // 3. 创建纹理 (自动设置为只读)
    texture_create_info create_info = {0};
    create_info.width = image_data->width;
    create_info.height = image_data->height;
    create_info.channel_count = image_data->channel_count;
    create_info.format = TEXTURE_FORMAT_RGBA8;
    create_info.usage = TEXTURE_USAGE_SAMPLED |        // 可采样
                        TEXTURE_USAGE_TRANSFER_DST;    // 可上传
    create_info.generate_mipmaps = true;

    texture* tex = texture_system_create(&create_info);

    // 4. 上传像素数据
    texture_write_data(tex, 0, image_data->pixels,
                      image_data->width * image_data->height * 4);

    // 5. 生成 Mipmaps
    if (create_info.generate_mipmaps) {
        texture_generate_mipmaps(tex);
    }

    resource_system_unload(&image_resource);
    return tex;
}

程序化创建

从 CPU 生成纹理:

/**
 * @brief 创建程序化纹理
 */
texture* create_procedural_texture(u32 width, u32 height) {
    // 1. 创建纹理
    texture_create_info create_info = {0};
    create_info.width = width;
    create_info.height = height;
    create_info.format = TEXTURE_FORMAT_RGBA8;
    create_info.usage = TEXTURE_USAGE_SAMPLED |
                        TEXTURE_USAGE_TRANSFER_DST;
    create_info.generate_mipmaps = true;

    texture* tex = texture_system_create(&create_info);

    // 2. 生成像素数据 (例如:棋盘格)
    u8* pixels = kallocate(width * height * 4, MEMORY_TAG_TEXTURE);

    for (u32 y = 0; y < height; ++y) {
        for (u32 x = 0; x < width; ++x) {
            u32 index = (y * width + x) * 4;

            // 棋盘格: 每 64 像素一个格子
            b8 is_white = ((x / 64) + (y / 64)) % 2 == 0;

            pixels[index + 0] = is_white ? 255 : 64;   // R
            pixels[index + 1] = is_white ? 255 : 64;   // G
            pixels[index + 2] = is_white ? 255 : 64;   // B
            pixels[index + 3] = 255;                    // A
        }
    }

    // 3. 上传数据
    texture_write_data(tex, 0, pixels, width * height * 4);

    // 4. 生成 Mipmaps
    texture_generate_mipmaps(tex);

    kfree(pixels, width * height * 4, MEMORY_TAG_TEXTURE);
    return tex;
}

/**
 * @brief 创建 Perlin 噪声纹理
 */
texture* create_noise_texture(u32 width, u32 height, f32 scale) {
    texture_create_info create_info = {0};
    create_info.width = width;
    create_info.height = height;
    create_info.format = TEXTURE_FORMAT_R8;  // 单通道
    create_info.usage = TEXTURE_USAGE_SAMPLED | TEXTURE_USAGE_TRANSFER_DST;

    texture* tex = texture_system_create(&create_info);

    u8* pixels = kallocate(width * height, MEMORY_TAG_TEXTURE);

    for (u32 y = 0; y < height; ++y) {
        for (u32 x = 0; x < width; ++x) {
            // Perlin 噪声
            f32 fx = (f32)x / width * scale;
            f32 fy = (f32)y / height * scale;
            f32 noise = perlin_noise_2d(fx, fy);  // 返回 [-1, 1]

            // 映射到 [0, 255]
            u8 value = (u8)((noise + 1.0f) * 0.5f * 255.0f);
            pixels[y * width + x] = value;
        }
    }

    texture_write_data(tex, 0, pixels, width * height);

    kfree(pixels, width * height, MEMORY_TAG_TEXTURE);
    return tex;
}

渲染目标创建

创建用于渲染的纹理:

/**
 * @brief 创建渲染目标纹理
 */
texture* create_render_target(
    u32 width,
    u32 height,
    texture_format format,
    b8 is_depth
) {
    texture_create_info create_info = {0};
    create_info.width = width;
    create_info.height = height;
    create_info.format = format;

    if (is_depth) {
        // 深度/模板附件
        create_info.usage = TEXTURE_USAGE_SAMPLED |
                           TEXTURE_USAGE_DEPTH_ATTACHMENT;
    } else {
        // 颜色附件
        create_info.usage = TEXTURE_USAGE_SAMPLED |
                           TEXTURE_USAGE_COLOR_ATTACHMENT;
    }

    // 不需要 Mipmaps (渲染目标通常不需要)
    create_info.generate_mipmaps = false;

    texture* tex = texture_system_create(&create_info);

    KDEBUG("Created render target: %ux%u, format=%d, depth=%d",
           width, height, format, is_depth);

    return tex;
}

// 使用示例
texture* color_rt = create_render_target(1920, 1080, TEXTURE_FORMAT_RGBA8, false);
texture* depth_rt = create_render_target(1920, 1080, TEXTURE_FORMAT_DEPTH32F, true);

🔧 可写纹理实现

纹理配置扩展

扩展纹理配置结构:

// engine/src/systems/texture_system.h

/**
 * @brief 纹理创建配置
 */
typedef struct texture_create_info {
    const char* name;                // 纹理名称 (可选)
    u32 width;                       // 宽度
    u32 height;                      // 高度
    texture_format format;           // 格式 (RGBA8, Depth32F, etc.)
    texture_usage usage;             // 用途标志 (组合)
    b8 generate_mipmaps;             // 是否生成 Mipmaps
    u32 mip_levels;                  // Mipmap 级别数 (0 = 自动计算)
    u32 array_layers;                // 数组层数 (Cubemap = 6, 2D = 1)
    texture_type type;               // 类型 (2D, 3D, Cubemap)
} texture_create_info;

/**
 * @brief 纹理类型
 */
typedef enum texture_type {
    TEXTURE_TYPE_2D = 0,
    TEXTURE_TYPE_3D,
    TEXTURE_TYPE_CUBE
} texture_type;

创建可写纹理

通用纹理创建函数:

// engine/src/systems/texture_system.c

/**
 * @brief 创建纹理 (支持各种用途)
 */
texture* texture_system_create(const texture_create_info* info) {
    // 1. 分配纹理结构
    texture* tex = kallocate(sizeof(texture), MEMORY_TAG_TEXTURE);

    tex->width = info->width;
    tex->height = info->height;
    tex->format = info->format;
    tex->usage = info->usage;
    tex->type = info->type;

    // 2. 计算 Mipmap 级别
    if (info->generate_mipmaps && info->mip_levels == 0) {
        tex->mip_levels = (u32)floor(log2(KMAX(info->width, info->height))) + 1;
    } else {
        tex->mip_levels = info->mip_levels > 0 ? info->mip_levels : 1;
    }

    tex->array_layers = info->array_layers > 0 ? info->array_layers : 1;

    // 3. 调用渲染器后端创建
    if (!renderer_create_texture(tex)) {
        KERROR("Failed to create texture");
        kfree(tex, sizeof(texture), MEMORY_TAG_TEXTURE);
        return NULL;
    }

    // 4. 注册纹理
    if (info->name) {
        string_ncopy(tex->name, info->name, TEXTURE_NAME_MAX_LENGTH);
        register_texture(tex);
    }

    KDEBUG("Created texture: %s (%ux%u, mips=%u, usage=0x%X)",
           info->name ? info->name : "(unnamed)",
           tex->width, tex->height, tex->mip_levels, tex->usage);

    return tex;
}

写入纹理数据

从 CPU 写入像素数据:

/**
 * @brief 写入纹理数据
 * @param tex 目标纹理
 * @param mip_level Mipmap 级别 (0 = 基础级别)
 * @param data 像素数据
 * @param data_size 数据大小 (字节)
 */
void texture_write_data(
    texture* tex,
    u32 mip_level,
    const void* data,
    u64 data_size
) {
    // 验证参数
    if (!tex || !data) {
        KERROR("texture_write_data: Invalid parameters");
        return;
    }

    if (mip_level >= tex->mip_levels) {
        KERROR("texture_write_data: Invalid mip level %u (max %u)",
               mip_level, tex->mip_levels - 1);
        return;
    }

    // 检查用途标志
    if (!(tex->usage & TEXTURE_USAGE_TRANSFER_DST)) {
        KWARN("texture_write_data: Texture '%s' not created with TRANSFER_DST usage",
              tex->name);
    }

    // 调用渲染器后端
    renderer_texture_write_data(tex, mip_level, data, data_size);

    KDEBUG("Wrote %llu bytes to texture '%s' (mip level %u)",
           data_size, tex->name, mip_level);
}

🖥️ Vulkan实现细节

ImageView创建

为不同用途创建 ImageView:

// engine/src/renderer/vulkan/vulkan_backend.c

/**
 * @brief 创建 Vulkan Image View
 */
VkImageView create_image_view(
    vulkan_context* context,
    VkImage image,
    VkFormat format,
    VkImageAspectFlags aspect_flags,
    u32 mip_levels,
    VkImageViewType view_type
) {
    VkImageViewCreateInfo view_info = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
    view_info.image = image;
    view_info.viewType = view_type;  // 2D, 3D, Cube, etc.
    view_info.format = format;

    // 组件映射 (通常保持默认)
    view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
    view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
    view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
    view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;

    // 子资源范围
    view_info.subresourceRange.aspectMask = aspect_flags;
    view_info.subresourceRange.baseMipLevel = 0;
    view_info.subresourceRange.levelCount = mip_levels;
    view_info.subresourceRange.baseArrayLayer = 0;
    view_info.subresourceRange.layerCount = 1;

    VkImageView view;
    VK_CHECK(vkCreateImageView(
        context->device.logical_device,
        &view_info,
        context->allocator,
        &view
    ));

    return view;
}

/**
 * @brief 创建纹理 (Vulkan 后端)
 */
b8 vulkan_renderer_create_texture(texture* tex) {
    vulkan_context* context = &vulkan_state->context;

    // 1. 确定格式和 Aspect
    VkFormat vk_format;
    VkImageAspectFlags aspect_flags;
    convert_texture_format(tex->format, &vk_format, &aspect_flags);

    // 2. 确定用途
    VkImageUsageFlags usage = convert_texture_usage(tex->usage);

    // 3. 创建 Image
    VkImageCreateInfo image_info = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
    image_info.imageType = VK_IMAGE_TYPE_2D;
    image_info.extent.width = tex->width;
    image_info.extent.height = tex->height;
    image_info.extent.depth = 1;
    image_info.mipLevels = tex->mip_levels;
    image_info.arrayLayers = tex->array_layers;
    image_info.format = vk_format;
    image_info.tiling = VK_IMAGE_TILING_OPTIMAL;  // GPU 最优布局
    image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    image_info.usage = usage;
    image_info.samples = VK_SAMPLE_COUNT_1_BIT;
    image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

    VkImage image;
    VK_CHECK(vkCreateImage(
        context->device.logical_device,
        &image_info,
        context->allocator,
        &image
    ));

    // 4. 分配内存
    VkMemoryRequirements mem_reqs;
    vkGetImageMemoryRequirements(context->device.logical_device, image, &mem_reqs);

    VkMemoryAllocateInfo alloc_info = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
    alloc_info.allocationSize = mem_reqs.size;
    alloc_info.memoryTypeIndex = find_memory_type(
        context,
        mem_reqs.memoryTypeBits,
        VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT  // GPU 本地内存
    );

    VkDeviceMemory memory;
    VK_CHECK(vkAllocateMemory(
        context->device.logical_device,
        &alloc_info,
        context->allocator,
        &memory
    ));

    // 5. 绑定内存
    VK_CHECK(vkBindImageMemory(context->device.logical_device, image, memory, 0));

    // 6. 创建 Image View
    VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_2D;
    if (tex->type == TEXTURE_TYPE_CUBE) {
        view_type = VK_IMAGE_VIEW_TYPE_CUBE;
    }

    VkImageView view = create_image_view(
        context,
        image,
        vk_format,
        aspect_flags,
        tex->mip_levels,
        view_type
    );

    // 7. 存储 Vulkan 对象
    vulkan_texture* vk_tex = kallocate(sizeof(vulkan_texture), MEMORY_TAG_RENDERER);
    vk_tex->image = image;
    vk_tex->memory = memory;
    vk_tex->view = view;
    vk_tex->format = vk_format;

    tex->internal_data = vk_tex;

    return true;
}

内存分配

优化内存分配:

/**
 * @brief 查找合适的内存类型
 */
u32 find_memory_type(
    vulkan_context* context,
    u32 type_filter,
    VkMemoryPropertyFlags properties
) {
    VkPhysicalDeviceMemoryProperties mem_props;
    vkGetPhysicalDeviceMemoryProperties(context->device.physical_device, &mem_props);

    for (u32 i = 0; i < mem_props.memoryTypeCount; ++i) {
        if ((type_filter & (1 << i)) &&
            (mem_props.memoryTypes[i].propertyFlags & properties) == properties) {
            return i;
        }
    }

    KERROR("Failed to find suitable memory type!");
    return -1;
}

布局转换

纹理布局转换(用于不同用途):

/**
 * @brief 转换图像布局
 */
void transition_image_layout(
    vulkan_context* context,
    VkImage image,
    VkFormat format,
    VkImageLayout old_layout,
    VkImageLayout new_layout,
    u32 mip_levels
) {
    // 创建一次性命令缓冲
    VkCommandBuffer cmd = begin_single_time_commands(context);

    VkImageMemoryBarrier barrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
    barrier.oldLayout = old_layout;
    barrier.newLayout = new_layout;
    barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier.image = image;

    // 设置 Aspect Mask
    if (new_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) {
        barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
        if (has_stencil_component(format)) {
            barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT;
        }
    } else {
        barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    }

    barrier.subresourceRange.baseMipLevel = 0;
    barrier.subresourceRange.levelCount = mip_levels;
    barrier.subresourceRange.baseArrayLayer = 0;
    barrier.subresourceRange.layerCount = 1;

    // 设置访问掩码和管线阶段
    VkPipelineStageFlags src_stage;
    VkPipelineStageFlags dst_stage;

    if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED &&
        new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
        // 准备接收数据
        barrier.srcAccessMask = 0;
        barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
        dst_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;

    } else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
               new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
        // 传输完成,准备着色器读取
        barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
        src_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
        dst_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;

    } else if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED &&
               new_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) {
        // 准备作为颜色附件
        barrier.srcAccessMask = 0;
        barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
        src_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
        dst_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;

    } else {
        KERROR("Unsupported layout transition!");
        return;
    }

    vkCmdPipelineBarrier(
        cmd,
        src_stage, dst_stage,
        0,
        0, NULL,
        0, NULL,
        1, &barrier
    );

    end_single_time_commands(context, cmd);
}

🎬 渲染到纹理

Framebuffer设置

创建使用自定义纹理的 Framebuffer:

/**
 * @brief 创建渲染到纹理的 Framebuffer
 */
framebuffer* create_render_to_texture_framebuffer(
    texture* color_target,
    texture* depth_target
) {
    framebuffer_create_info fb_info = {0};
    fb_info.width = color_target->width;
    fb_info.height = color_target->height;
    fb_info.attachment_count = depth_target ? 2 : 1;

    // 颜色附件
    fb_info.attachments[0].texture = color_target;
    fb_info.attachments[0].type = RENDERPASS_ATTACHMENT_COLOR;
    fb_info.attachments[0].load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
    fb_info.attachments[0].store_op = RENDERPASS_ATTACHMENT_STORE_OP_STORE;

    // 深度附件 (可选)
    if (depth_target) {
        fb_info.attachments[1].texture = depth_target;
        fb_info.attachments[1].type = RENDERPASS_ATTACHMENT_DEPTH;
        fb_info.attachments[1].load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
        fb_info.attachments[1].store_op = RENDERPASS_ATTACHMENT_STORE_OP_DONT_CARE;
    }

    framebuffer* fb = framebuffer_create(&fb_info);
    return fb;
}

渲染流程

完整的渲染到纹理流程:

/**
 * @brief 渲染场景到纹理
 */
void render_scene_to_texture(
    texture* render_target,
    camera* cam,
    scene* scene_data
) {
    // 1. 创建 Framebuffer (如果还没有)
    static framebuffer* fb = NULL;
    if (!fb) {
        texture* depth = create_render_target(
            render_target->width,
            render_target->height,
            TEXTURE_FORMAT_DEPTH32F,
            true
        );
        fb = create_render_to_texture_framebuffer(render_target, depth);
    }

    // 2. 开始渲染通道
    renderpass_begin_info begin_info = {0};
    begin_info.framebuffer = fb;
    begin_info.clear_color = (vec4){0.1f, 0.1f, 0.1f, 1.0f};
    begin_info.clear_depth = 1.0f;

    renderer_begin_renderpass(&begin_info);

    // 3. 设置视口和裁剪
    renderer_set_viewport(0, 0, render_target->width, render_target->height);
    renderer_set_scissor(0, 0, render_target->width, render_target->height);

    // 4. 绑定着色器
    shader* scene_shader = shader_system_get("Builtin.MaterialShader");
    shader_bind(scene_shader);

    // 5. 设置相机
    mat4 view = camera_get_view(cam);
    mat4 projection = camera_get_projection(cam);
    shader_set_uniform(scene_shader, "view", &view);
    shader_set_uniform(scene_shader, "projection", &projection);

    // 6. 绘制场景
    for (u32 i = 0; i < scene_data->mesh_count; ++i) {
        mesh* m = scene_data->meshes[i];

        // 设置模型矩阵
        mat4 model = mesh_get_world_transform(m);
        shader_set_uniform(scene_shader, "model", &model);

        // 绑定材质
        material_system_apply(m->material, scene_shader);

        // 绘制
        geometry_draw(m->geometry);
    }

    // 7. 结束渲染通道
    renderer_end_renderpass();

    // 现在 render_target 包含渲染的场景!
}

读回纹理

从 GPU 读取纹理数据到 CPU:

/**
 * @brief 从纹理读取数据到 CPU
 * @param tex 源纹理
 * @param mip_level Mipmap 级别
 * @param out_data 输出缓冲区 (必须预先分配)
 * @param data_size 缓冲区大小
 */
b8 texture_read_data(
    texture* tex,
    u32 mip_level,
    void* out_data,
    u64 data_size
) {
    // 检查用途标志
    if (!(tex->usage & TEXTURE_USAGE_TRANSFER_SRC)) {
        KERROR("texture_read_data: Texture not created with TRANSFER_SRC usage");
        return false;
    }

    // Vulkan 实现
    vulkan_context* context = &vulkan_state->context;
    vulkan_texture* vk_tex = (vulkan_texture*)tex->internal_data;

    // 1. 创建临时缓冲区 (CPU 可访问)
    VkBuffer staging_buffer;
    VkDeviceMemory staging_memory;
    create_buffer(
        context,
        data_size,
        VK_BUFFER_USAGE_TRANSFER_DST_BIT,
        VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
        &staging_buffer,
        &staging_memory
    );

    // 2. 转换图像布局
    transition_image_layout(
        context,
        vk_tex->image,
        vk_tex->format,
        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
        tex->mip_levels
    );

    // 3. 复制图像到缓冲区
    VkCommandBuffer cmd = begin_single_time_commands(context);

    VkBufferImageCopy region = {0};
    region.bufferOffset = 0;
    region.bufferRowLength = 0;
    region.bufferImageHeight = 0;
    region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    region.imageSubresource.mipLevel = mip_level;
    region.imageSubresource.baseArrayLayer = 0;
    region.imageSubresource.layerCount = 1;
    region.imageOffset = (VkOffset3D){0, 0, 0};
    region.imageExtent = (VkExtent3D){
        tex->width >> mip_level,
        tex->height >> mip_level,
        1
    };

    vkCmdCopyImageToBuffer(
        cmd,
        vk_tex->image,
        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
        staging_buffer,
        1,
        &region
    );

    end_single_time_commands(context, cmd);

    // 4. 恢复图像布局
    transition_image_layout(
        context,
        vk_tex->image,
        vk_tex->format,
        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
        VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
        tex->mip_levels
    );

    // 5. 映射缓冲区并复制到 CPU
    void* mapped_data;
    vkMapMemory(context->device.logical_device, staging_memory, 0, data_size, 0, &mapped_data);
    kcopy_memory(out_data, mapped_data, data_size);
    vkUnmapMemory(context->device.logical_device, staging_memory);

    // 6. 清理
    vkDestroyBuffer(context->device.logical_device, staging_buffer, context->allocator);
    vkFreeMemory(context->device.logical_device, staging_memory, context->allocator);

    KDEBUG("Read %llu bytes from texture (mip level %u)", data_size, mip_level);
    return true;
}

🎮 实际应用场景

1. 镜子/水面反射

// 渲染镜子反射
void render_mirror_reflection(mirror* m, scene* scene_data, camera* main_cam) {
    // 1. 创建镜子纹理 (如果还没有)
    if (!m->reflection_texture) {
        m->reflection_texture = create_render_target(512, 512, TEXTURE_FORMAT_RGBA8, false);
    }

    // 2. 计算镜子相机 (反射主相机)
    camera mirror_cam;
    calculate_mirror_camera(&mirror_cam, main_cam, m->plane);

    // 3. 渲染到镜子纹理
    render_scene_to_texture(m->reflection_texture, &mirror_cam, scene_data);

    // 4. 渲染镜子表面 (使用反射纹理)
    shader* mirror_shader = shader_system_get("Mirror");
    shader_bind(mirror_shader);
    shader_bind_texture(mirror_shader, 0, m->reflection_texture);
    geometry_draw(m->quad_geometry);
}

2. 阴影贴图

// 渲染阴影贴图
void render_shadow_map(directional_light* light, scene* scene_data) {
    // 1. 创建阴影贴图 (高分辨率深度纹理)
    if (!light->shadow_map) {
        light->shadow_map = create_render_target(2048, 2048, TEXTURE_FORMAT_DEPTH32F, true);
    }

    // 2. 设置光源相机 (正交投影)
    camera light_cam;
    light_cam.position = vec3_mul_scalar(light->direction, -100.0f);
    light_cam.projection = mat4_orthographic(-50, 50, -50, 50, 0.1f, 200.0f);
    light_cam.view = mat4_look_at(light_cam.position, (vec3){0,0,0}, (vec3){0,1,0});

    // 3. 渲染深度
    render_scene_to_texture(light->shadow_map, &light_cam, scene_data);

    // 4. 主渲染中使用阴影贴图
    shader* main_shader = shader_system_get("Builtin.MaterialShader");
    shader_bind_texture(main_shader, 3, light->shadow_map);  // Binding 3 = shadow map
    mat4 light_space_matrix = mat4_mul_mat4(light_cam.projection, light_cam.view);
    shader_set_uniform(main_shader, "light_space_matrix", &light_space_matrix);
}

3. 后处理效果

// 后处理管线
void render_with_post_processing(scene* scene_data, camera* cam) {
    // 1. 创建场景渲染目标
    static texture* scene_texture = NULL;
    if (!scene_texture) {
        scene_texture = create_render_target(1920, 1080, TEXTURE_FORMAT_RGBA16F, false);
    }

    // 2. 渲染场景到纹理
    render_scene_to_texture(scene_texture, cam, scene_data);

    // 3. 应用后处理效果
    post_process_bloom(scene_texture);
    post_process_tone_mapping(scene_texture);

    // 4. 最终输出到屏幕
    renderer_begin_default_renderpass();
    render_fullscreen_quad(scene_texture);
    renderer_end_renderpass();
}

void post_process_bloom(texture* scene_tex) {
    // 提取亮区
    static texture* bright_tex = NULL;
    if (!bright_tex) {
        bright_tex = create_render_target(960, 540, TEXTURE_FORMAT_RGBA16F, false);
    }

    shader* bright_shader = shader_system_get("BrightPass");
    render_fullscreen_with_shader(scene_tex, bright_tex, bright_shader);

    // 模糊
    static texture* blur_temp = NULL;
    if (!blur_temp) {
        blur_temp = create_render_target(960, 540, TEXTURE_FORMAT_RGBA16F, false);
    }

    shader* blur_shader = shader_system_get("GaussianBlur");
    for (u32 i = 0; i < 5; ++i) {
        // 水平模糊
        blur_shader_set_direction(blur_shader, (vec2){1, 0});
        render_fullscreen_with_shader(bright_tex, blur_temp, blur_shader);

        // 垂直模糊
        blur_shader_set_direction(blur_shader, (vec2){0, 1});
        render_fullscreen_with_shader(blur_temp, bright_tex, blur_shader);
    }

    // 合并
    shader* combine_shader = shader_system_get("BloomCombine");
    shader_bind_texture(combine_shader, 0, scene_tex);   // 原始场景
    shader_bind_texture(combine_shader, 1, bright_tex);  // 模糊的亮区
    render_fullscreen_quad_with_shader(combine_shader);
}

❓ 常见问题

1. 何时使用可写纹理 vs 只读纹理?

决策指南:

使用只读纹理 (默认):
✓ 从文件加载的贴图 (Diffuse, Normal, etc.)
✓ 静态环境贴图
✓ UI 图标和字体
✓ 不需要动态更新的纹理

使用可写纹理:
✓ 渲染目标 (后处理、阴影贴图)
✓ 动态环境贴图 (实时反射)
✓ 程序化纹理 (噪声、粒子)
✓ 需要 CPU 更新的纹理
✓ 镜子/传送门效果

内存和性能:
• 只读纹理: 更小的内存占用,稍快
• 可写纹理: 稍大的内存占用,性能通常无差异
• 现代 GPU 的差异通常可忽略
2. 渲染到纹理会影响性能吗?

性能影响分析:

渲染到纹理的成本:
┌────────────────────────────────────┐
│ 操作                  │ 相对成本  │
├────────────────────────────────────┤
│ 创建纹理/Framebuffer  │ 一次性    │
│ 切换 Framebuffer      │ ~0.1 ms   │
│ 清除纹理              │ ~0.05 ms  │
│ 渲染场景              │ 取决于场景 │
│ 布局转换              │ ~0.01 ms  │
└────────────────────────────────────┘

优化建议:
✓ 重用 Framebuffer (不要每帧创建)
✓ 降低渲染目标分辨率 (例如阴影贴图 2048x2048)
✓ 使用 LOD (远处物体用低分辨率渲染)
✓ 批量渲染 (减少 Framebuffer 切换)
✗ 避免频繁读回 CPU (很慢!)

实际案例:
• 阴影贴图 (2048x2048): ~1-2 ms
• 后处理 (1920x1080): ~0.5-1 ms
• 镜子反射 (512x512): ~0.3-0.5 ms
• 总开销: 通常 <10% 帧时间
3. 如何优化多个渲染目标?

优化策略:

// 1. 使用纹理数组 (减少切换)
texture* shadow_maps[4];  // 4 个光源
for (u32 i = 0; i < 4; ++i) {
    shadow_maps[i] = create_render_target(1024, 1024, TEXTURE_FORMAT_DEPTH32F, true);
}

// 2. 重用深度缓冲
texture* shared_depth = create_render_target(1920, 1080, TEXTURE_FORMAT_DEPTH32F, true);
// 多个颜色附件可以共享同一个深度缓冲

// 3. 使用 MRT (Multiple Render Targets)
framebuffer_create_info fb_info = {0};
fb_info.attachment_count = 3;
fb_info.attachments[0] = color_rt;    // Color
fb_info.attachments[1] = normal_rt;   // Normals
fb_info.attachments[2] = position_rt; // Positions
// 一次渲染输出到多个纹理!

// 4. 延迟更新 (不是每帧)
if (frame_count % 10 == 0) {
    // 每 10 帧更新一次环境贴图
    render_environment_cubemap();
}
4. 纹理格式如何选择?

格式选择指南:

用途推荐格式原因
普通颜色RGBA8标准,兼容性好
HDR 颜色RGBA16F支持高动态范围
深度Depth32F高精度深度
法线贴图RGBA8足够精度
单通道R8 或 R16F节省内存
阴影贴图Depth32F 或 Depth24Stencil8取决于需求

内存占用:

1920x1080 纹理:
┌────────────────────────────────────┐
│ 格式        │ 每像素 │ 总内存     │
├────────────────────────────────────┤
│ RGBA8       │ 4 B    │ 7.91 MB    │
│ RGBA16F     │ 8 B    │ 15.82 MB   │
│ Depth32F    │ 4 B    │ 7.91 MB    │
│ R8          │ 1 B    │ 1.98 MB    │
└────────────────────────────────────┘

建议:
• 仅在需要时使用 HDR (16F/32F)
• 单通道纹理用 R8 而不是 RGBA8
• 移动设备考虑压缩格式 (ETC2, ASTC)
5. 如何实现屏幕截图功能?

屏幕截图实现:

/**
 * @brief 截取屏幕
 */
void screenshot_capture(const char* filename) {
    // 1. 获取当前 Swapchain 图像
    texture* screen_tex = renderer_get_current_backbuffer();

    // 2. 确保有 TRANSFER_SRC 用途
    if (!(screen_tex->usage & TEXTURE_USAGE_TRANSFER_SRC)) {
        KERROR("Backbuffer doesn't support TRANSFER_SRC");
        return;
    }

    // 3. 读取像素数据
    u32 width = screen_tex->width;
    u32 height = screen_tex->height;
    u64 data_size = width * height * 4;  // RGBA8

    u8* pixels = kallocate(data_size, MEMORY_TAG_TEMP);
    texture_read_data(screen_tex, 0, pixels, data_size);

    // 4. 保存为 PNG
    stbi_write_png(filename, width, height, 4, pixels, width * 4);

    kfree(pixels, data_size, MEMORY_TAG_TEMP);

    KINFO("Screenshot saved: %s", filename);
}

// 使用
if (input_is_key_down(KEY_F12)) {
    screenshot_capture("screenshot.png");
}

📝 练习

练习 1: 实现简单的后处理效果

任务: 实现灰度和反色后处理效果。

// Grayscale Shader (GLSL)
// grayscale.frag

#version 450

in vec2 in_texcoord;
uniform sampler2D u_scene_texture;
out vec4 out_color;

void main() {
    vec4 color = texture(u_scene_texture, in_texcoord);

    // 灰度转换 (Luminance)
    float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));

    out_color = vec4(vec3(gray), color.a);
}

// Invert Shader (GLSL)
// invert.frag

#version 450

in vec2 in_texcoord;
uniform sampler2D u_scene_texture;
out vec4 out_color;

void main() {
    vec4 color = texture(u_scene_texture, in_texcoord);

    // 反色
    out_color = vec4(1.0 - color.rgb, color.a);
}

// C 代码
void render_with_post_process(post_process_type type) {
    // 1. 渲染场景到纹理
    static texture* scene_rt = NULL;
    if (!scene_rt) {
        scene_rt = create_render_target(1920, 1080, TEXTURE_FORMAT_RGBA8, false);
    }

    render_scene_to_texture(scene_rt, main_camera, current_scene);

    // 2. 应用后处理
    shader* post_shader = NULL;
    switch (type) {
        case POST_PROCESS_GRAYSCALE:
            post_shader = shader_system_get("Grayscale");
            break;
        case POST_PROCESS_INVERT:
            post_shader = shader_system_get("Invert");
            break;
        default:
            post_shader = shader_system_get("Passthrough");
    }

    // 3. 渲染到屏幕
    renderer_begin_default_renderpass();
    shader_bind(post_shader);
    shader_bind_texture(post_shader, 0, scene_rt);
    render_fullscreen_quad();
    renderer_end_renderpass();
}
练习 2: 实现动态 Cubemap

任务: 创建动态环境贴图用于实时反射。

/**
 * @brief 动态 Cubemap
 */
typedef struct dynamic_cubemap {
    texture* cubemap;           // 6 个面的 Cubemap
    camera cameras[6];          // 6 个方向的相机
    vec3 position;              // Cubemap 中心位置
    b8 needs_update;            // 是否需要更新
} dynamic_cubemap;

/**
 * @brief 创建动态 Cubemap
 */
dynamic_cubemap* dynamic_cubemap_create(vec3 position, u32 resolution) {
    dynamic_cubemap* dcm = kallocate(sizeof(dynamic_cubemap), MEMORY_TAG_RENDERER);

    // 1. 创建 Cubemap 纹理
    texture_create_info create_info = {0};
    create_info.width = resolution;
    create_info.height = resolution;
    create_info.format = TEXTURE_FORMAT_RGBA8;
    create_info.usage = TEXTURE_USAGE_SAMPLED | TEXTURE_USAGE_COLOR_ATTACHMENT;
    create_info.type = TEXTURE_TYPE_CUBE;
    create_info.array_layers = 6;  // 6 faces

    dcm->cubemap = texture_system_create(&create_info);
    dcm->position = position;

    // 2. 设置 6 个相机
    // +X, -X, +Y, -Y, +Z, -Z
    vec3 directions[6] = {
        {1, 0, 0}, {-1, 0, 0},  // ±X
        {0, 1, 0}, {0, -1, 0},  // ±Y
        {0, 0, 1}, {0, 0, -1}   // ±Z
    };
    vec3 ups[6] = {
        {0, -1, 0}, {0, -1, 0},  // +X, -X
        {0, 0, 1}, {0, 0, -1},   // +Y, -Y
        {0, -1, 0}, {0, -1, 0}   // +Z, -Z
    };

    for (u32 i = 0; i < 6; ++i) {
        dcm->cameras[i].position = position;
        dcm->cameras[i].forward = directions[i];
        dcm->cameras[i].up = ups[i];
        dcm->cameras[i].projection = mat4_perspective(deg_to_rad(90.0f), 1.0f, 0.1f, 100.0f);
    }

    dcm->needs_update = true;

    return dcm;
}

/**
 * @brief 更新动态 Cubemap
 */
void dynamic_cubemap_update(dynamic_cubemap* dcm, scene* scene_data) {
    if (!dcm->needs_update) return;

    // 渲染 6 个面
    for (u32 face = 0; face < 6; ++face) {
        // 创建 Framebuffer (每个面)
        framebuffer* fb = create_cubemap_face_framebuffer(dcm->cubemap, face);

        // 渲染到该面
        render_scene_to_texture_with_framebuffer(
            fb,
            &dcm->cameras[face],
            scene_data
        );

        framebuffer_destroy(fb);
    }

    dcm->needs_update = false;
}

// 使用
dynamic_cubemap* env_cubemap = dynamic_cubemap_create((vec3){0, 1, 0}, 512);

// 每帧或定期更新
if (frame_count % 30 == 0) {  // 每 0.5 秒更新
    dynamic_cubemap_update(env_cubemap, current_scene);
}

// 在材质中使用
shader_bind_cubemap(material_shader, 4, env_cubemap->cubemap);
练习 3: 实现基础阴影贴图

任务: 实现方向光的阴影贴图。

/**
 * @brief 阴影贴图系统
 */
typedef struct shadow_map {
    texture* depth_texture;      // 深度纹理
    framebuffer* framebuffer;    // Framebuffer
    mat4 light_space_matrix;     // 光源空间矩阵
    u32 resolution;              // 分辨率
} shadow_map;

/**
 * @brief 创建阴影贴图
 */
shadow_map* shadow_map_create(u32 resolution) {
    shadow_map* sm = kallocate(sizeof(shadow_map), MEMORY_TAG_RENDERER);
    sm->resolution = resolution;

    // 创建深度纹理
    sm->depth_texture = create_render_target(
        resolution,
        resolution,
        TEXTURE_FORMAT_DEPTH32F,
        true
    );

    // 创建 Framebuffer
    framebuffer_create_info fb_info = {0};
    fb_info.width = resolution;
    fb_info.height = resolution;
    fb_info.attachment_count = 1;
    fb_info.attachments[0].texture = sm->depth_texture;
    fb_info.attachments[0].type = RENDERPASS_ATTACHMENT_DEPTH;

    sm->framebuffer = framebuffer_create(&fb_info);

    return sm;
}

/**
 * @brief 渲染阴影贴图
 */
void shadow_map_render(
    shadow_map* sm,
    directional_light* light,
    scene* scene_data
) {
    // 1. 计算光源空间矩阵
    vec3 light_pos = vec3_mul_scalar(light->direction, -50.0f);
    mat4 light_view = mat4_look_at(light_pos, (vec3){0,0,0}, (vec3){0,1,0});
    mat4 light_proj = mat4_orthographic(-20, 20, -20, 20, 0.1f, 100.0f);
    sm->light_space_matrix = mat4_mul_mat4(light_proj, light_view);

    // 2. 开始渲染通道
    renderer_begin_renderpass_with_framebuffer(sm->framebuffer);

    // 3. 设置视口
    renderer_set_viewport(0, 0, sm->resolution, sm->resolution);

    // 4. 绑定深度着色器
    shader* depth_shader = shader_system_get("DepthOnly");
    shader_bind(depth_shader);
    shader_set_uniform(depth_shader, "light_space_matrix", &sm->light_space_matrix);

    // 5. 渲染场景 (仅深度)
    for (u32 i = 0; i < scene_data->mesh_count; ++i) {
        mesh* m = scene_data->meshes[i];
        mat4 model = mesh_get_world_transform(m);
        shader_set_uniform(depth_shader, "model", &model);
        geometry_draw(m->geometry);
    }

    // 6. 结束渲染通道
    renderer_end_renderpass();
}

// Depth-Only Shader (GLSL)
// depth_only.vert

#version 450

layout(location = 0) in vec3 in_position;

uniform mat4 light_space_matrix;
uniform mat4 model;

void main() {
    gl_Position = light_space_matrix * model * vec4(in_position, 1.0);
}

// depth_only.frag (空,只写入深度)

#version 450

void main() {
    // 深度自动写入
}

// 主渲染中使用阴影
shader* main_shader = shader_system_get("Builtin.MaterialShader");
shader_bind_texture(main_shader, 3, shadow_map->depth_texture);
shader_set_uniform(main_shader, "light_space_matrix", &shadow_map->light_space_matrix);

// Fragment Shader 中采样阴影
float shadow = texture(u_shadow_map, shadow_coord.xy).r;
float current_depth = shadow_coord.z;
float in_shadow = current_depth > shadow + 0.005 ? 1.0 : 0.0;

恭喜!你已经掌握了可写纹理系统! 🎉

Tutorial written by 上手实验室

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值