GLTF的文件结构解读,由浅入深版

GLTF文件结构初探

GLTF (.gltf)

        是JSON格式的文本文件。GLTF文件包含了3D模型的结构、材质、动画和其他元数据,可以直接打开并阅读(例如用文本编辑器)。        

GLB (.glb)

        二进制文件,GLB文件将3D模型的所有数据,包括几何、材质、动画和纹理,都打包在一个单一的二进制文件中。二进制格式,GLB文件通常比GLTF文件加载更快,占用空间更小。

        GLTF适合需要手动编辑或调试3D模型文件的情况,GLB适合需要高效传输和加载3D模型的应用场景,尤其是在网络传输中。

GLTF文件样例

        来源:网上找的一个.glb龙的模型。

        通过Blender转换成.gltf结构之后查看。

具有纹理的.glb文件

通过Blender转换后的.gltf文件,缺少纹理

        .gltf文件内容:

{
  "asset":{
    "generator":"Khronos glTF Blender I/O v3.6.28",
    "version":"2.0"
  },
  "scene":0,
  "scenes":[
    {
      "name":"Scene",
      "nodes":[
        3
      ]
    }
  ],
  "nodes":[
    {
      "mesh":0,
      "name":"Mesh_0"
    },
    {
      "mesh":1,
      "name":"Mesh_1"
    },
    {
      "children":[
        0,
        1
      ],
      "name":"dragon_asian.1"
    },
    {
      "children":[
        2
      ],
      "name":"RootNode.0",
      "scale":[
        0.009999999776482582,
        0.009999999776482582,
        0.009999999776482582
      ]
    }
  ],
  "materials":[
    {
      "alphaCutoff":0.5,
      "alphaMode":"MASK",
      "doubleSided":true,
      "name":"Material_0",
      "occlusionTexture":{
        "index":0
      },
      "pbrMetallicRoughness":{}
    },
    {
      "doubleSided":true,
      "name":"Material_1",
      "occlusionTexture":{
        "index":1
      },
      "pbrMetallicRoughness":{}
    }
  ],
  "meshes":[
    {
      "name":"Mesh_0",
      "primitives":[
        {
          "attributes":{
            "POSITION":0,
            "NORMAL":1,
            "TEXCOORD_0":2
          },
          "indices":3,
          "material":0
        }
      ]
    },
    {
      "name":"Mesh_1",
      "primitives":[
        {
          "attributes":{
            "POSITION":4,
            "NORMAL":5,
            "TEXCOORD_0":6
          },
          "indices":7,
          "material":1
        }
      ]
    }
  ],
  "textures":[
    {
      "sampler":0,
      "source":0
    },
    {
      "sampler":0,
      "source":1
    }
  ],
  "images":[
    {
      "bufferView":4,
      "mimeType":"image/png",
      "name":"Image_3"
    },
    {
      "bufferView":9,
      "mimeType":"image/png",
      "name":"Image_7"
    }
  ],
  "accessors":[
    {
      "bufferView":0,
      "componentType":5126,
      "count":101491,
      "max":[
        2.050593376159668,
        6.596331596374512,
        2.6598503589630127
      ],
      "min":[
        -2.3590798377990723,
        0.09649088978767395,
        -2.5816965103149414
      ],
      "type":"VEC3"
    },
    {
      "bufferView":1,
      "componentType":5126,
      "count":101491,
      "type":"VEC3"
    },
    {
      "bufferView":2,
      "componentType":5126,
      "count":101491,
      "type":"VEC2"
    },
    {
      "bufferView":3,
      "componentType":5125,
      "count":101496,
      "type":"SCALAR"
    },
    {
      "bufferView":5,
      "componentType":5126,
      "count":46404,
      "max":[
        2.4441118240356445,
        5.79871940612793,
        2.5048890113830566
      ],
      "min":[
        -2.445387840270996,
        -0.005728369578719139,
        -3.0280914306640625
      ],
      "type":"VEC3"
    },
    {
      "bufferView":6,
      "componentType":5126,
      "count":46404,
      "type":"VEC3"
    },
    {
      "bufferView":7,
      "componentType":5126,
      "count":46404,
      "type":"VEC2"
    },
    {
      "bufferView":8,
      "componentType":5123,
      "count":46407,
      "type":"SCALAR"
    }
  ],
  "bufferViews":[
    {
      "buffer":0,
      "byteLength":1217892,
      "byteOffset":0,
      "target":34962
    },
    {
      "buffer":0,
      "byteLength":1217892,
      "byteOffset":1217892,
      "target":34962
    },
    {
      "buffer":0,
      "byteLength":811928,
      "byteOffset":2435784,
      "target":34962
    },
    {
      "buffer":0,
      "byteLength":405984,
      "byteOffset":3247712,
      "target":34963
    },
    {
      "buffer":0,
      "byteLength":4686983,
      "byteOffset":3653696
    },
    {
      "buffer":0,
			"byteLength":556848,
			"byteOffset":8340680,
			"target":34962
		},
		{
			"buffer":0,
			"byteLength":556848,
			"byteOffset":8897528,
			"target":34962
		},
		{
			"buffer":0,
			"byteLength":371232,
			"byteOffset":9454376,
			"target":34962
		},
		{
			"buffer":0,
			"byteLength":92814,
			"byteOffset":9825608,
			"target":34963
		},
		{
			"buffer":0,
			"byteLength":6688734,
			"byteOffset":9918424
		}
	],
	"samplers":[
		{
			"magFilter":9729,
			"minFilter":9987
		}
	],
	"buffers":[
		{
			"byteLength":16607160,
      "uri":"data:xxxx"
		}
	]
}

        glTF就是把几个JSON数组放到了一个JSON对象里面去,主要包括:

        Scene 场景 、 Node 结点、Texture 纹理、Material 材质、Mesh 网格、Primitive 图元、Accessor 访问器、Sampler 采样器、Buffer 缓冲、BufferView “缓冲视图” 和 Image 图像。

        层级关系如图所示:

