Jump to content

  • Log In with Google      Sign In   
  • Create Account

packing a float into a A8R8G8B8 texture (shader)


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
13 replies to this topic

#1 gjaegy   Members   -  Reputation: 104

Like
0Likes
Like

Posted 03 April 2007 - 05:08 AM

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

Sponsor:

#2 WilyCoder   Members   -  Reputation: 100

Like
0Likes
Like

Posted 04 April 2007 - 08:50 AM

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).

#3 stanlo   Members   -  Reputation: 212

Like
0Likes
Like

Posted 04 April 2007 - 07:02 PM

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.

#4 Ysaneya   Members   -  Reputation: 1247

Like
0Likes
Like

Posted 04 April 2007 - 09:34 PM

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.


#5 Unfadable   Members   -  Reputation: 140

Like
0Likes
Like

Posted 07 April 2007 - 12:09 PM

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?

#6 stanlo   Members   -  Reputation: 212

Like
0Likes
Like

Posted 07 April 2007 - 06:41 PM

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.

#7 Unfadable   Members   -  Reputation: 140

Like
0Likes
Like

Posted 08 April 2007 - 02:56 AM

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.

#8 Schrompf   Prime Members   -  Reputation: 954

Like
0Likes
Like

Posted 08 April 2007 - 03:43 AM

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.

#9 Ysaneya   Members   -  Reputation: 1247

Like
0Likes
Like

Posted 08 April 2007 - 06:46 AM

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.


#10 Ysaneya   Members   -  Reputation: 1247

Like
0Likes
Like

Posted 08 April 2007 - 07:01 AM

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.


#11 jamesw   Members   -  Reputation: 400

Like
0Likes
Like

Posted 08 April 2007 - 01:18 PM

Looks like this is already a solved problem, but here's another thread on it:

Codesampler thread;

That one shows how you can do a limited amount of additive blends as well. I'm still stuck on filtering though.

#12 gjaegy   Members   -  Reputation: 104

Like
0Likes
Like

Posted 12 April 2007 - 12:42 AM

for your information, I am using the code given in that CodeSampler thread:

encode (fDist is the normalized distance):

const float4 bitSh = float4( 256*256*256, 256*256, 256, 1);
const float4 bitMsk = float4( 0, 1.0/256.0, 1.0/256.0, 1.0/256.0);

float4 comp;
comp = fDist * bitSh;
comp = frac(comp);
comp -= comp.xxyz * bitMsk;
return comp;


decode (vec is the rgba encoded value):

const float4 bitShifts = float4(1.0/(256.0*256.0*256.0), 1.0/(256.0*256.0), 1.0/256.0, 1);
return dot(vec.xyzw , bitShifts);


The code given by Ysaneya introduces some artefacts, certainly cause by the missing "comp -= comp.xxyz * bitMsk;" (which is actually the difference between Ysaneya's code and the one given in the CodeSampler thread).

Right now the result is very good, it allows me to store the scene camera depth texture using a ARGB8 rendertarget, which allows using the same Z-buffer as the main surface (and thus writing the Z buffer during my camera-depth texture generation, allowing early Z-cull during the main scene rendering pass)!

Well done guys, thanks a lot !

#13 Eosie   Members   -  Reputation: 122

Like
0Likes
Like

Posted 14 April 2007 - 04:45 AM

Your code does not work with one value - 1. You always get 0 after decoding because frac(1) = 0.

#14 gjaegy   Members   -  Reputation: 104

Like
0Likes
Like

Posted 15 April 2007 - 07:04 PM

thanks for the feedback.

It is not a problem in my case, just have to divide my distance by (zfar + 1).

I guess in most of the cases a similar solution exists.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS