glTF格式介绍总目录:https://blog.youkuaiyun.com/qq_31709249/article/details/86477520
在以前的系列文章中,已经介绍了glTF文件的文件结构,而且拿了一个最简单的例子——三角形的glTF文件简单解析了一下。这一节来解析一个稍微复杂一点的glTF文件——正方形模型。正方形模型来源于glTF官方示例,里面有很多glTF格式的模型,下载下来可以用win10自带的3D模型查看器查看。(注意,里面模型有1.0版本的和2.0版本的,但是目前主流的软件都支持2.0版本的,如Cesium、Win10自带的3D模型查看器和编辑器等,包括前面的一系列教程都是介绍2.0版本的,所以下载模型的时候,下载2.0版本的)。
下载文件后,可以看到glTF文件夹下有两个文件:Box.gltf和Box0.bin。根据前面文件结构的知识可以知道,Box.gltf是一个json文件,描述3D模型如何组织;Box0.bin则是二进制数据文件,里面包含顶点坐标、纹理坐标等数据。用VSCode打开Box.gltf文件,可以看到里面的内容如下:
{
"asset": {
"generator": "COLLADA2GLTF",
"version": "2.0"
},
"scene": 0,
"scenes": [
{
"nodes": [
0
]
}
],
"nodes": [
{
"children": [
1
],
"matrix": [
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
-1.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
1.0
]
},
{
"mesh": 0
}
],
"meshes": [
{
"primitives": [
{
"attributes": {
"NORMAL": 1,
"POSITION": 2
},
"indices": 0,
"mode": 4,
"material": 0
}
],
"name": "Mesh"
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 36,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"max": [
1.0,
1.0,
1.0
],
"min": [
-1.0,
-1.0,
-1.0
],
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 288,
"componentType": 5126,
"count": 24,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
}
],
"materials": [
{
"pbrMetallicRoughness": {
"baseColorFactor": [
0.800000011920929,
0.0,
0.0,
1.0
],
"metallicFactor": 0.0
},
"name": "Red"
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 576,
"byteLength": 72,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 576,
"byteStride": 12,
"target": 34962
}
],
"buffers": [
{
"byteLength": 648,
"uri": "Box0.bin"
}
]
}
然后逐步解析里面的部分。
首先是scene,根据前面的教程可知,scene是整个场景的入口,可以看到里面只有一个场景0。然后再看scenes,里面有一个nodes数组,说明scene的根节点只有一个。再看nodes数组,前面说过,nodes数组是整个场景结构的组织者,可以看到里面有一个children数组,里面有一个元素值为1,说明对应nodes数组中下标为1(第二个)node。第二个node为:
{
"mesh": 0
}
说明该node对应的mesh为meshes数组中下标为0(第一个)的元素。再看meshes数组,根据meshes教程可知,该meshes中只有一个图元(primitives),里面的属性有NORMAL(法向量),且法向量对应的Accessor下标为1(NORMAL值为1)、POSITION(顶点位置坐标),且位置坐标对应的Accessor下标为2(POSITION值为2),此图元是用索引的方式绘制的(有Indices属性),绘制方式为TRIANGLES(mode值为4),对应的材质下标为0。
接着解析数据部分,先看Buffer
通过buffers属性可知,只有一个buffer,并且对应的文件为"Box0.bin",文件字节长度为648字节。
再看BufferView
"bufferViews": [
{
"buffer": 0,
"byteOffset": 576,
"byteLength": 72,
"target": 34963
},
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 576,
"byteStride": 12,
"target": 34962
}
]
通过bufferViews属性可知,buffer[0]有两个分块,第一个分块为对应的字节范围为576——572+72=648,对应的数据是ELEMENT_ARRAY_BUFFER(target值为34963,详情见相关教程),第一个分块对应的字节范围为0——576.对应的数据类型为ARRAY_BUFFER(target值为34962,详情见相关教程),需要注意的是,有一个属性byteStride属性 值为12,根据前面教程中数据交错的知识可知,第一个属性和第二个属性之间的间距为12字节。
继续看Accessor
"accessors": [
{
"bufferView": 0,
"byteOffset": 0,
"componentType": 5123,
"count": 36,
"max": [
23
],
"min": [
0
],
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 24,
"max": [
1.0,
1.0,
1.0
],
"min": [
-1.0,
-1.0,
-1.0
],
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 288,
"componentType": 5126,
"count": 24,
"max": [
0.5,
0.5,
0.5
],
"min": [
-0.5,
-0.5,
-0.5
],
"type": "VEC3"
}
],
通过accessors属性可知,有三个accessor。我们知道索引对应的是第一个accessor,先看第一个accessori,根据属性我们可知,索引数组对应第一个bufferView,在bufferView中起点为0,数据类型为unsigned int(2个字节),一共有36个值。同理可知,法向量对应第二个bufferView,在bufferView中起点为0,数据类型为vec3,一共有24个值;位置坐标对应第二个bufferView,在bufferView中起点为288,数据类型为vec3,一共有24个值。
ok,知道了这些,我们就可以写程序来读取bin文件中的值,来看看里面的数据长啥样。读取程序的代码如下:
#include "stdafx.h"
#include <fstream>
#include <vector>
#include <iostream>
using namespace std;
int main()
{
const int n = 48;
std::ifstream f = ifstream("Box0.bin", std::ios::binary);
for (int i = 0; i < n; i++)
{
float a, b, c;
f.read((char*)&a, sizeof(float));
f.read((char*)&b, sizeof(float));
f.read((char*)&c, sizeof(float));
std::cout <<" ("<< a << ", " << b << ", " << c <<")"<< std::endl;
}
for (int i = 0; i < 36; i++)
{
short elements;
f.read((char*)&elements, sizeof(short));
cout <<" "<< elements <<",";
if ((i + 1) % 4 == 0)
cout << endl;
}
return 0;
}
最终读取的结果如下:
我们可以看到,前面24个为法向量,中间24个为位置坐标,最后的36个为索引坐标。和我们理解的一致。
最后的用OpenGL或者OSG,将这个正方体绘制出来,我用的是OSG,最后结果如下:
OK,大功告成。