Appearance
第十九章 · 渲染分割 (Render Splitting)
19.1 渲染模式对比
渲染分割 = 将渲染任务分成多个 Pass,每个 Pass 处理不同的渲染任务
三种主要渲染模式:
┌───────────────────────────────┐
│ 前向渲染 (Forward Rendering) │
│ 最亮的物体在最前 │
│ 每个像素计算所有光源 │
└───────────────────────────────┘
┌───────────────────────────────┐
│ 延迟渲染 (Deferred Rendering) │
│ 先渲染 G-Buffer │
│ 再对每个像素计算所有光源 │
└───────────────────────────────┘
┌───────────────────────────────┐
│ Clustered/Forward+ Rendering │
│ 将空间分簇,每簇计算部分光源 │
│ 前向的改进版 │
└───────────────────────────────┘19.2 前向渲染 (Forward Rendering)
前向渲染 = 传统渲染模式
流程:
1. 按深度排序物体(近到远)
2. 对每个物体:
- 设置 Uniform(模型、视角、光照)
- 渲染物体(绘制调用)
3. 后处理
优点:
- 实现简单
- 对少量光源高效
- 透明物体处理简单
缺点:
- 光源多时性能差(O(N光源 × N物体))
- 重复计算(相同光照被多个物体重新计算)19.2.1 前向渲染管线
python
def forward_render(scene, camera):
# 1. 渲染不透明物体(按深度排序)
opaque_objects = sorted(scene.objects,
key=lambda o: distance(camera.pos, o.pos))
for obj in opaque_objects:
update_ubo(camera, obj)
bind_pipeline(obj.material)
bind_descriptor_sets(obj.material)
draw(obj.mesh)
# 2. 渲染透明物体(从后往前)
transparent_objects = sorted(scene.transparent_objects,
key=lambda o: distance(camera.pos, o.pos),
reverse=True)
for obj in transparent_objects:
enable_blending()
update_ubo(camera, obj)
bind_pipeline(obj.material)
draw(obj.mesh)
# 3. 后处理
render_pass_post_process()19.3 延迟渲染 (Deferred Rendering)
延迟渲染 = 先渲染几何信息,再计算光照
Pass 1: Geometry Pass (G-Buffer)
┌──────┐ ┌───┐ ┌───┐ ┌──────┐
│ Pos │ │ Norm│ │ Col │ │ Depth │
└───────┘ └───┘ └───┘ └──────┘
所有物体渲染到 G-Buffer(4 个纹理附件)
Pass 2: Lighting Pass
┌────────┐ ┌───────┐ ┌───────┐
│ PointLight1 → G-Buffer → 计算所有光源 │
│ PointLight2 → G-Buffer → 每个像素计算所有光源 │
│ DirectionalLight → G-Buffer → 高效! │
└───────┘ └───────┘ └───────┘
优点:
- 光源数量不影响性能(O(光源) = O(像素))
- 无重复计算
缺点:
- MSAA 困难
- 透明物体处理复杂
- 内存带宽要求高19.3.1 G-Buffer 创建
python
# --- 创建 G-Buffer ---
g_buffer_attachments = [
# 位置
create_render_target(device, width, height, vkbottle.Format.R32G32B32_SFLOAT),
# 法线
create_render_target(device, width, height, vkbottle.Format.R16G16B16A16_SFLOAT),
# 颜色/材质
create_render_target(device, width, height, vkbottle.Format.R16G16B16A16_SFLOAT),
# 深度
create_depth_image(device, width, height),
]19.3.2 光照 Pass
python
# 光照 Pass 只渲染一个全屏四边形
# Shader 从 G-Buffer 读取信息并计算光照
def lighting_pass(device, command_buffer, g_buffer, lights, viewport):
# 1. 开始 Render Pass(使用 G-Buffer 作为附件)
render_pass_begin_info = vkbottle.RenderPassBeginInfo(
renderPass=g_buffer_render_pass,
framebuffer=g_buffer_framebuffer,
renderArea=vkbottle.Rect2D(offset=(0, 0), extent=viewport),
)
# 2. 绑定全屏四边形管线
command_buffer.bind_pipeline_graphics(light_pipeline)
# 3. 绑定 G-Buffer 描述符
command_buffer.bind_descriptor_sets(
vkbottle.PipelineBindPoint.GRAPHICS,
light_pipeline_layout,
[g_buffer_descriptor_set],
)
# 4. 绑定光源 Uniform
command_buffer.bind_descriptor_sets(
vkbottle.PipelineBindPoint.GRAPHICS,
light_pipeline_layout,
[light_descriptor_set],
)
# 5. 绘制全屏四边形
command_buffer.draw(vertexCount=3, instanceCount=1,
firstVertex=0, firstInstance=0)
# 6. 结束
command_buffer.end_render_pass()19.4 Clustered/Forward+ Rendering
Clustered Rendering = 空间分簇 + 前向渲染
1. 将相机视锥体分簇(3D grid)
2. 对每个光源,确定它影响的簇
3. 对每个簇,只渲染影响该簇的光源
优点:
- 前向的性能
- 延迟的扩展性
- 混合两种模式的优点
复杂度:
- 高(需要 GPU 计算着色器)
- 需要 Cluster 分配
- 需要 Cluster 感知的光源查询19.5 多 Pass 渲染流程
多 Pass 渲染通用流程:
Pass 1: Scene → G-Buffer/RT1
Pass 2: RT1 → RT2 (后处理)
Pass 3: RT2 → Screen (呈现)
每个 Pass 需要:
1. 新的 Render Pass(或复用)
2. 新的 Framebuffer
3. 同步屏障(确保数据就绪)
4. 切换 Pipeline
5. 切换描述符