将GLSL中的intBitsToFloat和Java中的floatBitsToInt转换回

时间:2018-12-30 12:17:28

标签: javascript bit-manipulation webgl

我正在尝试使用intBitsToFloat在GLSL 3.3着色器中将整数标识符编码为Float输出(我正在使用highp输出float矢量),然后使用readPixels将此值输入pixelData = new Float32Array(4)。然后,我使用floatToIntBits(pixelData[0])将其解码回JS int,其中<​​/ p>

var int8    = new Int8Array(4)
var int32   = new Int32Array(int8.buffer, 0, 1)
var float32 = new Float32Array(int8.buffer, 0, 1)

var floatToIntBits = function (f) {
    float32[0] = f
    return int32[0]
}

现在我得到了一个非常奇怪的结果。即当我:

  • 在GLSL中编码一个[0,2^23-1]范围内的值,我在JS中总是得到0
  • 在GLSL中编码一个[2^23,...[范围内的值,我在JS中得到正确的值
  • 在GLSL中编码一个[-2^23+1,-1]范围内的值,我在JS中总是得到-2^23+1
  • 在GLSL中编码]..., -2^23]范围内的值,我在JS中得到正确的结果

有人知道为什么会发生吗?

1 个答案:

答案 0 :(得分:1)

我猜这是因为您使用的是函数,而不是直接从readPixels的结果中读取值。 Javascript只有1个数字类型,实际上是C / C ++ double,因此,当您从typedarray中读取任何值时,它就会转换为double

让我们在没有GLSL的情况下进行测试

const i32 = new Int32Array([
  123,
  -123,
  20000000,
 -20000000,
]);
console.log(i32[0], i32[1], i32[2], i32[3]);
const f32 = new Float32Array(i32.buffer);

var int8    = new Int8Array(4)
var int32   = new Int32Array(int8.buffer, 0, 1)
var float32 = new Float32Array(int8.buffer, 0, 1)

var floatToIntBits = function (f) {
    float32[0] = f
    return int32[0]
}

console.log(floatToIntBits(f32[0]), floatToIntBits(i32[1]), floatToIntBits(i32[2]), floatToIntBits(i32[3]));

我上面得到的结果是

123 -123 20000000 -20000000
123 -1024065536 1268291200 -879192448

换句话说,我猜你在叫gl.readPixels这样的东西

const pixels = new Float32Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.FLOAT);

现在像素是Float32s。

然后您得到一个这样的人

const v = pixels[someOffset];

此刻v已转换为双精度型。它不再是您想要的int位。而是这样做

const pixels = new Float32Array(width * height * 4);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.FLOAT);
const intPixels = new Int32Array(pixels.buffer);
const v = intPixels[someOffset];

为明确起见,从值数组中拉出值后,该值将转换为双精度

假设您做了

console.log(floatToIntBits(pixels[0]))

这就是实际发生的情况

  1. 将float32值从字节偏移为0 * 4的像素中拉出
  2. 该值转换为双精度此处丢失数据
  3. floatToIntBits用这个双精度值
  4. 调用
  5. floatToIntBits将f转换为已经存在的错误数据,并将其放入float32数组中,并转换为float32
  6. floatToIntBits从int32中获取一个int
  7. 该int被强制转换为double
  8. 返回双精度数
  9. 该double传递给console.log

比较
console.log(intPixels[0]);

这就是实际发生的情况

  1. 将int32值从字节偏移0 * 4处的intPixels中拉出
  2. 该值将转换为双精度
  3. 那双被传递给console.log

double可以容纳53位整数而不会损失精度,因此将int32转换为double不会丢失任何数据。然后,当您将值作为浮点数取出时,它也被转换为双精度数,但是将浮点数转换为双精度数不会使位保持相同,因此当您尝试将位读取为整数时,它们不再您所期望的位。

也就是说,如果您只需要int,则可以创建一个int纹理,将其附加到帧缓冲区,将整数呈现到其中,然后将它们读取为整数。

const gl = document.createElement('canvas').getContext('webgl2');
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32I, 1, 1, 0, gl.RGBA_INTEGER, gl.INT, null);
log('errors:', gl.getError() !== gl.NONE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
log('errors:', gl.getError() !== gl.NONE);
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
log('errors:', gl.getError() !== gl.NONE);
// just for sanity check: spec says this should work
const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
log('fb good:', status === gl.FRAMEBUFFER_COMPLETE);

gl.clearBufferiv(gl.COLOR, 0, [-456, -123, 789, 112233]);

const pixel = new Int32Array(4);
gl.readPixels(0, 0, 1, 1, gl.RGBA_INTEGER, gl.INT, pixel);
log('pixel:', pixel);

log('errors:', gl.getError() !== gl.NONE);


function log(...args) {
  const elem = document.createElement('pre');
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}