合批的核心是将多个物体从多个 Draw Call 变为一个 Draw Call。这要求所有物体的几何数据(顶点、法线等)被合并到一个大的缓冲区中。
1. 静态合批(Static Batching)
- 区别点: 提前完成(运行时或加载时),永久存储合并后的数据。
- 适用场景: 场景中位置和姿态固定不变的物体,且使用相同材质。
伪代码逻辑
静态合批的核心在于:在预处理阶段,将所有物体的局部顶点坐标通过其模型矩阵:
$$
Mmodel
$$
转换为世界坐标,然后写入一个大的 VBO(顶点缓冲区对象)。
// 预处理阶段 (发生在运行时之前或加载时)
// 1. 初始化一个大的顶点和索引数组
MergedVertices = []
MergedIndices = []
VertexOffset = 0
// 2. 遍历所有静态物体 (Object_A, Object_B, ...)
FOR each StaticObject IN Scene.StaticObjects:
// 获取该物体从局部空间到世界空间的模型矩阵
M_World = StaticObject.ModelMatrix
// 遍历物体的所有顶点
FOR each Vertex IN StaticObject.Vertices:
// *** 核心操作:将局部坐标 P_Local 转换到世界坐标 P_World ***
P_World = M_World * Vertex.Position
// 将转换后的 P_World 加入到总的数组中
MergedVertices.ADD(P_World, P_World_Normal, P_World_Tangent, ...)
// 调整索引:因为顶点被合并,索引需要加上之前的偏移量
FOR each Index IN StaticObject.Indices:
AdjustedIndex = Index + VertexOffset
MergedIndices.ADD(AdjustedIndex)
// 更新下一个物体的起始偏移量
VertexOffset += StaticObject.VertexCount
// 3. 将 MergedVertices 和 MergedIndices 上传到 GPU 的 VBO 和 EBO
Upload(MergedVertices, MergedIndices)
// 渲染阶段 (只需要一个 Draw Call)
Render(LargeVBO, LargeEBO) // 只需要一个 Draw Call
2.动态合批(Dynamic Batching)
- 区别点: 运行时每一帧实时完成,数据临时存储。
- 适用场景: 小的、运动中的、且共享相同材质的物体。
伪代码逻辑
动态合批的核心在于:在每一帧的渲染循环中,CPU 实时进行数据转换和合并,然后上传。
// 渲染循环中 (每一帧执行)
// 1. 初始化临时缓冲区
TempVertices = []
TempIndices = []
VertexOffset = 0
DrawCount = 0
// 2. 遍历所有满足合批条件的动态小物体 (不超过顶点限制)
FOR each DynamicObject IN Scene.DynamicObjects:
IF DynamicObject.CanBatch AND DynamicObject.Material == CurrentBatchMaterial:
// 获取当前帧的 Model 矩阵
M_World = DynamicObject.ModelMatrix
// *** 核心操作:实时转换顶点到世界坐标 ***
FOR each Vertex IN DynamicObject.Vertices:
P_World = M_World * Vertex.Position
TempVertices.ADD(P_World, P_World_Normal, ...)
// 调整索引并加入
FOR each Index IN DynamicObject.Indices:
TempIndices.ADD(Index + VertexOffset)
VertexOffset += DynamicObject.VertexCount
DrawCount++
ELSE:
// 如果批次中断,先渲染当前批次
IF TempVertices.COUNT > 0:
Upload(TempVertices, TempIndices)
Render(TempVBO, TempEBO) // Draw Call #1
// 重启新批次或单独渲染此物体
// ...
// 3. 渲染最终批次 (如果存在)
IF TempVertices.COUNT > 0:
Upload(TempVertices, TempIndices)
Render(TempVBO, TempEBO) // Draw Call #N
