Que sont le GLTF et le GLB?
GLTF (GL Transmission Format) est un format de fichier pour stocker des scènes et des modèles 3D, qui est extrêmement facile à comprendre (la structure est écrite dans la norme JSON), extensible et interagit facilement avec les technologies Web modernes. Ce format compresse bien les scènes tridimensionnelles et minimise le traitement d'exécution des applications à l'aide de WebGL et d'autres API. Le GLTF est maintenant activement promu par le groupe Khronos en tant que JPEG du monde 3D. GLTF version 2.0 est actuellement utilisé. Il existe également une version binaire de ce format appelée GLB, la seule différence étant que tout est stocké dans un fichier avec l'extension GLB.
Cet article fait partie 1 de 2. Dans ce document, nous considérerons des artefacts de format et leurs attributs tels que Scene, Node, Buffer, BufferView, Accessor et Mesh . Et dans le deuxième article, nous examinerons le reste: matériel, texture, animations, peau et appareil photo. Des informations plus générales sur le format peuvent être trouvées ici .
Si vous souhaitez travailler personnellement avec ce format tout en consultant un article, vous pouvez télécharger les modèles GLTF 2.0 à partir du référentiel officiel de Khronos sur GitHub

Le problème et sa solution
Initialement, le format GLTF a été conçu par le groupe Khronos comme une solution pour la transmission de contenu 3D sur Internet et a été conçu pour minimiser le nombre d'importateurs et de convertisseurs, dont différents types sont créés lorsque vous travaillez avec des API graphiques.

Actuellement, GLTF et son frère binaire GLB sont utilisés comme formats unifiés dans les programmes de CAO (Autodesk Maya, Blender, etc.), dans les moteurs de jeux (Unreal Engine, Unity, etc.), les applications AR / VR et les services sociaux. réseaux, etc.
Les représentants du groupe Khronos déclarent ce qui suit:
- GLTF est universel - il peut être utilisé aussi bien pour une géométrie simple que pour des scènes complexes avec des animations, des matériaux divers, etc.
- Il est assez compact. Oui, cela peut être soutenu, car tout dépend des algorithmes de conversion, et je connais personnellement des cas où GLTF était plus grand que l'original, par exemple, le fichier FBX, mais dans la plupart des cas, il l'est.
- La facilité d'analyse des données est la racine plus de ce format. La hiérarchie GLTF utilise JSON, et la géométrie est stockée sous forme binaire, aucun décodage n'est nécessaire!
Système de coordonnées et unités
GLTF utilise un système de coordonnées droitier, c'est-à-dire que le produit croisé de + X et + Y donne + Z, où + Y est l'axe supérieur. L'avant de l'élément GLTF 3D fait face à l'axe + Z. Les unités de mesure pour toutes les distances linéaires sont les mètres, les angles sont mesurés en radians et la rotation positive des objets est dans le sens antihoraire. Les transformations de nœuds et les chemins de canaux des animations sont des vecteurs ou quaternions tridimensionnels avec les types de données et la sémantique suivants:
translation : vecteur tridimensionnel contenant la translation le long des axes x, y et z
rotation : quaternion (x, y, z, w), où w est un scalaire
échelle : un vecteur tridimensionnel contenant des facteurs d'échelle x, y et z

GLTF - un regard intérieur
Comme mentionné ci-dessus, GLTF, en règle générale, se compose de 2 fichiers: le premier au format .gltf, qui stocke la structure de la scène 3D sous la forme de JSON et le deuxième fichier au format .bin, qui stocke directement toutes les données de cette scène.
La structure de format est strictement hiérarchique et a la forme suivante:

En parlant davantage de la structure, j'utiliserai des exemples du fichier GLTF le plus simple, qui stocke 1 triangle unilatéral avec le matériau par défaut. Si vous le souhaitez, vous pouvez le copier et le coller dans n'importe quel visualiseur GLTF pour "ressentir" le contenu du fichier personnellement. Dans ma pratique, j'en ai utilisé différents, mais j'ai opté pour cela , qui utilise Three.js sous le capot. Une bonne option serait également d'utiliser Visual Studio Code avec le plugin GLTF. Vous aurez donc le choix immédiatement parmi 3 moteurs: Babylon.js, Cesium, Three.js
Éléments de scène et de nœud
Tout d'abord, le nœud principal appelé Scene est le premier. Il s'agit du point racine du fichier où tout commence. Ce nœud contient un tableau de scènes que GLTF stocke et le choix de celle qui sera chargée par défaut après l'ouverture du fichier. Le contenu de la scène 3D commence par l'objet suivant, appelé «nœud». Un tableau de scènes et de nœuds n'a pas été mentionné en vain, car la possibilité de stocker plusieurs scènes dans un fichier est implémentée, mais en pratique, ils essaient de stocker une scène dans un fichier.
{ "scenes" : [ { "nodes" : [ 0 ] } ], "nodes" : [ { "mesh" : 0 } ], "scene": 0
Chaque nœud est un «point d'entrée» pour décrire des objets individuels. Si l'objet est complexe et se compose de plusieurs mailles, alors un tel objet sera décrit par les nœuds «parent» et «enfant». Par exemple, une voiture, qui se compose d'une carrosserie et de roues, peut être décrite comme suit: le nœud principal décrit la voiture et, en particulier, sa carrosserie. Ce nœud contient une liste de «nœuds enfants» qui, à leur tour, décrivent les composants restants, tels que, par exemple, les roues. Tous les éléments seront traités récursivement. Les nœuds peuvent avoir des animations TRS (translation, rotation, échelle aka déplacement, rotation et mise à l'échelle). Outre le fait que ces transformations affectent directement le maillage lui-même, elles affectent également les nœuds enfants exactement de la même manière. En plus de tout ce qui précède, je pense qu'il convient de mentionner que les "caméras" internes, le cas échéant, qui sont responsables de l'affichage de l'objet dans le cadre pour l'utilisateur, sont également attachées à l'objet Node. Les objets se réfèrent les uns aux autres en utilisant les attributs appropriés: la scène a un attribut de nœud, un objet de nœud a un attribut de maillage. Pour une compréhension plus simple, tout ce qui précède est illustré dans la figure suivante.

Buffer, BufferView et Accessor
L'objet tampon signifie le stockage de données binaires, non traitées, sans structure, sans héritage, sans valeur. Le tampon stocke des informations sur la géométrie, les animations et le skinning. Le principal avantage des données binaires est qu'elles sont traitées de manière extrêmement efficace par le GPU, comme ne nécessitent pas d'analyse supplémentaire, sauf, éventuellement, la décompression. Les données dans le tampon peuvent être trouvées par l'attribut URI, qui indique clairement où se trouvent les données et il n'y a que 2 options: soit les données sont stockées dans un fichier externe au format .bin, soit elles sont incorporées dans le JSON lui-même. Dans le premier cas, l'URI contient un lien vers un fichier externe, dans ce cas, le dossier dans lequel se trouve le fichier GLTF est considéré comme la racine. Dans le second cas, le fichier aura le format .glb, ce qui nous renvoie au plus compact, en termes de nombre de fichiers, le frère jumeau GLTF, le format GLB. Les données du fichier binaire sont stockées telles quelles, octet par octet.

JSON dans notre exemple de triangle ressemblera à ceci:
Un exemple de tampon encodé en base64:
"buffers" : [ { "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", "byteLength" : 44 } ],
Si vous avez un fichier externe, JSON convertira sa vue comme suit:
"buffers" : [ { "uri" : "duck.bin", "byteLength" : 102040 } ],
Le bloc Buffers possède également un attribut byteLength supplémentaire, qui stocke la valeur de la taille du tampon.
La première étape de la structuration des données à partir du tampon est l'objet BufferView. BufferView peut être appelé une "tranche" d'informations provenant de Buffer, qui se caractérise par un certain décalage d'octets depuis le début du tampon. Cette «tranche» est décrite à l'aide de 2 attributs: le décompte de «décalage» depuis le début du tampon de lecture et la longueur de la tranche elle-même. Un exemple simple de plusieurs objets BufferView pour illustrer leur utilisation sur la base de notre exemple:
"bufferViews" : [ { "buffer" : 0, "byteOffset" : 0, "byteLength" : 6, "target" : 34963 }, { "buffer" : 0, "byteOffset" : 8, "byteLength" : 36, "target" : 34962 } ],
Comme vous pouvez le voir, cet exemple contient 4 attributs principaux:
- Buffer pointe vers l'index du buffer (le numéro de séquence dans le tableau de buffer commence à 0).
- byteOffset - définit le «décalage» de l'origine en octets pour cette «tranche»
- byteLength - définit la longueur de la «tranche»
- target - définit le type de données contenues dans bufferView
Le premier BufferView contient les 6 premiers octets du tampon et n'a pas de décalage. Avec la deuxième «tranche», tout est un peu plus compliqué: comme vous pouvez le voir, son décalage est sur le 8ème octet, au lieu du 6ème attendu. Ces 2 octets sont vides et ont été ajoutés lors du processus de génération de buffer grâce à un processus appelé "padding". Il est nécessaire que la valeur ajuste la valeur des octets limites à 4 octets. Cette astuce est nécessaire pour une lecture plus rapide et plus facile des données du tampon.

