教程 43 - 渲染目标和可配置渲染通道

上一篇:可写纹理 | 下一篇:待续 | 返回目录


📚 快速导航


目录


📖 简介

在教程 42 中,我们实现了可写纹理,可以作为渲染目标使用。但我们的渲染通道(Renderpass)系统还是硬编码的:只支持渲染到 Swapchain。

本教程将重构渲染系统,实现:

  • 渲染目标系统 (Render Targets):统一管理颜色、深度、模板附件
  • 可配置渲染通道 (Configurable Renderpasses):动态创建不同配置的渲染通道
  • 多通道渲染管线 (Multi-pass Rendering):支持复杂的渲染流程

New System 新系统 Old System 旧系统 Render Target 1
阴影贴图 Render Target 2
场景颜色 Render Target 3
后处理 Configurable
Renderpasses
可配置渲染通道 Swapchain Only
只能渲染到屏幕 Fixed Renderpass
硬编码渲染通道 Final Output
最终输出

为什么需要可配置渲染通道?

复制代码
现代渲染管线示例:
┌────────────────────────────────────┐
│ Pass 1: Shadow Map                 │
│ • 渲染到深度纹理                    │
│ • 只需深度附件                      │
│ • 分辨率: 2048x2048                │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ Pass 2: G-Buffer (延迟渲染)        │
│ • 渲染到多个颜色附件                │
│ • Albedo + Normal + Position       │
│ • 分辨率: 1920x1080                │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ Pass 3: Lighting                   │
│ • 读取 G-Buffer                    │
│ • 渲染到单个颜色附件                │
│ • 分辨率: 1920x1080                │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ Pass 4: Post-Processing            │
│ • 读取 Lighting 结果                │
│ • 应用 Bloom, Tone Mapping         │
│ • 渲染到 Swapchain                 │
└────────────────────────────────────┘

每个 Pass 需要:
• 不同的附件配置
• 不同的分辨率
• 不同的加载/存储操作
• 不同的清除值

→ 需要可配置的渲染通道系统!

🎯 学习目标

目标 描述
理解渲染目标 掌握渲染目标的概念和用途
设计可配置渲染通道 实现灵活的渲染通道配置系统
管理多个渲染通道 构建多通道渲染管线
优化渲染流程 正确设置加载/存储操作
应用实际场景 实现延迟渲染、后处理等

🖼️ 渲染目标概念

什么是渲染目标

渲染目标 (Render Target) 是渲染操作的输出目的地:

c 复制代码
// 渲染目标 = 一组附件
typedef struct render_target {
    char name[64];                    // 名称
    u32 attachment_count;             // 附件数量
    render_target_attachment* attachments;  // 附件数组
} render_target;

/**
 * @brief 渲染目标附件
 */
typedef struct render_target_attachment {
    render_target_attachment_type type;  // 类型 (颜色/深度/模板)
    texture* texture;                    // 纹理
    render_target_attachment_load_op load_op;    // 加载操作
    render_target_attachment_store_op store_op;  // 存储操作
    vec4 clear_color;                    // 清除颜色
    f32 clear_depth;                     // 清除深度
    u32 clear_stencil;                   // 清除模板
} render_target_attachment;

可视化:

复制代码
Render Target 结构:
┌──────────────────────────────────────┐
│ Render Target "SceneRT"              │
├──────────────────────────────────────┤
│ Attachment 0: Color                  │
│ • Texture: 1920x1080 RGBA8           │
│ • Load Op: Clear                     │
│ • Store Op: Store                    │
│ • Clear Color: (0.1, 0.1, 0.1, 1.0)  │
├──────────────────────────────────────┤
│ Attachment 1: Depth                  │
│ • Texture: 1920x1080 Depth32F        │
│ • Load Op: Clear                     │
│ • Store Op: DontCare                 │
│ • Clear Depth: 1.0                   │
└──────────────────────────────────────┘

使用:
renderer_begin_renderpass(sceneRT);
// ... 绘制场景 ...
renderer_end_renderpass();
// → Color texture 包含渲染结果

渲染目标vs纹理

关键区别:

对比项 纹理 (Texture) 渲染目标 (Render Target)
定义 图像数据 渲染输出配置
包含 像素数据 纹理 + 操作配置
用途 采样、存储 渲染输出
关系 资源 资源 + 配置
c 复制代码
// 纹理:只是数据
texture* color_tex = create_render_target_texture(1920, 1080, TEXTURE_FORMAT_RGBA8);

// 渲染目标:纹理 + 如何使用
render_target* scene_rt = render_target_create();
render_target_add_attachment(scene_rt, color_tex, CLEAR, STORE);

// 使用渲染目标
renderer_begin_renderpass(scene_rt);
// 自动:
// 1. 清除 color_tex (根据 CLEAR 操作)
// 2. 设置为渲染输出
// 3. 结束时存储结果 (根据 STORE 操作)

渲染目标类型

常见的渲染目标类型:

复制代码
1. Swapchain 渲染目标 (最终输出)
┌────────────────────────────────────┐
│ Swapchain RT                       │
│ • Color: Swapchain Image           │
│ • Depth: Depth Buffer              │
│ • 输出到屏幕                        │
└────────────────────────────────────┘

2. 离屏渲染目标 (中间结果)
┌────────────────────────────────────┐
│ Offscreen RT                       │
│ • Color: Custom Texture            │
│ • Depth: Custom Depth              │
│ • 后续用于采样                      │
└────────────────────────────────────┘

3. G-Buffer 渲染目标 (多个颜色附件)
┌────────────────────────────────────┐
│ G-Buffer RT                        │
│ • Color 0: Albedo                  │
│ • Color 1: Normal                  │
│ • Color 2: Position                │
│ • Depth: Depth Buffer              │
└────────────────────────────────────┘

4. 阴影贴图渲染目标 (仅深度)
┌────────────────────────────────────┐
│ Shadow Map RT                      │
│ • Depth: Shadow Depth              │
│ • 无颜色附件                        │
└────────────────────────────────────┘

5. MSAA 渲染目标 (多重采样)
┌────────────────────────────────────┐
│ MSAA RT                            │
│ • Color: MSAA Texture (4x)         │
│ • Resolve: Regular Texture         │
│ • Depth: MSAA Depth                │
└────────────────────────────────────┘

🏗️ 渲染通道架构

固定渲染通道的问题

之前的硬编码渲染通道:

c 复制代码
// 旧系统:硬编码渲染通道
void renderer_begin_frame() {
    // 固定:只能渲染到 Swapchain
    vkCmdBeginRenderPass(..., swapchain_framebuffer, ...);
}

