我应该如何在 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>
我用 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>