Il vaut la peine de dire quelques mots sur l'attribut cible. Il est utilisé pour classer le type d'informations référencé par bufferView. Il n'y a que 2 options: soit la valeur 34962, qui est utilisée pour faire référence aux attributs de sommet (attributs de sommet - 34962 - ARRAY_BUFFER) ou 34963, qui est utilisée pour les indices de sommet (indices de sommet - 34963 - ELEMENT_ARRAY_BUFFER). La touche finale pour comprendre et structurer toutes les informations dans Buffer est l'objet Accessor.
Accessor est un objet qui accède à BufferView et contient des attributs qui déterminent le type et l'emplacement des données de BufferView. Le type de données d'accesseur est codé en type et componentType. La valeur de l'attribut type est une chaîne et a les valeurs suivantes: SCALAIRE pour les valeurs scalaires, VEC3 pour les vecteurs tridimensionnels et MAT4 pour une matrice 4x4 ou le quaternion, qui est utilisé pour décrire la rotation.
ComponentType, à son tour, indique le type de composant de ces données. Il s'agit d'une constante GL, qui peut avoir des valeurs telles que 5126 (FLOAT) ou 5123 (UNSIGNED_SHORT), par exemple, pour indiquer que les éléments ont une virgule flottante, etc.
Diverses combinaisons de ces propriétés peuvent être utilisées pour décrire des types de données arbitraires. Un exemple basé sur notre triangle.
"accessors" : [ { "bufferView" : 0, "byteOffset" : 0, "componentType" : 5123, "count" : 3, "type" : "SCALAR", "max" : [ 2 ], "min" : [ 0 ] }, { "bufferView" : 1, "byteOffset" : 0, "componentType" : 5126, "count" : 3, "type" : "VEC3", "max" : [ 1.0, 1.0, 0.0 ], "min" : [ 0.0, 0.0, 0.0 ] } ],
Analysons les attributs représentés dans JSON:
- bufferView - indique le numéro de séquence du BufferView du tableau BufferView utilisé par Accessor. BufferView, à son tour, stocke des informations sur les index.
- byteOffset - décalage d'octet pour commencer à lire les données de l' accesseur actuel. Plusieurs objets Accessor peuvent référencer un BufferView.
- componentType est une constante indiquant le type d'éléments. Il peut avoir des valeurs 5123, ce qui correspond au type de données UNSIGNED_SHORT, ou 5126 pour FLOAT.
- count - affiche le nombre d'éléments stockés dans le tampon.
- type - définit le type de données: scalaire, vecteur, matrice.
- max et min - attributs qui déterminent la valeur minimale et maximale de la position de ces éléments dans l'espace.
Mesh
L'objet Meshes contient des informations sur les maillages situés dans la scène. Un nœud (objet nœud) ne peut stocker qu'un maillage. Chaque objet de type mesh contient un tableau de type mesh.primitive, à son tour, les primitives sont des objets primitifs (par exemple, des triangles) dont le maillage lui-même est constitué. Cet objet contient de nombreux attributs supplémentaires, mais tout cela sert un seul objectif - le stockage correct des informations sur l'affichage de l'objet. Les principaux attributs du maillage:
- POSITION - position des sommets le long des axes XYZ
- NORMAL - Normales de sommet XYZ normalisées
- TANGENT - Tangentes XYZW des sommets. W indique où la tangente est dirigée et a une valeur de +1 ou -1.
- TEXCOORD_0 - Coordonnées de texture UV. Plusieurs ensembles peuvent être stockés.
- COLOR_0 - Couleurs RVB ou RVBA des sommets.
- JOINTS_0 - cet attribut contient les index des articulations / articulations du tableau d'articulations correspondant, qui devraient affecter le sommet (vertex).
- WEIGHTS_0 - les données de cet attribut déterminent les poids indiquant dans quelle mesure l'articulation affecte le sommet.
- poids - attribut responsable des poids de morphing.
- matériau - contient l'index, qui est le nombre de matériaux dans le tableau Matériaux
Cet objet aura la forme suivante pour notre cas:
"meshes" : [ { "primitives" : [ { "attributes" : { "POSITION" : 1 }, "indices" : 0 } ] } ],
Malheureusement, en raison de la restriction, tout le matériel ne tenait pas dans un seul article, donc le reste peut être trouvé dans le deuxième article , dans lequel nous examinerons les artefacts restants: Matériel, Texture, Animations, Skin et Appareil photo , ainsi que collecter un fichier GLTF de travail minimal.
Suite dans la 2ème partie: https://habr.com/en/post/448298/