天天看点

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

文章目录

  • ​​1、简介​​
  • ​​1.1 层级结构​​
  • ​​1.2 术语定义​​
  • ​​2、glTF文件预览​​
  • ​​2.1 VSCode(v2.0)​​
  • ​​2.2 glTF Viewer(v2.0, js)​​
  • ​​2.3 babylon.js(v2.0,js)​​
  • ​​3、tinygltf (v2.0, C++)​​
  • ​​3.1 下载和编译​​
  • ​​3.2 官网代码示例​​
  • ​​4、TinyGLTFLoader (v1.0, C++)​​
  • ​​4.1 下载和编译​​
  • ​​4.2 picojson库​​
  • ​​4.3 官网代码示例​​
  • ​​4.4 自己测试代码1​​
  • ​​4.5 自己测试代码2​​
  • ​​结语​​

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)
【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)
【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)

1.1 层级结构

  • glTF Object Hierarchy

    这里来几张作者收集的结构图说明gltf的各种对象组成。

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

1.2 术语定义

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

【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)
  • 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)
  • 打开和预览gltf文件
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)
  • 打开和预览glb文件(gltf二进制格式)
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)

2.2 glTF Viewer(v2.0, js)

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

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

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)

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)

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)
  • 通过CMake生成该库的vs2017的工程文件
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)
  • 生成的vs2017的工程文件的相关文件截图
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)
  • vs2017打开工程文件,进行编译库文件
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)
  • 也可以通过premake5来生成该库vs2017的工程文件,如下图所示:
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)
  • tinygltf自带例子中一个小例子编译后的运行结果
  • 【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)

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)
Tiny glTF loader, header only C++ glTF 1.x parsing library.
【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)

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)
【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)
【CAD开发】glTF和b3dm文件格式读取一(C++,tinygltf)

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;
}      

结语