文章目录

1、简介

官网地址:
​​​https://www.khronos.org/gltf/​

glTF™ 是一种免版税规范,用于通过引擎和应用程序高效传输和加载 3D 场景和模型。
glTF 定义了一种可扩展的发布格式,通过在整个行业中实现 3D 内容的互操作使用来简化创作工作流程和交互式服务。

glTF™(GL 传输格式)用于在 Web 和本机应用程序中传输和加载 3D 模型。glTF 减少了 3D 模型的大小以及解包和渲染这些模型所需的运行时处理。这种格式在 Web 上很常用,并且在 Unity3D、Unreal Engine 4 和 Godot 等各种 3D 引擎中都有支持。

glTF 的内部结构模仿了图形芯片在实时渲染时常用的内存缓冲区,因此可以将资产交付到桌面、Web 或移动客户端,并以最少的处理迅速显示。因此,在导出到 glTF 时,四边形和 n 边形会自动转换为三角形。

b3dm就是在原来gltf单个模型的基础之上,做了批量化的数据组织方式,多了feature table和batch table两个文件。3D Tiles 是一种开放规范,用于在桌面、Web 和移动应用程序中共享、可视化、融合和与大量异构 3D 地理空间内容交互。

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_glb


【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_b3dm_02


【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_gis_03

1.1 层级结构


  • glTF Object Hierarchy
    这里来几张作者收集的结构图说明gltf的各种对象组成。
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_b3dm_04


  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_gis_05


  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_glb_06

1.2 术语定义

glTF 规范使用常见的工程和图形术语,如image、buffer、texture等来识别和描述某些glTF结构及其属性、状态和行为。本节在规范的上下文中定义了这些术语的基本含义。规范文本提供了更完整的术语定义,并详细阐述、扩展或澄清了这些定义。当本节中定义的术语在规范中以规范语言使用时,规范中的定义支配并取代这些术语在其他技术上下文中(即规范之外)可能具有的任何含义。

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_gis_07

  • accessor
    An object describing the number and the format of data elements stored in a binary buffer.
  • animation
    An object describing the keyframe data, including timestamps, and the target property affected by it.
  • back-facing
    See facingness.
  • buffer
    An external or embedded resource that represents a linear array of bytes.
  • buffer view
    An object that represents a range of a specific buffer, and optional metadata that controls how the buffer’s content is interpreted.
  • camera
    An object defining the projection parameters that are used to render a scene.
  • facingness
    A classification of a triangle as either front-facing or back-facing, depending on the orientation (winding order) of its vertices.
  • front-facing
    See facingness.
  • image
    A two dimensional array of pixels encoded as a standardized bitstream, such as PNG.
  • indexed geometry
    A mesh primitive that uses a separate source of data (index values) to assemble the primitive’s topology.
  • linear blend skinning
    A skinning method that computes a per-vertex transformation matrix as a linear weighted sum of transformation matrices of the designated nodes.
  • material
    A parametrized approximation of visual properties of the real-world object being represented by a mesh primitive.
  • mesh
    A collection of mesh primitives.
  • mesh primitive
    An object binding indexed or non-indexed geometry with a material.
  • mipmap
    A set of image representations consecutively reduced by the factor of 2 in each dimension.
  • morph target
    An altered state of a mesh primitive defined as a set of difference values for its vertex attributes.
  • node
    An object defining the hierarchy relations and the local transform of its content.
  • non-indexed geometry
    A mesh primitive that uses linear order of vertex attribute values to assemble the primitive’s topology.
  • normal
    A unit XYZ vector defining the perpendicular to the surface.
  • root node
    A node that is not a child of any other node.
  • sampler
    An object that controls how image data is sampled.
  • scene
    An object containing a list of root nodes to render.
  • skinning
    The process of computing and applying individual transforms for each vertex of a mesh primitive.
  • tangent
    A unit XYZ vector defining a tangential direction on the surface.
  • texture
    An object that combines an image and its sampler.
  • topology type
    State that controls how vertices are assembled, e.g. as lists of triangles, strips of lines, etc.
  • vertex attribute
    A property associated with a vertex.
  • winding order
    The relative order in which vertices are defined within a triangle
  • wrapping
    A process of selecting an image pixel based on normalized texture coordinates.

2、glTF文件预览

2.1 VSCode(v2.0)

安装第三方扩展glTF Tool。

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_b3dm_08


  • 打开和预览gltf文件
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_b3dm_09


  • 打开和预览glb文件(gltf二进制格式)
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_glb_10

