Sign in to follow this  
HippyNerd

Packing a float into a texture

Recommended Posts

HippyNerd    122
I know there are probably a couple of ways to do this. But is there a stardard way people use to pack a floating point into a texture. I would like to extract it as a float in a fragment shader. If its a 32 bit int I can cleanly take 8 bits and store them in the RGBA channel, then in my fragment shader do something like vec4 texVal = glTexture1d(tex,value.u); float number = dot(texVal,vec4(1,256,2^16,2^24) (except I'll also have to account for normalisation of the texture values to [-1,1]..gah) Just wondering if there was a way of doing this with a float, or even if I can use some other channel (such as a depth texture). To store the values. (I know above if I can figure out how to pack an int, I can convert my float to an int in the main application and convert it back in the frag shader...however all these conversions will start slowing things down) Thanks,

Share this post


Link to post
Share on other sites
HippyNerd    122
It looks like extensiosn such as ATI_TEXTURE_FLOAT allow 32 bit values to be stored in textures, without being clamped.

Not sure how to access these from the other end via GLSL though. Working on that now...

Share this post


Link to post
Share on other sites
Buttza    216
This is the way I do it in my engine. I don't know if there is a better way, but it works very well.

I pack a floating point number into 24 bits (3 channels) of a ARGB texture because I need to keep the alpha for depth testing. If you'd wish to use all 32bits you'd use a similar method, using the extra 8 bits for higher accuracy in the integer or fractional parts, or of course you could just use a 32bit floating point texture. But we are talking about packing here. :)

Here is the packing code:


float3 PackDepth24(float depth)
{
//
// Calculate the integer and fractional parts of the depth
//
float depthInteger = floor(depth);

float depthFraction = frac(depth);

//
// Calculate the upper and lower 8 bits
//
float depthUpper = floor(depthInteger / 256.0f);

float depthLower = depthInteger - (depthUpper * 256.0f);

//
// Pack the values in a float3 which will be converted to 8bit RGB later
//
return float3(depthUpper / 256.0f, depthLower / 256.0f, depthFraction);
}




The above provides 16 bits for the integer part and 8 bits for the fractional part. This obviously allows for a floating point number in the range of 0-65535 with 8 bits of accuracy for the fraction.

Unpacking is performed like so:


float UnpackDepth24(float3 depth)
{
//
// Unpacking multiplier
//
const float3 unpack = {65536.0f, 256.0f, 1.0f};

//
// Unpack the depth
//
return dot(unpack, depth);
}




In my case I didnt need such a large range in the integer part, I only needed from 0-1000. By limiting your range, you can give yourself greater accuracy in the fractional part.

To do this, simply shift upwards like you would for an integer.

The packing code now becomes:


float3 PackDepth24(float depth)
{
//
// Shift the depth up 6 bits (eg. multiply by 64) so that we get 6 more bits of the fraction
//
float shiftedDepth = depth * 64.0f;

//
// Calculate the integer and fractional parts of the depth
//
float depthInteger = floor(shiftedDepth);

float depthFraction = frac(shiftedDepth);

//
// Calculate the upper and lower 8 bits
//
float depthUpper = floor(depthInteger / 256.0f);

float depthLower = depthInteger - (depthUpper * 256.0f);

//
// Pack the values in a float3 which will be converted to 8bit RGB later
//
return float3(depthUpper / 256.0f, depthLower / 256.0f, depthFraction);
}




And the unpacking becomes:


float UnpackDepth24(float3 depth)
{
//
// Unpacking multiplying
//
const float3 unpack = {65536.0f, 256.0f, 1.0f};

//
// Unpack the sample
//
float unpackedShadowSample = dot(unpack, depth);

//
// Unshift the sample by 6 bits and return
//
return unpackedShadowSample / 64.0f;
}




So now you have a floating point number with an integer part that ranges from 0-1024 and 14 bits of accuracy in the fractional part.

There may be another way that preserves the entire sign, mantissa and exponent, but this is the method that best suited me.

Hope that helps.

Share this post


Link to post
Share on other sites
HippyNerd    122
Thanks heaps Buttza, i'm implementing a method simmilar to your idea now. I hadn't really thought about separating out the fractional part like you have. This is cool.

Share this post


Link to post
Share on other sites
Buttza    216
superpig, it is. :P The beauty of it is it uses the most bits where they count.

I would still like to see a version which fully preserves a 32, 24 or 16bit floating point number. If its even possible.

I was thinking you may be able to use 'frexp' to get the mantissa and the exponent, then like above, allowing 16bits for the mantissa, 8 bits for the exponent and i guess another 8 bits to store the sign. Though it still isn't a true 32bit float because your wasting 7bits.

Share this post


Link to post
Share on other sites
lancekt    348
What's wrong with just using an fp texture? I haven't had any problems accessing those from GLSL.

If you need to render to an fp texture, you might want to try the RenderTexture library, which you can get from gpgpu.org--it makes the whole process pretty easy by encapsulating all the driver-specific stuff like the fp formats.

(Sorry in advance if I misunderstood the question. ;)

Edit: I guess if you're using hardware that doesn't support fp textures you can't do that, but I'm pretty sure any card you can use GLSL with supports them.

Share this post


Link to post
Share on other sites
Sunray    188
You can render to floating point textures in OpenGL. Atleast on NVIDIA hardware, since ATI doesn't have any support for FBO (pbuffers does not counts).

GL_ARB_texture_float
GL_EXT_framebuffer_object
http://oss.sgi.com/projects/ogl-sample/registry/

Share this post


Link to post
Share on other sites
Buttza    216
Regarding the last two posts. Of course you can use floating point texture formats. But in some cases, eg. limited hardware (eg. RGB textures only), compatibility across all hardware, doing things like storing a 24bit float in 3 channels and an 8bit alpha in the other channel cant be done using the standard texture formats. Thats when packing comes in handy.

Share this post


Link to post
Share on other sites
ptl    157
You can pack/unpack floating point values in textures like Buttza said, however it results in some lack of precision, and some performance penality because of the by-hand unpacking in the shader. I recently tested performance of floating point textures against interger ones (32 bits only), and I seems that my GeForceFX 5900 Ultra outputs exactly the same fps with the two code paths, so floating point textures are the good choice. The only drawback it has is that you can have nor filtering nor mipmapping in hardware (must do it by hand), but it seems that GeForce 6800 series have corrected this drawback, so floating point textures has became a wonderful world ;)

ptl

Share this post


Link to post
Share on other sites
HippyNerd    122
Ok that's good to hear. I was just about to start benchmarking the differences in the two approaches. I've got butza's methods implemented, but I'll also go for floating textures now. Its for a uni project so no harm done in having learnt the alternate way of doing this as well. Thanks for all the really helpful info guys.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this