How GLSL unpackUnorm4x8 Decompresses Integers
This article provides an overview of how the GLSL built-in function
unpackUnorm4x8 unpacks a single 32-bit unsigned integer
into a four-component floating-point vector (vec4). It
covers the step-by-step mathematical process of extracting individual
8-bit sequences, converting them from fixed-point integers to normalized
floating-point numbers in the range \([0.0,
1.0]\), and mapping them to their corresponding vector
channels.
The Purpose of unpackUnorm4x8
In graphics pipelines, packing four 8-bit color or attribute channels
(such as RGBA) into a single 32-bit unsigned integer (uint)
is a standard technique to minimize memory bandwidth and storage
overhead. The GLSL function unpackUnorm4x8 reverses this
process, taking a packed uint and expanding it into a
normalized vec4. The "Unorm" designation indicates that the
resulting floats are unsigned and normalized between 0.0
and 1.0.
Step 1: Bitwise Extraction
A 32-bit unsigned integer consists of 4 bytes (8 bits each). When
unpackUnorm4x8 executes, it treats the integer as
little-endian with respect to vector indexing and extracts four separate
8-bit unsigned integer values:
- Component 0 (
x/r): Bits 0 through 7 - Component 1 (
y/g): Bits 8 through 15 - Component 2 (
z/b): Bits 16 through 23 - Component 3 (
w/a): Bits 24 through 31
Using standard bitwise operations, extracting these values is equivalent to shifting and masking:
uint c0 = (packedVal ) & 0xFFu;
uint c1 = (packedVal >> 8) & 0xFFu;
uint c2 = (packedVal >> 16) & 0xFFu;
uint c3 = (packedVal >> 24) & 0xFFu;Each resulting component c0 through c3 is
an integer ranging from 0 to 255 (\(2^8 - 1\)).
Step 2: Floating-Point Normalization
After isolation, each 8-bit integer is converted to an unsigned normalized float. Normalization maps the discrete integer range \([0, 255]\) linearly onto the continuous floating-point interval \([0.0, 1.0]\).
To achieve this, the integer value is divided by the maximum possible value of an 8-bit unsigned integer (\(255.0\)):
\[\text{float\_val} = \frac{\text{uint\_val}}{255.0}\]
- An integer value of
0becomes0.0 / 255.0 = 0.0. - An integer value of
128becomes128.0 / 255.0 ≈ 0.50196. - An integer value of
255becomes255.0 / 255.0 = 1.0.
Step 3: Vector Assembly
The final step is packing these four normalized floating-point
numbers into a single vec4.
In pure GLSL, the exact programmatic equivalent of
unpackUnorm4x8(packedVal) can be represented as:
vec4 manualUnpackUnorm4x8(uint p) {
return vec4(
float(p & 0xFFu) / 255.0,
float((p >> 8) & 0xFFu) / 255.0,
float((p >> 16) & 0xFFu) / 255.0,
float((p >> 24) & 0xFFu) / 255.0
);
}Hardware GPUs implement this operation directly at the instruction level, allowing this extraction and normalization to occur in a single cycle without software-level bit-shifting or software division.