glTF Normalized Integers to Float Conversion
In glTF assets, vertex attributes can be stored as integers to save
bandwidth and memory, then converted to floating-point values during
processing. When an accessor sets the normalized boolean
property to true, runtime parsers and graphics hardware
automatically convert these signed or unsigned integer components into
normalized floating-point numbers. This article explains how glTF maps
these raw integer components to the standard [0.0, 1.0] and
[-1.0, 1.0] floating-point ranges.
The Role of the Accessor
In the glTF 2.0 specification, a binary buffer is interpreted through
an accessor object. The accessor defines:
componentType: The integer data type (such as byte, unsigned byte, short, or unsigned short).type: The element structure (e.g.,SCALAR,VEC2,VEC3,VEC4).normalized: A boolean indicating whether the integer values represent normalized floats.
If normalized is false, the values remain
raw integers (used primarily for vertex indices or joint IDs). If
normalized is true, parsers and rendering APIs
map the raw integer bits to continuous floating-point ranges.
Unsigned Integer Conversion
Unsigned integers represent numbers from zero up to the maximum value
that the bit width can hold (\(2^b -
1\)). glTF maps unsigned integers uniformly across the range
[0.0, 1.0].
The conversion formula divides the raw integer \(c\) by the maximum representable value of that type:
\[f = \frac{c}{2^b - 1}\]
Supported Unsigned Types
UNSIGNED_BYTE(5121, 8-bit): \[f = \frac{c}{255.0}\] A value of0maps to0.0, and255maps to1.0.UNSIGNED_SHORT(5123, 16-bit): \[f = \frac{c}{65535.0}\] A value of0maps to0.0, and65535maps to1.0.UNSIGNED_INT(5125, 32-bit): \[f = \frac{c}{4294967295.0}\]
Signed Integer Conversion
Signed integers represent values ranging from \(-2^{b-1}\) to \(2^{b-1} - 1\). glTF maps these integers to
the range [-1.0, 1.0].
Because two's complement representation has one more negative value
than positive values (for example, -128 to 127 in an 8-bit signed byte),
dividing directly by the negative maximum would make zero asymmetric. To
maintain symmetry around zero, the divisor is \(2^{b-1} - 1\), and the result is clamped to
-1.0 to handle the extra negative integer.
The conversion formula is:
\[f = \max\left(\frac{c}{2^{b-1} - 1}, -1.0\right)\]
Supported Signed Types
BYTE(5120, 8-bit signed): \[f = \max\left(\frac{c}{127.0}, -1.0\right)\] Both-128and-127map to-1.0,0maps to0.0, and127maps to1.0.SHORT(5122, 16-bit signed): \[f = \max\left(\frac{c}{32767.0}, -1.0\right)\] Both-32768and-32767map to-1.0,0maps to0.0, and32767maps to1.0.INT(5124, 32-bit signed): \[f = \max\left(\frac{c}{2147483647.0}, -1.0\right)\]
Hardware and API Handling
When passing glTF data to modern graphics APIs (WebGL, OpenGL, Vulkan, DirectX, or Metal), custom software parsing is often unnecessary:
- WebGL / OpenGL: Passing
normalized = truetoglVertexAttribPointerinstructs the GPU to execute these formulas directly in hardware when loading vertex attributes into the vertex shader. - Vulkan / DirectX: Using normalized formats (such as
VK_FORMAT_R8G8B8A8_UNORMorDXGI_FORMAT_R16G16_SNORM) causes the input assembler to automatically decode the integers into floating-point vectors before shader execution.