Graphics Reference
In-Depth Information
Case
Channels
Vertex Quaternion
x
(
int8
)
y
(
int8
)
z
(
int8
)
h
(
1b
)
w
(
7b
)
x
(
float32
)
y
(
float32
)
z
(
float32
)
w
(
float32
)
Model Transform
Instance Data
x
(
int16
)
y
(
int16
)
z
(
int16
)
w
(
0, calculated
)
Bone Transform
x
(
float16
)
y
(
float16
)
z
(
float16
)
w
(
float16
)
Tab l e 1. 1.
Quaternion packing for different scenarios.
h
stands for quaternion handed-
ness.
First, we could store in the same byte a handedness bit and a sign bit for
w
and then reconstruct
w
in the shader as we operate onto normalized quaternions.
We abandoned this approach, as unpacking
w
would lead to the
sqrt
instruction,
which is costly and should be avoided in the shader.
Instead, we packed a handedness into the first bit and packed
w
into the
remaining seven last bits (as an unsigned integer). It turned out that even seven
bits is enough in the case of vertex quaternion. Furthermore, this approach
resulted in a very fast unpacking, as shown in Listing 1.1. A nice property of
this packing is that it interpolates correctly in all cases of vertex quaternions and
morph targets. We implicitly use the fact that it is never a case in practice to
interpolate quaternions with different handedness. In this case, the first bit of
quaternions to be interpolated would always be the same, resulting in correct
interpolation of low seven bits.
For model transforms, as we operate them on the CPU side, we store the
quaternion values in
float32
format. To pack a quaternion of instance rotation,
we opted for
int16
for several reasons. First, we wanted to keep instance vertex
layout as small as possible, and 32-bit formats are definitely an overkill for a
quaternion in terms of precision. Quaternion's values are limited to the [
−
1
,
1]
domain, so we need only fixed-point precision. Unfortunately, 8-bit formats were
not enough as they provide around 1-2
◦
angle steps. While this was enough
for a vertex quaternion, in the case of encoding an instance's position, such a
float
UnpackFirstBitFromByte
(
float
argument
)
{
return
saturate
((
argument
255.0
f
−
127.5
f
)
100.0
f
);
}
float
UnpackLow7BitsFromByte
(
float
firstBit
,
float
argument
)
{
return
(
argument
255.0
f
−
128.0
f
firstBit
) / 127.0
f
;
}
Listing 1.1.
Vertex quaternion unpacking.