开发者

Pack four bytes in a float

开发者 https://www.devze.com 2023-02-07 10:40 出处:网络
I\'m writing a shader (HLSL), and I need to pack a color value into the R32 format. I\'ve found various pieces of code for packing a float into the R8G8B8A8 format, but none of them seem to work in re

I'm writing a shader (HLSL), and I need to pack a color value into the R32 format. I've found various pieces of code for packing a float into the R8G8B8A8 format, but none of them seem to work in reverse. I'm targeting SM3.0, so (afaik) bit operations are not an option.

To sum it up, I need to be able to do this:

float4 color = ...; // Where color ranges from 0 -> 1
float packedValue = pack(color);

Anyone know how to do this?

UPDATE

I've gotten some headway... perhaps this will help to clarify the question.

My temporary solution is as such:

const int PRECISION = 64;

float4 unpack(float value)
{   
    float4 color;

    color.a = value % PRECISION;
    value = floor(value / PRECISION);

    color.b = 开发者_如何学Govalue % PRECISION;
    value = floor(value / PRECISION);

    color.g = value % PRECISION;
    value = floor(value / PRECISION);

    color.r = value;

    return color / (PRECISION - 1);
}

float pack(float4 color)
{   
    int4 iVal = floor(color * (PRECISION - 1));

    float output = 0;

    output += iVal.r * PRECISION * PRECISION * PRECISION;
    output += iVal.g * PRECISION * PRECISION;
    output += iVal.b * PRECISION;
    output += iVal.a;

    return output;
}

I'm basically... pretending I'm using integer types :s

Through guess and check, 64 was the highest number I could use while still maintaining a [0...1] range. Unfortunately, that also means I'm losing some precision - 6 bits instead of 8.


Have a look at: http://diaryofagraphicsprogrammer.blogspot.com/2009/10/bitmasks-packing-data-into-fp-render.html

The short answer is that it's not possible to do a lossless packing of 4 floats into 1 float.

Even if you do find a way to pack 4 floats, storing their exponent and significand, the packing and unpacking process may be prohibitively expensive.


Do like your sample code for 3 components to pack them into into the s=significand, and do exp2(color.g)(1+s) to pack the other component into the exponent. You'll still lose precision, but it won't be as bad as if you try to pack everything into the significand like you seem to be doing.

Unfortunately there's no way to avoid losing precision, as there are a bunch of NaN and Inf float values that are both indistinguishable from each other and hard to work with (and possibly not even supported by your GPU, depending on how old it is).


You can't pack 4 bytes, but you can pack two numbers up to 2048. To encode and decode:

float encoded = number1 + number2 * 2048
int decoded1 = encoded % 2048
int decoded2 = floor(encoded / 2048)

A 32-bit float has 23 bits of integer precision (https://en.wikipedia.org/wiki/Single-precision_floating-point_format), allowing integers up to 8,388,608 to be reliably stored. Two numbers of 2,048 use 4,194,304 integers of that range.

You can technically store two numbers up to the square root of 8,388,608, which is 2,896. But 2048 is cleaner to look at, and likely doesn't make a difference.

This has been tested thoroughly in a shader that uses a 32-bit float RGBA to transport 8 separate 0 - 2,047 values into a shader. It's only once you exceed 8,388,608 that rounding errors start to appear, e.g. a stored component of 1,029 comes back as 1,028.

You can do the same for a half float, which has 11 bits of precision (2,048 total), allowing you to pack two values of up to 45 -- or in the real world, two values of 0 - 31.

Since sampling is the most expensive thing to do in a shader, if you have the choice between two half float textures and one full float texture, I would go with the full float texture and encode more information in it, because it's less sampling.

Also be sure that you use no interpolation in the sampler. In Unity this means using "point" instead of "linear" or "trilinear".

0

精彩评论

暂无评论...
验证码 换一张
取 消