2.2 glTF Viewer(v2.0, js)

​https://gltf-viewer.donmccurdy.com/​

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_gltf_11


【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_b3dm_12

2.3 babylon.js(v2.0,js)

​https://sandbox.babylonjs.com/​

Babylon.js Sandbox - View glTF, glb, obj and babylon files. Drag and drop gltf, glb, obj or babylon files to view them.

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_b3dm_13

3、tinygltf (v2.0, C++)

​https://github.com/syoyo/tinygltf​

Header only C++11 tiny glTF 2.0 library
Header only C++ tiny glTF library(loader/saver).

TinyGLTF is a header only C++11 glTF 2.0 https://github.com/KhronosGroup/glTF library.

TinyGLTF uses Niels Lohmann’s json library(https://github.com/nlohmann/json), so now it requires C++11 compiler. If you are looking for old, C++03 version, please use devel-picojson branch(but not maintained anymore).

注意:目前该库仅支持glTF 2.0格式。

它的OpenGL示例编译依赖库需要额外下载。本身就是几个头文件直接使用。

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_glb_14

3.1 下载和编译

cd C:\Users\tomcat\Desktop\test
git clone https://github.com/syoyo/tinygltf.git
cd tinygltf
mkdir bin
cd bin
cmake ..
## or
cmake -G "Visual Studio 15 2017" .. -A x64
.\\tools\\windows\\premake5.exe vs2017


  • 从GitHub下载该库的源代码
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_glb_15


  • 通过CMake生成该库的vs2017的工程文件
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_gis_16


  • 生成的vs2017的工程文件的相关文件截图
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_3dtiles_17


  • vs2017打开工程文件,进行编译库文件
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_b3dm_18


  • 也可以通过premake5来生成该库vs2017的工程文件,如下图所示:
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_b3dm_19


  • tinygltf自带例子中一个小例子编译后的运行结果
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_gltf_20

3.2 官网代码示例

/ Define these only in *one* .cc file.
#define TINYGLTF_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
// #define TINYGLTF_NOEXCEPTION // optional. disable exception handling.
#include "tiny_gltf.h"

using namespace tinygltf;

Model model;
TinyGLTF loader;
std::string err;
std::string warn;

bool ret = loader.LoadASCIIFromFile(&model, &err, &warn, argv[1]);
//bool ret = loader.LoadBinaryFromFile(&model, &err, &warn, argv[1]); // for binary glTF(.glb)

if (!warn.empty()) {
printf("Warn: %s\n", warn.c_str());
}

if (!err.empty()) {
printf("Err: %s\n", err.c_str());
}

if (!ret) {
printf("Failed to parse glTF\n");
return -1;
}

4、TinyGLTFLoader (v1.0, C++)

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_glb_21

Tiny glTF loader, header only C++ glTF 1.x parsing library.

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_glb_22

4.1 下载和编译

​https://github.com/syoyo/tinygltfloader​​​注意:目前该库仅支持glTF 1.0格式。
这个库不需要编译,直接引用头文件.h就可以了。

4.2 picojson库

​https://github.com/kazuho/picojson/​

a header-file-only, JSON parser serializer in C++
copyright © 2009-2010 Cybozu Labs, Inc. Copyright © 2011-2015 Kazuho Oku

代码示例如下:

const char* json = "{\"a\":1}";
picojson::value v;
std::string err;
const char* json_end = picojson::parse(v, json, json + strlen(json), &err);
if (! err.empty()) {
std::cerr << err << std::endl;
}
picojson::value v;

// parse the input
std::cin >> v;
std::string err = picojson::get_last_error();
if (! err.empty()) {
std::cerr << err << std::endl;
exit(1);
}

// check if the type of the value is "object"
if (! v.is<picojson::object>()) {
std::cerr << "JSON is not an object" << std::endl;
exit(2);
}

// obtain a const reference to the map, and print the contents
const picojson::value::object& obj = v.get<picojson::object>();
for (picojson::value::object::const_iterator i = obj.begin();
i != obj.end();
++i) {
std::cout << i->first << ': ' << i->second.to_str() << std::endl;
}

4.3 官网代码示例

Copy stb_image.h, picojson.h and tiny_gltf_loader.h to your project.

// Define these only in *one* .cc file.
#define TINYGLTF_LOADER_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#include "tiny_gltf_loader.h"

using namespace tinygltf;

Scene scene;
TinyGLTFLoader loader;
std::string err;

bool ret = loader.LoadASCIIFromFile(scene, err, argv[1]);
//bool ret = loader.LoadBinaryFromFile(scene, err, argv[1]); // for binary glTF(.glb)
if (!err.empty()) {
printf("Err: %s\n", err.c_str());
}

if (!ret) {
printf("Failed to parse glTF\n");
return -1;
}

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_3dtiles_23

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_3dtiles_24


【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)_3dtiles_25

