Float32Array在与WebGL(Chrome)交互时不更新ArrayBuffer以反映指定的值

 手机用户2602934117 发布于 2023-02-07 06:17

为了使用OES_texture_float扩展使用Chrome中的WebGL手动打包并将RGBA浮点组件纹理绑定到GPU ,像素组件数据必须存储在Float32Array中.

例如,对于一个简单的3像素纹理,每个有4个浮点组件,首先会声明一个普通的JS数组:

var pixels = [1.01, 1.02, 1.03, 1.04, 2.01, 2.02, 2.03, 2.04, 3.01, 3.02, 3.03];

然后,为了将普通JS数组转换为可以提供给GPU的强类型浮点数组,我们只需使用Float32Array构造函数,它可以将一个普通的JS数组作为输入:

pixels = new Float32Array(pixels);

现在我们将纹理表示为一个强类型的浮点数组,我们可以使用texImage2D使用已经建立的WebGL上下文(这个工作超出了本问题的范围)将其提供给GPU:

gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 3, 1, 0, gl.RGBA, gl.FLOAT, pixels);

进行适当的渲染调用显示这些浮点数被传递到GPU中(通过将输出浮点数编码为片段颜色)而没有错误(尽管由于转换而略微损失精度).

问题

从普通JS数组转换为Float32Array实际上是一个非常昂贵的操作,并且在Float32Array中操作已经转换的浮点数要快得多 - 这个操作似乎是根据大多数JS规范支持的:https:// developer. mozilla.org/en-US/docs/Web/API/Float32Array

一旦建立,您可以使用对象的方法或使用标准数组索引语法(即使用括号表示法)引用数组中的元素.

问题发生在:

使用普通的JS预设值数组创建Float32Array

我们使用[]表示法更改Float32Array中的一个或多个值,即:

pixels[0] = 420.4;
pixels[1] = 420.4;

我们使用texImage2D将Float32Array传递给GPU,并且使用上面提到的相同方法显示,Float32Array的初始设置值以某种方式使其进入GPU而没有将两个值更改为420.4

WTF?

我最好的猜测是因为强类型数组(通常)在内部表示为缓冲区和视图,我正在更新视图,缓冲区不反映更改.将Float32Array记录到浏览器控制台显示,在这种情况下,两个更改的数字似乎确实已更改.但是因为无法通过Chrome中的控制台读取ArrayBuffer的内容,所以就我的技能而言,这是一个调试死胡同.

使用NodeJS REPL尝试相同的创建,转换,更新和检查方法(不涉及GPU),揭示缓冲区值在评估期间更新,pixels[0] = 420.4;并且在读取缓冲区时不以"延迟"方式更新.

Chrome可能会延迟更新底层缓冲区,但将这些数据复制到GPU不会触发getter,而是将其从内存中复制.

临时解决方案

在找到并纠正基础问题(如果适用的话)之前,一旦在WebGL纹理的上下文中创建Float32Arrays,它们本质上是不可变的(无法更改).似乎还有一个.set()附加到类型化数组的方法,但是:

 pixels.set(new Float32Array([420.4]), index);

似乎很多外部装箱/转换来绕过懒惰缓冲区,尤其是声称允许[]访问的缓冲区.