问题:
✗ 无法渲染到自定义纹理
✗ 无法创建多个渲染通道
✗ 无法配置附件操作
✗ 无法实现延迟渲染、后处理等

可配置渲染通道

新系统:动态创建渲染通道:

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

/**
 * @brief 渲染通道配置
 */
typedef struct renderpass_config {
    const char* name;                     // 名称
    u32 attachment_count;                 // 附件数量
    renderpass_attachment_config* attachments;  // 附件配置
    f32 render_area_x, render_area_y;     // 渲染区域
    f32 render_area_w, render_area_h;
    vec4 clear_color;                     // 清除颜色
    f32 clear_depth;                      // 清除深度
    u32 clear_stencil;                    // 清除模板
} renderpass_config;

/**
 * @brief 附件配置
 */
typedef struct renderpass_attachment_config {
    renderpass_attachment_type type;      // 颜色/深度/模板
    b8 is_from_swapchain;                 // 是否来自 Swapchain
    texture_format format;                // 格式
    renderpass_attachment_load_op load_op;   // 加载操作
    renderpass_attachment_store_op store_op; // 存储操作
    b8 present_after;                     // 是否用于呈现
} renderpass_attachment_config;

/**
 * @brief 加载操作
 */
typedef enum renderpass_attachment_load_op {
    RENDERPASS_ATTACHMENT_LOAD_OP_DONT_CARE = 0,  // 不关心(随机值)
    RENDERPASS_ATTACHMENT_LOAD_OP_LOAD,           // 加载现有内容
    RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR           // 清除
} renderpass_attachment_load_op;

/**
 * @brief 存储操作
 */
typedef enum renderpass_attachment_store_op {
    RENDERPASS_ATTACHMENT_STORE_OP_DONT_CARE = 0, // 不关心(丢弃)
    RENDERPASS_ATTACHMENT_STORE_OP_STORE          // 存储
} renderpass_attachment_store_op;

使用示例:

c 复制代码
// 创建阴影贴图渲染通道
renderpass_config shadow_config = {0};
shadow_config.name = "ShadowMapPass";
shadow_config.attachment_count = 1;

// 深度附件
renderpass_attachment_config depth_attach = {0};
depth_attach.type = RENDERPASS_ATTACHMENT_DEPTH;
depth_attach.format = TEXTURE_FORMAT_DEPTH32F;
depth_attach.load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
depth_attach.store_op = RENDERPASS_ATTACHMENT_STORE_OP_STORE;  // 保存深度

shadow_config.attachments = &depth_attach;
shadow_config.clear_depth = 1.0f;

renderpass* shadow_pass = renderpass_create(&shadow_config);

// 使用
renderer_begin_renderpass(shadow_pass, shadow_framebuffer);
// ... 渲染阴影 ...
renderer_end_renderpass();

渲染通道生命周期

完整的渲染通道生命周期:

复制代码
渲染通道生命周期:
┌────────────────────────────────────┐
│ 1. 配置 (Configure)                │
│    renderpass_config config;       │
│    config.attachments = ...;       │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 2. 创建 (Create)                   │
│    renderpass* rp = create(&cfg);  │
│    • 创建 VkRenderPass             │
│    • 编译附件描述                   │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 3. 绑定目标 (Bind Target)          │
│    framebuffer* fb = create(rp);   │
│    • 创建 Framebuffer              │
│    • 绑定纹理                       │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 4. 开始 (Begin)                    │
│    begin_renderpass(rp, fb);       │
│    • 执行加载操作 (Clear/Load)     │
│    • 设置渲染状态                   │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 5. 渲染 (Render)                   │
│    draw_mesh(cube);                │
│    draw_mesh(sphere);              │
│    • 绘制命令                       │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 6. 结束 (End)                      │
│    end_renderpass();               │
│    • 执行存储操作 (Store/Discard)  │
│    • 布局转换                       │
└──────────────┬─────────────────────┘
               │
               ▼
┌────────────────────────────────────┐
│ 7. 销毁 (Destroy)                  │
│    renderpass_destroy(rp);         │
│    • 清理资源                       │
└────────────────────────────────────┘

⚙️ 渲染通道配置

附件配置

详细的附件配置选项:

c 复制代码
/**
 * @brief 配置颜色附件
 */
renderpass_attachment_config create_color_attachment(
    texture_format format,
    renderpass_attachment_load_op load_op,
    renderpass_attachment_store_op store_op
) {
    renderpass_attachment_config attach = {0};
    attach.type = RENDERPASS_ATTACHMENT_COLOR;
    attach.format = format;
    attach.load_op = load_op;
    attach.store_op = store_op;
    attach.is_from_swapchain = false;
    attach.present_after = false;
    return attach;
}

/**
 * @brief 配置深度附件
 */
renderpass_attachment_config create_depth_attachment(
    texture_format format,
    renderpass_attachment_load_op load_op,
    renderpass_attachment_store_op store_op
) {
    renderpass_attachment_config attach = {0};
    attach.type = RENDERPASS_ATTACHMENT_DEPTH;
    attach.format = format;
    attach.load_op = load_op;
    attach.store_op = store_op;
    return attach;
}

加载/存储操作组合:

Load Op Store Op 用途 性能
Clear Store 首次渲染,保存结果 中等
Load Store 增量渲染,保存结果 慢(需要加载)
Clear DontCare 临时渲染,不保存
DontCare Store 无需初始化,保存 中等
DontCare DontCare 仅用于测试 最快
c 复制代码
// 示例 1: 阴影贴图
// • Clear depth (清除深度)
// • Store (保存,后续采样)
renderpass_attachment_config shadow_depth = {0};
shadow_depth.type = RENDERPASS_ATTACHMENT_DEPTH;
shadow_depth.load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
shadow_depth.store_op = RENDERPASS_ATTACHMENT_STORE_OP_STORE;

// 示例 2: 临时深度缓冲
// • Clear depth
// • DontCare (不保存,仅用于深度测试)
renderpass_attachment_config temp_depth = {0};
temp_depth.type = RENDERPASS_ATTACHMENT_DEPTH;
temp_depth.load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
temp_depth.store_op = RENDERPASS_ATTACHMENT_STORE_OP_DONT_CARE;  // 优化!

// 示例 3: 增量绘制
// • Load (加载现有内容)
// • Store (保存更新后的内容)
renderpass_attachment_config incremental_color = {0};
incremental_color.type = RENDERPASS_ATTACHMENT_COLOR;
incremental_color.load_op = RENDERPASS_ATTACHMENT_LOAD_OP_LOAD;
incremental_color.store_op = RENDERPASS_ATTACHMENT_STORE_OP_STORE;

子通道配置

Vulkan 支持子通道 (Subpasses):