基础结构图

gltf文件结构

解读 GLTF 最终要的方法

        核心是 索引。只要明白一切的数据存储都是通过索引调用实现的,加上知道顶层是如何一步步索引到数据层(即需要了解层级结构),GLTF的文件结构就不难理解了。

        首先抛开材质不谈,所有的顶点数据,法线数据和采样uv坐标数据都存在了数据层,即buffer,对应JSON文件中 Buffer 那一栏,可以是直接在GLTF文件中存储,也可以是直接外接作为一个.bin文件。

        再来看最顶层的scene。

"scenes":[
  {
    "name":"Scene",
    "nodes":[
      3
    ]
  }
]

        这里secne中包含nodes的列表,nodes中写着3,表示从JSON的nodes中拿列表中的第三个。

  "nodes":[
    {
      "mesh":0,
      "name":"Mesh_0"
    },
    {
      "mesh":1,
      "name":"Mesh_1"
    },
    {
      "children":[
        0,
        1
      ],
      "name":"dragon_asian.1"
    },
    {
      "children":[
        2
      ],
      "name":"RootNode.0",
      "scale":[
        0.009999999776482582,
        0.009999999776482582,
        0.009999999776482582
      ]
    }
  ],

        nodes[3]中有一个children列表,表示他有子节点,子节点的索引是2,也就是nodes[2],而nodes[2]中仍然有子节点,是nodes[0]和nodes[1],接着看下去,发现nodes[0]中有个mesh,nodes[1]中也有个mesh,分别对应mesh的索引 0 和 1 ,这时候再去看mesh数组。

"meshes":[
  {
    "name":"Mesh_0",
    "primitives":[
      {
        "attributes":{
          "POSITION":0,
          "NORMAL":1,
          "TEXCOORD_0":2
        },
        "indices":3,
        "material":0
      }
    ]
  },
  {
    "name":"Mesh_1",
    "primitives":[
      {
        "attributes":{
          "POSITION":4,
          "NORMAL":5,
          "TEXCOORD_0":6
        },
        "indices":7,
        "material":1
      }
    ]
  }
],

        mesh[0]中有网格自己的名字,以及它的图元信息,具体看primitives(图元)中的内容,首先图元中有自己的位置、法线和贴图UV的索引,还有外层的 顶点 索引和材质索引。这些索引对应着的是在accessors (访问器)中拿对应的内容。

        拿 indices (顶点索引)的索引举例,是7,代表从 accessors 的列表中拿到第七个数据,accessors中存放的内容是bufferView,bufferView可以理解为如何去拿取数据层中的信息。因为数据层都是以byte的形式存放的,而顶点数据、法线数据、uv数据等有的是float类型,有的是unsigned int类型,所以才会有bufferView这种东西来教你如何正确地去拿这些信息。

