动画大量简单的形状

时间:2018-09-09 07:56:32

标签: javascript webgl

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

我觉得我一定缺少关于WebGL的知识,因为这似乎不是一个不常见的目标,但是据我了解,这基本上是最坏的情况,因为顶点缓冲区必须基本上在每一帧都被替换。有更好的方法吗?

1 个答案:

答案 0 :(得分:1)

如果是我,我将计算起点和终点,将其放在顶点缓冲区中,并在它们之间进行约束。与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>

希望您明白这一点。由您决定是否要添加其他任何数据(每种颜色的点数,每个点的大小,如果您要使用正方形以外的其他形状,请使用纹理作为点,或者使用三角形。为线添加类似的数据等)。重要的是将开始数据和结束数据放在一起,并在它们之间拖拉

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

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

否则,通过bufferDatabufferSubData上传每一帧都没有错。 Here's an example中的this talk更新了10000个对象,其中JavaScript正在计算对象的位置,并更新所有210596个顶点位置,并每帧通过bufferData上传值。

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

画布/ SVG

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

因此,对于可能是4000个canvas api调用的1000个对象,每个对象可能在内部执行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),这两个调用非常浅(它们并没有做很多工作),其中ctx.arcctx.fill正在做大量工作。在此之上,您可以设计在单个绘制调用中绘制100或1000个圆的解决方案。