一、GLB简介
GLB是以GL传输格式(gltf)保存3D模型的一种二进制文件格式。
-
GLTF文件结构图
参考文档:https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.pdf
参考手册: https://www.khronos.org/files/gltf20-reference-guide.pdf
-
GLB文件结构
包含头文件块和数据块两部分,其中头文件以
uint32
存储专用符magic
,版本号version
以及文件长度length
信息。数据块又分为
JSON
和BIN
两部分,JSON
主要存储一些数据说明,包含scenes
、nodes
、meshs
、accessors
、bufferView
、buffer
等信息说明,BIN
以base64编码存储数据。- 数据存储方式
- 数据存储内容
参考:https://docs.fileformat.com/3d/glb/
二、关键数据
解析GLB文件,可以先获取JSON
块,然后再依据说明依次解析里面的数据
-
meshs
-
primitives
表明
mesh
包含的数据内容,每个值以索引的方式指向buffer view-
attributes
POSITION
: accessors_ID (表示顶点坐标值)NORMAL
: accessors_ID (表示顶点归一化后坐标值) -
indices
: accessors_ID (表示索引)
-
-
accessors
访问器,可以理解成访问数据的一个接口,以数组形式存储,每个
item
内容如下:bufferView
: buffer_view_IDcomponentType
: 数据类型(如:unsigned short / float ...)count
: 数据个数type
: 数据存储类型 (如:``SCALAR标量 /
VEC3` 3D向量 ...)
-
bufferView
说明每个数据流的信息
buffer
: buffer_IDbyteOffset
: 数据起始位置byteLength
: 数据长度
-
buffer
base64编码的数据
byteLength
: 数据长度
-
三、GLB数据
在Windows下搜索3D查看器,点击文件 ==> 3D资源库 ==> 选择模型 ==> 文件另存为即可得到glb模型文件
四、GLB读取
借助JsonCPP
库可以读取头部JSON信息,然后解析出位置和索引的相关信息,然后分别进行解析其个数、存储类型等信息。
展开代码,查看头文件代码
- GLBFileHandler.h
class GLBFileHandler
{
public:
GLBFileHandler();
~GLBFileHandler();
bool Read(const std::string& fileName, Mesh& mesh);
bool Write(const std::string& fileName, Mesh& mesh); // TODO
private:
bool ReadBinary(const std::string& fileName, Mesh& mesh);
private:
static const size_t headerByteLength_;
};
展开代码,查看源文件代码
- GLBFileHandler.cpp
const size_t GLBFileHandler::headerByteLength_ = 3 * sizeof(uint32_t);
GLBFileHandler::GLBFileHandler()
{
}
GLBFileHandler::~GLBFileHandler()
{
}
namespace
{
struct GLBHeader
{
uint32_t magic; // magic equals 0x46546C67. It is ASCII string glTF, and can be used to identify data as Binary glTF
uint32_t version; // indicates the version of Binary glTF container format
uint32_t fileLength; // the total length of the Binary glTF, including Header and all chunks in bytes, chunks that contains JSON content and binary data
};
struct GLBChunks
{
uint32_t length; // length of chunkData in bytes
uint32_t type; // 0x4E4F534A(ASCII string JSON: Structured JSON content, this chunk must be padded with trailing Space chars (0x20).), 0x004E4942(ASCII string BIN: Binary buffer, must be padded with trailing zeros (0x00) )
// uint8_t* data; // length of chunkData in bytes
};
// enum componentType {SIGNED_BYTE = 5120, UNSIGNED_BYTE, SIGNIED_SHORT, UNSIGNED_SHORT, UNSIGNED_INT, SIGNED_FLOAT};
inline int GetComponentTypeByte(const int typeId) {
if (typeId < 5120 || typeId > 5126) {
return 0;
}
if (typeId == 5120 || typeId == 5121) { // signed byte / unsigned byte
return sizeof(unsigned char);
}
else if (typeId == 5122 || typeId == 5123) { // signed short / unsigned short
return sizeof(unsigned short);
}
else if (typeId == 5125 || typeId == 5126){ // unsigned int / float
return sizeof(unsigned int);
}
else {
return 0;
}
}
inline int GetComponentNumber(const std::string typeStr) {
if (typeStr == "SCALAR") {
return 1;
}
else if (typeStr == "VEC2") {
return 2;
}
else if (typeStr == "VEC3") {
return 3;
}
else if (typeStr == "VEC4") {
return 4;
}
else if (typeStr == "MAT2") {
return 4;
}
else if (typeStr == "MAT3") {
return 9;
}
else if (typeStr == "MAT4") {
return 16;
}
else {
return 0;
}
}
inline void GetIndex(const int componentType, const int count, const int dataSize, std::ifstream& binaryFile, std::vector<uint32_t>& indexs) {
if (componentType == 5123) {
uint16_t idx;
for (int i = 0; i < count; ++i) {
binaryFile.read((char*)(&idx), dataSize);
indexs.push_back(idx);
}
}
else if (componentType == 5125) {
uint32_t idx;
for (int i = 0; i < count; ++i) {
binaryFile.read((char*)(&idx), dataSize);
indexs.push_back(idx);
}
}
else if (componentType == 5120) {
char idx;
for (int i = 0; i < count; ++i) {
binaryFile.read((char*)(&idx), dataSize);
indexs.push_back(idx);
}
}
else if (componentType == 5121) {
unsigned char idx;
for (int i = 0; i < count; ++i) {
binaryFile.read((char*)(&idx), dataSize);
indexs.push_back(idx);
}
}
else if (componentType == 5122) {
short idx;
for (int i = 0; i < count; ++i) {
binaryFile.read((char*)(&idx), dataSize);
indexs.push_back(idx);
}
}
}
}
bool GLBFileHandler::ReadBinary(const std::wstring& fileName, Mesh& mesh, double scaleFactor)
{
std::ifstream binaryFile;
binaryFile.open(fileName, std::ios::in | std::ios::binary);
if (!binaryFile)
{
std::cout << "open file " + fileName + " failed") << std::endl;
return false;
}
GLBHeader glbHeader;
binaryFile.read((char*)&glbHeader, headerByteLength_);
if (glbHeader.magic != 0x46546C67) { // 0x46546C67 equals glTF(ASCII string)
std:: cout << "file " + fileName + "is not a valid GLB file!" << std::endl;
return false;
}
// Json Chunk
GLBChunks glbJSONChunk;
binaryFile.read((char*)&(glbJSONChunk.length), sizeof(uint32_t));
binaryFile.read((char*)&(glbJSONChunk.type), sizeof(uint32_t));
if (glbJSONChunk.type != 0x4E4F534A || glbJSONChunk.length % 4 != 0) {
// The start and end of each chunk must be aligned to 4-byte boundary and padding should be used for this purpose.
std::cout << "file " + fileName + "is not a valid GLB file, JSON not the first chunk!" << std::endl;
return false;
}
uint8_t * glbJSONChunkData = new uint8_t[glbJSONChunk.length];
binaryFile.read((char*)(glbJSONChunkData), glbJSONChunk.length);
Json::CharReaderBuilder readerBuild;
Json::CharReader* reader(readerBuild.newCharReader());
Json::Value jsonData;
JSONCPP_STRING jsonErrs;
if (!reader->parse(reinterpret_cast<char*>(glbJSONChunkData), reinterpret_cast<char*>(glbJSONChunkData + glbJSONChunk.length), &jsonData, &jsonErrs)) {
std::cout << "file " + fileName + "is not a valid GLB file, json data is not Available!" << std::endl;
return false;
}
// scenes --> nodes --> meshes --> accessors --> bufferViews --> buffer
if (!jsonData.isMember("scenes") || !jsonData.isMember("nodes") || !jsonData.isMember("meshes") || !jsonData.isMember("accessors")
|| !jsonData.isMember("bufferViews") || !jsonData.isMember("buffers"))
{
std::cout << "file " + fileName + "is not a valid GLB file, json data has not enough keys!" << std::endl;
return false;
}
if (jsonData["scenes"].size() < 1 || jsonData["nodes"].size() < 1 || jsonData["meshes"].size() < 1) {
std::cout << "file " + fileName + "is not a valid GLB file, JSON value has not nodes!" << std::endl;
return false;
}
// get accessors and bufferViews
if (jsonData["accessors"].size() < 1 || jsonData["bufferViews"].size() < 1) {
std::cout << "file " + fileName + "is not a valid GLB file, json data has not accessors or bufferViews!" << std::endl;
return false;
}
Json::Value accessors = jsonData["accessors"];
Json::Value bufferViews = jsonData["bufferViews"];
// mesh's position/normal and indices
Json::Value meshs = jsonData["meshes"][0];
if (meshs["primitives"].size() < 1) {
std::cout << "file " + fileName + "is not a valid GLB file, meshs has not primitives!" << std::endl;
return false;
}
Json::Value primitives = meshs["primitives"][0];
int indicesAccessorId = -1;
if (primitives.isMember("indices")) {
indicesAccessorId = primitives["indices"].asInt();
}
if (primitives["attributes"].size() < 1) {
std::cout << "file " + fileName + "is not a valid GLB file, primitives has not attributes!" << std::endl;
return false;
}
Json::Value attributes = primitives["attributes"];
std::vector<std::string> keys = attributes.getMemberNames();
int positionAccessorId = -1;
if (attributes.isMember("POSITION")) {
positionAccessorId = attributes["POSITION"].asInt();
}
// get indices/position 's accessors
int indicesCount = 0, positionCount = 0;
int indicesComponentType = 5125, positionComponentType = 5126;
std::string indicesType = "SCALAR", positionType = "VEC3";
int indicesBufferOffset = 0, positionBufferOffset = 0;
int indicesBufferLen = 0, positionBufferLen = 0;
if (indicesAccessorId >= 0) {
indicesCount = accessors[indicesAccessorId]["count"].asInt();
indicesComponentType = accessors[indicesAccessorId]["componentType"].asInt();
indicesType = accessors[indicesAccessorId]["type"].asString();
int indicesBufferViewId = accessors[indicesAccessorId]["bufferView"].asInt(); // may be not defined
Json::Value bufferView = bufferViews[indicesBufferViewId];
indicesBufferOffset = bufferView["byteOffset"].asInt();
indicesBufferLen = bufferView["byteLength"].asInt();
}
if (positionAccessorId >= 0) {
positionCount = accessors[positionAccessorId]["count"].asInt();
positionComponentType = accessors[positionAccessorId]["componentType"].asInt();
positionType = accessors[positionAccessorId]["type"].asString();
int positionBufferViewId = accessors[positionAccessorId]["bufferView"].asInt();
Json::Value bufferView = bufferViews[positionBufferViewId];
positionBufferOffset = bufferView["byteOffset"].asInt();
positionBufferLen = bufferView["byteLength"].asInt();
}
GLBChunks glbBINChunk;
binaryFile.read((char*)&(glbBINChunk.length), sizeof(uint32_t));
binaryFile.read((char*)&(glbBINChunk.type), sizeof(uint32_t));
if (glbBINChunk.type != 0x004E4942) {
std::cout << "file " + fileName + "is not a valid GLB file, Not BIN chunk!" << std::endl;
return false;
}
std::vector<uint32_t> indexs;
if (indicesAccessorId >= 0)
{
binaryFile.seekg(indicesBufferOffset, binaryFile.cur);
int dataSize = GetComponentTypeByte(indicesComponentType) * GetComponentNumber(indicesType);
assert(indicesBufferLen == indicesCount * dataSize);
GetIndex(indicesComponentType, indicesCount, dataSize, binaryFile, indexs);
positionBufferOffset = positionBufferOffset - indicesBufferOffset - indicesBufferLen;
}
std::vector<core::Vector3f> coords;
if (positionAccessorId >= 0)
{
binaryFile.seekg(positionBufferOffset, binaryFile.cur);
int dataSize = GetComponentTypeByte(positionComponentType) * GetComponentNumber(positionType);
assert(positionBufferLen == positionCount * dataSize);
core::Vector3f coord;
for (int i = 0; i < positionCount; ++i) {
binaryFile.read((char*)(&coord), dataSize);
coords.push_back(coord);
}
}
const int pointCount = coords.size();
mesh.ReservePoint(pointCount);
mesh.ReserveIndex(indexs.size() / 3);
for (uint32_t i = 0; i < pointCount; ++i)
{
mesh.AddPoint(coords[i][0] * scaleFactor, coords[i][1] * scaleFactor, coords[i][2] * scaleFactor);
}
for (uint32_t i = 0; i < indexs.size(); i += 3) {
mesh.AddFace(indexs[i], indexs[i + 1], indexs[i + 2]);
}
return true;
}
bool GLBFileHandler::Read(const std::wstring& fileName, Mesh& mesh, const FileOptions& fileOption)
{
bool returnValue = ReadBinary(fileName, mesh, fileOption.ScaleFactorsToDefault());
//find the file name as the mesh name
std::wstring fname = fileName;
// Remove directory if present.
// Do this before extension removal incase directory has a period character.
const size_t last_slash_idx = fname.find_last_of(L"\\/");
if (std::string::npos != last_slash_idx)
{
fname.erase(0, last_slash_idx + 1);
}
// Remove extension if present.
const size_t period_idx = fname.rfind('.');
if (std::wstring::npos != period_idx)
{
fname.erase(period_idx);
}
mesh.SetName(fname);
return true;
}
五、参考连接
https://zhuanlan.zhihu.com/p/65265611