GPU Gems 2 · Chapter 4. Segment Buffering

把空间相邻的重复实例排序并拼进 segment buffer,再按可见集合合并连续区间,减少 draw call,同时保留不重复的场景布局。

学习目标

  • 能说明大量小实例为什么会被 draw call 和 render-state 切换,而不是三角形数量本身拖慢
  • 能实现“空间排序 → 大 buffer → 可见区间合并”的三步 segment buffering 流程
  • 能判断 segment buffering 何时会破坏独立剔除,以及 texture atlas 如何减少 light map 状态切换
  • 能使用实验调整实例分布和可见性,解释 draw range 数量为何随连续性变化

实例化能减少建模工作,却不必然减少批次数。如果场景里有成百上千个小物体,每个物体都带着自己的变换、纹理或 vertex stream,驱动和 CPU 可能在真正画三角形之前就耗尽时间。

Many instances → many batches → CPU overhead逐实例绘制batch 1batch 2batch 3batch 4batch 5batch 6batch 7batch 8batch 9transform / light map / streamsegment buffering一个大 buffer记录每个实例的 segmentvisible segments → 合并 range少量 draw calls
问题不在三角形太多,而在很多很小的实例把 CPU 和驱动拖进了状态切换。

的目标是把“许多独立实例”变成“一个可切片的大实例”。它不要求场景中的模型重复排列成规则网格,也不要求艺术家预先手工合并地图。

1. 先把实例变成空间顺序

是这项技术的关键。可以把实例位置送进三维 k-d tree、octree 或其他空间结构,然后采用稳定的遍历顺序生成一维列表。只要实例列表没有变化,这一步通常只在加载期执行。

空间顺序不是为了改变物体位置,而是为了改变它们在大 buffer 中的邻接关系。相机看到一个局部区域时,附近实例更可能形成连续段,从而在一次 indexed draw 中覆盖更多物体。

Segment buffering:从空间列表到绘制范围1Spatial ordering让相邻对象在数组中相2Build buffer变换到世界空间并记录 segment3Merge ranges只绘制可见且相邻的 buffer 区间顺序决定 buffer 布局;布局决定哪些可见 segment 能合并
排序只在实例列表改变时做;每帧的工作集中在可见性筛选和相邻区间合并。

2. 构建 uber-instance 并保留 segment 元数据

会在构建期遍历空间排序后的实例,把每个模型变换到世界空间后写进一份 vertex/index buffer。每个实例在这份 buffer 中的起点、顶点数、索引起点和索引数都必须保存。

每个这样的记录就是一个 。segment 让合并可逆:渲染器虽然提交的是大 buffer 的范围,但仍然知道哪些范围对应哪些原始实例,因此可以把视锥体外的对象排除掉。

3. 从 visibility set 合并连续范围

先由原始实例得到,再映射到 segment 列表。遍历这些 segment 时,如果下一个可见 segment 紧跟在当前 range 后面,就扩展同一个 draw range;遇到不可见或不兼容的段,就结束当前 range。

Visibility set → adjacent segments → draw rangesS0visibleS1visibleS2skipS3visibleS4visibleS5visibleS6skipS7visibleS8skipS9visiblerange S0–S1range S3–S5range S7结果仍等价于逐实例绘制,但提交次数随连续可见段减少
segment 仍保留独立可见性;只有在大 buffer 中连续的可见段才适合合并。

这样做的输出应该与逐实例绘制相同:不可见实例不产生像素,可见实例保持原来的世界变换和材质含义。性能收益来自提交次数和状态变化下降,而不是牺牲独立剔除。

4. 材质状态与 texture atlas

如果每个实例都有不同的 light map,顶点和索引虽然可以相邻,纹理绑定仍然会在它们之间插入状态切换。把这类状态差异转成坐标差异:构建每个 segment 时改写其 UV,让它采样 atlas 中自己的矩形区域。

Light map 状态:独立纹理 vs texture atlas分散纹理instance 0 → bindinstance 1 → bindinstance 2 → bindinstance 3 → bind每段插入状态变化Texture atlas改 UV 区域,减少绑定切换
如果每个实例都切换独立 light map,就无法随意合并;atlas 把状态切换收拢到坐标。

atlas 不是自动解决所有材质差异的魔法。透明模式、shader、采样器或不同渲染 pass 仍可能要求拆分;正确的合并条件是顶点布局和所有影响绘制的状态都兼容。

5. 动手实验:连续性比数量更重要

把实例数量调高,再打开可见性剔除。你可能会看到可见实例减少,却没有按相同比例减少 draw range,因为被剔除的段把剩余可见段切碎了。再改变空间叶大小,观察排序粒度如何影响连续性。

Segment buffering 实验

观察可见实例如何从大 buffer 中合并成 draw ranges。

spatially ordered segment buffer可见段:10 / 12range 01range 36range 811叶大小 3 · draw ranges 3

当前要提交 3 个 draw range。剔除越多不一定越少:若可见段被打散,合并机会会下降。

实验将 segment 简化成一维条带,但保留了真实流程的两个不变量:每个实例有自己的段记录,只有相邻可见段才能合并。真实系统还需要按材质、pass 和 atlas 区域分组。

小结

  • segment buffering 面向的是大量小实例造成的 draw-call 和状态切换瓶颈。
  • spatial ordering 让空间相近对象在大 buffer 中相邻,为区间合并创造条件。
  • uber-instance 只是一份合并几何;segment 元数据和 visibility set 负责保留剔除能力。
  • 只有连续且状态兼容的 segment 才能合成一个 draw range。
  • texture atlas 可把 light map 绑定切换转成 UV 区域变化,但不会消除 shader 或 pass 不兼容。

练习

问题 1|三步实现 请为静态树林写出 segment buffering 的构建期和渲染期流程,并标出哪些步骤不应每帧重复。

问题 2|剔除判断 一个不可见实例位于两个可见实例之间。为什么不能把三者无条件合成一个 draw range?

问题 3|状态兼容 大 buffer 的几何连续,但每个实例使用独立 light map。你会先改索引还是先设计 texture atlas?为什么?

名词解释

本章出现的专业名词,用大白话再讲一遍。

segment buffer
把多个实例拼成一个大 vertex/index buffer、同时保存各段元数据的结构。
spatial ordering
按空间邻近性生成实例一维顺序的组织方式。
uber-instance
由多个实例拼成、可按区间切片绘制的合并几何。
segment
大 buffer 中对应一个原始实例的顶点和索引范围。
visibility set
经过可见性筛选后需要绘制的实例集合。
texture atlas
把多张纹理放进一张大纹理并用 UV 子区域区分的布局。

资料与写作方式声明

本章以GPU Gems 2 · Chapter 4. Segment Buffering权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…