A gltf triangle
The present essay describes how to create a triangle model in glTF format. We will focus on the production of a buffer binary file with custom geometry and the description of buffer data by means of bufferViews and accessors.Please have a look at the Github repository for the code related to the present example, as well as for other experiments with the glTF format. To view a glTF scene, open the directory in a glTF viewer.
References
The following text is based on the glTF tutorial written by The Khronos Group. I strongly recommend to read the tutorial, it covers many of the concepts introduced here.
This essay has to be intended as a complement to the first five chapters of the tutorial. Some arguments discussed in the tutorial are missing here, like scenes and nodes, whereas other arguments are discussed with more details. In particular, we are going to see how to get a buffer binary file storing some custom geometry data, an argument that may be difficult to grasp at first glance.
Define vertices indexes and positions
We start by defining the triangle we want to draw. Let say we want the triangle with the first vertex at position (1, 0, 0)
, the second vertex at position (0, 1, 0)
and the third vertex at position (0, 0, 0)
. Take a paper and a pencil and draw the triangle in a right-handed cartesian coordinate system, you will see that the face direction is counter-clockwise.
The face direction is defined by the order of the three vertices that make up the triangle, as well as their apparent order on-screen, and its primary use is to allow the culling (removal) of not visible triangles on closed surfaces.
We can store the described triangle in a js object like the following:
Note that there are many possible ways to encode the wanted triangle in a data structure, the above definition is convenient because, as we will see, it resembles how data is going to be stored in buffers. The reason behind this data structure will be better understood once accessors and rendering modes will be introduced.
In the next section, we are going to store the data in a binary file.
Store data in an external binary file
Binary data is referred to by a buffer, a buffer URI may point to an external binary file or it may be a data URI that encodes the binary data directly in the glTF file.
There is not a unique way to store the data in a buffer, here we are going to use the same convention described in the tutorial. We first store indexes data as UNSIGNED_SHORT
components and, with the appropriate byte offset, we later add vertices data to the buffer as FLOAT
components. The code snippet below defines a function that takes data
as input and writes a binary file called buffer.bin
in the file system.
Create the node script
The above function is meant to be used in node.js. Create the script below and run it in order to get the binary file.
In order to use the data encoded in the binary file, we need to appropriately describe, in the glTF file, how data is stored. In the next section, we are going to do that by means of buffer views and accessors.
Describe data with buffer views and accessors
In the previous section, we created a binary file containing two pieces of information: the vertices indexes and the vertices positions. With buffer views we describe these two views of data in the buffer.
Accessors add additional information about the type and layout of the data described in the buffer views. In the first buffer view we have 3 elements of type SCALAR
with components of type UNSIGNED_SHORT
. In the second buffer we have 3 elements of type VEC3
with components of type FLOAT
.
Buffer views and accessors completely describe the data stored in the buffer, now it is time to use this data.
Define the meshes
Having the data and its description, we are ready to define meshes that use the data. A mesh describes a geometric object that appears in the scene, and it is made of smaller building blocks called mesh primitive objects.
A mesh primitive may describe individual points, lines, or triangles according to the selected rendering mode. Different rendering modes will use data in different ways, as described in the OpenGL primitive documentation.
The code below defines a single mesh, made of a single primitive, with vertices indices referenced in the first accessor ("indices": 0
) and vertices positions in the second accessor ("POSITION": 1
).
The final model
In the previous sections we described some of the parts componing the final glTF file. We discussed about bufferViews and accessors for the interpretation of the buffer data, but we missed out the parts related to scenes and nodes, well covered in the tutorial.
You can see the full glTF file in the repository.
Note that, at this stage, only one face of the model is visible, by default the one with counter-clockwise direction, as result of the face culling. Adding a bit of complexity to our example, we can easily obtain a double-faces triangle using the TRIANGLE_STRIP
rendering mode. To do that, we assign "mode": 5
to our primitive and create the binary file for a data structure with "indexes": [0, 1, 2, 0]
. Having now different data, we need to update the first buffer view with "byteLength": 8
and the first accessor with "count": 4
.
In a double-faces triangle in TRIANGLE_STRIP
mode, every group of 3 adjacent vertices forms a triangle. The first triangle, defined by indices (0, 1, 2)
, has a counter-clockwise face direction. The second triangle, of indices (1, 2, 0)
, also has a counter-clockwise face direction but, being in TRIANGLE_STRIP
mode, the face direction is reversed (read the documentation for details).
Summary
The purpose of glTF is to define a format for the efficient transfer of 3D content over networks. The previous sections are based on the first five chapters of the glTF tutorial. We discussed about the production and interpretation of a buffer binary file storing a custom geometry and we touched arguments like mesh primitives and rendering modes, key concepts for the creation of any 3D model.