【问题标题】:How to quickly pack a float to 4 bytes?如何快速将浮点数打包为 4 个字节?
【发布时间】:2021-02-24 07:59:13
【问题描述】:

我一直在寻找一种在 WebGL 纹理上存储浮点数的方法。我在互联网上找到了some solutions,但那些只处理 [0..1) 范围内的浮点数。我希望能够存储任意浮点数,为此,需要扩展这样的函数以存储指数(比如在第一个字节上)。不过,我不太明白这些是如何工作的,所以如何做到这一点并不明显。简而言之:

将浮点数打包成 4 个字节的有效算法是什么?

【问题讨论】:

  • 我正在标记 C/OpenGL,因为这些问题的答案很容易移植到 WebGL。可以吗?
  • C 中的答案与 WebGL 中的答案完全不同。例如,在 C 中,您基本上可以直接重新解释字节(一个浮点数已经是 4 个字节);在 JavaScript 中,您需要一个不同的解决方案。
  • 好的,我删除了 C,@MichaelAaronSafyan
  • floating point textures 怎么样?在 95.5% 的可用性下,我认为它们使用起来非常安全。 @MichaelAaronSafyan 好吧,它在 javascript new Uint8Array(new Float32Array([523.151231]).buffer) 中并没有太大的不同。
  • 鉴于@LJᛃ 的评论暗示,即使在 Javascript 中,也可以像在 C 中一样简单地将 4 字节浮点数重新解释为 4 个单独的字节,我绝对对什么需要“编码”感到困惑。

标签: javascript floating-point glsl webgl


【解决方案1】:

这并不快,但可行。 (请注意,GLSL 1.00 浮点文字在编译器中存在转换错误)。

struct Bitset8Bits {
    mediump vec4 bit0;
    mediump vec4 bit1;
    mediump vec4 bit2;
    mediump vec4 bit3;
    mediump vec4 bit4;
    mediump vec4 bit5;
    mediump vec4 bit6;
    mediump vec4 bit7;
};


vec4 when_gt (vec4 l, vec4 r) {
  return max(sign(l - r), 0.0);
}


Bitset8Bits unpack_4_bytes (lowp vec4 byte) {
    Bitset8Bits result;

    result.bit7 = when_gt(byte, vec4(127.5));
    vec4 bits0to6 = byte - 128.0 * result.bit7;

    result.bit6 = when_gt(bits0to6, vec4(63.5));
    vec4 bits0to5 = bits0to6 - 64.0 * result.bit6;

    result.bit5 = when_gt(bits0to5, vec4(31.5));
    vec4 bits0to4 = bits0to5 - 32.0 * result.bit5;

    result.bit4 = when_gt(bits0to4, vec4(15.5));
    vec4 bits0to3 = bits0to4 - 16.0 * result.bit4;

    result.bit3 = when_gt(bits0to3, vec4(7.5));
    vec4 bits0to2 = bits0to3 - 8.0 * result.bit3;

    result.bit2 = when_gt(bits0to2, vec4(3.5));
    vec4 bits0to1 = bits0to2 - 4.0 * result.bit2;

    result.bit1 = when_gt(bits0to1, vec4(1.5));
    vec4 bit0 = bits0to1 - 2.0 * result.bit1;

    result.bit0 = when_gt(bit0, vec4(0.5));

    return result;
}

float when_gt (float l, float r) {
  return max(sign(l - r), 0.0);
}




vec4 pack_4_bytes (Bitset8Bits state) {

  vec4 data;

  data = state.bit0
    + 2.0 * state.bit1
    + 4.0 * state.bit2
    + 8.0 * state.bit3
    + 16.0 * state.bit4
    + 32.0 * state.bit5
    + 64.0 * state.bit6
    + 128.0 * state.bit7;

  return data;
}

