javascript - 对大量简单形状进行动画处理

标签 javascript webgl

我目前有一个用 Canvas 编写的渲染系统。它遇到了性能问题,我希望 WebGL 可以帮助解决其中一些问题。基本上它可以归结为许多简单的形状,如圆形、三 Angular 形、矩形和直线。问题在于很大一部分形状可能会在帧之间转换(请参阅 this d3 example for an example use case, though with relatively few shapes )。

我觉得我一定错过了一些关于 WebGL 的东西,因为这看起来并不是一个不常见的目标,但据我了解,这基本上是最坏的情况,因为顶点缓冲区基本上必须在每一帧进行替换。有更好的方法吗?

最佳答案

如果是我,我会计算起点和终点,将它们放入顶点缓冲区中,并在它们之间进行 lerp。与 morph targets 相同。

示例:

const numAreas = 30;
const numPointsPerArea = 100;
const maxDistFromArea = 10;
const areaWidth = 300;
const areaHeight = 150;

const endPositions = [];
for (let a = 0; a < numAreas; ++a) {
  const areaX = rand(maxDistFromArea, areaWidth - maxDistFromArea);
  const areaY = rand(maxDistFromArea, areaHeight - maxDistFromArea);;
  for (let p = 0; p < numPointsPerArea; ++p) {
    const x = areaX + rand(-maxDistFromArea, maxDistFromArea);
    const y = areaY + rand(-maxDistFromArea, maxDistFromArea);
    endPositions.push(x, y);
  }
}

const startPositions = [];
for (let a = 0; a < numAreas * numPointsPerArea; ++a) {
  startPositions.push(rand(areaWidth), rand(areaHeight));
}

function rand(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return min + Math.random() * (max - min);
}

const vs = `
attribute vec4 startPosition;
attribute vec4 endPosition;
uniform float u_lerp;
uniform mat4 u_matrix;

void main() {
  vec4 position = mix(startPosition, endPosition, u_lerp);
  gl_Position = u_matrix * position;
  gl_PointSize = 2.0;
}
`;

const fs = `
precision mediump float;
void main() {
  gl_FragColor = vec4(1, 0, 0, 1);
}
`

const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// put data in vertex buffers
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
  startPosition: { data: startPositions, numComponents: 2 },
  endPosition: { data: endPositions, numComponents: 2, },
});

const easingFunc = easingSineOut;

function render(time) {
  time *= 0.001;  // convert to seconds
  
  gl.useProgram(programInfo.program);
  
  // gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
  twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);

  // set uniforms
  twgl.setUniforms(programInfo, {
    u_matrix: m4.ortho(0, 300, 150, 0, -1, 1),
    u_lerp: easingFunc(Math.min(1, time % 2)),
  });

  // gl.drawXXX
  twgl.drawBufferInfo(gl, bufferInfo, gl.POINTS);

  requestAnimationFrame(render);
}
requestAnimationFrame(render);

function easingSineOut(t) {
  return Math.sin(t * Math.PI * .5);
}
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

希望你明白这一点。您可以添加所需的任何其他数据(每个颜色的点、每个点的大小、如果您想要正方形以外的其他形状,则使用点的纹理,或者使用三 Angular 形。为线条添加类似的数据等...)重要的部分是放置开始和结束数据并在它们之间进行调整

attribute vec4 startPosition;
attribute vec4 endPosition;
uniform float u_lerp;

void main() {
  vec4 position = mix(startPosition, endPosition, u_lerp);

否则通过 bufferData 上传每一帧都没有问题或bufferSubDataHere's an example来自this talk更新 10000 个对象,其中 JavaScript 计算对象的位置并更新所有 210596 个顶点位置并通过 bufferData 上传值每一帧。

通过 bufferData 上传和 canvas/svg 上传之间的最大区别在于,使用 WebGL 可以从循环中删除大量内容。考虑一下

Canvas /SVG

  • 对于每个对象
    • 计算位置
    • 调用多个绘制函数
      • 例如:ctx.fillStyle、ctx.begin()、ctx.arc()、ctx.fill()
      • 绘制函数生成点,复制到缓冲区(通过 gl.bufferData)
      • 绘制函数在内部调用 gl.draw

因此,对于 1000 个对象,可能有 4000 个 Canvas api 调用,每个调用都可能执行 gl.bufferData内部,多个 gl.drawXXX通话和其他事情

WebGL

  • 对于每个对象
    • 计算位置
  • gl.bufferData
  • gl.drawXXX

在 WebGL 情况下,您仍然计算 1000 个位置,但可以避免潜在的 3999 个 api 调用和 999 个对 bufferData 的调用,并将其全部替换为一次绘制调用和一次对 bufferData 的调用。

Canvas 和 SVG API 并不神奇。它们执行的操作与您在 WebGL 中手动执行的操作相同。不同之处在于它们是通用的,因此无法优化到相同的水平,并且需要大量函数来产生输出。当然ctx.fillStyle, ctx.beginPath, ctx.arc, ctx.fill只有 4 行代码,因此比在 WebGL 中绘制圆要少得多,但在 WebGL 中,一旦定义了圆,您就可以在 2 次调用(gl.uniform、gl.draw)中绘制更多圆,而这 2 次调用则可以绘制更多圆。调用非常浅(他们没有做太多工作),其中 ctx.arcctx.fill正在做大量的工作。除此之外,您可以设计在一次绘制调用中绘制 100 或 1000 个圆圈的解决方案。

关于javascript - 对大量简单形状进行动画处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52242479/

相关文章:

javascript - 无法让引导动画在滑动窗口中的 div 上工作

three.js - 清理 ThreeJS 场景 - 泄漏?

javascript - WebGL 错误 : ArrayBuffer not big enough for request in case of gl. 亮度

javascript - WebGL:纹理渲染无法正常工作

javascript - 在 Three.js 中,如何在相机方向上将对象直接移离相机?

javascript - Plotly 3dscatter 和色阶

javascript - 嵌套数组操作看起来像一个矩阵

javascript - 如何在 Three.js 中使用深度缓冲区屏蔽对象?

javascript - react : How to make password characters visible when typing each character and hide(*) after few time interval in react?

javascript - 通过点击事件改变div的背景颜色