javascript - style - title js
dibujar 10,000 objetos en lienzo javascript (5)
Aquí hay algunos pasos que puede hacer para aumentar el rendimiento:
- Primero deshágase de
save
/restore
, son llamadas muy caras y pueden reemplazarse consetTransform
- Desenrolle el bucle para hacer más dentro de la iteración.
- Caché todas las propiedades
Ejemplo con bucle desenrollado para 4 iteraciones:
for(var nObject = 0,
len = objects.length, // cache these
x = coords.x,
y = coords.y; nObject < len; nObject++){
ctx.setTransform(1,0,0,1, x, y); // sets absolute transformation
ctx.rotate(objects[nObject].position*0.01);
ctx.translate(radio,0);
ctx.drawImage(imgToDraw,0,0);
objects[nObject++].position++;
ctx.setTransform(1,0,0,1,x, y);
ctx.rotate(objects[nObject].position*0.01);
ctx.translate(radio,0);
ctx.drawImage(imgToDraw,0,0);
objects[nObject++].position++;
ctx.setTransform(1,0,0,1,x, y);
ctx.rotate(objects[nObject].position*0.01);
ctx.translate(radio,0);
ctx.drawImage(imgToDraw,0,0);
objects[nObject++].position++;
ctx.setTransform(1,0,0,1,x, y);
ctx.rotate(objects[nObject].position*0.01);
ctx.translate(radio,0);
ctx.drawImage(imgToDraw,0,0);
objects[nObject++].position++;
}
ctx.setTransform(1,0,0,1,0,0); // reset transform for rAF loop
(aunque no esperes el rendimiento en tiempo real).
Aunque es quizás un poco inútil dibujar 2000 objetos en un área tan pequeña. Si está tras el efecto , sugeriría este enfoque:
- Crear un lienzo fuera de la pantalla
- Produzca 5-8 cuadros con el método anterior y guárdelos como imágenes
- Reproduzca esas 5-8 imágenes como están en lugar de hacer todos los cálculos.
Si necesitas un look más fluido simplemente produce más marcos. Puede almacenar cada fotograma en un solo lienzo basado en celdas que luego usará como hoja de sprites. Al dibujar, por supuesto, debe tener cuidado de que las posiciones actuales sean estáticas en lugar de moverse cuando están animadas. La rotación y la posición resultante es otro factor.
Necesito dibujar más de 10,000 imágenes (32x32 px) en el lienzo, pero más de 2000 dibuja el rendimiento es muy malo.
Este es un pequeño ejemplo:
estructura de objeto {position:0}
for(var nObject = 0; nObject < objects.length; nObject++){
ctx.save();
ctx.translate(coords.x,coords.y);
ctx.rotate(objects[nObject].position/100);
ctx.translate(radio,0);
ctx.drawImage(img,0,0);
ctx.restore();
objects[nObject].position++;
}
Con este código traduzco las imágenes alrededor de unas coordenadas.
¿Qué recomienda para mejorar el rendimiento?
actualizar:
Intento acodar pero las actuaciones empeoran.
Creo que this es lo que necesitas.
Eric Rowell (creador de KineticJS) ha realizado algunas pruebas de estrés aquí.
Y él dice esto:
"Cree 10 capas, cada una de las cuales contiene 1000 formas para crear 10,000 formas. Esto mejora enormemente el rendimiento porque solo se tendrán que dibujar 1,000 formas a la vez cuando se elimina un círculo de una capa en lugar de las 10,000 formas".
"Tenga en cuenta que tener demasiadas capas también puede ralentizar el rendimiento. Descubrí que usar 10 capas, cada una compuesta por 1,000 formas, tiene un mejor desempeño que 20 capas con 500 formas o 5 capas con 2,000 formas".
Actualización: necesitaría ejecutar casos de prueba en los que el procedimiento más optimizado sería para usted. Ejemplo: 10000 formas se pueden lograr ya sea por:
10000 formas * 1 capa
5000 formas * 2 capas
2500 formas * 4 capas
Lo que sea que funcione para ti, elige eso! Depende de tu código.
Después de varias pruebas, he llegado a las siguientes conclusiones:
- Canvas no tiene capacidad para esta tarea.
- El lienzo en capas solo ayuda al rendimiento cuando los elementos estáticos no necesitan ser rediseñados constantemente.
- Añadir límite de impresión de coordenadas ayuda mucho en la prestación.
- alternativas a funciones lentas
- No imprimir elementos finalmente se ocultará por otro elemento con un índice z más alto (trabajando en él).
El resultado final es una pequeña mezcla de todas las contribuciones. pero necesita mejorar.
Probado con 30,000 objetos y el rendimiento se mantiene a 60 / fps.
var banPrint = true;
for(nOverlap = nObject; nOverlap < objects.length; nOverlap++){
if(
objects[nOverlap].position == objects[nObject].position
&& nOverlap != nObject
){
banPrint = false;
break;
}
}
Puedo conseguirte 10.000, pero hay dos inconvenientes principales.
Puede notar que las imágenes no respetan la transparencia por completo, es posible arreglarlo ... pero eso está fuera del alcance de esta respuesta.
Deberá utilizar las matemáticas para realizar cualquier tipo de transformación, ya que la matriz de transformación del lienzo estándar no se puede aplicar a ImageData
Demo en vivo
Explicación del código y métodos.
Entonces, para obtener el rendimiento más rápido posible con un lienzo y una gran cantidad de objetos, necesita usar ImageData . Básicamente, se trata de acceder al elemento del lienzo en un nivel por píxel, y te permite hacer todo tipo de cosas interesantes. Utilicé dos métodos primarios.
También aquí hay un buen tutorial que lo incluye un poco para ayudar a comprenderlo mejor.
Entonces, lo que hice es crear primero un lienzo temporal para la imagen.
imgToDraw.onload = function () {
// In memory canvas
imageCanvas = document.createElement("canvas"),
iCtx = imageCanvas.getContext("2d");
// set the canvas to the size of the image
imageCanvas.width = this.width;
imageCanvas.height = this.height;
// draw the image onto the canvas
iCtx.drawImage(this, 0, 0);
// get the ImageData for the image.
imageData = iCtx.getImageData(0, 0, this.width, this.height);
// get the pixel component data from the image Data.
imagePixData = imageData.data;
// store our width and height so we can reference it faster.
imgWidth = this.width;
imgHeight = this.height;
draw();
};
Siguiente es la pieza principal que está en la función de representación
Sólo estoy publicando la parte correspondiente.
// create new Image data. Doing this everytime gets rid of our
// need to manually clear the canvas since the data is fresh each time
var canvasData = ctx.createImageData(canvas.width, canvas.height),
// get the pixel data
cData = canvasData.data;
// Iterate over the image we stored
for (var w = 0; w < imgWidth; w++) {
for (var h = 0; h < imgHeight; h++) {
// make sure the edges of the image are still inside the canvas
// This also is VERY important for perf reasons
// you never want to draw outside of the canvas bounds with this method
if (entity.x + w < width && entity.x + w > 0 &&
entity.y + h > 0 && entity.y + h < height) {
// get the position pixel from the image canvas
var iData = (h * imgWidth + w) * 4;
// get the position of the data we will write to on our main canvas
// the values must be whole numbers ~~ is just Math.floor basically
var pData = (~~ (entity.x + w) + ~~ (entity.y + h) * width) * 4;
// copy the r/g/b/ and alpha values to our main canvas from
// our image canvas data.
cData[pData] = imagePixData[iData];
cData[pData + 1] = imagePixData[iData + 1];
cData[pData + 2] = imagePixData[iData + 2];
// this is where alpha blending could be applied
if(cData[pData + 3] < 100){
cData[pData + 3] = imagePixData[iData + 3];
}
}
}
}
// now put all of that image data we just wrote onto the actual canvas.
ctx.putImageData(canvasData, 0, 0);
La principal ventaja de esto es que, si necesita dibujar una cantidad ridícula de objetos en el lienzo, no puede usar drawImage
, la manipulación de píxeles es su amigo.
Si las imágenes no se superponen, entonces la imagen resultante es de 3200x3200 píxeles, que es más de lo que la mayoría de las pantallas pueden mostrar. Por lo tanto, puede intentar obtener el cuadro delimitador de la imagen transformada y omitir los que están fuera del área visible (aunque el lienzo ya debería hacer eso por usted).
Otra idea es combinar las imágenes pequeñas en imágenes más grandes y transformarlas juntas como un grupo.
Si desea organizar las imágenes en un anillo, puede dibujarlas una vez como un anillo, guardarlas como una imagen y luego girar la "imagen del anillo" en lugar de cada imagen individual.
Por último, eche un vistazo a WebGL, que podría ser más eficiente que la API de canvas
2D.