c 复制代码
/**
 * @brief 子通道配置
 */
typedef struct subpass_config {
    u32 color_attachment_count;           // 颜色附件数量
    u32* color_attachments;               // 颜色附件索引
    u32* input_attachments;               // 输入附件索引 (从前一个子通道读取)
    b8 has_depth;                         // 是否有深度附件
    u32 depth_attachment;                 // 深度附件索引
} subpass_config;

/**
 * @brief 多子通道渲染通道
 */
typedef struct multipass_renderpass_config {
    u32 attachment_count;
    renderpass_attachment_config* attachments;
    u32 subpass_count;
    subpass_config* subpasses;
} multipass_renderpass_config;

子通道应用:

复制代码
延迟渲染 (2 个子通道):
┌────────────────────────────────────┐
│ Subpass 0: G-Buffer Pass           │
│ • Color Outputs:                   │
│   - Attachment 0: Albedo           │
│   - Attachment 1: Normal           │
│   - Attachment 2: Position         │
│ • Depth: Attachment 3              │
└──────────────┬─────────────────────┘
               │ 自动布局转换
               ▼
┌────────────────────────────────────┐
│ Subpass 1: Lighting Pass           │
│ • Input Attachments:               │
│   - 读取 Attachment 0, 1, 2        │
│ • Color Output:                    │
│   - Attachment 4: Final Color      │
└────────────────────────────────────┘

优点:
• 单个 RenderPass (减少开销)
• Tile-based GPU 优化 (移动设备)
• 自动管理附件依赖

缺点:
• 配置复杂
• 不是所有 GPU 都优化子通道

依赖配置

子通道间的依赖关系:

c 复制代码
/**
 * @brief 子通道依赖
 */
typedef struct subpass_dependency {
    u32 src_subpass;                      // 源子通道索引
    u32 dst_subpass;                      // 目标子通道索引
    VkPipelineStageFlags src_stage_mask;  // 源管线阶段
    VkPipelineStageFlags dst_stage_mask;  // 目标管线阶段
    VkAccessFlags src_access_mask;        // 源访问掩码
    VkAccessFlags dst_access_mask;        // 目标访问掩码
} subpass_dependency;

// 示例:G-Buffer → Lighting 依赖
subpass_dependency gbuffer_to_lighting = {0};
gbuffer_to_lighting.src_subpass = 0;  // G-Buffer Pass
gbuffer_to_lighting.dst_subpass = 1;  // Lighting Pass
gbuffer_to_lighting.src_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
gbuffer_to_lighting.dst_stage_mask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
gbuffer_to_lighting.src_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
gbuffer_to_lighting.dst_access_mask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;

🎨 渲染目标创建

颜色目标

创建颜色渲染目标:

c 复制代码
/**
 * @brief 创建颜色渲染目标
 */
render_target* create_color_render_target(
    const char* name,
    u32 width,
    u32 height,
    texture_format format
) {
    render_target* rt = kallocate(sizeof(render_target), MEMORY_TAG_RENDERER);
    string_ncopy(rt->name, name, 64);

    // 1. 创建颜色纹理
    texture_create_info tex_info = {0};
    tex_info.name = name;
    tex_info.width = width;
    tex_info.height = height;
    tex_info.format = format;
    tex_info.usage = TEXTURE_USAGE_SAMPLED | TEXTURE_USAGE_COLOR_ATTACHMENT;
    texture* color_tex = texture_system_create(&tex_info);

    // 2. 创建附件
    rt->attachment_count = 1;
    rt->attachments = kallocate(sizeof(render_target_attachment), MEMORY_TAG_ARRAY);

    rt->attachments[0].type = RENDERPASS_ATTACHMENT_COLOR;
    rt->attachments[0].texture = color_tex;
    rt->attachments[0].load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
    rt->attachments[0].store_op = RENDERPASS_ATTACHMENT_STORE_OP_STORE;
    rt->attachments[0].clear_color = (vec4){0.0f, 0.0f, 0.0f, 1.0f};

    return rt;
}

// 使用
render_target* scene_rt = create_color_render_target(
    "SceneColorRT",
    1920,
    1080,
    TEXTURE_FORMAT_RGBA8
);

深度模板目标

创建深度渲染目标:

c 复制代码
/**
 * @brief 创建深度渲染目标
 */
render_target* create_depth_render_target(
    const char* name,
    u32 width,
    u32 height
) {
    render_target* rt = kallocate(sizeof(render_target), MEMORY_TAG_RENDERER);
    string_ncopy(rt->name, name, 64);

    // 1. 创建深度纹理
    texture_create_info tex_info = {0};
    tex_info.name = name;
    tex_info.width = width;
    tex_info.height = height;
    tex_info.format = TEXTURE_FORMAT_DEPTH32F;
    tex_info.usage = TEXTURE_USAGE_SAMPLED | TEXTURE_USAGE_DEPTH_ATTACHMENT;
    texture* depth_tex = texture_system_create(&tex_info);

    // 2. 创建附件
    rt->attachment_count = 1;
    rt->attachments = kallocate(sizeof(render_target_attachment), MEMORY_TAG_ARRAY);

    rt->attachments[0].type = RENDERPASS_ATTACHMENT_DEPTH;
    rt->attachments[0].texture = depth_tex;
    rt->attachments[0].load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
    rt->attachments[0].store_op = RENDERPASS_ATTACHMENT_STORE_OP_STORE;
    rt->attachments[0].clear_depth = 1.0f;

    return rt;
}

// 使用
render_target* shadow_rt = create_depth_render_target(
    "ShadowMapRT",
    2048,
    2048
);

多重采样目标

创建 MSAA 渲染目标:

c 复制代码
/**
 * @brief 创建 MSAA 渲染目标
 */