vec4 brians_float_pack (
    float original_value) {

    // Remove the sign
    float pos_value = abs(original_value);

    float exp_real = floor(log2(pos_value));
    float multiplier = pow(2.0, exp_real);
    float normalized = pos_value / multiplier - 1.0;

    float exp_v = exp_real + 127.0;
    // if exp_v == -Inf -> 0
    // if exp_v == +Inf -> 255
    // if exp_v < -126.0 -> denormalized (remove the "1")
    // otherwise + 127.0;

    Bitset8Bits packed_v;

    packed_v.bit7.a =
        step(sign(original_value) - 1.0, -1.5); // pos

    // Exponent 8 bits

    packed_v.bit6.a = when_gt(exp_v, 127.5);
    float bits0to6 = exp_v - 128.0 * packed_v.bit6.a;

    packed_v.bit5.a = when_gt(bits0to6, 63.5);
    float bits0to5 = bits0to6 - 64.0 * packed_v.bit5.a;

    packed_v.bit4.a = when_gt(bits0to5, 31.5);
    float bits0to4 = bits0to5 - 32.0 * packed_v.bit4.a;

    packed_v.bit3.a = when_gt(bits0to4, 15.5);
    float bits0to3 = bits0to4 - 16.0 * packed_v.bit3.a;

    packed_v.bit2.a = when_gt(bits0to3, 7.5);
    float bits0to2 = bits0to3 - 8.0 * packed_v.bit2.a;

    packed_v.bit1.a = when_gt(bits0to2, 3.5);
    float bits0to1 = bits0to2 - 4.0 * packed_v.bit1.a;

    packed_v.bit0.a = when_gt(bits0to1, 1.5);
    float bit0 = bits0to1 - 2.0 * packed_v.bit0.a;

    packed_v.bit7.b = when_gt(bit0, 0.5);

    // Significand 23 bits

    float factor = 0.5;
    // 0.4999999
    
    // Significand MSB bit 22:
    packed_v.bit6.b =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit6.b;
    factor = 0.5 * factor;

    packed_v.bit5.b =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit5.b;
    factor = 0.5 * factor;

    packed_v.bit4.b =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit4.b;
    factor = 0.5 * factor;

    packed_v.bit3.b =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit3.b;
    factor = 0.5 * factor;

    packed_v.bit2.b =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit2.b;
    factor = 0.5 * factor;

    packed_v.bit1.b =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit1.b;
    factor = 0.5 * factor;

    packed_v.bit0.b =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit0.b;
    factor = 0.5 * factor;


    packed_v.bit7.g =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit7.g;
    factor = 0.5 * factor;

    packed_v.bit6.g =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit6.g;
    factor = 0.5 * factor;

    packed_v.bit5.g =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit5.g;
    factor = 0.5 * factor;

    packed_v.bit4.g =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit4.g;
    factor = 0.5 * factor;

    packed_v.bit3.g =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit3.g;
    factor = 0.5 * factor;

    packed_v.bit2.g =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit2.g;
    factor = 0.5 * factor;

    packed_v.bit1.g =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit1.g;
    factor = 0.5 * factor;

    packed_v.bit0.g =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit0.g;
    factor = 0.5 * factor;


    packed_v.bit7.r =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit7.r;
    factor = 0.5 * factor;

    packed_v.bit6.r =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit6.r;
    factor = 0.5 * factor;

    packed_v.bit5.r =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit5.r;
    factor = 0.5 * factor;

    packed_v.bit4.r =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit4.r;
    factor = 0.5 * factor;

    packed_v.bit3.r =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit3.r;
    factor = 0.5 * factor;

    packed_v.bit2.r =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit2.r;
    factor = 0.5 * factor;

    packed_v.bit1.r =
        when_gt(normalized, factor - 0.00000005);
    normalized = normalized - factor * packed_v.bit1.r;
    factor = 0.5 * factor;

    // LSB bit 0
    packed_v.bit0.r =
        when_gt(normalized, factor - 0.00000005);

    vec4 result = pack_4_bytes(packed_v);

    return result;
}

【讨论】:

  • 特别感谢 Evan Wallace evanw.github.io/float-toy 帮助我找出格式并进行调试。
  • 太棒了,我希望人们在面对这个可怕的问题时能找到你的答案。
【解决方案2】:

一种简单的方法是首先同意您支持的浮点范围,然后在打包之前将其重新映射到 [0...1) 范围。

const MIN = -100;
const MAX = 100;

function packRemap(v){
    return (v - MIN) / (MAX - MIN);
}

function unpackRemap(p){
    return MIN + p * (MAX - MIN);
}

