📚 快速导航
目录
📖 简介
在教程 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 上手实验室