优化 canvas 多个对象渲染

Optimizing canvas multiple objects rendering

我正在尝试制作一个蚂蚁模拟器,我使用基本的 Javascript canvas 渲染器

这是渲染代码的一部分:

render(simulation) {
    
    let ctx = this.ctx;
    
    // Clear previous frame
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    
    // Pheromones
    let pheromone;
    let pheromoneXPos;
    let pheromoneYPos;
    for (let i = 0; i < simulation.pheromones.length; i++) {
      pheromone = simulation.pheromones[i];
      pheromoneXPos = pheromone.position[0];
      pheromoneYPos = pheromone.position[1];
      ctx.fillRect(pheromoneXPos, pheromoneYPos, 1, 1);
    }
    
    
    // ... rendering other stuff
    
}

这是一个示例(它运行流畅只是因为我使用的对象减少了约 90%):

simulation.pheromones.length 很大(~25000),渲染 1 帧的时间太长(在我的电脑上是 250ms)
我应该怎么做才能让它渲染得更快?我应该改用渲染引擎(如 PixiJS)吗?还是我用错了?

注意:大部分对象(信息素)与上一帧相同(它们每秒仅更新一次或两次)。

由于您的所有矩形似乎都具有相同的样式,因此您可以将所有这些矩形组合在一个子路径中,并使用这个更复杂的子路径仅调用一次 fill()

const canvas = document.querySelector("canvas");
const w = canvas.width = 500;
const h = canvas.height = 500;
const ctx = canvas.getContext("2d");
const makePheromone = () => ({
  x: w / 2,
  y: h / 2,
  dx: Math.random() * 20 - 10,
  dy: Math.random() * 20 - 10
});
const pheromones = Array.from( { length: 50000 }, makePheromone );
ctx.fillStyle = "red";

requestAnimationFrame( draw );

function draw() {
  ctx.clearRect( 0, 0, canvas.width, canvas.height );
  ctx.beginPath();
  pheromones.forEach( (obj) => {
    obj.x += obj.dx;
    if( obj.x < 0 ) {
      obj.x += w; 
    }
    else if( obj.x > w ) {
      obj.x -= w; 
    }
    obj.y += obj.dy;
    if( obj.y < 0 ) {
      obj.y += h; 
    }
    else if( obj.y > h ) {
      obj.y -= h; 
    }
    ctx.rect( obj.x, obj.y, 1, 1 );
  });
  ctx.fill();
  requestAnimationFrame( draw );
}
<canvas width="5000" height="5000"></canvas>

现在,在你的情况下,你是否真的要为这些 'pheromones' 制作动画还不清楚。
如果你不这样做,那么只绘制一次并存储你将在每一帧渲染的 canvas 中的 ImageBitmap

const canvas = document.querySelector("canvas");
const w = canvas.width = 500;
const h = canvas.height = 500;
const ctx = canvas.getContext("2d");

getPheromonesBitmap()
  .then( (bmp) => {
    let x = w / 2;
    let y = h / 2;
    const circle = new Path2D();
    circle.arc( 0, 0, 50, 0, Math.PI * 2 ); 
    function draw( t ) {
      x += Math.sin( t / 2000 );
      y += Math.cos( t / 2000 );  
      ctx.setTransform( 1, 0, 0, 1, 0, 0 );
      ctx.clearRect( 0, 0, w, h );
      // draw all the pheromones as a single bitmap
      ctx.drawImage( bmp, 0, 0 );
      ctx.translate( x, y );
      ctx.fill( circle );
      requestAnimationFrame( draw );
    }
    requestAnimationFrame( draw );
  } );

function getPheromonesBitmap() {
  const makePheromone = () => ({
    x: Math.random() * w,
    y: Math.random() * h
  });
  const pheromones = Array.from( { length: 50000 }, makePheromone );
  ctx.fillStyle = "red";
  ctx.clearRect( 0, 0, canvas.width, canvas.height );
  ctx.beginPath();
  pheromones.forEach( (obj) => {
    ctx.rect( obj.x, obj.y, 1, 1 );
  });
  ctx.fill();
  const prom = createImageBitmap( canvas );
  ctx.beginPath();
  ctx.clearRect( 0, 0, w, h );
  return prom;
}
<canvas width="5000" height="5000"></canvas>

对于不支持 createImageBitmap 的浏览器(对于这种简单的情况,只支持 Safari),我写了一个可用的 polyfill here

最后,如果您确实在像素边界绘制 1x1px 矩形,您还可以使用 ImageData 来渲染这些矩形,这在所有实体都有自己的颜色时特别有效:

const canvas = document.querySelector("canvas");
const w = canvas.width = 500;
const h = canvas.height = 500;
const ctx = canvas.getContext("2d");
const makePheromone = () => ({
  x: w / 2,
  y: h / 2,
  dx: Math.random() * 20 - 10,
  dy: Math.random() * 20 - 10,
  color: Math.random() * 0xFFFFFF
});
const pheromones = Array.from( { length: 50000 }, makePheromone );
const img = new ImageData( w, h );
// we use an Uint32Array to set each pixel in one pass
const arr = new Uint32Array( img.data.buffer );

requestAnimationFrame( draw );

function draw() {
  pheromones.forEach( (obj) => {
    obj.x += obj.dx;
    if( obj.x < 0 ) {
      obj.x += w; 
    }
    else if( obj.x > w ) {
      obj.x -= w; 
    }
    obj.y += obj.dy;
    if( obj.y < 0 ) {
      obj.y += h; 
    }
    else if( obj.y > h ) {
      obj.y -= h; 
    }
    const index = Math.floor( obj.y * w + obj.x );
    arr[ index ] = obj.color + 0xFF000000;
  });
  ctx.putImageData( img, 0, 0 );
  requestAnimationFrame( draw );
}
<canvas width="5000" height="5000"></canvas>