Анимация большого количества простых фигур - PullRequest
0 голосов
/ 09 сентября 2018

У меня есть система рендеринга, написанная на холсте. Это касается проблем с производительностью, и я надеюсь, что WebGL поможет с некоторыми из них. В основном это сводится к множеству простых форм, таких как круги, треугольники, прямоугольники и линии. Проблема заключается в том, что высокий процент фигур, скорее всего, будет переведен из кадра в кадр (см. этот пример d3 для примера использования, хотя с относительно небольшим числом фигур ).

Мне кажется, что я что-то упускаю из-за WebGL, потому что это не кажется необычной целью, но из того, что я понимаю, это в основном наихудший сценарий, потому что буферы вершин должны были бы в основном заменяться каждым кадром. Есть ли лучший способ сделать это?

1 Ответ

0 голосов
/ 10 сентября 2018

Если бы это был я, я бы вычислял начальную и конечную точки, помещал их в буферы вершин и перебирал между ними. То же, что и превращение целей .

Пример:

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);

В противном случае нет ничего плохого в загрузке каждого кадра через bufferData или bufferSubData. Вот пример из этого выступления обновление 10000 объектов, где JavaScript вычисляет положение объектов и обновление всех 210596 позиций вершин и загрузка значений через bufferData каждый кадр.

Большая разница между загрузкой через bufferData и canvas / svg заключается в том, что с WebGL вы удаляете тонну из своих циклов. Рассмотрим

Canvas / SVG

  • за каждый объект
    • вычислить позицию
    • вызов нескольких функций отрисовки
      • Например: ctx.fillStyle, ctx.begin (), ctx.arc (), ctx.fill ()
      • функции рисования генерируют точки, копируют в буфер (через gl.bufferData)
      • функции рисования вызывают gl.draw внутри

Таким образом, для 1000 объектов, которые могут быть 4000 вызовов API api, каждый из которых, возможно, выполняет gl.bufferData внутренне, несколько вызовов gl.drawXXX и другие вещи

WebGL

  • за каждый объект
    • вычислить позицию
  • gl.bufferData
  • gl.drawXXX

В случае WebGL вы все еще вычисляете 1000 позиций, но вы избегаете потенциально 3999 вызовов API и 999 вызовов bufferData и заменяете их одним вызовом draw и одним вызовом bufferData.

API Canvas и SVG не волшебны. Они делают то же самое, что вы делаете вручную в WebGL. Разница в том, что они являются общими, поэтому они не могут оптимизировать до одного и того же уровня, и им требуется множество функций для получения выходных данных. Конечно, ctx.fillStyle, ctx.beginPath, ctx.arc, ctx.fill - это всего лишь 4 строки кода, так что кода в WebGL гораздо меньше, чем рисования круга, но в WebGL, определив круг, вы можете нарисовать больше из них за 2 вызова (gl.uniform, gl. ничья), и эти 2 вызова невероятно мелкие (они не выполняют много работы), где ctx.arc и ctx.fill выполняют массу работы. Кроме того, вы можете создавать решения, которые рисуют 100 или 1000 кругов за один вызов.

...