在GLSL中转换intBitsToFloat,在Javascript中转换floatBitsToInt



我试图使用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]
}

现在我得到了一个非常奇怪的结果。也就是说,当我:

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

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

我猜这是因为您使用的是一个函数,而不是直接从readPixels的结果中读取值。Javascript只有一个数字类型,它实际上是一个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];

此时,CCD_ 15已被转换为双精度。它不再是你想要的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];

为了澄清,从typedarray中提取一个值的那一刻,它就被转换为双值

假设你做了

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

转化为实际发生的事情就是

  1. float32值从字节偏移量为0*4的像素中提取
  2. 该值被转换为双精度此处数据丢失
  3. floatToIntBits是用这个双值调用的
  4. floatToIntBits将已经坏的数据f转换为float32,并将其放入float32数组
  5. floatToIntBits从int32中获取一个int
  6. 该int被强制转换为双精度
  7. 那个替身回来了
  8. 该双精度被传递给console.log

与比较

console.log(intPixels[0]);

转化为实际发生的事情就是

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

双精度可以保存53位整数,因此将int32转换为双精度不会丢失任何数据。当你把值作为浮点值取出时,它也被转换成了double,但将浮点值转换成double并不能保持比特不变,所以当你试图把比特作为int读取时,它们不再是你期望的比特。

也就是说,如果你只想要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);
}

最新更新