"accessors":[
    {
      "bufferView":0,
      "componentType":5126,
      "count":101491,
      "max":[
        2.050593376159668,
        6.596331596374512,
        2.6598503589630127
      ],
      "min":[
        -2.3590798377990723,
        0.09649088978767395,
        -2.5816965103149414
      ],
      "type":"VEC3"
    },
    {
      "bufferView":1,
      "componentType":5126,
      "count":101491,
      "type":"VEC3"
    },
    {
      "bufferView":2,
      "componentType":5126,
      "count":101491,
      "type":"VEC2"
    },
    {
      "bufferView":3,
      "componentType":5125,
      "count":101496,
      "type":"SCALAR"
    },
    {
      "bufferView":5,
      "componentType":5126,
      "count":46404,
      "max":[
        2.4441118240356445,
        5.79871940612793,
        2.5048890113830566
      ],
      "min":[
        -2.445387840270996,
        -0.005728369578719139,
        -3.0280914306640625
      ],
      "type":"VEC3"
    },
    {
      "bufferView":6,
      "componentType":5126,
      "count":46404,
      "type":"VEC3"
    },
    {
      "bufferView":7,
      "componentType":5126,
      "count":46404,
      "type":"VEC2"
    },
    {
      "bufferView":8,
      "componentType":5123,
      "count":46407,
      "type":"SCALAR"
    }
  ],

        接着我们看到了 bufferView的索引是8,表示拿取bufferViews[8],componentType表示的是数据类型,5123代表的是16位无符号整数,还有5125等不同的数据类型,这个会在最后补充类型数据。count代表拿的这个数据有多少个,有46407个无符号整数,type代表拿的数据类型是一个什么类型,此处为SCALAR,是一个标量,标量有什么存的,就是5123,16位无符号整数。接着我们可以看最后的数据层了。

  "bufferViews":[
    {
      "buffer":0,
      "byteLength":1217892,
      "byteOffset":0,
      "target":34962
    },
    {
      "buffer":0,
      "byteLength":1217892,
      "byteOffset":1217892,
      "target":34962
    },
    {
      "buffer":0,
      "byteLength":811928,
      "byteOffset":2435784,
      "target":34962
    },
    {
      "buffer":0,
      "byteLength":405984,
      "byteOffset":3247712,
      "target":34963
    },
    {
      "buffer":0,
      "byteLength":4686983,
      "byteOffset":3653696
    },
    {
      "buffer":0,
			"byteLength":556848,
			"byteOffset":8340680,
			"target":34962
		},
		{
			"buffer":0,
			"byteLength":556848,
			"byteOffset":8897528,
			"target":34962
		},
		{
			"buffer":0,
			"byteLength":371232,
			"byteOffset":9454376,
			"target":34962
		},
		{
			"buffer":0,
			"byteLength":92814,
			"byteOffset":9825608,
			"target":34963
		},
		{
			"buffer":0,
			"byteLength":6688734,
			"byteOffset":9918424
		}
	],

        bufferViews[8]拿到的是buffer中的内容,buffer的索引是0,代表从buffer列表中拿到第0个,byteLength是92814,代表该部分数据长度是92814字节,byteOffset代表偏移的字节数,从buffers[0] + 9825608 处开始拿数据,"target": 34963 是在描述一个缓冲区对象(Buffer Object)的目标,通常与 OpenGL API 的常量对应。具体来说,34963 是 OpenGL 的常量 GL_ELEMENT_ARRAY_BUFFER 的数值。"target": 34962代表 (GL_ARRAY_BUFFER),"target": 0 或未指定则代表未绑定到特定 OpenGL 缓冲区目标,由应用程序自行决定如何使用这些数据。

        由此我们已经完成了一整个GLTF文件的内容解析。现在回头来看,最终要的两点,一索引, 而层级结构的调用顺序。其中还有材质的部分没有讲到,但是掌握这些已经足够应对大部分的情况了。