render_target* create_msaa_render_target(
    const char* name,
    u32 width,
    u32 height,
    u32 sample_count  // 2, 4, 8, etc.
) {
    render_target* rt = kallocate(sizeof(render_target), MEMORY_TAG_RENDERER);
    string_ncopy(rt->name, name, 64);

    // 1. 创建 MSAA 颜色纹理
    texture_create_info msaa_info = {0};
    msaa_info.name = name;
    msaa_info.width = width;
    msaa_info.height = height;
    msaa_info.format = TEXTURE_FORMAT_RGBA8;
    msaa_info.usage = TEXTURE_USAGE_COLOR_ATTACHMENT;
    msaa_info.sample_count = sample_count;  // 多重采样
    texture* msaa_tex = texture_system_create(&msaa_info);

    // 2. 创建解析纹理 (单采样)
    texture_create_info resolve_info = msaa_info;
    resolve_info.sample_count = 1;
    resolve_info.usage = TEXTURE_USAGE_SAMPLED | TEXTURE_USAGE_COLOR_ATTACHMENT;
    texture* resolve_tex = texture_system_create(&resolve_info);

    // 3. 创建附件
    rt->attachment_count = 2;
    rt->attachments = kallocate(sizeof(render_target_attachment) * 2, MEMORY_TAG_ARRAY);

    // MSAA 附件
    rt->attachments[0].type = RENDERPASS_ATTACHMENT_COLOR;
    rt->attachments[0].texture = msaa_tex;
    rt->attachments[0].load_op = RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR;
    rt->attachments[0].store_op = RENDERPASS_ATTACHMENT_STORE_OP_DONT_CARE;  // 不保存 MSAA

    // 解析附件
    rt->attachments[1].type = RENDERPASS_ATTACHMENT_COLOR_RESOLVE;
    rt->attachments[1].texture = resolve_tex;
    rt->attachments[1].store_op = RENDERPASS_ATTACHMENT_STORE_OP_STORE;  // 保存解析结果

    return rt;
}

🖥️ Vulkan实现

VkRenderPass创建

从配置创建 Vulkan RenderPass:

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

/**
 * @brief 创建 Vulkan RenderPass
 */
VkRenderPass create_vulkan_renderpass(
    vulkan_context* context,
    const renderpass_config* config
) {
    // 1. 准备附件描述
    VkAttachmentDescription* attachments = kallocate(
        sizeof(VkAttachmentDescription) * config->attachment_count,
        MEMORY_TAG_RENDERER
    );

    for (u32 i = 0; i < config->attachment_count; ++i) {
        const renderpass_attachment_config* attach_config = &config->attachments[i];

        VkAttachmentDescription* attach = &attachments[i];
        attach->format = convert_texture_format(attach_config->format);
        attach->samples = VK_SAMPLE_COUNT_1_BIT;
        attach->loadOp = convert_load_op(attach_config->load_op);
        attach->storeOp = convert_store_op(attach_config->store_op);
        attach->stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
        attach->stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;

        // 初始布局
        if (attach_config->load_op == RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR ||
            attach_config->load_op == RENDERPASS_ATTACHMENT_LOAD_OP_DONT_CARE) {
            attach->initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        } else {
            attach->initialLayout = attach_config->type == RENDERPASS_ATTACHMENT_COLOR
                                       ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
                                       : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
        }

        // 最终布局
        if (attach_config->present_after) {
            attach->finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
        } else if (attach_config->store_op == RENDERPASS_ATTACHMENT_STORE_OP_STORE) {
            attach->finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
        } else {
            attach->finalLayout = attach->initialLayout;
        }
    }

    // 2. 准备子通道
    VkAttachmentReference color_refs[8];
    VkAttachmentReference depth_ref = {0};
    u32 color_count = 0;
    b8 has_depth = false;

    for (u32 i = 0; i < config->attachment_count; ++i) {
        if (config->attachments[i].type == RENDERPASS_ATTACHMENT_COLOR) {
            color_refs[color_count].attachment = i;
            color_refs[color_count].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
            color_count++;
        } else if (config->attachments[i].type == RENDERPASS_ATTACHMENT_DEPTH) {
            depth_ref.attachment = i;
            depth_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
            has_depth = true;
        }
    }

    VkSubpassDescription subpass = {0};
    subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    subpass.colorAttachmentCount = color_count;
    subpass.pColorAttachments = color_refs;
    subpass.pDepthStencilAttachment = has_depth ? &depth_ref : NULL;

    // 3. 子通道依赖
    VkSubpassDependency dependency = {0};
    dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
    dependency.dstSubpass = 0;
    dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.srcAccessMask = 0;
    dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;

    // 4. 创建 RenderPass
    VkRenderPassCreateInfo create_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
    create_info.attachmentCount = config->attachment_count;
    create_info.pAttachments = attachments;
    create_info.subpassCount = 1;
    create_info.pSubpasses = &subpass;
    create_info.dependencyCount = 1;
    create_info.pDependencies = &dependency;

    VkRenderPass renderpass;
    VK_CHECK(vkCreateRenderPass(
        context->device.logical_device,
        &create_info,
        context->allocator,
        &renderpass
    ));

    kfree(attachments, sizeof(VkAttachmentDescription) * config->attachment_count, MEMORY_TAG_RENDERER);

    KDEBUG("Created Vulkan RenderPass: %s", config->name);
    return renderpass;
}

/**
 * @brief 转换加载操作
 */
VkAttachmentLoadOp convert_load_op(renderpass_attachment_load_op op) {
    switch (op) {
        case RENDERPASS_ATTACHMENT_LOAD_OP_DONT_CARE:
            return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
        case RENDERPASS_ATTACHMENT_LOAD_OP_LOAD:
            return VK_ATTACHMENT_LOAD_OP_LOAD;
        case RENDERPASS_ATTACHMENT_LOAD_OP_CLEAR:
            return VK_ATTACHMENT_LOAD_OP_CLEAR;
        default:
            KWARN("Unknown load op: %d", op);
            return VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    }
}

/**
 * @brief 转换存储操作
 */
VkAttachmentStoreOp convert_store_op(renderpass_attachment_store_op op) {
    switch (op) {
        case RENDERPASS_ATTACHMENT_STORE_OP_DONT_CARE:
            return VK_ATTACHMENT_STORE_OP_DONT_CARE;
        case RENDERPASS_ATTACHMENT_STORE_OP_STORE:
            return VK_ATTACHMENT_STORE_OP_STORE;
        default:
            KWARN("Unknown store op: %d", op);
            return VK_ATTACHMENT_STORE_OP_DONT_CARE;
    }
}

Framebuffer管理

创建并管理 Framebuffer:

c 复制代码
/**
 * @brief 创建 Framebuffer
 */
VkFramebuffer create_framebuffer(
    vulkan_context* context,
    VkRenderPass renderpass,
    render_target* rt
) {
    // 收集附件 ImageView
    VkImageView* attachments = kallocate(
        sizeof(VkImageView) * rt->attachment_count,
        MEMORY_TAG_RENDERER
    );

    u32 width = 0, height = 0;
    for (u32 i = 0; i < rt->attachment_count; ++i) {
        vulkan_texture* vk_tex = (vulkan_texture*)rt->attachments[i].texture->internal_data;
        attachments[i] = vk_tex->view;

        if (i == 0) {
            width = rt->attachments[i].texture->width;
            height = rt->attachments[i].texture->height;
        }
    }

    // 创建 Framebuffer
    VkFramebufferCreateInfo create_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
    create_info.renderPass = renderpass;
    create_info.attachmentCount = rt->attachment_count;
    create_info.pAttachments = attachments;
    create_info.width = width;
    create_info.height = height;
    create_info.layers = 1;

    VkFramebuffer framebuffer;
    VK_CHECK(vkCreateFramebuffer(
        context->device.logical_device,
        &create_info,
        context->allocator,
        &framebuffer
    ));

    kfree(attachments, sizeof(VkImageView) * rt->attachment_count, MEMORY_TAG_RENDERER);

    KDEBUG("Created Framebuffer for RT '%s' (%ux%u)", rt->name, width, height);
    return framebuffer;
}

