O que são GLTF e GLB?
GLTF (GL Transmission Format) é um formato de arquivo para armazenar cenas e modelos 3D, extremamente fácil de entender (a estrutura é escrita no padrão JSON), extensível e interage facilmente com as modernas tecnologias da web. Esse formato compacta bem as cenas tridimensionais e minimiza o processamento em tempo de execução dos aplicativos usando o WebGL e outras APIs. O GLTF agora é promovido ativamente pelo Khronos Group como um JPEG do mundo 3D. Atualmente, o GLTF versão 2.0 é usado. Há também uma versão binária desse formato chamada GLB, cuja única diferença é que tudo é armazenado em um arquivo com a extensão GLB.
Este artigo é parte 1 de 2. Nele, consideraremos artefatos de formato e seus atributos como Scene, Node, Buffer, BufferView, Accessor e Mesh . E no segundo artigo , veremos o resto: Material, Textura, Animações, Pele e Câmera. Informações mais gerais sobre o formato podem ser encontradas aqui .
Se, durante a visualização do artigo, você quiser trabalhar pessoalmente com este formato, poderá fazer o download dos modelos GLTF 2.0 do repositório oficial Khronos no GitHub

O problema e sua solução
Inicialmente, o formato GLTF foi concebido pelo Khronos Group como uma solução para transmissão de conteúdo 3D pela Internet e foi projetado para minimizar o número de importadores e conversores, diferentes tipos criados ao trabalhar com APIs gráficas.

Atualmente, o GLTF e seu irmão binário GLB são usados como formatos unificados em programas CAD (Autodesk Maya, Blender, etc.), em mecanismos de jogos (Unreal Engine, Unity etc.), aplicativos AR / VR e serviços sociais. redes etc.
Os representantes do Grupo Khronos declaram o seguinte:
- O GLTF é universal - pode ser usado igualmente bem para geometria simples, bem como para cenas complexas com animações, vários materiais, etc.
- É bastante compacto. Sim, isso pode ser discutido, porque tudo depende dos algoritmos de conversão, e eu pessoalmente conheço casos em que o GLTF era maior que o original, por exemplo, arquivo FBX, mas na maioria dos casos é.
- A facilidade da análise de dados é a raiz desse formato. A hierarquia GLTF usa JSON, e a geometria é armazenada em formato binário, sem decodificação!
Sistema e unidades de coordenadas
O GLTF usa um sistema de coordenadas destro, ou seja, o produto cruzado de + X e + Y fornece + Z, onde + Y é o eixo superior. A frente do ativo 3D GLTF está voltada para o eixo + Z. As unidades de medida para todas as distâncias lineares são metros, os ângulos são medidos em radianos e a rotação positiva dos objetos é no sentido anti-horário. As transformações de nós e os caminhos de canal das animações são vetores tridimensionais ou quaternions com os seguintes tipos de dados e semântica:
translação : um vetor tridimensional que contém a translação ao longo dos eixos x, ye z
rotação : quaternion (x, y, z, w), onde w é um escalar
escala : um vetor tridimensional contendo fatores de escala x, ye z

GLTF - uma visão interna
Como mencionado acima, o GLTF, via de regra, consiste em 2 arquivos: o primeiro com o formato .gltf, que armazena a estrutura da cena 3D no formato JSON e o segundo arquivo no formato .bin, que armazena diretamente todos os dados dessa cena.
A estrutura do formato é estritamente hierárquica e tem o seguinte formato:

Falando mais sobre a estrutura, usarei exemplos do arquivo GLTF mais simples, que armazena 1 triângulo unilateral com o material padrão. Se desejar, você pode copiá-lo e colá-lo em qualquer visualizador de GLTF para "sentir" o conteúdo do arquivo pessoalmente. Na minha prática, usei diferentes, mas resolvi isso , que usa o Three.js sob o capô. Também uma boa opção seria usar o Visual Studio Code com o plug-in GLTF. Então você terá uma escolha imediata entre três mecanismos: Babylon.js, Cesium, Three.js
Elementos de cena e nó
As primeiras coisas primeiro é o nó principal chamado Cena. Este é o ponto raiz do arquivo em que tudo começa. Este nó contém uma matriz de cenas que o GLTF armazena e a escolha da que será carregada por padrão após a abertura do arquivo. O conteúdo da cena 3D começa com o próximo objeto, chamado "Nó". Um conjunto de cenas e nós não foi mencionado em vão, porque a capacidade de armazenar várias cenas em um arquivo é implementada, mas, na prática, eles tentam armazenar uma cena em um arquivo.
{ "scenes" : [ { "nodes" : [ 0 ] } ], "nodes" : [ { "mesh" : 0 } ], "scene": 0
Cada nó é um "ponto de entrada" para descrever objetos individuais. Se o objeto for complexo e consistir em várias malhas, esse objeto será descrito pelos nós "pai" e "filho". Por exemplo, um carro, que consiste em uma carroceria e rodas, pode ser descrito da seguinte forma: o nó principal descreve o carro e, em particular, sua carroceria. Este nó contém uma lista de "nós filhos", que, por sua vez, descrevem os componentes restantes, como, por exemplo, rodas. Todos os elementos serão processados recursivamente. Os nós podem ter animações TRS (translação, rotação, escala, também conhecida como deslocamento, rotação e escala). Além do fato de que essas transformações afetam diretamente a própria malha, elas também afetam os nós filhos exatamente da mesma maneira. Além de todas as opções acima, acho que vale a pena mencionar que as "câmeras" internas, se houver, responsáveis pela exibição do objeto no quadro para o usuário, também estão anexadas ao objeto Node. Os objetos se referem um ao outro usando os atributos apropriados: scene possui um atributo de nó, um objeto de nó possui um atributo de malha. Para uma compreensão mais simples, todas as opções acima são ilustradas na figura a seguir.

Buffer, BufferView e Acessador
Objeto de buffer significa armazenamento de dados binários, não processados, sem estrutura, sem herança, sem valor. O buffer armazena informações sobre geometria, animações e aparência. A principal vantagem dos dados binários é que eles são processados com extrema eficiência pela GPU, como não requer análise adicional, exceto, possivelmente, descompressão. Os dados no buffer podem ser encontrados pelo atributo URI, o que claramente deixa claro onde os dados estão localizados e existem apenas duas opções: os dados são armazenados em um arquivo externo no formato .bin ou são incorporados no próprio JSON. No primeiro caso, o URI contém um link para um arquivo externo; nesse caso, a pasta na qual o arquivo GLTF está localizado é considerada a raiz. No segundo caso, o arquivo terá o formato .glb, que nos remete aos mais compactos, em termos de número de arquivos, GLTF, irmão gêmeo, o formato GLB. Os dados no arquivo binário são armazenados como estão, byte a byte.

JSON em nosso exemplo de triângulo ficará assim:
Um exemplo de um buffer codificado em base64:
"buffers" : [ { "uri" : "data:application/octet-stream;base64,AAABAAIAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAAAAAACAPwAAAAA=", "byteLength" : 44 } ],
Se você tiver um arquivo externo, o JSON converterá sua visualização para o seguinte:
"buffers" : [ { "uri" : "duck.bin", "byteLength" : 102040 } ],
O bloco Buffers também possui um atributo adicional byteLength, que armazena o valor do tamanho do buffer.
A primeira etapa na estruturação dos dados do buffer é o objeto BufferView. O BufferView pode ser chamado de "fatia" de informações do Buffer, caracterizada por uma certa mudança de bytes desde o início do buffer. Essa “fatia” é descrita usando 2 atributos: a contagem de “shift” desde o início do buffer de leitura e o tamanho da própria fatia. Um exemplo simples de vários objetos BufferView para ilustrar seu uso com base em nosso exemplo:
"bufferViews" : [ { "buffer" : 0, "byteOffset" : 0, "byteLength" : 6, "target" : 34963 }, { "buffer" : 0, "byteOffset" : 8, "byteLength" : 36, "target" : 34962 } ],
Como você pode ver, este exemplo contém 4 atributos principais:
- O buffer aponta para o índice do buffer (o número de sequência na matriz do buffer começa em 0).
- byteOffset - define a "mudança" da origem em bytes para esta "fatia"
- byteLength - define o comprimento da "fatia"
- target - define o tipo de dados contidos no bufferView
O primeiro BufferView contém os primeiros 6 bytes do buffer e não possui deslocamento. Com a segunda "fatia", tudo fica um pouco mais complicado: como você pode ver, a mudança ocorre no 8º byte, em vez do esperado 6º. Esses 2 bytes estão vazios e foram adicionados durante o processo de geração de buffer, graças a um processo chamado "padding". É necessário que o valor ajuste o valor dos bytes de limite para 4 bytes. Esse truque é necessário para uma leitura mais rápida e fácil dos dados do buffer.

Vale a pena dizer algumas palavras sobre o atributo target. É usado para classificar o tipo de informação referenciada pelo bufferView. Existem apenas duas opções: será o valor 34962, que é usado para se referir aos atributos de vértice (atributos de vértice - 34962 - ARRAY_BUFFER) ou 34963, que é usado para os índices de vértice (índices de vértice - 34963 - ELEMENT_ARRAY_BUFFER). O toque final para entender e estruturar todas as informações no Buffer é o objeto Accessor.
O acessador é um objeto que acessa o BufferView e contém atributos que determinam o tipo e o local dos dados do BufferView. O tipo de dados do acessador é codificado em type e componentType. O valor do atributo type é uma string e possui os seguintes valores: SCALAR para valores escalares, VEC3 para vetores tridimensionais e MAT4 para uma matriz 4x4 ou quaternion, que é usado para descrever a rotação.
ComponentType, por sua vez, indica o tipo de componente desses dados. Essa é uma constante GL, que pode ter valores como 5126 (FLOAT) ou 5123 (UNSIGNED_SHORT), por exemplo, para indicar que os elementos têm um ponto flutuante etc.
Várias combinações dessas propriedades podem ser usadas para descrever tipos de dados arbitrários. Um exemplo baseado em nosso 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 ] } ],
Vamos analisar os atributos representados no JSON:
- bufferView - indica o número de sequência do BufferView da matriz BufferView que o Accessor usa. O BufferView, por sua vez, armazena informações sobre índices.
- byteOffset - deslocamento de byte para começar a ler dados do atual Accessor. Vários objetos Accessor podem fazer referência a um BufferView.
- componentType é uma constante indicando o tipo de elementos. Pode ter valores 5123, que correspondem ao tipo de dados UNSIGNED_SHORT, ou 5126 para FLOAT.
- contagem - exibe quantos elementos estão armazenados no buffer.
- type - define o tipo de dados: escalar, vetor, matriz.
- max e min - atributos que determinam o valor mínimo e máximo da posição desses elementos no espaço.
Malha
O objeto Malhas contém informações sobre as malhas localizadas na cena. Um nó (objeto do nó) pode armazenar apenas 1 malha. Cada objeto do tipo malha contém uma matriz do tipo malha. Primitivas, por sua vez, primitivas são objetos primitivos (por exemplo, triângulos) nos quais a malha em si consiste. Este objeto contém muitos atributos adicionais, mas tudo isso serve a um propósito - o armazenamento correto de informações sobre a exibição do objeto. Os principais atributos da malha:
- POSIÇÃO - posição dos vértices ao longo dos eixos XYZ
- NORMAL - Normais normalizadas de vértices XYZ
- TANGENTE - XYZW tangentes de vértices. W indica para onde a tangente é direcionada e tem um valor de +1 ou -1.
- TEXCOORD_0 - Coordenadas de textura UV. Vários conjuntos podem ser armazenados.
- COLOR_0 - Cores RGB ou RGBA dos vértices.
- JOINTS_0 - esse atributo contém o índice de juntas / juntas da matriz de juntas correspondente, o que deve afetar o vértice (vértice).
- WEIGHTS_0 - os dados desse atributo determinam os pesos indicando o quanto a junta afeta o vértice.
- pesos - atributo responsável pelos pesos morphing.
- material - contém o índice, que é o número de material na matriz Materiais
Este objeto terá o seguinte formulário para o nosso caso:
"meshes" : [ { "primitives" : [ { "attributes" : { "POSITION" : 1 }, "indices" : 0 } ] } ],
Infelizmente, devido à restrição, todo o material não se encaixou em um artigo, portanto o restante pode ser encontrado no segundo artigo , no qual consideraremos os artefatos restantes: Material, Textura, Animações, Aparência e Câmera , além de coletar um arquivo GLTF mínimo de trabalho.
Continuação na 2ª parte: https://habr.com/en/post/448298/