【讨论】:

    【解决方案3】:

    嗯,float 是一个 32 位数字 (23 bits for mantissa + 1 bit implicitly, 8 bits for exponent and 1 bit for sign),RGBA8 纹理的纹素也是 32 位的。因此我们只需要一个编码方案,它可以用 JS(或任何其他语言)打包并用 GLSL 解包(给定 os GLSL ES 1.0 的限制,例如缺少按位操作)。这是我的建议(在 C++ 中):

    #include <cstdint>
    #include <iostream>
    #include <cmath>
    
    // for storing result of encoding
    struct rgba {
        uint8_t r, g, b, a;
    };
    
    rgba float2rgba(float x) {
        union {
            float xc;
            uint32_t xi;
        };
    
        // let's "convert" our float number to uint32_t so we can mess with it's bits
        xc = x;
    
        // in v we'll pack sign bit and mantissa, that would be exactly 24 bits
        int_least32_t v =
            // sign bit
            (xi >> 31 & 1) |
            // mantissa
            ((xi & 0x7fffff) << 1);
    
        rgba r;
    
        // then we just split into bytes and store them in RGB channels
        r.r = v / 0x10000;
        r.g = (v % 0x10000) / 0x100;
        r.b = v % 0x100;
    
        // and we'll put the exponent to alpha channel
        r.a = xi >> 23 & 0xff;
    
        return r;
    }
    
    float rgba2float(rgba r) {
        // let's "rebuild" mantissa and sign bit first
        uint32_t v = (r.b / 2) + r.g * 0x80 + r.r * 0x8000;
    
        return
            // let's apply sign (it's in least significant bit of v)
            (r.b % 2 ? -1.f : 1.f) *
            // and reconstruct the number itself
            (1.f + v * pow(2.f, -23.f)) * pow(2.f, static_cast<unsigned>(r.a) - 127);
    }
    
    int main() {
        const float a = -1.34320e32f;
        rgba r = float2rgba(a);
        std::cout <<
            a << '\n' <<
            static_cast<unsigned>(r.r) << ',' <<
            static_cast<unsigned>(r.g) << ',' <<
            static_cast<unsigned>(r.b) << ',' <<
            static_cast<unsigned>(r.a) << '\n' <<
            rgba2float(r) << std::endl;
    }
    

    输出:

    -1.3432e+32
    167,214,213,233
    -1.3432e+32
    

    【讨论】:

    • 嘿,这很好,谢谢!虽然我很遗憾要求使用 C++,因为 GLSL ES 2.0 上没有许多操作 :(
    • 是的,现在我看不到将我建议的打包代码移植到 GLSL ES 2.0 的方法,但是 解包(例如,从纹理 rgbas 读取并转换它们回到浮点数)是可能的(仅在那里使用算术运算)。稍后我会用 GLSL sn-p 更新我的答案。
    【解决方案4】:

    由于我找不到任何可以解决我的问题的方法,我已经组装了这个解决方案:

    function fract(x){ 
      return x - Math.floor(x);
    };
    
    function packFloat(x) {
      var s = x > 0 ? 1 : -1;
      var e = Math.floor(Math.log2(s*x));
      var m = s*x/Math.pow(2, e);
      return [
        Math.floor(fract((m-1)*256*256)*256),
        Math.floor(fract((m-1)*256)*256),
        Math.floor(fract((m-1)*1)*256),
        ((e+63) + (x>0?128:0))];
    }
    
    function unpackFloat(v){
      var s = v[3] >= 128 ? 1 : -1;
      var e = v[3] - (v[3] >= 128 ? 128 : 0) - 63;
      var m = 1 + v[0]/256/256/256 + v[1]/256/256 + v[2]/256;
      return s * Math.pow(2, e) * m;
    };
    
    for (var i=0; i<10; ++i){
      var num = (Math.random()*2.0-1.0)*1000;
      console.log(num, packFloat(num), unpackFloat(packFloat(num)));
    }
    

    它将浮点数来回转换为 4 个字节。与其他解决方案相反,它不限于小范围或预定义范围,并且能够表示形状上的任何数字s * m * 2^e,其中s = -1 or 1m = 1 til 2(精度为 24 位),和e = -63 to 64。将其移植到 GLSL 很简单,因为它只使用常见的浮点运算。

    【讨论】:

    • 做得很好。尽管您可能希望在 GLSL 中重写它以避免分支(如果可能)。还想指出,求幂和固定线性范围之间的真正区别是精度。线性范围在区间中提供相同级别的精度。而当您偏离中心时,求幂的精度会下降。当然,哪种表示方式最好取决于您的浮点数的分布。
    【解决方案5】:

    我不确定我是否理解这个问题。

    为什么不只使用浮点纹理?

    var ext = gl.getExtension("OES_texture_float");
    if (!ext) {
       // sorry no floating point support)
    }
    

    至于将数据放入纹理中,您只需使用Float32Array

    var data = new Float32Array([0.123456, Math.sqrt(2), ...]);
    gl.texImage2D(gl.TARGET_2D, 0, gl.RGBA, width, height, 0, 
                  gl.RGBA, gl.FLOAT, data);
    

    大多数硬件都支持从浮点纹理读取。较少支持渲染到浮点纹理。见WebGL iOS render to floating point texture

    我还要指出,您可以从 JavaScript 中的浮点数中获取字节

    var arrayOf10Floats = new Float32Array(10);
    var arrayOf40bytes = new Uint8Array(arrayOf10Floats.buffer);
    

    这两个数组共享相同的内存。它们都只是底层ArrayBufferArrayBufferViews。

    【讨论】:

    • 这是一个很好的建议,谢谢。我避开了它们,因为它们不适用于 headless-gl,但现在我认为这可能是一个更好的解决方案。
    • 在 ES2 中不可能。
    • @Pixel,这个问题不是关于 ES2 而是关于 WebGL。如果您想了解有关 ES2 的信息,请提出另一个问题。 Tho,ES2 通过扩展确实支持所有这些。查看ES2 extension header,您会发现OES_texture_floatEXT_color_buffer_float 都被定义为有效的ES2 扩展。如果您的特定驱动程序/设备不支持浮点并不意味着 ES2 不支持它,只是您的设备。这实际上与 WebGL 相同。这是一项可选功能。
    • 对于 WebGL2,我同意(根据规范 khronos.org/registry/webgl/specs/latest/2.0),但 WebGL1 基于 ES2(khronos.org/registry/webgl/specs/1.0)。正如您所说,扩展程序可能会起作用,但不能保证一般用途。
    • 不知道你的意思是什么。从答案中可以清楚地看到。它被称为“扩展”,甚至显示检查它。
    猜你喜欢
    • 2021-06-04
    • 2016-08-17
    • 2014-08-13
    • 2011-06-16
    • 2012-12-27
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    相关资源
    最近更新 更多