渲染通道执行

执行渲染通道:

c 复制代码
/**
 * @brief 开始渲染通道
 */
void renderer_begin_renderpass(renderpass* rp, render_target* rt) {
    vulkan_context* context = &vulkan_state->context;
    vulkan_renderpass* vk_rp = (vulkan_renderpass*)rp->internal_data;

    // 准备清除值
    VkClearValue* clear_values = kallocate(
        sizeof(VkClearValue) * rt->attachment_count,
        MEMORY_TAG_RENDERER
    );

    for (u32 i = 0; i < rt->attachment_count; ++i) {
        if (rt->attachments[i].type == RENDERPASS_ATTACHMENT_COLOR) {
            clear_values[i].color.float32[0] = rt->attachments[i].clear_color.r;
            clear_values[i].color.float32[1] = rt->attachments[i].clear_color.g;
            clear_values[i].color.float32[2] = rt->attachments[i].clear_color.b;
            clear_values[i].color.float32[3] = rt->attachments[i].clear_color.a;
        } else if (rt->attachments[i].type == RENDERPASS_ATTACHMENT_DEPTH) {
            clear_values[i].depthStencil.depth = rt->attachments[i].clear_depth;
            clear_values[i].depthStencil.stencil = rt->attachments[i].clear_stencil;
        }
    }

    // 开始 RenderPass
    VkRenderPassBeginInfo begin_info = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
    begin_info.renderPass = vk_rp->handle;
    begin_info.framebuffer = vk_rp->framebuffer;
    begin_info.renderArea.offset = (VkOffset2D){0, 0};
    begin_info.renderArea.extent = (VkExtent2D){
        rt->attachments[0].texture->width,
        rt->attachments[0].texture->height
    };
    begin_info.clearValueCount = rt->attachment_count;
    begin_info.pClearValues = clear_values;

    vkCmdBeginRenderPass(
        context->graphics_command_buffers[context->image_index],
        &begin_info,
        VK_SUBPASS_CONTENTS_INLINE
    );

    kfree(clear_values, sizeof(VkClearValue) * rt->attachment_count, MEMORY_TAG_RENDERER);
}

/**
 * @brief 结束渲染通道
 */
void renderer_end_renderpass() {
    vulkan_context* context = &vulkan_state->context;
    vkCmdEndRenderPass(context->graphics_command_buffers[context->image_index]);
}

🔁 多通道渲染管线

延迟渲染

实现延迟渲染管线:

c 复制代码
/**
 * @brief 延迟渲染系统
 */
typedef struct deferred_renderer {
    // Pass 1: G-Buffer
    renderpass* gbuffer_pass;
    render_target* gbuffer_rt;
    texture* albedo_tex;
    texture* normal_tex;
    texture* position_tex;
    texture* depth_tex;

    // Pass 2: Lighting
    renderpass* lighting_pass;
    render_target* lighting_rt;
    texture* final_color_tex;
} deferred_renderer;

/**
 * @brief 创建延迟渲染器
 */
deferred_renderer* deferred_renderer_create(u32 width, u32 height) {
    deferred_renderer* dr = kallocate(sizeof(deferred_renderer), MEMORY_TAG_RENDERER);

    // 1. 创建 G-Buffer 纹理
    dr->albedo_tex = create_render_target_texture(width, height, TEXTURE_FORMAT_RGBA8);
    dr->normal_tex = create_render_target_texture(width, height, TEXTURE_FORMAT_RGBA16F);
    dr->position_tex = create_render_target_texture(width, height, TEXTURE_FORMAT_RGBA16F);
    dr->depth_tex = create_depth_texture(width, height);

    // 2. 创建 G-Buffer 渲染目标
    dr->gbuffer_rt = render_target_create();
    render_target_add_attachment(dr->gbuffer_rt, dr->albedo_tex, CLEAR, STORE);
    render_target_add_attachment(dr->gbuffer_rt, dr->normal_tex, CLEAR, STORE);
    render_target_add_attachment(dr->gbuffer_rt, dr->position_tex, CLEAR, STORE);
    render_target_add_attachment(dr->gbuffer_rt, dr->depth_tex, CLEAR, DONT_CARE);

    // 3. 创建 G-Buffer Pass
    renderpass_config gbuffer_config = create_gbuffer_pass_config();
    dr->gbuffer_pass = renderpass_create(&gbuffer_config);

    // 4. 创建 Lighting 纹理和渲染目标
    dr->final_color_tex = create_render_target_texture(width, height, TEXTURE_FORMAT_RGBA8);
    dr->lighting_rt = render_target_create();
    render_target_add_attachment(dr->lighting_rt, dr->final_color_tex, CLEAR, STORE);

    // 5. 创建 Lighting Pass
    renderpass_config lighting_config = create_lighting_pass_config();
    dr->lighting_pass = renderpass_create(&lighting_config);

    return dr;
}

/**
 * @brief 渲染帧 (延迟渲染)
 */
void deferred_renderer_render_frame(
    deferred_renderer* dr,
    scene* scene_data,
    camera* cam
) {
    // Pass 1: G-Buffer
    renderer_begin_renderpass(dr->gbuffer_pass, dr->gbuffer_rt);

    shader* gbuffer_shader = shader_system_get("GBuffer");
    shader_bind(gbuffer_shader);
    shader_set_uniform(gbuffer_shader, "view", &cam->view);
    shader_set_uniform(gbuffer_shader, "projection", &cam->projection);

    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(gbuffer_shader, "model", &model);
        material_system_apply(m->material, gbuffer_shader);
        geometry_draw(m->geometry);
    }

    renderer_end_renderpass();

    // Pass 2: Lighting
    renderer_begin_renderpass(dr->lighting_pass, dr->lighting_rt);

    shader* lighting_shader = shader_system_get("DeferredLighting");
    shader_bind(lighting_shader);

    // 绑定 G-Buffer 纹理
    shader_bind_texture(lighting_shader, 0, dr->albedo_tex);
    shader_bind_texture(lighting_shader, 1, dr->normal_tex);
    shader_bind_texture(lighting_shader, 2, dr->position_tex);

    // 绑定光源信息
    shader_set_uniform(lighting_shader, "light_position", &scene_data->light.position);
    shader_set_uniform(lighting_shader, "light_color", &scene_data->light.color);

    // 绘制全屏四边形
    render_fullscreen_quad();

    renderer_end_renderpass();

    // Pass 3: 复制到 Swapchain
    blit_texture_to_swapchain(dr->final_color_tex);
}

