How to read GLTF/GLB files manually

Recommendation: Use the NSDT scene editor to quickly build a 3D application scene

file type

GLTF files come in two different main file types: .gltf and .glb.

A GLTF file is essentially just a renamed json file, they are often compared to .bin files that contain things like vertex data, but these can also be included directly in the json.

GLB files are similar to GLTF files, but everything is contained in the same file. It is divided into three parts, a small header, json string and binary buffer.

The picture comes from the official gltf github

GLTF format

In GLTF, everything related to meshes, animations, and skinning is stored in buffers, and while reading from a raw binary file without a library might seem daunting at first, it's actually not very Disaster. We'll do it step by step.

In this article we describe how to read vertex position data from a single mesh from .gltf and .glb files.

Before we get to the actual code, we need to understand how to use the json portion of the file to find what we want, since we have to jump around to find anything. You can start at the scene level and work your way down if you want, but since I plan to only use formatting for a single mesh, I'll start with the graph's mesh node.

Let's say our GLTF file looks like this (note that the actual file will contain more data):

{
    "accessors" : [
        {
         "bufferView": 0,
         "byteOffset": 0,
         "componentType": 5126,
         "count": 197,
         "max": [ -0.004780198, 0.0003038254, 0.007360002 ],
         "min": [ -0.008092392, -0.008303153, -0.007400591 ],
         "type": "VEC3"
      }
    ],
   "buffers": [
      {
         "byteLength": 2460034,
         "uri": "example.bin"
      }
   ],
    "bufferViews": [
      {
         "buffer": 0,
         "byteLength": 306642,
         "target": 34963,
         "byteOffset": 2153392
      },
    ],
    "meshes": [
      {
         "name": "example mesh",
         "primitives": [
            {
               "attributes": {
                  "POSITION": 0,
                  "NORMAL": 1,
                  "TEXCOORD_0": 2,
                  "TANGENT": 3
               },
               "indices": 4,
               "material": 0,
               "mode": 4
            }
         ]
      }
    ]
}

To look up the mesh's position data, we first need to access the "meshes" key at index 0, then the first primitive. (As far as I know, primitives are essentially just subgrids. Then we'll retrieve Properties->Position. This will give us the index of the accessor. Inserting it, we can get from the first accessor "bufferView" value. This then gives us the index of the buffer view, which we can finally use to get the buffer to retrieve data from. In this case, the buffer is stored in the external file "example.bin" .After opening the file, we go to the position given to us by "byteOffset" in the accessor, and finally read the buffer data.

Let's start to introduce how to read data from gltf/glb files respectively. During this process, you can use GLTF editor to edit 3D model files and verify model data.

Read from GLTF file

I'll be using c++ in my example code, but the steps should be roughly the same for any other language.

// First define our filname, would probbably be better to prompt the user for one
const std::string& gltfFilename = "example.gltf"

// open the gltf file
std::ifstream jsonFile(gltfFilename, std::ios::binary);

// parse the json so we can use it later
Json::Value json;

try{
    jsonFile >> json;
}catch(const std::exception& e){
    std::cerr << "Json parsing error: " << e.what() << std::endl;
}
jsonFile.close();

// Extract the name of the bin file, for the sake of simplicity I'm assuming there's only one
std::string binFilename = json["buffers"][0]["uri"].asString();

// Open it with the cursor at the end of the file so we can determine it's size,
// We could techincally read the filesize from the gltf file, but I trust the file itself more
std::ifstream binFile = std::ifstream(binFilename, std::ios::binary | std::ios::ate);

// Read file length and then reset cursor
size_t binLength = binFile.tellg();
binFile.seekg(0);


std::vector<char> bin(binLength);
binFile.read(bin.data(), binLength);
binFile.close();



// Now that we have the files read out, let's actually do something with them
// This code prints out all the vertex positions for the first primitive

// Get the primitve we want to print out: 
Json::Value& primitive = json["meshes"][0]["primitives"][0];


// Get the accessor for position: 
Json::Value& positionAccessor = json["accessors"][primitive["attributes"]["POSITION"].asInt()];


// Get the bufferView 
Json::Value& bufferView = json["bufferViews"][positionAccessor["bufferView"].asInt()];


// Now get the start of the float3 array by adding the bufferView byte offset to the bin pointer
// It's a little sketchy to cast to a raw float array, but hey, it works.
float* buffer = (float*)(bin.data() + bufferView["byteOffset"].asInt());

// Print out all the vertex positions 
for (int i = 0; i < positionAccessor["count"].asInt(); ++i)
{
    std::cout << "(" << buffer[i*3] << ", " << buffer[i*3 + 1] << ", " << buffer[i*3 + 2] << ")" << std::endl;
}

// And as a cherry on top, let's print out the total number of verticies
std::cout << "vertices: " << positionAccessor["count"].asInt() << std::endl;

Read from GLB file:

Reading from the .glb file is a bit more difficult since we can't just feed it into a JSON parser, but it's doable. Referring to the image above in the file types section, we can find all the information about the required file format:

std::ifstream binFile = std::ifstream(glbFilename, std::ios::binary); 

binFile.seekg(12); //Skip past the 12 byte header, to the json header
uint32_t jsonLength;
binFile.read((char*)&jsonLength, sizeof(uint32_t)); //Read the length of the json file from it's header

std::string jsonStr;
jsonStr.resize(jsonLength);
binFile.seekg(20); // Skip the rest of the JSON header to the start of the string
binFile.read(jsonStr.data(), jsonLength); // Read out the json string

// Parse the json
Json::Reader reader;
if(!reader.parse(jsonStr, _json))
	std::cerr << "Problem parsing assetData: " << jsonStr << std::endl;

// After reading from the json, the file cusor will automatically be at the start of the binary header

uint32_t binLength;
binFile.read((char*)&binLength, sizeof(binLength)); // Read out the bin length from it's header
binFile.seekg(sizeof(uint32_t), std::ios_base::cur); // skip chunk type

std::vector<char> bin(binLength);
binFile.read(bin.data(), binLength);


//Now you're free to use the data the same way we did above

Summarize

Hope this helps you. I know it's a bit lacking in detail in some areas, so I'll probably come back and update it with more info on animations and skins once I know more. But until then, bye.

Original link: Understanding glTF 2.0 format

Guess you like

Origin blog.csdn.net/ygtu2018/article/details/132685320