4.4 自己测试代码1

根据前面贴的结构图,编写代码遍历gltf1.0文件包含的所有几何数据(顶点、法线、面、纹理坐标、纹理图片数据等)。

//***********************************************************************
// Purpose: tiny_gltf_loader遍历gltf v1.0文件的几何数据
// Author: 爱看书的小沐
// Date: 2022-4-19
// Languages: C++
// Platform: Visual Studio 2017
// OS: Win10 win64
// ***********************************************************************

#define TINYGLTF_LOADER_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#include "tiny_gltf_loader.h"

void DumpNode(const tinygltf::Node &node, tinygltf::Scene &scene) {

for (size_t i = 0; i < node.meshes.size(); i++) {
std::map<std::string, tinygltf::Mesh>::const_iterator it_mesh = scene.meshes.find(node.meshes[i]);
if (it_mesh == scene.meshes.end()) {
continue;
}

const tinygltf::Mesh& mesh = it_mesh->second;
for (size_t i = 0; i < mesh.primitives.size(); i++) {
const tinygltf::Primitive &primitive = mesh.primitives[i];
if (primitive.indices.empty()) return;

///
// get texture data
tinygltf::Material &mat = scene.materials[primitive.material];
if (mat.values.find("diffuse") != mat.values.end()) {
std::string diffuseTexName = mat.values["diffuseTex"].string_value;
//std::string diffuseTexName2 = mat.values["diffuse"].string_value;
if (scene.textures.find(diffuseTexName) != scene.textures.end()) {
tinygltf::Texture &tex = scene.textures[diffuseTexName];
if (scene.images.find(tex.source) != scene.images.end()) {
tinygltf::Image &image = scene.images[tex.source];
image.image;
image.width;
image.height;
image.component;
image.image.size();

}
}
}

///
// get face data primitive.indices
const tinygltf::Accessor indexAccessor = scene.accessors[primitive.indices];
indexAccessor.type;
indexAccessor.componentType;
indexAccessor.byteStride;
indexAccessor.byteOffset;
indexAccessor.count;
const tinygltf::BufferView &bufferView = scene.bufferViews[indexAccessor.bufferView];
const tinygltf::Buffer &buffer = scene.buffers[bufferView.buffer];
printf("\n");

float* pointArr = NULL;
float* normalArr = NULL;
float* textureArr = NULL;
unsigned short* faceIndex = NULL;
int faceCount = 0;
int pointCount = 0;
int normalCount = 0;
int texCount = 0;

faceCount = indexAccessor.count;
faceIndex = new unsigned short[faceCount];
//assert(bufferView.byteLength == faceCount *sizeof(unsigned short));
memcpy(faceIndex, &buffer.data[0] + bufferView.byteOffset+ indexAccessor.byteOffset, faceCount * sizeof(unsigned short));

///
// get point and texture data
std::map<std::string, std::string>::const_iterator it(primitive.attributes.begin());
std::map<std::string, std::string>::const_iterator itEnd(primitive.attributes.end());

for (; it != itEnd; it++) {
assert(scene.accessors.find(it->second) != scene.accessors.end());
const tinygltf::Accessor &accessor = scene.accessors[it->second];
const tinygltf::BufferView &bufferView_pt = scene.bufferViews[accessor.bufferView];
const tinygltf::Buffer &buffer_pt = scene.buffers[bufferView_pt.buffer];

int count = 1;
if (accessor.type == TINYGLTF_TYPE_SCALAR) {
count = 1;
}
else if (accessor.type == TINYGLTF_TYPE_VEC2) {
count = 2;
}
else if (accessor.type == TINYGLTF_TYPE_VEC3) {
count = 3;
}
else if (accessor.type == TINYGLTF_TYPE_VEC4) {
count = 4;
}
else {
assert(0);
}

if (it->first.compare("POSITION") == 0) {
pointCount = accessor.count * count;
pointArr = new float[pointCount];
memcpy(pointArr, &buffer_pt.data[0]+ bufferView_pt.byteOffset + accessor.byteOffset, pointCount* sizeof(float));
}
else if (it->first.compare("NORMAL") == 0) {
normalCount = accessor.count * count;
normalArr = new float[normalCount];
memcpy(normalArr, &buffer_pt.data[0] + bufferView_pt.byteOffset + accessor.byteOffset, normalCount * sizeof(float));
}
else if (it->first.compare("TEXCOORD_0") == 0) {
texCount = accessor.count * count;
textureArr = new float[texCount];
memcpy(textureArr, &buffer_pt.data[0] + bufferView_pt.byteOffset + accessor.byteOffset, texCount * sizeof(float));
}

if ((it->first.compare("POSITION") == 0) ||
(it->first.compare("NORMAL") == 0) ||
(it->first.compare("TEXCOORD_0") == 0)) {

//#define GL_BYTE 0x1400 5120
//#define GL_UNSIGNED_BYTE 0x1401 5121
//#define GL_SHORT 0x1402 5122
//#define GL_UNSIGNED_SHORT 0x1403 5123
//#define GL_INT 0x1404 5124
//#define GL_UNSIGNED_INT 0x1405 5125
//#define GL_FLOAT 0x1406 5126

accessor.type;
accessor.componentType;
accessor.byteStride;
accessor.byteOffset;
accessor.count;
const tinygltf::BufferView &bufferView = scene.bufferViews[accessor.bufferView];
const tinygltf::Buffer &buffer = scene.buffers[bufferView.buffer];
printf("\n");

}
}
}
}

for (size_t i = 0; i < node.children.size(); i++) {
std::map<std::string, tinygltf::Node>::const_iterator it =
scene.nodes.find(node.children[i]);

if (it != scene.nodes.end()) {
//DrawNode(scene, it->second);
}
}
}