GLTF文件的略深解读

        一个glTF文件包括一个或多个Scene,其中必定有一个是默认场景,用scene指定。每个场景就是一棵Node构成的树,因此肯定有一个根结点。有些Node含有Mesh,每个Mesh可能有一个或多个Primitive,Primitive其实就是一堆三角形+材质。每个Node有自己对应的变换矩阵,也就是MVP变换里面的model,这一矩阵说明了本Node在父结点的坐标系中怎么摆放。这一变换定义在matrix中(16x16的行主序的矩阵),或者也可以用TRS的方式给出,即分别说明移动translation、旋转rotation和缩放scale。translate是个三维向量(XYZ),rotation是个四元数(WXYZ),scale是个三维向量。

        glTF的坐标系采用右手系,向上为Y。OpenGL则是左手系

        因此,如果三角形的顶点数据存放在Buffer当中,同一个Buffer可以用不同的方式的方式看待,也就是BufferView(类似Vulkan的BufferView)。Accessor定义了我们怎样访问BufferView(涉及offset、stride、数据类型等等,和定义VAO时要考虑的很像)。Buffer中的数据可以直接在文件中用base64编码给出,也可以给定一个URI(可以理解为文件的路径)引用一个.bin文件。

        顶点数据一般就是POSITION位置、NORMAL法向量、TEXCOORD纹理坐标,这三者对应的Accessor都会在Primitive当中指明。Primitive还要给出顶点索引对应的Accessor(对应我们调用draw call时要绑定的索引缓冲)。

        每个Primitive一般有自己的Material。每个材质有可能有对应的Texture。Texture又引用对应的Sampler和Image。Sampler说明放大/缩小的时候采用的插值方式、纹理坐标超过[−1,1][−1,1]如何处理。Image则就是贴图,一般也是用URI应用外部的图片文件。

GLTF文件结构图

tinygltf库中定义的几个主要结构体

struct Material {
  std::string name;

  std::vector<double> emissiveFactor;  // length 3. default [0, 0, 0]
  std::string alphaMode;               // default "OPAQUE"
  double alphaCutoff;                  // default 0.5
  bool doubleSided;                    // default false;

  PbrMetallicRoughness pbrMetallicRoughness;

  NormalTextureInfo normalTexture;
  OcclusionTextureInfo occlusionTexture;
  TextureInfo emissiveTexture;

  // For backward compatibility
  // TODO(syoyo): Remove `values` and `additionalValues` in the next release.
  ParameterMap values;
  ParameterMap additionalValues;

  ExtensionMap extensions;
  Value extras;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;

  Material() : alphaMode("OPAQUE"), alphaCutoff(0.5), doubleSided(false) {}
  DEFAULT_METHODS(Material)

  bool operator==(const Material &) const;
};

struct BufferView {
  std::string name;
  int buffer{-1};        // Required
  size_t byteOffset{0};  // minimum 0, default 0
  size_t byteLength{0};  // required, minimum 1. 0 = invalid
  size_t byteStride{0};  // minimum 4, maximum 252 (multiple of 4), default 0 =
                         // understood to be tightly packed
  int target{0};  // ["ARRAY_BUFFER", "ELEMENT_ARRAY_BUFFER"] for vertex indices
                  // or atttribs. Could be 0 for other data
  Value extras;
  ExtensionMap extensions;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;

  bool dracoDecoded{false};  // Flag indicating this has been draco decoded

  BufferView()
      : buffer(-1),
        byteOffset(0),
        byteLength(0),
        byteStride(0),
        target(0),
        dracoDecoded(false) {}
  DEFAULT_METHODS(BufferView)
  bool operator==(const BufferView &) const;
};

struct Accessor {
  int bufferView;  // optional in spec but required here since sparse accessor
                   // are not supported
  std::string name;
  size_t byteOffset;
  bool normalized;    // optional.
  int componentType;  // (required) One of TINYGLTF_COMPONENT_TYPE_***
  size_t count;       // required
  int type;           // (required) One of TINYGLTF_TYPE_***   ..
  Value extras;
  ExtensionMap extensions;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;

  std::vector<double>
      minValues;  // optional. integer value is promoted to double
  std::vector<double>
      maxValues;  // optional. integer value is promoted to double

