Search code examples
c++graphicsgltf

Serialize mesh data to gltf


I'm using (more precisely, "I'd like to use") the tinygltf library to "flush" my mesh data in the gltf (or glb) format.

I'm not tied to a particular input file type, this is data generated from my program so I was looking into a basic example that creates a triangle in gltf from scratch. It's hard to comprehend this code and something I managed to do is to abstract a function to create a buffer out of (float) vertex data (because the example uses hardcoded single byte hex values):

#define BYTE3(value) static_cast<unsigned char> ((value >> 24) & 0xFF)
#define BYTE2(value) static_cast<unsigned char> ((value >> 16) & 0xFF)
#define BYTE1(value) static_cast<unsigned char> ((value >> 8) & 0xFF)
#define BYTE0(value) static_cast<unsigned char> ((value) & 0xFF)

    struct VertexData {
        uint32_t u;

        VertexData(float val) 
        {
            memcpy(&u, &val, sizeof(float));
        }
    };

    std::vector<unsigned char> triangle_to_buffer(
        VertexData v1x, VertexData v1y, VertexData v1z, 
        VertexData v2x, VertexData v2y, VertexData v2z, 
        VertexData v3x, VertexData v3y, VertexData v3z)
    {
        return {
            // 6 bytes of indices and two bytes of padding
            0x00,
            0x00,
            0x01,
            0x00,
            0x02,
            0x00,
            0x00, 0x00,
            // 36 bytes of floating point numbers
            BYTE0(v1x.u), BYTE1(v1x.u), BYTE2(v1x.u), BYTE3(v1x.u),
            BYTE0(v1y.u), BYTE1(v1y.u), BYTE2(v1y.u), BYTE3(v1y.u),
            BYTE0(v1z.u), BYTE1(v1z.u), BYTE2(v1z.u), BYTE3(v1z.u),
            BYTE0(v2x.u), BYTE1(v2x.u), BYTE2(v2x.u), BYTE3(v2x.u),
            BYTE0(v2y.u), BYTE1(v2y.u), BYTE2(v2y.u), BYTE3(v2y.u),
            BYTE0(v2z.u), BYTE1(v2z.u), BYTE2(v2z.u), BYTE3(v2z.u),
            BYTE0(v3x.u), BYTE1(v3x.u), BYTE2(v3x.u), BYTE3(v3x.u),
            BYTE0(v3y.u), BYTE1(v3y.u), BYTE2(v3y.u), BYTE3(v3y.u),
            BYTE0(v3z.u), BYTE1(v3z.u), BYTE2(v3z.u), BYTE3(v3z.u), };
    }

How would one go about generalizing the example code to "flush" mesh data, i.e. data that is just triplets of points (each point has float x,y,z coordinates) designating the triangles composing the mesh? Is generalizing the example code by creating one buffer (and one pair of accessors etc) for each triangle an acceptable solution?


Solution

  • No, one should never place each triangle into its own accessor.

    At the start of your buffer are 6 hard-coded bytes forming an array of unsigned shorts, [ 0, 1, 2 ] with 2 bytes of padding after that (to align to a 32-bit boundary). These indices can help out with a large pool of vertex data when vertices are reused by multiple triangles. For example if you had 4 vertices and wanted to make a quad, you could specify a pair of triangles:

    [
      0, 1, 2,
      0, 2, 3
    ]
    

    The above indices assume there will be 4 vertices and make a pair of triangles covering the quad. As with the example you linked, there would still be only 2 accessors, each with its own bufferView. The lengths of each would be a bit longer, as the indices would now be 6 unsigned shorts, and the vertices would be 12 floats.

    This particular example may be doing you a little bit of a disservice by mixing the indices and vertices together the way it does. The length of the indices accessor will be the number of triangles times 3 unsigned shorts per triangle. The length of the other accessor depends on the number of vertices (times 3 floats per vertex), where vertices can be reused by specifying them multiple times in the index list.

    Order is significant too, at least when not using doubleSided mode. The list of indices should wind counter-clockwise around the front face of each triangle. Clockwise winding indicates the back face of a triangle, which may be culled away when doubleSided is false.