packing a float into a A8R8G8B8 texture (shader)

Started by
12 comments, last by gjaegy 17 years ago
Hi guys, I would like to pack a float in the range [0.0..1.0] into an integer texture (A8R8G8B8 format), and also converting the texture value back into a float. I don't want to use a 32F texture. Is that possible? Does anyone have an efficient way of packing/unpacking such a value? I am using HLSL, but it seems there is no existing function for that. Thanks a lot, Greg
Gregory Jaegy[Homepage]
Advertisement
Any sampling of the packed texture may result in you getting back an incorrect value from what you originally put in (depending on what filtering you specify).

If you pack data into the format you specified, and try to build a float value from it in a shader, I doubt it would work.

4 short ints = 4 bytes
1 float = 4 bytes

I understand your logic, but current shading languages (at least GLSL) dont let you manipulate bits (bit shifting) like C/C++ does.

Why don't you want to use floating point textures? They were implemented for a reason (access to floating point values in a texture).
It's perfectly reasonable to want to encode a float into an A8R8G8B8 texture. A lot of older hardware doesn't support floating point textures, and moderately old hardware is really slow when using them.



Lets take an example - say I want to encode 98742/216236 with 24 bits of precision into X8R8G8B8. Random numbers I came up with by mashing on the keyboard.


Well, 98742/216236 = 0.42889250633567028616881555337687 according to windows calculator.

First, lets pretend we only want 8 bits of precision. How do we do it? Well, we only have 256 possible values for our number to be. If we just returned this in our pixel shader, it would get truncated to something. The question is, what does it get truncated to?

Remember we only have 256 possible values. Lets scale it to a range of 0-255 then. To do this, we just multiply by 255.

0.42889250633567028616881555337687*255 = 109.3675891155959229730479661111
(again, windows calculator)

So we have 109.####.... All we keep is the 109 though. But we actually output normalized values to our pixel shader, so we divide this by 255 again, and get
109/255 = 0.42745098039215686274509803921569

Our error right now is 0.001441525943513423423717514161

So now lets go on to 16 bit precision. We stored 109/255, but we lost all those decimal places after the 109. Let's try to store some of those too!

The previous number was 109.3675891155959229730479661111
and we lost .3675891155959229730479661111

Using the same method,
.3675891155959229730479661111*255 = 93.7352244769603581272313583305
floor(93.7352244769603581272313583305) = 93

So now we're basically storing 109 and 93. One more time for good measure!

0.7352244769603581272313583305*255 = 187.4822416248913224439963742775
floor(187.4822416248913224439963742775) = 187




Now we have bytes 109, 93, 187. Each can be stored in 8 bits. To decode we just backtrack:

109/255^1 + 93/255^2 + 187/255^3 = 0.42889247725233884403434576444957

Our original number was 0.42889250633567028616881555337687

The difference is 0.000000029083331442134469788927
Much better than 0.001441525943513423423717514161!

I hope I didn't confuse you. Hopefully you can see how this would translate into shader code - I could post some as I use it in multiple projects, but right now I should stop slacking off at work =P.
In ASM language (easily translated with equivalent instructions in GLSL or HLSL):

To encode from float to RGBA:

PARAM packFactors = { 1.0, 256.0, 65536.0, 16777216.0 };MUL val, val, packFactors;FRC val, val;


To decode from RGBA to float:

PARAM extract = { 1.0, 0.00390625, 0.0000152587890625, 0.000000059604644775390625 };DP4 val, val, extract;


Y.
Ysaneya,

Can you elaborate on this technique some more?

FRC val, val;

How can you store a fractional value in a component of A8R8G8B8? Do you mean frc * 256?
Quote:Original post by Unfadable
How can you store a fractional value in a component of A8R8G8B8? Do you mean frc * 256?


Even though you're stuffing it into A8R8G8B8, it still expects normalized values [0, 1] as output from the pixel shader.
Quote:
Even though you're stuffing it into A8R8G8B8, it still expects normalized values [0, 1] as output from the pixel shader.


Fair enough, I'm a little dense sometimes.

Lets say my number is 5.43

val = 5.43 * packFactors = { 5.43, 1390.08, 355860.48, 91100282.88}

frc(val) = {0.43, 0.08, 0.48, 0.88};

Did I do this correctly? When I do the extraction part, I'm not getting my original number back.
Be aware of the fact that filtering that texture will produce semi-random values as a result. You'll have to deactivate any filtering for that.
----------
Gonna try that "Indie" stuff I keep hearing about. Let's start with Splatter.
The FRC instruction will return on each component its fractional part. So if your input vector is (1.29, 9.571, 0.23, 55.68) it will return (0.29, 0.571, 0.23, 0.68).

I didn't mention it because I thought it was obvious, but this piece of code expects the inputs to be in the [0-1] range. If the numbers you want to encode/decode are bigger than 1, you need to divide by a constant (the maximum range) when encoding, and multiply by this same constant when decoding.

It is also true that filtering will mess up the results.

Y.
Here's an example. Let's say your input number of 0.431211921. You want to encode that into an RGBA (8 bits integer per channel) pixel.

First you multiply the number by the encoding factors:
(0.431211921, 0.431211921, 0.431211921, 0.431211921) *
(1.0, 256.0, 65536.0, 16777216.0) =
(0.431211921, 110.3902518, 28259.90445, 7234535.54)

Next, you apply the FRC instruction, you get:
(0.431211921, 0.3902518, 0.90445, 0.54).

At this point, those values get stored in your integer RGBA pixel, so they get rounded to 8 bits:
(110/256, 99/256, 231/256, 138/256).

So if you output to the color buffer the value of the pixel, you'll end up with color (110, 99, 231, 138).

Now, for decoding, you get your original pixel value and perform a DP4 with the decoding coefficients:
(110/256, 99/256, 231/256, 138/256) ^
(1.0, 0.00390625, 0.0000152587890625, 0.000000059604644775390625) =
0.4296875 + 0.00151062 + 0.000013767 + 0.000000031 =
0.431211918

... and the original value was 0.431211921

Note that I used my calculator to do the calculations, and it didn't have enough digits for the lower values, so the actual precision should be even better than that.

Y.

This topic is closed to new replies.

Advertisement