在 GLSL 中转换 intBitsToFloat 并在 Javascript 中转换回 floatBitsToInt

Converting intBitsToFloat in GLSL and floatBitsToInt back in Javascript

我正在尝试使用 intBitsToFloat 将 GLSL 3.3 着色器中的整数标识符编码为浮点输出(我使用的是 highp 输出浮点向量),然后我使用 readPixels 来获取它值变成 pixelData = new Float32Array(4)。然后我使用 floatToIntBits(pixelData[0]) 在 JS 中将它解码回 Int,其中

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]
}

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

有谁知道为什么会发生这种情况?

我猜这是因为您使用的是函数,而不是直接从 readPixels 的结果中读取值。 Javascript 只有 1 种数字类型,实际上是 C/C++ double 所以当你从类型数组中读取任何值时,它都会转换为 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. 从字节偏移量 0 * 4 处的像素中提取 float32 值
  2. 该值被转换为双精度值此处数据丢失
  3. 使用此双精度值调用 floatToIntBits
  4. floatToIntBits 将 f 已经坏的数据加倍转换为 float32,因为它将它放入 float32 数组
  5. floatToIntBits 从 int32 中获取一个 int
  6. int 被转换为 double
  7. 返回 double
  8. double 传递给 console.log

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

转化为实际发生的事情是

  1. 在字节偏移量 0 * 4 处从 intPixels 中提取一个 int32 值
  2. 该值转换为双精度值
  3. 那个双精度传递给 console.log

double 可以保存 53 位整数而不损失精度,因此将 int32 转换为 double 不会丢失任何数据。那时你将值作为浮点数提取出来,它也被转换为双精度数,但是将浮点数转换为双精度数并不能使位保持不变,所以当你尝试将位作为 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);
}