教程 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 上手实验室

相关推荐
BoBoZz192 小时前
RotationAroundLine 模型的旋转
python·vtk·图形渲染·图形处理
张世争3 小时前
windows 使用 cmake 方式源码编译 SDL2
windows·cmake·sdl2
BoBoZz193 小时前
PolyDataContourToImageData 3D集合图像转换成等效3D二值图像
python·vtk·图形渲染·图形处理
MonkeyKing_sunyuhua3 小时前
ubuntu22.04安装nginx
运维·windows·nginx
张世争4 小时前
windows clion lvgl 使用 sdl2
windows·clion·sdl2
cws2004015 小时前
HeidiSQL 使用操作说明书
运维·数据库·windows·mysql·heidisql
RealizeInnerSelf丶5 小时前
Web 网页如何唤起本地 Windows 应用并传递参数(含 Electron 自动注册 + 手动配置指南)
前端·windows
武藤一雄6 小时前
一款基于WPF开发的BEJSON转换工具
windows·c#·json·wpf
DoomGT6 小时前
Audio - UE5中的音效播放重启问题
游戏·ue5·游戏引擎·虚幻·虚幻引擎