G-Buffer Shader:

glsl 复制代码
// gbuffer.frag

#version 450

layout(location = 0) in vec3 in_position;
layout(location = 1) in vec2 in_texcoord;
layout(location = 2) in vec3 in_normal;

// 输出到多个渲染目标
layout(location = 0) out vec4 out_albedo;
layout(location = 1) out vec4 out_normal;
layout(location = 2) out vec4 out_position;

uniform sampler2D u_diffuse_map;

void main() {
    // Albedo
    out_albedo = texture(u_diffuse_map, in_texcoord);

    // Normal (世界空间)
    out_normal = vec4(normalize(in_normal), 1.0);

    // Position (世界空间)
    out_position = vec4(in_position, 1.0);
}

Deferred Lighting Shader:

glsl 复制代码
// deferred_lighting.frag

#version 450

layout(location = 0) in vec2 in_texcoord;

// G-Buffer 输入
uniform sampler2D u_gbuffer_albedo;
uniform sampler2D u_gbuffer_normal;
uniform sampler2D u_gbuffer_position;

// 光源
uniform vec3 u_light_position;
uniform vec3 u_light_color;
uniform vec3 u_camera_position;

layout(location = 0) out vec4 out_color;

void main() {
    // 从 G-Buffer 读取
    vec3 albedo = texture(u_gbuffer_albedo, in_texcoord).rgb;
    vec3 normal = texture(u_gbuffer_normal, in_texcoord).rgb;
    vec3 position = texture(u_gbuffer_position, in_texcoord).rgb;

    // 光照计算
    vec3 light_dir = normalize(u_light_position - position);
    float diff = max(dot(normal, light_dir), 0.0);

    vec3 view_dir = normalize(u_camera_position - position);
    vec3 half_dir = normalize(light_dir + view_dir);
    float spec = pow(max(dot(normal, half_dir), 0.0), 32.0);

    vec3 ambient = albedo * 0.1;
    vec3 diffuse = albedo * u_light_color * diff;
    vec3 specular = u_light_color * spec;

    out_color = vec4(ambient + diffuse + specular, 1.0);
}

前向渲染增强

增强的前向渲染(多个 Pass):

c 复制代码
/**
 * @brief 前向渲染 + 后处理
 */
void forward_renderer_with_post_process(
    scene* scene_data,
    camera* cam
) {
    // Pass 1: 阴影贴图
    render_shadow_map(scene_data->directional_light);

    // Pass 2: 主场景渲染
    static render_target* scene_rt = NULL;
    if (!scene_rt) {
        scene_rt = create_scene_render_target(1920, 1080);
    }

    renderer_begin_renderpass(scene_pass, scene_rt);
    render_scene(scene_data, cam);
    renderer_end_renderpass();

    // Pass 3: 后处理
    apply_post_processing(scene_rt->attachments[0].texture);
}

后处理链

多步骤后处理:

c 复制代码
/**
 * @brief 后处理链
 */
void apply_post_processing(texture* scene_tex) {
    static texture* temp_tex = NULL;
    if (!temp_tex) {
        temp_tex = create_render_target_texture(1920, 1080, TEXTURE_FORMAT_RGBA8);
    }

    // Step 1: Bloom 提取
    texture* bloom_bright = extract_bright_areas(scene_tex);

    // Step 2: Bloom 模糊
    texture* bloom_blurred = gaussian_blur(bloom_bright, 5);

    // Step 3: Bloom 合成
    texture* bloomed = combine_bloom(scene_tex, bloom_blurred);

    // Step 4: Tone Mapping
    texture* tone_mapped = apply_tone_mapping(bloomed);

    // Step 5: 输出到屏幕
    blit_to_swapchain(tone_mapped);
}

🎮 实际应用

完整的游戏渲染管线:

c 复制代码
/**
 * @brief 游戏渲染管线
 */
void game_render_frame(game_state* state) {
    // 1. 阴影贴图 Pass
    for (u32 i = 0; i < state->light_count; ++i) {
        render_shadow_map(&state->lights[i], state->scene);
    }

    // 2. 主场景 Pass
    renderer_begin_renderpass(state->scene_pass, state->scene_rt);

    // 绑定阴影贴图
    for (u32 i = 0; i < state->light_count; ++i) {
        shader_bind_texture(main_shader, 3 + i, state->lights[i].shadow_map);
    }

    // 渲染场景
    render_scene(state->scene, state->camera);

    renderer_end_renderpass();

    // 3. UI Pass
    renderer_begin_renderpass(state->ui_pass, state->ui_rt);
    render_ui(state->ui_state);
    renderer_end_renderpass();

    // 4. 后处理 Pass
    apply_post_processing(state->scene_rt->attachments[0].texture);

    // 5. 最终合成到屏幕
    renderer_begin_swapchain_renderpass();
    composite_final_image(state->post_process_result, state->ui_rt);
    renderer_end_renderpass();
}

❓ 常见问题

1. 何时使用 Load vs Clear?

Load vs Clear 决策:

场景 操作 原因
首次渲染 Clear 初始化附件
增量渲染 Load 保留现有内容
完全覆盖 DontCare 优化性能
临时附件 DontCare 不需要初始化
c 复制代码
// 示例 1: 阴影贴图
// • 每帧完全重新渲染
// • 使用 Clear
shadow_depth.load_op = CLEAR;

// 示例 2: 粒子系统叠加
// • 在现有场景上绘制粒子
// • 使用 Load
particle_color.load_op = LOAD;

// 示例 3: 全屏后处理
// • 完全覆盖屏幕
// • 使用 DontCare (优化!)
postprocess_color.load_op = DONT_CARE;

2. Store vs DontCare 如何选择?

Store vs DontCare 性能影响:

复制代码
移动 GPU (Tile-based):
┌────────────────────────────────────┐
│ Store: 需要写回主内存               │
│ • 带宽开销大                        │
│ • 电量消耗高                        │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ DontCare: 仅在 Tile 内存           │
│ • 无带宽开销                        │
│ • 电量节省                          │
│ • 性能提升 10-30%                   │
└────────────────────────────────────┘

决策:
✓ Store: 后续需要采样的纹理
✓ DontCare: 仅用于当前 Pass 的附件

示例:
// 深度缓冲 (不需要采样)
depth.store_op = DONT_CARE;  // ✓ 优化!

