gltf解析概述

gltf是一种json格式文件,其内容是描述一个三维的场景。官网上提供了两个版本的格式,分别是1.0和2.0的版本,由于两个版本的差别较大,在这里只讨论2.0的解析思路。闲话少说,开始正题。

一、文件的形式
    gltf有两种形式的文件,一种是后缀为glb的文件,是gltf的二进制形式;一种是后缀为gltf的形式。

    1、glb文件格式 glb虽然是gltf的二进制文件,但是也可以通过文本文件打开,打开后可以看到文件分为三大部分。 第一部分:文件头 首个4字节表示,在打开的文件里,以字符的形式能够看到的。 第二个4字节表示版本。 第三个4字节表示整个文件的长度。 到此文件头的内容就结束了。 第二部分:gltf块 第一个4字节表示块的长度。 第二个4字节表示块的类型,这个块一般是JSON。也就是表示此块的内容为gltf的内容。 第三个4字节表示gltf的具体的数据内容。 第三部分:data块 第一个字节表示块的长度。 第二个字节表示块的类型,一般为BIN,表示gltf的bin文件的数据内容。 可以是顶点属性数据:顶点、法线、纹理坐标、权重、颜色空间等数据,还有场景中的mesh用到的纹理数据。 纹理数据读取出来的一般为rgba数据,其中a根据解析,可以有也可以没有。 第三个数据表真实的数据块,一般为二进制数据。

    其具体的解析和gltf文件是一样的,会在解析gltf文件部分细述。

    2、gltf文件 gltf文件又分为内嵌式数据和外部数据文件式两种方式 。

        1)内嵌式数据方式
    gltf文件中的uri字段对应的内容,为真实数据内容,经过base64编码,写入文件。
    解析时,需要对这个字段内容进行解码得到原始数据,纹理的原始数据一般是有一定的格式的。如:png、jpg、image    等格式。
    顶点属性数据内容在通过base64解码后,可以直接使用。包含有顶点、法线、纹理坐标、颜色空间、权重等数据。

        2)外部数据文件方式
     gltf文件中的uri字段对应的字符串为文件的路径,一般是和.gltf文件在同一目录中。解析时,需要从这个路径中读取文件中的数据。

