0
$\begingroup$

I know, this is a silly question, but since I need this so often, I just want to double check that I made no mistake.

Working with most shader languages, a texture can store $8$ bits per channels, which in the shader programm appear as value in the range range $r_8:=\{0, 1/255, 2/255,\dots,1\}$. What I want to do is to transform between a single $32$ bit floating point value in $(0, 1)$ (I don't care about the floating point specifics here and just think of it as real number) and a $4$-tuple of values in $r_8$, so $\text{split}:(0,1)\to r_8^4$ and $\text{comb}:r_8^4\to(0,1)$.

I specifically want that $\text{comb}(\text{split}(x))=\text{round}(256^4 x)/256^4$ for almost all $x$ (I don't care about the ones lying on boundaries between bins).

My implementation is the following:

  • $\text{split}(x)_i := \text{floor}(256^ix)/(255\cdot 256^{i-1})$
  • $\text{comb}(x_0,\dots,x_3):=255(x_0/256+\dots+x_3/256^4)+256^4/2$

Is this correct or did I overlook something?

Edit

I realized that the first part should be $$\text{split}(x)_i:=\lfloor(256(256^ix-\lfloor256^ix\rfloor)\rfloor/255$$

$\endgroup$

1 Answer 1

1
$\begingroup$

This will not work. The principle you are attempting to use would work (with some adjustment) if you were converting between a 32-bit unsigned integer representation and four 8-bit integers, because there are exactly enough bits to use. But the 32-bit “single precision” floating point format uses some of its bits to represent the exponent and the sign:

Diagram of 32-bit single precision floating point format

(Image by Wikipedia user Fresheneesz)

There are only 23 bits available (effectively 24) to represent an integer value. So, you cannot simply pack four 8-bit integers into an mathematically-integer value stored in a 32-bit float — you will run out of precision, and the component stored in the low bits will be corrupted.

However, it is possible to pack the amount of data you want into a float; the key is that you have to reinterpret the bits of the float as a 32-bit integer or vice versa. In GLSL, this is provided by the functions floatBitsToUint() and uintBitsToFloat(). Once you have the unsigned integer, you can proceed as you intended — but the produced float will not be in the range 0 to 1.

$\endgroup$
2
  • $\begingroup$ Hey, thanks a lot for the answer! I realized that I'd need $\text{split}(x)_i:=\lfloor(256(256^ix-\lfloor256^ix\rfloor)\rfloor/255$, but regarding your argument about $32$ bit floats, the specification says that precision highp float; just guarantees at least $32$ bit precision, but it could be higher, so do you think my idea could make more sense in this regard? If the precision is only $32$, I won't get $256^4$ bins, but it also wouldn't lead to any large errors, I think? $\endgroup$
    – fweth
    Feb 6, 2023 at 7:02
  • 1
    $\begingroup$ @fweth What you care about for this purpose is the number of mantissa bits, not the total number of bits. 32-bit floats have 24 (effective) mantissa bits, and 64-bit floats have 53 mantissa bits. As I already described, 24 bits means you lose 1 of your 4 components. 53 bits is sufficient for everything, but from what I've heard, even if a GPU supports 64-bit floats, they're so slow as to be unwise to use. (I don't know whether highp ever maps to 64-bit.) $\endgroup$
    – Kevin Reid
    Feb 6, 2023 at 16:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.