// 场景颜色 (后处理需要)
color.store_op = STORE;  // ✓ 必须保存

3. 多渲染通道vs单渲染通道子通道?

对比:

方案 优点 缺点 适用
多渲染通道 简单、灵活、易调试 切换开销 桌面 GPU
单渲染通道+子通道 Tile 优化、带宽节省 复杂、限制多 移动 GPU
c 复制代码
// 方案 1: 多渲染通道 (简单)
renderer_begin_renderpass(gbuffer_pass, gbuffer_rt);
// ... render G-Buffer ...
renderer_end_renderpass();

renderer_begin_renderpass(lighting_pass, lighting_rt);
// ... render lighting ...
renderer_end_renderpass();

// 方案 2: 单渲染通道 + 子通道 (优化)
renderer_begin_renderpass(deferred_pass, combined_rt);
// Subpass 0: G-Buffer
renderer_next_subpass();
// Subpass 1: Lighting (读取 G-Buffer)
renderer_end_renderpass();

建议:
• 桌面/主机: 使用多渲染通道 (简单)
• 移动设备: 使用子通道 (性能)

4. 如何调试渲染通道?

调试技巧:

c 复制代码
// 1. 可视化渲染目标
void debug_visualize_render_target(render_target* rt, u32 attachment_index) {
    renderer_begin_swapchain_renderpass();

    shader* debug_shader = shader_system_get("DebugQuad");
    shader_bind(debug_shader);
    shader_bind_texture(debug_shader, 0, rt->attachments[attachment_index].texture);

    render_fullscreen_quad();

    renderer_end_renderpass();
}

// 使用: F1 = Albedo, F2 = Normal, F3 = Position
if (input_is_key_pressed(KEY_F1)) {
    debug_visualize_render_target(gbuffer_rt, 0);  // Albedo
} else if (input_is_key_pressed(KEY_F2)) {
    debug_visualize_render_target(gbuffer_rt, 1);  // Normal
}

// 2. 验证清除值
renderpass_config config = {0};
config.clear_color = (vec4){1.0f, 0.0f, 1.0f, 1.0f};  // 洋红色 = 明显错误

// 如果看到洋红色 = 该区域没有被绘制

// 3. 导出纹理
texture_read_data(rt->attachments[0].texture, 0, pixels, size);
stbi_write_png("debug_color.png", width, height, 4, pixels, width * 4);

5. 如何优化多通道性能?

性能优化清单:

复制代码
✓ 重用渲染目标 (不要每帧创建/销毁)
✓ 使用合适的分辨率 (阴影贴图可以更低)
✓ 正确设置 Store Op (不需要的用 DontCare)
✓ 批量渲染 (减少 Draw Call)
✓ 延迟更新 (环境贴图不需要每帧更新)

代码示例:
// ✓ 创建一次,重用
static render_target* shadow_rt = NULL;
if (!shadow_rt) {
    shadow_rt = create_shadow_render_target(2048, 2048);
}

// ✓ 降低分辨率
render_target* bloom_rt = create_color_render_target(960, 540, ...);  // 半分辨率

// ✓ DontCare 优化
temp_depth.store_op = RENDERPASS_ATTACHMENT_STORE_OP_DONT_CARE;

// ✓ 延迟更新
if (frame_count % 10 == 0) {
    update_environment_cubemap();
}

📝 练习

练习 1: 实现简单的 G-Buffer 可视化

任务: 创建可切换显示不同 G-Buffer 层的调试工具。

c 复制代码
/**
 * @brief G-Buffer 调试器
 */
typedef enum gbuffer_debug_mode {
    GBUFFER_DEBUG_OFF,
    GBUFFER_DEBUG_ALBEDO,
    GBUFFER_DEBUG_NORMAL,
    GBUFFER_DEBUG_POSITION,
    GBUFFER_DEBUG_DEPTH
} gbuffer_debug_mode;

static gbuffer_debug_mode debug_mode = GBUFFER_DEBUG_OFF;

/**
 * @brief 处理输入
 */
void update_debug_input() {
    if (input_is_key_pressed(KEY_F1)) debug_mode = GBUFFER_DEBUG_ALBEDO;
    if (input_is_key_pressed(KEY_F2)) debug_mode = GBUFFER_DEBUG_NORMAL;
    if (input_is_key_pressed(KEY_F3)) debug_mode = GBUFFER_DEBUG_POSITION;
    if (input_is_key_pressed(KEY_F4)) debug_mode = GBUFFER_DEBUG_DEPTH;
    if (input_is_key_pressed(KEY_F5)) debug_mode = GBUFFER_DEBUG_OFF;
}

/**
 * @brief 渲染 G-Buffer 调试视图
 */
void render_gbuffer_debug(deferred_renderer* dr) {
    update_debug_input();

    if (debug_mode == GBUFFER_DEBUG_OFF) {
        // 正常渲染
        deferred_renderer_render_frame(dr, scene, camera);
        return;
    }

    // 调试模式:显示特定缓冲区
    renderer_begin_swapchain_renderpass();

    shader* debug_shader = shader_system_get("DebugQuad");
    shader_bind(debug_shader);

    texture* debug_tex = NULL;
    switch (debug_mode) {
        case GBUFFER_DEBUG_ALBEDO:
            debug_tex = dr->albedo_tex;
            break;
        case GBUFFER_DEBUG_NORMAL:
            debug_tex = dr->normal_tex;
            break;
        case GBUFFER_DEBUG_POSITION:
            debug_tex = dr->position_tex;
            break;
        case GBUFFER_DEBUG_DEPTH:
            debug_tex = dr->depth_tex;
            break;
    }

    shader_bind_texture(debug_shader, 0, debug_tex);
    render_fullscreen_quad();

    renderer_end_renderpass();

    // 显示提示文本
    const char* mode_names[] = {
        "Off", "Albedo", "Normal", "Position", "Depth"
    };
    ui_text_draw(mode_names[debug_mode], 10, 10);
}

练习 2: 实现级联阴影贴图

任务: 创建多个级联的阴影贴图以提高远近阴影质量。

c 复制代码
/**
 * @brief 级联阴影贴图
 */
#define CASCADE_COUNT 4

typedef struct cascaded_shadow_map {
    render_target* cascades[CASCADE_COUNT];
    mat4 light_space_matrices[CASCADE_COUNT];
    f32 split_depths[CASCADE_COUNT];
} cascaded_shadow_map;

/**
 * @brief 创建级联阴影贴图
 */
