WebGL从浮点渲染目标读取像素

11

关于WebGL中对浮点纹理的渲染支持级别存在一些混淆,例如在此处。OES_texture_float扩展似乎并没有强制实现它,如此处所述(作为FBO附件的浮点纹理的可选支持(已弃用)),但看起来一些供应商已经实现了它。因此,我基本上理解,在非ES桌面环境中,渲染到浮点纹理实际上是有效的。虽然我无法直接从浮点渲染目标中读取

我的问题是,是否有一种方法可以通过WebGLContext :: readPixels()调用和Float32Array目标从浮点纹理中读取?谢谢。

附带一个脚本,成功从字节纹理读取,但无法读取浮点纹理:

<html>
<head>
<script>
function run_test(use_float) {
    // Create canvas and context
    var canvas = document.createElement('canvas');
    document.body.appendChild(canvas);
    var gl = canvas.getContext("experimental-webgl");

    // Decide on types to user for texture
    var texType, bufferFmt;
    if (use_float) {
        texType = gl.FLOAT;
        bufferFmt = Float32Array;
    } else {
        texType = gl.UNSIGNED_BYTE;
        bufferFmt = Uint8Array;
    }

    // Query extension
    var OES_texture_float = gl.getExtension('OES_texture_float');
    if (!OES_texture_float) {
        throw new Error("No support for OES_texture_float");
    }

    // Clear
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.clearColor(1.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Create texture
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 512, 512, 0, gl.RGBA, texType, null);

    // Create and attach frame buffer
    var fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
    gl.bindTexture(gl.TEXTURE_2D, null);
    if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
        throw new Error("gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE");
    }

    // Clear
    gl.viewport(0, 0, 512, 512);
    gl.clear(gl.COLOR_BUFFER_BIT);
    var pixels = new bufferFmt(4 * 512 * 512);
    gl.readPixels(0, 0, 512, 512, gl.RGBA, texType, pixels);

    if (pixels[0] !== (use_float ? 1.0 : 255)) {
        throw new Error("pixels[0] === " + pixels[0].toString());
    }
}

function main() {
    run_test(false);
    console.log('Test passed using GL_UNSIGNED_BYTE');
    run_test(true);
    console.log('Test passed using GL_FLOAT');
}
</script>
</head>
<body onload='main()'>
</body>
</html>
4个回答

17

很遗憾,目前似乎仍然只有将RGBA组件读取为字节的方式适用于WebGL。如果您需要将浮点数编码为像素值,可以使用以下方法:

在您的分形着色器(GLSL/HLSL)中:

float shift_right (float v, float amt) { 
    v = floor(v) + 0.5; 
    return floor(v / exp2(amt)); 
}
float shift_left (float v, float amt) { 
    return floor(v * exp2(amt) + 0.5); 
}
float mask_last (float v, float bits) { 
    return mod(v, shift_left(1.0, bits)); 
}
float extract_bits (float num, float from, float to) { 
    from = floor(from + 0.5); to = floor(to + 0.5); 
    return mask_last(shift_right(num, from), to - from); 
}
vec4 encode_float (float val) { 
    if (val == 0.0) return vec4(0, 0, 0, 0); 
    float sign = val > 0.0 ? 0.0 : 1.0; 
    val = abs(val); 
    float exponent = floor(log2(val)); 
    float biased_exponent = exponent + 127.0; 
    float fraction = ((val / exp2(exponent)) - 1.0) * 8388608.0; 
    float t = biased_exponent / 2.0; 
    float last_bit_of_biased_exponent = fract(t) * 2.0; 
    float remaining_bits_of_biased_exponent = floor(t); 
    float byte4 = extract_bits(fraction, 0.0, 8.0) / 255.0; 
    float byte3 = extract_bits(fraction, 8.0, 16.0) / 255.0; 
    float byte2 = (last_bit_of_biased_exponent * 128.0 + extract_bits(fraction, 16.0, 23.0)) / 255.0; 
    float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0; 
    return vec4(byte4, byte3, byte2, byte1); 
}

 // (the following inside main(){}) return your float as the fragment color
 float myFloat = 420.420;
 gl_FragColor = encode_float(myFloat);

