我应该如何在 WebGL 中设置顶点限制?

How should I set a vertex limit in WebGL?

我用 WebGL

构建了一个 audio spectrogram

我目前正在根据 canvas 的高度创建缓冲区(反过来,基于 window 的高度):

const buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, 1024 * h, gl.DYNAMIC_DRAW);
gl.vertexAttribPointer(a_value, 1, gl.BYTE, true, 1, 0)
gl.enableVertexAttribArray(a_value)

// within rAF
// Assigning values with a rolling offset
gl.bufferSubData(gl.ARRAY_BUFFER, idx * 1024, freqData, 1024);
gl.drawArrays(gl.POINTS, 0, w * h)
idx = (idx + 1) % h

我的问题是 - 我觉得我应该限制我正在使用的 vertices/points 数量;但我应该如何选择这个限制?

在测试中(调整页面缩放调整生成的点)- 超过 200 万点似乎在我的 macbook 上工作;虽然提高了我的 CPU 使用率。

注意:我正在计划另一个使用图像纹理的版本(我认为这会解决这个问题),但我在不同的项目中遇到过几次这个问题

我不知道这是否真的是您问题的答案,但您可能应该为此使用纹理。使用纹理有多个优点

  • 您可以只用一个四边形渲染整个屏幕。

    这是基于目的地的渲染,这意味着它将完成最少的工作,每个目标像素一个工作单元,而使用 lines/points 您可能每个目标像素做更多的工作。这意味着您不必担心性能问题。

  • 纹理是随机访问的,这意味着您使用数据的方式比 buffers/attributes

  • 对纹理进行采样,以便更好地处理 freqData.length !== w 的情况。

  • 因为纹理是随机访问的,所以您可以将 idx 传递给着色器并使用它来操纵纹理坐标,以便顶行或底行始终是最新数据,其余行滚动.使用 attributes/buffers

  • 会更难
  • 可以通过将纹理附加到帧缓冲区来从 GPU 写入纹理。这也可以让您滚动使用 2 个纹理的位置,每个帧从 tex1 复制 h - 1 行到 tex2 但向上或向下移动一行。然后将 freqData 复制到第一行或最后一行。下一帧做同样的事情,但使用 tex2 作为源,使用 tex1 作为目标。

    这也可以让您滚动数据。它可以说比将 idx 传递到着色器并操纵纹理坐标稍慢,但它使纹理坐标的使用保持一致,因此如果您想进行任何更高级的可视化,则不必考虑 idx每个你采样纹理的地方。

    vertexshaderart.com 使用此技术,因此着色器不必考虑 idx 等值来确定纹理中最新数据的位置。最新的数据总是在纹理坐标 v = 0

这是一个示例。它不做最后两件事,只是使用纹理而不是缓冲区。

function start() {
  const audio = document.querySelector('audio');
  const canvas = document.querySelector('canvas');

  const audioCtx = new AudioContext();
  const source = audioCtx.createMediaElementSource(audio);
  const analyser = audioCtx.createAnalyser();
  const freqData = new Uint8Array(analyser.frequencyBinCount);

  source.connect(analyser);
  analyser.connect(audioCtx.destination);
  audio.play();

  const gl = canvas.getContext('webgl');

  const frag = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(frag, `
    precision mediump float;
    varying vec2 v_texcoord;

    uniform sampler2D tex;

    float P = 5.5;

    void main() {
      // these 2 lines convert from 0.0 -> 1.0 to -1. to +1
      // assuming that signed bytes were put in the texture.
      // This is what the previous buffer based code was doing
      // by using BYTE for its vertexAttribPointer type.
      // The thing is AFAICT the audio data from getByteFrequencyData
      // is unsigned data. See
      // https://webaudio.github.io/web-audio-api/#widl-AnalyserNode-getByteFrequencyData-void-Uint8Array-array
      // But, this is what the old code was doing
      // do I thought I should repeat it here.

      float value = texture2D(tex, v_texcoord).r * 2.;
      value = mix(value, -2. + value, step(1., value));

      float r = 1.0 + sin(value * P);
      float g = 1.0 - sin(value * P);
      float b = 1.0 + cos(value * P);

      gl_FragColor = vec4(r, g, b, 1);
    }
  `);
  gl.compileShader(frag);

  const vert = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vert, `
    attribute vec2 a_position;
    varying vec2 v_texcoord;

    void main() {
      gl_Position = vec4(a_position, 0, 1);

      // we can do this because we know a_position is a unit quad
      v_texcoord = a_position * .5 + .5;  
    }
  `);
  gl.compileShader(vert);

  const program = gl.createProgram();
  gl.attachShader(program, vert);
  gl.attachShader(program, frag);
  gl.linkProgram(program);

  const a_value = gl.getAttribLocation(program, 'a_value');
  const a_position = gl.getAttribLocation(program, 'a_position');
  gl.useProgram(program);

  const w = freqData.length;
  let h = 0;

  const pos_buffer = gl.createBuffer()
  gl.bindBuffer(gl.ARRAY_BUFFER, pos_buffer)
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    -1, -1,
     1, -1,
    -1,  1,
    -1,  1,
     1, -1,
     1,  1,
  ]), gl.STATIC_DRAW);
  gl.vertexAttribPointer(a_position, 2, gl.FLOAT, true, 0, 0);
  gl.enableVertexAttribArray(a_position);

  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  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);

  let idx = 0
  function render() {
    resizeCanvasToDisplaySize(gl.canvas);

    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    if (gl.canvas.height !== h) {
       // reallocate texture. Note: more work would be needed
       // to save old data. As is if the user resizes the 
       // data will be cleared
       h = gl.canvas.height;
       gl.bindTexture(gl.TEXTURE_2D, texture);
       gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, w, h, 0, 
                     gl.LUMINANCE, gl.UNSIGNED_BYTE, null);
       idx = 0;
    }

    analyser.getByteFrequencyData(freqData);

    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, idx, w, 1, 
                     gl.LUMINANCE, gl.UNSIGNED_BYTE, freqData);

    gl.drawArrays(gl.TRIANGLES, 0, 6);

    idx = (idx + 1) % h;

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

function resizeCanvasToDisplaySize(canvas) {
  const w = canvas.clientWidth;
  const h = canvas.clientHeight;
  if (canvas.width !== w || canvas.height !== h) {
    canvas.width = w;
    canvas.height = h;
  }
}

document.querySelector("#ui").addEventListener('click', (e) => {
  e.target.style.display = 'none';
  start();
});
body{ margin: 0; font-family: monospace; }
canvas { 
  position: absolute;
  left: 0;
  top: 0;
  width: 100vw; 
  height: 100vh; 
  display: block; 
  z-index: -1;
}
#ui {
  position: fixed;
  top: 0;
  width: 100vw;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
}
#ui>div {
  padding: 1em;
  background: #8ef;
  cursor: pointer;
}
<audio src="https://twgljs.org/examples/sounds/DOCTOR VOX - Level Up.mp3" crossOrigin=""></audio>
<div>music: <a href="http://youtu.be/eUX39M_0MJ8">DOCTOR VOX - Level Up</a></div>
<canvas></canvas>
<div id="ui"><div>click to start</div></div>