cascaded_shadow_map* csm_create(u32 resolution) {
    cascaded_shadow_map* csm = kallocate(sizeof(cascaded_shadow_map), MEMORY_TAG_RENDERER);

    // 创建 4 个级联
    for (u32 i = 0; i < CASCADE_COUNT; ++i) {
        csm->cascades[i] = create_depth_render_target(
            "CascadeShadowMap",
            resolution,
            resolution
        );
    }

    // 计算分割深度 (对数分布)
    f32 near = 0.1f;
    f32 far = 100.0f;
    f32 lambda = 0.75f;  // 对数/线性混合系数

    for (u32 i = 0; i < CASCADE_COUNT; ++i) {
        f32 p = (i + 1) / (f32)CASCADE_COUNT;
        f32 log_split = near * powf(far / near, p);
        f32 linear_split = near + (far - near) * p;
        csm->split_depths[i] = lambda * log_split + (1.0f - lambda) * linear_split;
    }

    return csm;
}

/**
 * @brief 渲染级联阴影
 */
void csm_render(
    cascaded_shadow_map* csm,
    directional_light* light,
    camera* cam,
    scene* scene_data
) {
    for (u32 cascade = 0; cascade < CASCADE_COUNT; ++cascade) {
        // 计算该级联的视锥体
        f32 near_depth = cascade == 0 ? cam->near : csm->split_depths[cascade - 1];
        f32 far_depth = csm->split_depths[cascade];

        // 计算光源空间矩阵
        csm->light_space_matrices[cascade] = calculate_light_space_matrix(
            light,
            cam,
            near_depth,
            far_depth
        );

        // 渲染到该级联
        renderer_begin_renderpass(shadow_pass, csm->cascades[cascade]);

        shader* depth_shader = shader_system_get("DepthOnly");
        shader_bind(depth_shader);
        shader_set_uniform(depth_shader, "light_space_matrix",
                          &csm->light_space_matrices[cascade]);

        render_scene_depth_only(scene_data);

        renderer_end_renderpass();
    }
}

// Fragment Shader 中采样级联
int select_cascade(float depth) {
    for (int i = 0; i < CASCADE_COUNT; ++i) {
        if (depth < split_depths[i]) {
            return i;
        }
    }
    return CASCADE_COUNT - 1;
}

float sample_shadow_cascaded() {
    float depth = gl_FragCoord.z;
    int cascade = select_cascade(depth);

    vec4 shadow_coord = light_space_matrices[cascade] * vec4(world_pos, 1.0);
    return texture(shadow_maps[cascade], shadow_coord.xy).r;
}

练习 3: 实现双缓冲后处理

任务: 实现乒乓缓冲用于多步后处理。

c 复制代码
/**
 * @brief 双缓冲后处理系统
 */
typedef struct ping_pong_buffer {
    render_target* buffers[2];
    u32 read_index;   // 当前读取索引
    u32 write_index;  // 当前写入索引
} ping_pong_buffer;

/**
 * @brief 创建乒乓缓冲
 */
ping_pong_buffer* ping_pong_create(u32 width, u32 height, texture_format format) {
    ping_pong_buffer* ppb = kallocate(sizeof(ping_pong_buffer), MEMORY_TAG_RENDERER);

    ppb->buffers[0] = create_color_render_target("PingPong0", width, height, format);
    ppb->buffers[1] = create_color_render_target("PingPong1", width, height, format);

    ppb->read_index = 0;
    ppb->write_index = 1;

    return ppb;
}

/**
 * @brief 交换缓冲区
 */
void ping_pong_swap(ping_pong_buffer* ppb) {
    u32 temp = ppb->read_index;
    ppb->read_index = ppb->write_index;
    ppb->write_index = temp;
}

/**
 * @brief 多步模糊示例
 */
void apply_multi_pass_blur(texture* input, u32 iterations) {
    static ping_pong_buffer* ppb = NULL;
    if (!ppb) {
        ppb = ping_pong_create(input->width, input->height, input->format);
    }

    shader* blur_shader = shader_system_get("GaussianBlur");

    // 初始:将输入复制到 ppb[0]
    blit_texture(input, ppb->buffers[0]->attachments[0].texture);

    // 迭代模糊
    for (u32 i = 0; i < iterations; ++i) {
        // 水平模糊: read → write
        renderer_begin_renderpass(blur_pass, ppb->buffers[ppb->write_index]);
        shader_bind(blur_shader);
        shader_set_uniform(blur_shader, "direction", &(vec2){1, 0});
        shader_bind_texture(blur_shader, 0,
                           ppb->buffers[ppb->read_index]->attachments[0].texture);
        render_fullscreen_quad();
        renderer_end_renderpass();

        ping_pong_swap(ppb);

        // 垂直模糊: read → write
        renderer_begin_renderpass(blur_pass, ppb->buffers[ppb->write_index]);
        shader_bind(blur_shader);
        shader_set_uniform(blur_shader, "direction", &(vec2){0, 1});
        shader_bind_texture(blur_shader, 0,
                           ppb->buffers[ppb->read_index]->attachments[0].texture);
        render_fullscreen_quad();
        renderer_end_renderpass();

        ping_pong_swap(ppb);
    }

    // 最终结果在 ppb[read_index]
    return ppb->buffers[ppb->read_index]->attachments[0].texture;
}

Tutorial written by 上手实验室

相关推荐
x-cmd23 分钟前
[260429] x-cmd v0.9.1:一键开启 DeepSeek-V4-Pro Max 模式 + 1M 上下文;顺手重构了 uuid 模块
windows·重构·uuid·claude·curl·x-cmd·deepseek-v4-pro
今夕资源网1 小时前
Windows 上安装 Claude Code并且接入DeepSeekV4-Pro的Max模式和激活1M上下文
windows
HLJ洛神千羽2 小时前
电脑右下角显示【周几或星期几】和【上下午】方法
windows
ITHAOGE152 小时前
下载 | Windows Server 2025官方原版ISO映像!(4月更新、标准版、数据中心版、26100.32690)
服务器·windows·科技·微软·电脑
专注VB编程开发20年3 小时前
Windows API 所有老式结构体4字节对齐,但是64位VBA,Twinbasic弄成了8字节对齐,大BUG
windows·bug
东篱把酒黄昏3 小时前
wsl和Windows混合开发高级配置详细指导
windows
天人合一peng4 小时前
unity 生成标记根据背景色变色为明显的颜色
unity·游戏引擎
魔士于安4 小时前
Unity 超市总动员 超市收银台 超市货架 超市购物手推车 超市常见商品
游戏·unity·游戏引擎·贴图·模型
CandyU24 小时前
Unity —— 数据持久化
unity·游戏引擎
zh路西法4 小时前
【Unity实现Oneshot胶卷显形】游戏窗口化与Win32API的使用
游戏·unity·游戏引擎