1 个回答
  • 包含Float32Arrays的类型化数组只是打包数组(想想C/C++).更新它们是即时的.如果你想在GPU上看到数据,你必须使用texImage2D再次上传数据,否则,没有魔法,没有疯狂的缓冲,非常直接.如果你知道C/C++它在功能上等同于

    void* arrayBuffer = malloc(sizeOfBuffer);
    float* viewAsFloat = (float*)arrayBuffer;
    

    Typed Arrays不是JS数组的视图.使用本机JS数组初始化类型化数组只是初始化类型化数组的一种方便方法.一旦创建,TypedArray就是一个新数组.

    您可以将多个ArrayBuffer视图放入同一个ArrayBuffer中.

    var b = new ArrayBuffer(16);  // make a 16 byte ArrayBuffer
    var bytes = new Uint8Array(b);  // make a Uint8Array view into ArrayBuffer
    var longs = new Uint32Array(b);  // make a Uint32Array view into ArrayBuffer
    var floats = new Float32Array(b);  // make a Float32Array view into ArrayBuffer
    
    // print the contents of the views
    console.log(bytes);
    console.log(longs);
    console.log(floats);
    
    // change a byte using one of the views
    bytes[1] = 255;
    
    // print the contents again
    console.log(bytes);
    console.log(longs);
    console.log(floats);
    

    将所有代码复制并粘贴到JavaScript控制台中.你应该看到类似的东西

    [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 
    [0, 0, 0, 0] 
    [0, 0, 0, 0] 
    [0, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 
    [65280, 0, 0, 0] 
    [9.147676375112406e-41, 0, 0, 0] 
    

    注意:在同一阵列缓冲区上使用不同类型的多个视图不是跨平台兼容的.换句话说,您将在大端平台与小端平台上获得不同的结果.目前还没有流行的大端平台,其浏览器支持TypedArrays,因此您可以忽略此问题,尽管您的页面可能会在未来的某个平台上中断.如果要以独立于平台的方式读/写数据,则应使用DataView.否则,在同一缓冲区上使用多个视图的要点是上传打包顶点数据,例如使用uint32 RGBA颜色打包的浮点位置.在这种情况下,它将跨平台工作,因为您没有使用视图读取/写入相同的数据.

    正如所指出的,JS本机数组和TypedArrays不相关,除了你可以使用JS本机数组来初始化TypedArray

    var jsArray = [1, 2, 3, 4];
    var floats = new Float32Array(jsArray);  // this is a new array, not a view.
    
    console.log(jsArray);
    console.log(floats);
    
    jsArray[1] = 567;  // effects only the JS array
    
    console.log(jsArray);
    console.log(floats);
    
    floats[2] = 89;  // effects only the float array
    
    console.log(jsArray);
    console.log(floats);
    

    粘贴到控制台我得到

    [1, 2, 3, 4] 
    [1, 2, 3, 4] 
    [1, 567, 3, 4] 
    [1, 2, 3, 4] 
    [1, 567, 3, 4] 
    [1, 2, 89, 4] 
    

    请注意,您可以从任何类型化数组中获取基础ArrayBuffer.

    var buffer = floats.buffer;
    

    并创建新的视图

    var longs = new Uint8Array(buffer);
    console.log(longs);
    
    // prints [0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 178, 66, 0, 0, 128, 64] 
    

    您还可以创建覆盖缓冲区一部分的视图.

    var offset = 8; // Offset is in bytes
    var length = 2; // Length is in units of type
    
    // a buffer that looks at the last 2 floats
    var f2 = new Float32Array(buffer, offset, length); 
    console.log(f2);
    
    // prints [89, 4]
    

    至于纹理和类型化数组,这里是一个使用Float32Array来更新浮点纹理的片段.

    main();
    function main() {
      var canvas = document.getElementById("canvas");
      var gl = canvas.getContext("webgl");
      if (!gl) {
        alert("no WebGL");
        return;
      }
      var f = gl.getExtension("OES_texture_float");
      if (!f) {
        alert("no OES_texture_float");
        return;
      }
    
      var program = twgl.createProgramFromScripts(
        gl, ["2d-vertex-shader", "2d-fragment-shader"]);
      gl.useProgram(program);
    
      var positionLocation = gl.getAttribLocation(program, "a_position");
      var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
      gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
    
      var buffer = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
        -1, -1, 1, -1, -1, 2,
        -1,  1, 1, -1,  1, 1]), gl.STATIC_DRAW);
      gl.enableVertexAttribArray(positionLocation);
      gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
    
      var tex = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, tex);
      var width = 64;
      var height = 64;
      var pixels = new Float32Array(width * height * 4);
      for (var y = 0; y < height; ++y) {
        for (var x = 0; x < width; ++x) {
          var offset = (y * width + x) * 4;
          pixels[offset + 0] = (x * 256 / width) * 1000;
          pixels[offset + 1] = (y * 256 / height) * 1000;
          pixels[offset + 2] = (x * y / (width * height)) * 1000;
          pixels[offset + 3] = 256000;
        }
      }
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT,
                    pixels);
      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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    
      function randInt(range) {
        return Math.floor(Math.random() * range);
      }
    
      function render() {
        // update a random pixel
        var x = randInt(width);
        var y = randInt(height);
        var offset = (y * width + x) * 4;
        pixels[offset + 0] = randInt(256000);
        pixels[offset + 1] = randInt(256000);
        pixels[offset + 2] = randInt(256000);
    
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT,
                      pixels);
    
        gl.drawArrays(gl.TRIANGLES, 0, 6);
        requestAnimationFrame(render);
      }
      render();
    }
    <script src="https://twgljs.org/dist/2.x/twgl.min.js"></script>
    <script id="2d-vertex-shader" type="x-shader/x-vertex">
    attribute vec2 a_position;
    void main() {
        gl_Position = vec4(a_position, 0, 1);
    }
    </script>
    <script id="2d-fragment-shader" type="x-shader/x-fragment">
    precision mediump float;
    uniform vec2 u_resolution;
    uniform sampler2D u_tex;
    void main() {
        vec2 texCoord = gl_FragCoord.xy / u_resolution;
        vec4 floatColor = texture2D(u_tex, texCoord);
        gl_FragColor = floatColor / 256000.0;  
    }
    </script>
    <canvas id="canvas"  ></canvas>
    2023-02-07 06:35 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有