Search code examples
unity-game-enginegraphicsshaderhlslcg

How can I pack a float3 into one float


I am doing some animation jobs. I need to pack some pivots into UV and then my shader can read them.

I need to pack 4 float3 into a float4. Therefore, I need to pack each float3 into a float.

These 4 float3 are (model space position1, direction1, model space position2, direction2). I know how to handle the directions because they are normalized. I can use something like:

 #define f3_f(c) (dot(round((c) * 255), float3(65536, 256, 1)))
 #define f_f3(f) (frac((f) / float3(16777216, 65536, 256)))

But how can I handle positions? I am using SM3.0 and I can't use bitwise operation.


Solution

  • Do you really need to pack it into a float (4 bytes), or can you pack it into a 32-bit unsigned integer (i.e. 4 bytes)?

    If, so take a look at the code in DirectXMath for converting to/from various formats as done in DirectXTex such as DXGI_FORMAT_R11G11B10_FLOAT. Since this format is positive only, you'll have to do a scale and bias to/from the format to handle the [-1,+1] range, but that's easy to do (0.5*value + 0.5 <-> 2*value - 1).

    // 3D vector: 11/11/10 floating-point components
    // The 3D vector is packed into 32 bits as follows: a 5-bit biased exponent
    // and 6-bit mantissa for x component, a 5-bit biased exponent and
    // 6-bit mantissa for y component, a 5-bit biased exponent and a 5-bit
    // mantissa for z. The z component is stored in the most significant bits
    // and the x component in the least significant bits. No sign bits so
    // all partial-precision numbers are positive.
    // (Z10Y11X11): [32] ZZZZZzzz zzzYYYYY yyyyyyXX XXXxxxxx [0]
    struct XMFLOAT3PK
    {
        union
        {
            struct
            {
                uint32_t xm : 6; // x-mantissa
                uint32_t xe : 5; // x-exponent
                uint32_t ym : 6; // y-mantissa
                uint32_t ye : 5; // y-exponent
                uint32_t zm : 5; // z-mantissa
                uint32_t ze : 5; // z-exponent
            };
            uint32_t v;
        };
    
        XMFLOAT3PK() = default;
    
        XMFLOAT3PK(const XMFLOAT3PK&) = default;
        XMFLOAT3PK& operator=(const XMFLOAT3PK&) = default;
    
        XMFLOAT3PK(XMFLOAT3PK&&) = default;
        XMFLOAT3PK& operator=(XMFLOAT3PK&&) = default;
    
        explicit XM_CONSTEXPR XMFLOAT3PK(uint32_t Packed) : v(Packed) {}
        XMFLOAT3PK(float _x, float _y, float _z);
        explicit XMFLOAT3PK(_In_reads_(3) const float *pArray);
    
        operator uint32_t () const { return v; }
    
        XMFLOAT3PK& operator= (uint32_t Packed) { v = Packed; return *this; }
    };
    
    // Converts float3 to the 11/11/10 format
    inline void XM_CALLCONV XMStoreFloat3PK
    (
        XMFLOAT3PK* pDestination,
        FXMVECTOR V
    )
    {
        assert(pDestination);
    
        __declspec(align(16)) uint32_t IValue[4];
        XMStoreFloat3A( reinterpret_cast<XMFLOAT3A*>(&IValue), V );
    
        uint32_t Result[3];
    
        // X & Y Channels (5-bit exponent, 6-bit mantissa)
        for(uint32_t j=0; j < 2; ++j)
        {
            uint32_t Sign = IValue[j] & 0x80000000;
            uint32_t I = IValue[j] & 0x7FFFFFFF;
    
            if ((I & 0x7F800000) == 0x7F800000)
            {
                // INF or NAN
                Result[j] = 0x7c0;
                if (( I & 0x7FFFFF ) != 0)
                {
                    Result[j] = 0x7c0 | (((I>>17)|(I>>11)|(I>>6)|(I))&0x3f);
                }
                else if ( Sign )
                {
                    // -INF is clamped to 0 since 3PK is positive only
                    Result[j] = 0;
                }
            }
            else if ( Sign )
            {
                // 3PK is positive only, so clamp to zero
                Result[j] = 0;
            }
            else if (I > 0x477E0000U)
            {
                // The number is too large to be represented as a float11, set to max
                Result[j] = 0x7BF;
            }
            else
            {
                if (I < 0x38800000U)
                {
                    // The number is too small to be represented as a normalized float11
                    // Convert it to a denormalized value.
                    uint32_t Shift = 113U - (I >> 23U);
                    I = (0x800000U | (I & 0x7FFFFFU)) >> Shift;
                }
                else
                {
                    // Rebias the exponent to represent the value as a normalized float11
                    I += 0xC8000000U;
                }
    
                Result[j] = ((I + 0xFFFFU + ((I >> 17U) & 1U)) >> 17U)&0x7ffU;
            }
        }
    
        // Z Channel (5-bit exponent, 5-bit mantissa)
        uint32_t Sign = IValue[2] & 0x80000000;
        uint32_t I = IValue[2] & 0x7FFFFFFF;
    
        if ((I & 0x7F800000) == 0x7F800000)
        {
            // INF or NAN
            Result[2] = 0x3e0;
            if ( I & 0x7FFFFF )
            {
                Result[2] = 0x3e0 | (((I>>18)|(I>>13)|(I>>3)|(I))&0x1f);
            }
            else if ( Sign )
            {
                // -INF is clamped to 0 since 3PK is positive only
                Result[2] = 0;
            }
        }
        else if ( Sign )
        {
            // 3PK is positive only, so clamp to zero
            Result[2] = 0;
        }
        else if (I > 0x477C0000U)
        {
            // The number is too large to be represented as a float10, set to max
            Result[2] = 0x3df;
        }
        else
        {
            if (I < 0x38800000U)
            {
                // The number is too small to be represented as a normalized float10
                // Convert it to a denormalized value.
                uint32_t Shift = 113U - (I >> 23U);
                I = (0x800000U | (I & 0x7FFFFFU)) >> Shift;
            }
            else
            {
                // Rebias the exponent to represent the value as a normalized float10
                I += 0xC8000000U;
            }
    
            Result[2] = ((I + 0x1FFFFU + ((I >> 18U) & 1U)) >> 18U)&0x3ffU;
        }
    
        // Pack Result into memory
        pDestination->v = (Result[0] & 0x7ff)
                          | ( (Result[1] & 0x7ff) << 11 )
                          | ( (Result[2] & 0x3ff) << 22 );
    }
    
    
    // Converts the 11/11/10 format to float3
    inline XMVECTOR XM_CALLCONV XMLoadFloat3PK
    (
        const XMFLOAT3PK* pSource
    )
    {
        assert(pSource);
    
        __declspec(align(16)) uint32_t Result[4];
        uint32_t Mantissa;
        uint32_t Exponent;
    
        // X Channel (6-bit mantissa)
        Mantissa = pSource->xm;
    
        if ( pSource->xe == 0x1f ) // INF or NAN
        {
            Result[0] = static_cast<uint32_t>(0x7f800000 | (static_cast<int>(pSource->xm) << 17));
        }
        else
        {
            if ( pSource->xe != 0 ) // The value is normalized
            {
                Exponent = pSource->xe;
            }
            else if (Mantissa != 0) // The value is denormalized
            {
                // Normalize the value in the resulting float
                Exponent = 1;
    
                do
                {
                    Exponent--;
                    Mantissa <<= 1;
                } while ((Mantissa & 0x40) == 0);
    
                Mantissa &= 0x3F;
            }
            else // The value is zero
            {
                Exponent = static_cast<uint32_t>(-112);
            }
    
            Result[0] = ((Exponent + 112) << 23) | (Mantissa << 17);
        }
    
        // Y Channel (6-bit mantissa)
        Mantissa = pSource->ym;
    
        if ( pSource->ye == 0x1f ) // INF or NAN
        {
            Result[1] = static_cast<uint32_t>(0x7f800000 | (static_cast<int>(pSource->ym) << 17));
        }
        else
        {
            if ( pSource->ye != 0 ) // The value is normalized
            {
                Exponent = pSource->ye;
            }
            else if (Mantissa != 0) // The value is denormalized
            {
                // Normalize the value in the resulting float
                Exponent = 1;
    
                do
                {
                    Exponent--;
                    Mantissa <<= 1;
                } while ((Mantissa & 0x40) == 0);
    
                Mantissa &= 0x3F;
            }
            else // The value is zero
            {
                Exponent = static_cast<uint32_t>(-112);
            }
    
            Result[1] = ((Exponent + 112) << 23) | (Mantissa << 17);
        }
    
        // Z Channel (5-bit mantissa)
        Mantissa = pSource->zm;
    
        if ( pSource->ze == 0x1f ) // INF or NAN
        {
            Result[2] = static_cast<uint32_t>(0x7f800000 | (static_cast<int>(pSource->zm) << 17));
        }
        else
        {
            if ( pSource->ze != 0 ) // The value is normalized
            {
                Exponent = pSource->ze;
            }
            else if (Mantissa != 0) // The value is denormalized
            {
                // Normalize the value in the resulting float
                Exponent = 1;
    
                do
                {
                    Exponent--;
                    Mantissa <<= 1;
                } while ((Mantissa & 0x20) == 0);
    
                Mantissa &= 0x1F;
            }
            else // The value is zero
            {
                Exponent = static_cast<uint32_t>(-112);
            }
    
            Result[2] = ((Exponent + 112) << 23) | (Mantissa << 18);
        }
    
        return XMLoadFloat3A( reinterpret_cast<const XMFLOAT3A*>(&Result) );
    }