Если бы это был я, я бы вычислял начальную и конечную точки, помещал их в буферы вершин и перебирал между ними. То же, что и превращение целей .
Пример:
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 кругов за один вызов.