¿Qué son GLTF y GLB?
GLTF (GL Transmission Format) es un formato de archivo para almacenar escenas y modelos en 3D, que es extremadamente fácil de entender (la estructura está escrita en el estándar JSON), extensible e interactúa fácilmente con las tecnologías web modernas. Este formato comprime bien las escenas tridimensionales y minimiza el procesamiento en tiempo de ejecución de las aplicaciones que utilizan WebGL y otras API. El GLTF ahora es promovido activamente por el Grupo Khronos como JPEG del mundo 3D. GLTF versión 2.0 se utiliza actualmente. También hay una versión binaria de este formato llamada GLB, cuya única diferencia es que todo se almacena en un archivo con la extensión GLB.
Este artículo es parte 1 de 2. En él, consideraremos artefactos de formato y sus atributos como Scene, Node, Buffer, BufferView, Accessor y Mesh . Y en el segundo artículo veremos el resto: Material, Textura, Animaciones, Skin y Cámara. Se puede encontrar información de formato más general aquí .
Si desea trabajar personalmente con este formato mientras ve un artículo, puede descargar los modelos GLTF 2.0 desde el repositorio oficial de Khronos en GitHub

El problema y su solución.
Inicialmente, el formato GLTF fue concebido por el Grupo Khronos como una solución para transmitir contenido 3D a través de Internet y fue diseñado para minimizar la cantidad de importadores y convertidores, de los cuales se crean diferentes tipos cuando se trabaja con API gráficas.

Actualmente, GLTF y su hermano binario GLB se utilizan como formatos unificados en programas CAD (Autodesk Maya, Blender, etc.), en motores de juegos (Unreal Engine, Unity, etc.), aplicaciones AR / VR y servicios sociales. redes, etc.
Los representantes del Grupo Khronos declaran lo siguiente:
- GLTF es universal: puede usarse igualmente bien para geometría simple, así como para escenas complejas con animaciones, diversos materiales, etc.
- Es bastante compacto. Sí, esto se puede argumentar, porque todo depende de los algoritmos de conversión, y personalmente conozco casos en los que GLTF era más grande que el original, por ejemplo, el archivo FBX, pero en la mayoría de los casos lo es.
- La facilidad de análisis de datos es la raíz más de este formato. La jerarquía GLTF usa JSON, y la geometría se almacena en forma binaria, ¡no se necesita decodificación!
Sistema coordinado y unidades
GLTF utiliza un sistema de coordenadas diestro, es decir, el producto cruzado de + X e + Y da + Z, donde + Y es el eje superior. El frente del activo 3D GLTF mira hacia el eje + Z. Las unidades de medida para todas las distancias lineales son metros, los ángulos se miden en radianes y la rotación positiva de los objetos es en sentido antihorario. Las transformaciones de nodo y las rutas de canal de las animaciones son vectores tridimensionales o cuaterniones con los siguientes tipos de datos y semántica:
traducción : un vector tridimensional que contiene la traducción a lo largo de los ejes x, y y z
rotación : cuaternión (x, y, z, w), donde w es un escalar
escala : un vector tridimensional que contiene factores de escala x, y y z

GLTF: una mirada al interior
Como se mencionó anteriormente, GLTF, como regla, consta de 2 archivos: el primero con el formato .gltf, que almacena la estructura de la escena 3D en forma de JSON y el segundo archivo con el formato .bin, que almacena directamente todos los datos de esta escena.
La estructura de formato es estrictamente jerárquica y tiene la siguiente forma:

Hablando más sobre la estructura, usaré ejemplos del archivo GLTF más simple, que almacena 1 triángulo unilateral con el material predeterminado. Si lo desea, puede copiarlo y pegarlo en cualquier visor GLTF para "sentir" el contenido del archivo personalmente. En mi práctica, usé diferentes, pero me decidí por esto , que usa Three.js debajo del capó. También una buena opción sería usar Visual Studio Code con el complemento GLTF. Entonces tendrá una opción inmediata de 3 motores: Babylon.js, Cesium, Three.js
Elementos de escena y nodo
Lo primero es el nodo principal llamado Scene. Este es el punto raíz en el archivo donde todo comienza. Este nodo contiene una serie de escenas que GLTF almacena y la elección de la que se cargará de forma predeterminada después de abrir el archivo. El contenido de la escena 3D comienza con el siguiente objeto, que se llama "Nodo". Se mencionó una variedad de escenas y nodos no en vano, porque Se implementa la capacidad de almacenar varias escenas en un archivo, pero en la práctica intentan almacenar una escena en un archivo.
{ "scenes" : [ { "nodes" : [ 0 ] } ], "nodes" : [ { "mesh" : 0 } ], "scene": 0
Cada nodo es un "punto de entrada" para describir objetos individuales. Si el objeto es complejo y consta de varias mallas, entonces dicho objeto será descrito por los nodos "padre" e "hijo". Por ejemplo, un automóvil, que consiste en un cuerpo y ruedas, se puede describir de la siguiente manera: el nodo principal describe el automóvil y, en particular, su cuerpo. Este nodo contiene una lista de "nodos secundarios" que, a su vez, describen los componentes restantes, como, por ejemplo, las ruedas. Todos los elementos serán procesados recursivamente. Los nodos pueden tener animaciones TRS (traslación, rotación, escala, también conocido como desplazamiento, rotación y escala). Además del hecho de que tales transformaciones afectan directamente a la malla en sí, también afectan a los nodos secundarios exactamente de la misma manera. Además de todo lo anterior, creo que vale la pena mencionar que las "cámaras" internas, si las hay, que son responsables de mostrar el objeto en el marco para el usuario, también están unidas al objeto Node. Los objetos se refieren entre sí utilizando los atributos apropiados: la escena tiene un atributo de nodo, un objeto de nodo tiene un atributo de malla. Para una comprensión más simple, todo lo anterior se ilustra en la siguiente figura.

Buffer, BufferView y Accessor
El objeto de almacenamiento intermedio significa el almacenamiento de datos binarios, sin procesar, sin estructura, sin herencia, sin valor. El búfer almacena información sobre geometría, animaciones y diseño. La principal ventaja de los datos binarios es que la GPU los procesa de manera extremadamente eficiente, ya que no requieren análisis adicionales, excepto, posiblemente, descompresión. El atributo URI puede encontrar los datos en el búfer, lo que deja en claro claramente dónde se encuentran los datos y solo hay 2 opciones: los datos se almacenan en un archivo externo con el formato .bin o se incrustan dentro del JSON. En el primer caso, el URI contiene un enlace a un archivo externo, en este caso, la carpeta en la que se encuentra el archivo GLTF se considera la raíz. En el segundo caso, el archivo tendrá el formato .glb, que nos remite al más compacto, en términos de la cantidad de archivos, el hermano gemelo GLTF, el formato GLB. Los datos en el archivo binario se almacenan tal cual, byte por byte.

JSON en nuestro ejemplo de triángulo se verá así:
Un ejemplo de un búfer codificado en base64:
"buffers" : [ { "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", "byteLength" : 44 } ],
Si tiene un archivo externo, JSON convertirá su vista a la siguiente:
"buffers" : [ { "uri" : "duck.bin", "byteLength" : 102040 } ],
El bloque Buffers también tiene un atributo byteLength adicional, que almacena el valor del tamaño del buffer.
El primer paso para estructurar los datos del búfer es el objeto BufferView. BufferView se puede llamar una "porción" de información de Buffer, que se caracteriza por un cierto desplazamiento de bytes desde el comienzo del buffer. Este "segmento" se describe utilizando 2 atributos: el recuento de "desplazamiento" desde el comienzo del búfer de lectura y la longitud del segmento en sí. Un ejemplo simple de varios objetos BufferView para ilustrar su uso basado en nuestro ejemplo:
"bufferViews" : [ { "buffer" : 0, "byteOffset" : 0, "byteLength" : 6, "target" : 34963 }, { "buffer" : 0, "byteOffset" : 8, "byteLength" : 36, "target" : 34962 } ],
Como puede ver, este ejemplo contiene 4 atributos principales:
- El búfer apunta al índice del búfer (el número de secuencia en la matriz de búfer comienza en 0).
- byteOffset : define el "desplazamiento" del origen en bytes para este "segmento"
- byteLength - define la longitud de la "rebanada"
- target - define el tipo de datos contenidos en bufferView
El primer BufferView contiene los primeros 6 bytes del búfer y no tiene desplazamiento. Con el segundo "segmento", todo es un poco más complicado: como puede ver, su desplazamiento es de 8 mbytes, en lugar del sexto esperado. Estos 2 bytes están vacíos y se agregaron durante el proceso de generación del búfer gracias a un proceso llamado "relleno". Es necesario que el valor ajuste el valor de los bytes límite a 4 bytes. Este truco es necesario para una lectura más rápida y fácil de los datos del búfer.

Vale la pena decir algunas palabras sobre el atributo de destino. Se utiliza para clasificar el tipo de información referenciada por bufferView. Solo hay 2 opciones: será el valor 34962, que se usa para referirse a los atributos de vértice (atributos de vértice - 34962 - ARRAY_BUFFER) o 34963, que se usa para los índices de vértice (índices de vértice - 34963 - ELEMENT_ARRAY_BUFFER). El toque final para comprender y estructurar toda la información en Buffer es el objeto Accesor.
Accessor es un objeto que accede a BufferView y contiene atributos que determinan el tipo y la ubicación de los datos de BufferView. El tipo de datos del descriptor de acceso está codificado en type y componentType. El valor del atributo type es una cadena y tiene los siguientes valores: SCALAR para valores escalares, VEC3 para vectores tridimensionales y MAT4 para una matriz 4x4 o el cuaternión, que se utiliza para describir la rotación.
ComponentType, a su vez, indica el tipo de componente de estos datos. Esta es una constante GL, que puede tener valores como 5126 (FLOAT) o 5123 (UNSIGNED_SHORT), por ejemplo, para indicar que los elementos tienen un punto flotante, etc.
Se pueden usar varias combinaciones de estas propiedades para describir tipos de datos arbitrarios. Un ejemplo basado en nuestro triángulo.
"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 ] } ],
Analicemos los atributos representados en JSON:
- bufferView : indica el número de secuencia de BufferView de la matriz BufferView que usa Accessor. BufferView, a su vez, almacena información sobre índices.
- byteOffset : cambio de byte para comenzar a leer datos del actual Accesor . Varios objetos Accessor pueden hacer referencia a un BufferView.
- componentType es una constante que indica el tipo de elementos. Puede tener valores 5123, que corresponden al tipo de datos UNSIGNED_SHORT, o 5126 para FLOAT.
- count : muestra cuántos elementos están almacenados en el búfer.
- tipo : define el tipo de datos: escalar, vector, matriz.
- max y min : atributos que determinan el valor mínimo y máximo de la posición de estos elementos en el espacio.
Malla
El objeto Mallas contiene información sobre las mallas ubicadas en la escena. Un nodo (objeto de nodo) puede almacenar solo 1 malla. Cada objeto de tipo mesh contiene una matriz de tipo mesh.primitive, a su vez, los primitivos son objetos primitivos (por ejemplo, triángulos) en los que se compone la malla. Este objeto contiene muchos atributos adicionales, pero todo esto tiene un propósito: el almacenamiento correcto de información sobre la visualización del objeto. Los principales atributos de la malla:
- POSICIÓN - posición de los vértices a lo largo de los ejes XYZ
- NORMAL : normales de vértice XYZ normalizadas
- TANGENTE - XYZW tangentes de vértices. W indica hacia dónde se dirige la tangente y tiene un valor de +1 o -1.
- TEXCOORD_0 : coordenadas de textura UV. Se pueden almacenar varios conjuntos.
- COLOR_0 : colores de vértices RGB o RGBA.
- JOINTS_0 : este atributo contiene los índices de uniones / Uniones de la matriz de uniones correspondiente, lo que debería afectar al vértice (vértice).
- WEIGHTS_0 : los datos de este atributo determinan los pesos que indican cuánto afecta la articulación al vértice.
- pesos : atributo responsable de los pesos de transformación.
- material : contiene el índice, que es el número de material en la matriz Materiales
Este objeto tendrá la siguiente forma para nuestro caso:
"meshes" : [ { "primitives" : [ { "attributes" : { "POSITION" : 1 }, "indices" : 0 } ] } ],
Desafortunadamente, debido a la restricción, todo el material no cabía en un artículo, por lo que el resto se puede encontrar en el segundo artículo , en el que consideraremos los artefactos restantes: Material, Textura, Animaciones, Piel y Cámara , así como también recopilaremos un archivo GLTF de trabajo mínimo.
Continuación en la 2da parte: https://habr.com/en/post/448298/