  struct {
    int count;
    bool isSparse;
    struct {
      int byteOffset;
      int bufferView;
      int componentType;  // a TINYGLTF_COMPONENT_TYPE_ value
    } indices;
    struct {
      int bufferView;
      int byteOffset;
    } values;
  } sparse;

  ///
  /// Utility function to compute byteStride for a given bufferView object.
  /// Returns -1 upon invalid glTF value or parameter configuration.
  ///
  int ByteStride(const BufferView &bufferViewObject) const {
    if (bufferViewObject.byteStride == 0) {
      // Assume data is tightly packed.
      int componentSizeInBytes =
          GetComponentSizeInBytes(static_cast<uint32_t>(componentType));
      if (componentSizeInBytes <= 0) {
        return -1;
      }

      int numComponents = GetNumComponentsInType(static_cast<uint32_t>(type));
      if (numComponents <= 0) {
        return -1;
      }

      return componentSizeInBytes * numComponents;
    } else {
      // Check if byteStride is a mulple of the size of the accessor's component
      // type.
      int componentSizeInBytes =
          GetComponentSizeInBytes(static_cast<uint32_t>(componentType));
      if (componentSizeInBytes <= 0) {
        return -1;
      }

      if ((bufferViewObject.byteStride % uint32_t(componentSizeInBytes)) != 0) {
        return -1;
      }
      return static_cast<int>(bufferViewObject.byteStride);
    }

    // unreachable return 0;
  }

  Accessor()
      : bufferView(-1),
        byteOffset(0),
        normalized(false),
        componentType(-1),
        count(0),
        type(-1) {
    sparse.isSparse = false;
  }
  DEFAULT_METHODS(Accessor)
  bool operator==(const tinygltf::Accessor &) const;
};

struct Primitive {
  std::map<std::string, int> attributes;  // (required) A dictionary object of
                                          // integer, where each integer
                                          // is the index of the accessor
                                          // containing an attribute.
  int material;  // The index of the material to apply to this primitive
                 // when rendering.
  int indices;   // The index of the accessor that contains the indices.
  int mode;      // one of TINYGLTF_MODE_***
  std::vector<std::map<std::string, int> > targets;  // array of morph targets,
  // where each target is a dict with attribues in ["POSITION, "NORMAL",
  // "TANGENT"] pointing
  // to their corresponding accessors
  ExtensionMap extensions;
  Value extras;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;

  Primitive() {
    material = -1;
    indices = -1;
    mode = -1;
  }
  DEFAULT_METHODS(Primitive)
  bool operator==(const Primitive &) const;
};

struct Mesh {
  std::string name;
  std::vector<Primitive> primitives;
  std::vector<double> weights;  // weights to be applied to the Morph Targets
  ExtensionMap extensions;
  Value extras;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;

  Mesh() = default;
  DEFAULT_METHODS(Mesh)
  bool operator==(const Mesh &) const;
};

class Node {
 public:
  Node() : camera(-1), skin(-1), mesh(-1) {}

  DEFAULT_METHODS(Node)

  bool operator==(const Node &) const;

  int camera;  // the index of the camera referenced by this node

  std::string name;
  int skin;
  int mesh;
  std::vector<int> children;
  std::vector<double> rotation;     // length must be 0 or 4
  std::vector<double> scale;        // length must be 0 or 3
  std::vector<double> translation;  // length must be 0 or 3
  std::vector<double> matrix;       // length must be 0 or 16
  std::vector<double> weights;  // The weights of the instantiated Morph Target

  ExtensionMap extensions;
  Value extras;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;
};

struct Buffer {
  std::string name;
  std::vector<unsigned char> data;
  std::string
      uri;  // considered as required here but not in the spec (need to clarify)
            // uri is not decoded(e.g. whitespace may be represented as %20)
  Value extras;
  ExtensionMap extensions;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;

  Buffer() = default;
  DEFAULT_METHODS(Buffer)
  bool operator==(const Buffer &) const;
};

struct Asset {
  std::string version = "2.0";  // required
  std::string generator;
  std::string minVersion;
  std::string copyright;
  ExtensionMap extensions;
  Value extras;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;

