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

时间:2017-07-14 10:51:59

标签: webgl

我用WebGL构建了audio spectrogram

audio spectrogram

我正在创建一个基于画布高度的缓冲区(依次是基于窗口的高度):

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

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

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

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

1 个答案:

答案 0 :(得分:1)

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

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

    这是基于目的地的意义,意味着它将完成最少量的工作,每个目标像素的一个工作单元,而对于线/点,您可能每个目标像素执行更多的工作。这意味着您不必担心性能。

  • 纹理是随机访问意味着您使用数据的方式多于缓冲区/属性

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

  • 因为纹理是随机访问,您可以将idx传递到着色器并使用它来操纵纹理坐标,以便顶部或底部线始终是最新数据,其余部分滚动。使用属性/缓冲区

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

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

    vertexshaderart.com使用此技术,因此着色器不必考虑像idx这样的值来确定纹理中最新数据的位置。最新数据始终位于纹理坐标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;
}