然后回到JavaScript端,在绘制调用完成后,您可以使用以下代码提取每个像素的编码浮点值:

var pixels = new Uint8Array(CANVAS.width * CANVAS.height * 4);
gl.readPixels(0, 0, CANVAS.width, CANVAS.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
pixels = new Float32Array(pixels.buffer);

// pixels now contains an array of floats, 1 float for each pixel

1
谢谢Adrian。我可以确认这个很好用。我在这个项目中使用了你的解决方案:https://github.com/stharding/function-plotter - Stephen
没问题,Stephen。顺便说一下,你的工作做得很好。如果你有任何问题,请直接联系我:adrian [at] gatosomina [dot] com - Adrian Seeley
1
非常感谢这个。它像魔法一样运行,并为我的模拟提供了所需的精度 + 我不知道 Float32Array(ArrayBuffer)会将 Int 转换为浮点数,这非常方便 :) 谢谢! - nicoptere

9

1
这已经不正确了。请参考@nraynaud的答案以及下面David Gilbertson的评论。 :-) - Elias Hasle

8
我正在添加最近发现的内容:Chrome可以让您读取浮点数,作为实现定义格式的一部分(请在规范中搜索“readPixels” link1),Firefox实现了WEBGL_color_buffer_float扩展,因此您只需加载扩展并读取浮点数,但我无法使用Safari读取浮点数。

你知道这是否意味着这个功能预期在WebGL 2中吗? - pailhead
1
https://www.khronos.org/registry/webgl/specs/latest/2.0/#readpixels 是的,我认为是这样。 - nraynaud
2
非常感谢您的评论。我在Chrome中可以调用gl.getContext("experimental-webgl"),然后调用gl.readPixels(0,0,width,height,gl.RGB,gl.FLOAT,buf),同时我绑定了一个浮点数FBO,其中buf是一个Float32Array。但是在Firefox中,我该如何使用这个扩展呢?我可以调用gl.getExtension("WEBGL_color_buffer_float"),它会返回一个WEBGL_color_buffer_float { },但我该怎么处理它呢? - matth
回答自己的问题 - 似乎只有在Firefox中支持使用gl.FLOATgl.RGBA,但是一旦调用gl.getExtension(“WEBGL_color_buffer_float”),它似乎可以工作。 - matth
2
值得一提的是,规范的1.0.3版本(上面链接的)没有提到“FLOAT”,但WebGL1的最新版本有所涵盖。 - David Gilbertson

1
自WebGL发布以来,情况已经发生了变化。基本上,WebGL要求您可以使用format = RGBA和type = UNSIGNED_BYTE调用readPixels。否则,每个帧缓冲附件类型的实现允许一个其他实现定义的格式/类型组合。
您可以通过以下方式查询该格式/类型组合是什么
gl.bindFramebuffer(gl.FRAMEBUFFER, someFramebuffer);
const format = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
const type = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE);

很遗憾,它的实现是定义的。例如,在我的个人设备中检查至少有一个,我的 Nvidia Macbook Pro 在 Chrome 中报告 RGBA/UNSIGNED_BYTE,其他浏览器/设备报告 RGBA/FLOAT。
如果启用了 EXT_color_buffer_float 扩展,则 WebGL2 需要能够读取 RGBA/FLOAT 以获取浮点纹理。
在 WebGL1 中的解决方法可能包括将 FLOAT 写入 RGBA/UNSIGNED_BYTE。请参见 this。您可以更改着色器,或者添加另一个传递,读取您的浮点结果纹理并写入 RGBA/UNSIGNED_BYTE 纹理,可能大4倍以获取所有 RGBA 值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接