• GLB文件格式解析


    一、GLB简介

    GLB是以GL传输格式(gltf)保存3D模型的一种二进制文件格式。

    二、关键数据

    解析GLB文件,可以先获取JSON块,然后再依据说明依次解析里面的数据

    • meshs

      • primitives

        表明mesh包含的数据内容,每个值以索引的方式指向buffer view

        • attributes

          POSITION: accessors_ID (表示顶点坐标值)

          NORMAL: accessors_ID (表示顶点归一化后坐标值)

        • indices: accessors_ID (表示索引)

      • accessors

        访问器,可以理解成访问数据的一个接口,以数组形式存储,每个item内容如下:

        • bufferView: buffer_view_ID
        • componentType: 数据类型(如:unsigned short / float ...)
        • count: 数据个数
        • type: 数据存储类型 (如:``SCALAR标量 /VEC3` 3D向量 ...)
      • bufferView

        说明每个数据流的信息

        • buffer: buffer_ID
        • byteOffset: 数据起始位置
        • 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

    https://blog.csdn.net/hankern/article/details/101796600

    https://blog.csdn.net/qq_33656619/article/details/122056159

  • 相关阅读:
    java8--- Optional使用
    java8--- (Function、Predicate、Consumer) 通用函数式接口
    java8--- Predicate 意义 代码
    Java8---函数式编程-示例
    java8-----lambda语法
    java8----Predicate接口的使用
    Windows 下安装 ElasticSearch 修改 elasticsearch.yml的坑
    kafka 安装教程
    list 转 map java8
    数组转字符串 java8
  • 原文地址:https://www.cnblogs.com/xiaxuexiaoab/p/16392946.html
Copyright © 2020-2023  润新知