Dynamic and static Sprite3D Draw call batching

Related Topics:
http://discuss.cocos2d-x.org/t/performance-question-sprite3d-performance/29606/23,
http://discuss.cocos2d-x.org/t/optimization-reduce-draw-call-for-models-with-different-parts/28488,
http://discuss.cocos2d-x.org/t/instancing-sprite3d-drawing-the-same-3d-object-400-times-in-a-scene/21347

Unity engine has this feature: dynamic and static batch drawing for 3d models.
https://docs.unity3d.com/Manual/DrawCallBatching.html
It would be great if cocos2d-x would have this also.
Now to create a game similar to Minecraft is ridiculously expensive. Each Sprite3D cube calls additional GL draw functions.

In this picture: GL calls: 262, GL Verts: 61858

Don’t expect much in the way of improvements to the 3D capabilities of cocos2d-x.

With help from shadowphiar
http://discuss.cocos2d-x.org/t/sprite3d-how-to-display-program-generated-geometry/25568/5?u=serebii01
I was able to achieve draw call batching of static 3d cube models like this:

void MapController::mergeCubes(){
    auto sprite3D = Sprite3D::create();
    std::vector< MeshVertexAttrib > attributes = {
        { 3, GL_FLOAT, GLProgram::VERTEX_ATTRIB_POSITION, 3 * sizeof(float) },
        { 3, GL_FLOAT, GLProgram::VERTEX_ATTRIB_NORMAL, 3 * sizeof(float) },
//        { 3, GL_FLOAT, GLProgram::VERTEX_ATTRIB_TANGENT,    3*sizeof(float) },
//        { 3, GL_FLOAT, GLProgram::VERTEX_ATTRIB_BINORMAL,   3*sizeof(float) },
        { 2, GL_FLOAT, GLProgram::VERTEX_ATTRIB_TEX_COORD, 2 * sizeof(float) },
    };
    int perVertexSizeInFloat =  8;  // 3+3+2
    std::vector< float > vertices;
    MeshData::IndexArray indices;
    auto size = 1.f;
    addCube(vertices, indices, 20.f, 6.f, 0.f, size);
    addCube(vertices, indices, 2.f, 10.f, 0.f, size);
    addCube(vertices, indices, 8.f, 2.f, 0.f, size);
    sprite3D->addMesh(Mesh::create(vertices, perVertexSizeInFloat, indices, attributes));
    sprite3D->setMaterial(Sprite3DMaterial::createBuiltInMaterial(Sprite3DMaterial::MaterialType::UNLIT, false));
    sprite3D->setPosition3D(cocos2d::Vec3(1.f,1.f, 1.f));
    sprite3D->setTexture("textures/paper_house.png");
    gameScene->addChild(sprite3D);
}

void MapController::addCube(std::vector<float> &vertices, cocos2d::MeshData::IndexArray& indices, float x, float y, float z, float s){
    int perVertexSizeInFloat =  8;  // 3+3+2
    size_t startindex = vertices.size() / perVertexSizeInFloat;
    vertices.insert ( vertices.end(), {
        // position  normal  tangent binormal uvTextCoords
        // +x
        x+s,y-s,z-s, 1,0,0,  /*0,0,-1, 0,-1,0,*/ 0.2765, 0.3439,
        x+s,y+s,z-s, 1,0,0,  /*0,0,-1, 0,-1,0,*/ 0.5004, 0.3439,
        x+s,y-s,z+s, 1,0,0,  /*0,0,-1, 0,-1,0,*/ 0.2765, 0.5679,
        x+s,y+s,z+s, 1,0,0,  /*0,0,-1, 0,-1,0,*/ 0.5004, 0.5679,
        // +y
        x-s,y+s,z-s, 0,1,0,  /*1,0,0,  0,0,1,*/  0.2765, 0.3444,
        x+s,y+s,z-s, 0,1,0,  /*1,0,0,  0,0,1,*/  0.0525, 0.3444,
        x-s,y+s,z+s, 0,1,0,  /*1,0,0,  0,0,1,*/  0.2765, 0.5684,
        x+s,y+s,z+s, 0,1,0,  /*1,0,0,  0,0,1,*/  0.0525, 0.5684,
        // +z
        x-s,y-s,z+s, 0,0,1,  /*/*0,0,-1, 1,0,0,*/   0.2780f, 0.7943f,
        x+s,y-s,z+s, 0,0,1,  /*0,0,-1, 1,0,0,*/   0.2780f, 0.5703f,
        x-s,y+s,z+s, 0,0,1,  /*0,0,-1, 1,0,0,*/   0.5019f, 0.7943f,
        x+s,y+s,z+s, 0,0,1,  /*0,0,-1, 1,0,0,*/   0.5019f, 0.5703f,
        // -x
        x-s,y-s,z-s, -1,0,0, /*0,0,1,  0,-1,0,*/ 0.9482, 0.3459,
        x-s,y+s,z-s, -1,0,0, /*0,0,1,  0,-1,0,*/ 0.9482, 0.5699,
        x-s,y-s,z+s, -1,0,0, /*0,0,1,  0,-1,0,*/ 0.7243, 0.5699,
        x-s,y+s,z+s, -1,0,0, /*0,0,1,  0,-1,0,*/ 0.7243, 0.3459,
        // -y
        x-s,y-s,z-s, 0,-1,0, /*-1,0,0, 0,0,1,*/  0.5014, 0.5694,
        x+s,y-s,z-s, 0,-1,0, /*-1,0,0, 0,0,1,*/  0.5014, 0.3454,
        x-s,y-s,z+s, 0,-1,0, /*-1,0,0, 0,0,1,*/  0.7253, 0.3454,
        x+s,y-s,z+s, 0,-1,0, /*-1,0,0, 0,0,1,*/  0.7253, 0.5694,
        // -z
        x-s,y-s,z-s, 0,0,-1,  /*0,0,1, 1,0,0,*/  0.4974, 0.3454,
        x+s,y-s,z-s, 0,0,-1,  /*0,0,1, 1,0,0,*/  0.4974, 0.1215,
        x-s,y+s,z-s, 0,0,-1,  /*0,0,1, 1,0,0,*/  0.7214, 0.3454,
        x+s,y+s,z-s, 0,0,-1,  /*0,0,1, 1,0,0,*/  0.7214, 0.1215,
    } );
    
    MeshData::IndexArray meshindices = { 0, 3, 2, 1, 3, 0,
        4, 6, 7, 4, 7, 5,
        8, 11, 10, 9, 11, 8,
        12,  14,  15,  13,  12,  15,
        16,  19,  18,  17,  19,  16,
        20,  22,  23,  21,  20,  23
    };
    
    for (auto i : meshindices)
    {
        indices.push_back((unsigned short)(startindex+i));
    }
}

This code merges 3 cubes that are same sizes, same texture, different position 3d.
Those 3 cubes get drawn just with 1 draw call instead of 3 draw calls.
Now if I want that those 3 cubes would have different textures I have following options:

  1. Merge several textures into one picture (textureAtlas). And redefine texture coordinates in addCube() method. But this comes with limitation that you should create texture atlases not bigger than 1024x1024 because of the performance issue on older devices (like iPhone 3). So it may be a problem to merge several textures into one picture if the texture size is reasonably big.
  2. If I would have for example 100 cubes and 10 different textures for them, I could merge cubes for each texture, so it would be 10 Sprite3D nodes each merged from 10 meshes each with material of different texture. So instead of 100 Gl draw calls I would get only 10 calls to render all 100 cubes.
2 Likes

Example of result:


In this picture: GL calls: 14 (only 1 for all cubes), GL Verts: 149401