  Asset() = default;
  DEFAULT_METHODS(Asset)
  bool operator==(const Asset &) const;
};

struct Scene {
  std::string name;
  std::vector<int> nodes;

  ExtensionMap extensions;
  Value extras;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;

  Scene() = default;
  DEFAULT_METHODS(Scene)
  bool operator==(const Scene &) const;
};

class Model {
 public:
  Model() = default;
  DEFAULT_METHODS(Model)

  bool operator==(const Model &) const;

  std::vector<Accessor> accessors;
  std::vector<Animation> animations;
  std::vector<Buffer> buffers;
  std::vector<BufferView> bufferViews;
  std::vector<Material> materials;
  std::vector<Mesh> meshes;
  std::vector<Node> nodes;
  std::vector<Texture> textures;
  std::vector<Image> images;
  std::vector<Skin> skins;
  std::vector<Sampler> samplers;
  std::vector<Camera> cameras;
  std::vector<Scene> scenes;
  std::vector<Light> lights;

  int defaultScene = -1;
  std::vector<std::string> extensionsUsed;
  std::vector<std::string> extensionsRequired;

  Asset asset;

  Value extras;
  ExtensionMap extensions;

  // Filled when SetStoreOriginalJSONForExtrasAndExtensions is enabled.
  std::string extras_json_string;
  std::string extensions_json_string;
};

GLTF文件中的 componentType

        在GLTF文件格式中,componentType用于指定数据类型。GLTF是一种开放的标准文件格式,用于存储3D模型。该格式设计紧凑、高效,并且易于传输和解析。GLTF文件通常用于WebGL应用程序以及其他需要高效传输3D数据的应用。

componentType 的定义

        componentType 属性通常出现在访问器(Accessor)对象中,用于定义存储在缓冲区(Buffer)中的数据类型。它表示元素数组的类型,决定了顶点属性、法线、颜色等数据的基本类型。

常见的 componentType 值及其含义

        GLTF规范中定义了一些常用的 componentType 值,每个值都对应一个特定的数据类型。以下是常见的 componentType 值及其含义:

  • 5120 (GL_BYTE): 8位有符号整数
  • 5121 (GL_UNSIGNED_BYTE): 8位无符号整数
  • 5122 (GL_SHORT): 16位有符号整数
  • 5123 (GL_UNSIGNED_SHORT): 16位无符号整数
  • 5125 (GL_UNSIGNED_INT): 32位无符号整数
  • 5126 (GL_FLOAT): 32位浮点数

GLTF 中的 mode 属性

        GLTF(GL Transmission Format)文件格式是用于传输和存储3D模型的标准格式之一。mode 属性是 GLTF 文件中定义几何体绘制方式的重要部分。它决定了如何将顶点数据解释为图元,如点、线、三角形等。

mode 属性的定义

                mode 属性出现在 primitive 对象中,用于指定绘制几何图元的方式。该属性接受整数值,每个值对应一种 OpenGL 图元类型。

常见的 mode 值及其含义

        以下是 GLTF 规范中定义的 mode 值及其含义:

  • 0 (POINTS): 绘制点
  • 1 (LINES): 绘制独立的线段
  • 2 (LINE_LOOP): 绘制线环
  • 3 (LINE_STRIP): 绘制连续的线段
  • 4 (TRIANGLES): 绘制独立的三角形
  • 5 (TRIANGLE_STRIP): 绘制三角形条带
  • 6 (TRIANGLE_FAN): 绘制三角形扇

文件的读取和管理

        首先读取解析glTF的JSON文件格式,解析完成后,就能知道buffer和image的读取方式,然后在二进制数据块中读取即可。

(1).buffer中的二进制文件

        一个buffer包含一个URI,指向包含实际数据的二进制文件(.bin文件),通过buffers、bufferview和accessors中,可以知道文件的数据类型,布局。读取出来的数据不需要解析,直接送进GPU进行渲染。

(2).图片文件

        一个image中包含一个URI,通过这个ID去获取具体的图片文件。

参考资料

glTF-Tutorials/gltfTutorial/gltfTutorial_002_BasicGltfStructure.md at master · javagl/glTF-Tutorials

glTF格式介绍——目录_glft格式-CSDN博客

Empty Space - OpenGL加载glTF模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值