静态合批与动态合批的原理

Number of views 1

合批的核心是将多个物体从多个 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

image1765441390811.png

0 Answers