二、解析
    解析文件,可以先解析其中一种文件,比如外部数据文件形式,只要能将这种gltf形式的文件解析,那么其它两种形式的文件与它类似,也可以解析。解析数据时,需要先将文件中的数据读取出来,存在对应的数据结构中。
    1、读取文件

    gltf文件的格式 :
    字段名一般为字符串,用“”括起来,“:”表明后面是字段对应的值,或者是字段内包含的内容。后面的值可是以一个具体的值、可以是一个数组(“[ ]”来表示)、可以是一个对象(由“{ }”来表示)也可以是一个对象数组(“[{},{}]”)。
    读取文件时,可以通过第三方的库也可以自己解析来读取。读取时可以不管顺序,从文件头开始一个字段一个字段的解析就行。
    具体的解析过程,就不啰嗦了,这个比较简单。读取文件时,有主要读取的字段有"accessors"、"bufferViews"、"buffers"、"images"、"materials"、"meshes"、"nodes"、"scene"、"scenes"、"textures"。如果gltf中有帧动画则还有"animations",如果是骨骼动画,则还有"skins"字段,且骨骼动画中包含帧动画,也就是说,有"skins"字段,就必有"animations"。

    2、解析文件

    解析文件时,我习惯于从scene开始解析,gltf文件本身就是一个场景文件。可以从scene开始一步一步去完成整个文件的解析。

    解析“scene”的值,这个值可以没有,也可以有。如果没有时, 表示当前或初始化时,默认的场景索引。如果有值,且值的合理范围应该是0至scenes.size()。如是值不在这个范围或是没有"scene"字段时,可以给出一个默认值。一般默认值为0,即场景数组scenes中的第一个场景。

    解析"scenes",通过scene的值,对应于scenes中的索引,scenes数组中是对象即“{}”,每一个对象,表示一个场景的,其中字段有name、nodes。分别表示这个场景的名称和这个场景的根节点的索引。根节点可以是一个,也可以是多个。一般为一个根节点。名称可以有,也可以没有。由此可以看出,scenes字段是必需要有,否则,整个文件中没有场景,显然不合理。

    解析"nodes",这个字段中保存了所有场景的所有的节点。通过scenes节点中找到的场景的根节点的索引。在这个数组中通过索引查询节点。
    再通过查询到的节点对应的索引,再向下查询节点,如此递归,递归时,做深度递归。子节点可以是"children",依然是个节点;可以是"mesh",是个要绘制的网格;可以是"skin",骨骼动画的蒙皮;可以是"camera"相机。
    如果是"children",那么就需要再取得这个节点的索引值,继续查询nodes数组。
    如果是"mesh",需要去"meshes"中继续查询。
    如果是"skin",需要去"skins"中继续查询。
    如果"camera",需要去"cameras"中继续查询。
    在递归遍历nodes数组时,节点中可能会有"translation"、"rotation"或"scale"中的一个或多个。也可能是"matrix",代表当前节点的姿态。递归的过程也是节点与节点建父子关系的过程。总是由父节点向子节点递归。
    需要注意的是当前节点如是为:"mesh"、"skin"或"camera"当前节点中还可以有自己的子节点。所以解析时,需要判断是否有"children"字段。如果有则说明

    解析“meshes”,这个字段中包含了构建一个网格所需要的数据"primitives",这是个数组,也就是说,可以有多个图元的信息。即子网格的概念,一个网格中可以有多个子网格。
    "primitives"包含的数据有"attributes"顶点属性数据对应的buffer的索引、"indices"顶点的索引数据buffer的索引、"mode"绘模式、"material"材质对应的索引。
    "attributes"又包含了"NORMAL"、"POSITION"、"TEXCOORD_0"、"TANGENT"、“JOINTS_0”、“WEIGHTS_0”等数据对应的索引。其对应的数据应该在"accessors"字段中的查询,比如:"POSITION"对应的索引是0,则在accessors[0]里存储相关的数据信息。在这里"POSITION"字段是必需要有的,顶点属性数据中如果没有顶点,那就不可能绘制出网格了。其它的数据,可以没有。
    "indices"表示的是索引数据,如果绘制网格用的是顶点,则可以没有这个数据。同样是从"accessors"数据组中取值。同 "attributes"中的属性数据对应的索引,查询的方法一致。
    "mode"表示的是绘制方式 ,其数值代表:0 代表GL_POINTS;1代表GL_LINES;2代表GL_LINE_LOOP;3代表GL_LINE_STRIP;4代表GL_TRIANGLES;5代表GL_TRIANGLE_STRIP;6代表GL_TRIANGLE_FAN。
    "material"是对应的"materials"数组中的索引。

    解析"accessors",这个字段中包含对应于"bufferView"的数组索引、"componentType"数据类型,"byteOffset"数据在buffer中的偏移、"count"数据的个数、"type"数据的组织形式和"max"、"min"数据的最大值和最小值。
    bufferView"的数组索引可以在"bufferViews"数组中查询。
    "componentType"的数值代表了数据的类型,如:5120表示byte类型;5121表示ubyte类型;5122表示short类型;5123表示ushort类型;5124表示int类型;5125表示uint类型;5126表示float类型;5130表示double类型。
    "byteOffset"表示数据的偏移,最终计算真实数据在buffer中的偏移时,还需要再加上buffer中的偏移。没有这个字段时,可以将这个值默认为0。
    "type"是字符串,以字符串的形式,表示:SCALAR标量、VEC2、VEC3、VEC4、MAT2、MAT3、MAT4。

    解析"bufferViews",这个字段中的"buffer"表示是"buffers"中的索引。"byteLength"数据的字节长度。"byteOffset"数据的字节偏移。"target"对应的数值表示数据在buffer中的组织形式。
    在一个buffer中,可以存放多个顶点属性数据,如可以将"NORMAL"、"POSITION"、"TEXCOORD_0"对应的真实数据放在一起保存。数据可以通过"byteOffset"确定数据在buffer中的起始位置;通过"byteLength"来获取数据长度,再通过"componentType"、"type"就能正确的解析出真实的数据了。

    解析"buffers"字段,是buffer数组,每个buffer中都存放真实的数据。一般包含"name"、"byteLength"和"uri"字段。其中,"name"和"uri"并不是必需有的。
    "byteLength"表示了数据的真实的字节长度。
    "uri"字段有三种情况,对应的文件三种形式:
    如果是glb文件,则没有这个字段,其数据可以通过之前的偏移,直接从glb的buffer中取出来;
    如果是外部文件方式 ,则这个字段是文件路径,一般是.bin的数据文件,其中的数据,可以直接读取使用的;
    如果是内嵌式的,则这个字段是经过base64编码后的字符串,解析时需要base64解码才可以使用这个数据。

    解析"materials"字段,包含"name"、"emissiveFactor"、"emissiveTexture"、"normalTexture"、"occlusionTexture"和"pbrMetallicRoughness",主要是提供了pbr渲染时,所需要的纹理信息。
    其中"emissiveTexture"、"normalTexture"、"occlusionTexture"三个字段中的"index"值和pbrMetallicRoughness"中包含的"baseColorTexture"和"metallicRoughnessTexture"中的"index"值,对应的是"textures"字段数组的索引。

    解析"textures"字段,一般包含"name"、"sampler"和"source"。其中"sampler"对应的是"samplers"数组中的索引;"source"对应的是"images"数组中的索引。

    解析"samplers"字段,包含"magFilter"、"minFilter"、"wrapS"、"wrapT"字段。
    "magFilter"、"minFilter"对应的是纹理的滤波试,其值:
    9728表示GL_NEAREST; 9729表示GL_LINEAR;9984表示GL_NEAREST_MIPMAP_NEAREST;9985表示GL_LINEAR_MIPMAP_NEAREST;9986表示GL_NEAREST_MIPMAP_LINEAR;9987表示GL_LINEAR_MIPMAP_LINEAR 。
    "wrapS"、"wrapT"对应的是纹理的环绕方式,其值如:
    33071表示GL_CLAMP_TO_EDGE;33648表示GL_MIRRORED_REPEAT;10497表示GL_REPEAT。

    解析"images"其中包含"mimeType"、"name"、"uri"和"bufferView"。其中"mimeType"给出了图片的格式。   "uri"也是分为三种情况,如果是glb文件,则这个字段为空,可以通过给出的"bufferView"的索引,查询相关的数据并读取。如果是内嵌式文件,则真实的数据,以base64编码的形式写入文件,解析时,需要base64解码来读取真实的内容,其中前前面的部分字节内容为"***;base64,"表示的是这个文件是什么格式的,并以base64进行了编码处理。之后的字段才是真实的图片内容。如果是外部文件格式的,则其内容为图片的路径。
    "bufferView"这个字段,也只是glb时,才会有。

    解析"cameras"字段,是个数组,可以有多个相机。一般有包含"type"字段,为字符串,其值为"perspective"或是"orthographic",表示这个相机是透视的还是正交的。这个类型不一样,其包含的字段也是不一样的。
    perspective时,包含"aspectRatio"、"yfov"、"zfar"、"znear"。
    orthographic时,包含"xmag"、"ymag"、"zfar"、"znear"。

    解析灯光,灯光并没有字段"light",而是要先找到"KHR_lights_punctual"字段,再从这个对象里找"lights"字段。然后解析出灯光的"name"、"color"和"type"。"type"以字符串的形式,表示当前的灯光是什么类型的,其值为"point"、"DOT"和"direction"。"color"的值是个数组,所以颜色的通道数,不一定是3或4。

     到此,基本的gltf的解析就完成了。至于,帧动画和骨骼动画的解析,有兴趣的可以自己解析一下,最主要一点是解析时,在获取到真实数据之前,gltf中都是由索引关联对应的。

     说明一点,以上解析属于自己的理解,有不对的地方,欢迎指正。

你可能感兴趣的:(c++)