static void Dump(const tinygltf::Scene &scene) {
std::cout << "=== Dump glTF ===" << std::endl;
std::cout << "asset.generator : " << scene.asset.generator
<< std::endl;
std::cout << "asset.premultipliedAlpha : " << scene.asset.premultipliedAlpha
<< std::endl;
std::cout << "asset.version : " << scene.asset.version
<< std::endl;
std::cout << "asset.profile.api : " << scene.asset.profile_api
<< std::endl;
std::cout << "asset.profile.version : " << scene.asset.profile_version
<< std::endl;
std::cout << std::endl;
std::cout << "=== Dump scene ===" << std::endl;
std::cout << "defaultScene: " << scene.defaultScene << std::endl;

{
std::map<std::string, tinygltf::Node>::const_iterator it(
scene.nodes.begin());
std::map<std::string, tinygltf::Node>::const_iterator itEnd(
scene.nodes.end());
std::cout << "nodes(items=" << scene.nodes.size() << ")" << std::endl;
for (; it != itEnd; it++) {
DumpNode(it->second, (tinygltf::Scene &)scene);
}
}
}

int main(int argc, char **argv) {
/*if (argc < 2) {
printf("Needs input.gltf\n");
exit(1);
}*/

tinygltf::Scene scene;
tinygltf::TinyGLTFLoader loader;
std::string err;
std::string input_filename("c:\\Users\\tomcat\\Desktop\\BlockB.b3dm");
std::string ext = GetFilePathExtension(input_filename);

bool ret = false;
if (ext.compare("glb") == 0) {
// assume binary glTF.
ret = loader.LoadBinaryFromFile(&scene, &err, input_filename.c_str());
}
else if (ext.compare("gltf") == 0) {
// assume ascii glTF.
ret = loader.LoadASCIIFromFile(&scene, &err, input_filename.c_str());
}
else if (ext.compare("b3dm") == 0) {
ret = loader.LoadB3dmFromFile(&scene, &err, input_filename.c_str());
}
else {
return false;
}

if (!err.empty()) {
printf("Err: %s\n", err.c_str());
}

if (!ret) {
printf("Failed to parse glTF\n");
return -1;
}

Dump(scene);

return 0;
}

4.5 自己测试代码2

在头文件tiny_gltf_loader.h中,增加读取b3dm文件格式(3d地图瓦片)的接口。

//***********************************************************************
// Purpose: tiny_gltf_loader增加读取b3dm文件格式的接口
// Author: 爱看书的小沐
// Date: 2022-4-19
// Languages: C++
// Platform: Visual Studio 2017
// OS: Win10 win64
// ***********************************************************************

// 读取b3dm文件的文件头,返回值为头部的长度
int ReadB3dmHeader(std::ifstream &f, bool &rtcCenterEnable, float *rtcCenterPos)
{
rtcCenterEnable = false;
if (rtcCenterPos) {
rtcCenterPos[0] = 0;
rtcCenterPos[1] = 0;
rtcCenterPos[2] = 0;
}
int header[7];
int len_header = 7 * sizeof(int);

f.seekg(0, f.beg);
std::vector<char> buf(len_header);
f.read(&buf.at(0), static_cast<std::streamsize>(len_header));
::memcpy(header, &buf.at(0), len_header);

int len_featureTable = header[3];
if (len_featureTable > 20 && rtcCenterPos != nullptr) {
char *json = new char[len_featureTable + 1];
f.read(json, len_featureTable);
json[len_featureTable] = '\0';

picojson::value v;
std::string err;
const char* json_end = picojson::parse(v, json, json + strlen(json), &err);
if (!err.empty()) {
std::cerr << err << std::endl;
}

// obtain a const reference to the map, and print the contents
const picojson::value::object& obj = v.get<picojson::object>();
for (picojson::value::object::const_iterator i = obj.begin(); i != obj.end() ; ++i) {
std::cout << i->first << ': ' << i->second.to_str() << std::endl;

if (i->first.compare("RTC_CENTER") == 0) {
const picojson::value::array& center = i->second.get<picojson::array>();
rtcCenterPos[0] = (float)center[0].get<double>();
rtcCenterPos[1] = (float)center[1].get<double>();
rtcCenterPos[2] = (float)center[2].get<double>();
rtcCenterEnable = true;
}
}

delete[] json;
}

return len_header + header[3] + header[4] + header[5] + header[6];
}

// 读取gltf文件的文件头,返回值为gltf的版本号
int ReadGltfHeader(std::ifstream &f, int offset)
{
std::vector<char> buf(16);

f.seekg(offset, f.beg);
f.read(&buf.at(0), static_cast<std::streamsize>(buf.size()));
//std::string strBuf;
//strBuf.clear();
//strBuf.assign(buf.begin(), buf.end());
return buf[4];
}

// 读取b3dm文件的b3dm头部和gltf头部
int TinyGLTFLoader::ReadB3dmVersion(const std::string &filename, bool &rtcCenterEnable, float *rtcCenterPos) {
std::stringstream ss;

std::ifstream f(filename.c_str(), std::ios::binary);
if (!f) {
return 0;
}

int len_b3dm = ReadB3dmHeader(f, rtcCenterEnable, rtcCenterPos);
int ver = ReadGltfHeader(f, len_b3dm);
f.close();
return ver;
}

// 读取b3dm文件的所有内容
bool TinyGLTFLoader::LoadB3dmFromFile(Scene *scene, std::string *err,
const std::string &filename,
unsigned int check_sections) {
std::stringstream ss;

std::ifstream f(filename.c_str(), std::ios::binary);
if (!f) {
ss << "Failed to open file: " << filename << std::endl;
if (err) {
(*err) = ss.str();
}
return false;
}

bool rtcCenterEnable = false;
float rtcCenterPos[3] = {0};
int len_b3dm = ReadB3dmHeader(f, rtcCenterEnable, rtcCenterPos);
int ver = ReadGltfHeader(f, len_b3dm);
if (ver != 1) return false;

f.seekg(0, f.end);
size_t sz = static_cast<size_t>(f.tellg());
std::vector<char> buf(sz - len_b3dm);

f.seekg(len_b3dm, f.beg);
f.read(&buf.at(0), static_cast<std::streamsize>(sz - len_b3dm));
f.close();

std::string basedir = GetBaseDir(filename);

bool ret = LoadBinaryFromMemory(
scene, err, reinterpret_cast<unsigned char *>(&buf.at(0)),
static_cast<unsigned int>(buf.size()), basedir, check_sections);

return ret;
}

结语

​如果您觉得该方法或代码有一点点用处,可以给作者点个赞;​​​╮( ̄▽ ̄)╭
​​​如果您感觉方法或代码不咋地​​​//(ㄒoㄒ)//​​,就在评论处留言,作者继续改进;​​​o_O???
​​​感谢各位童鞋们的支持!​​( ´ ▽´ )ノ ( ´ ▽´)っ!!!