html5 canvas大sprite sheet图片动画性能优化

Posted

技术标签:

【中文标题】html5 canvas大sprite sheet图片动画性能优化【英文标题】:html5 canvas large sprite sheet image animation performance optimization 【发布时间】:2020-03-22 05:22:17 【问题描述】:

我使用的方法来自 http://www.williammalone.com/articles/create-html5-canvas-javascript-sprite-animation/ 在画布上渲染精灵表动画

而且大部分情况下都可以正常工作

但是当精灵表图像,(在我的例子中是 30000*500,60 帧 png 文件)时,性能非常糟糕,尤其是在 android webview 上。

我认为图片资源占用了太多内存,但是如何优化它的内存使用呢?我已经尝试通过“ImageOptim”压缩图像,但它没有产生任何明显的差异。

在画布上渲染大型精灵表时,有人可以给我一些关于性能优化的建议吗?

【问题讨论】:

【参考方案1】:

使用ImageBitmaps。

您的图像可能太大而无法保存在图形卡内存中,这意味着每次都必须再次将其移动到那里,这确实会使事情变得非常缓慢。

ImageBitmap 对象允许保留原始位图数据以供 GPU 使用,无需额外。 因此,在初始化时,您将准备好 ImageBitmaps 并丢弃完整的图像。 现在只有准备好放置的小位图会被传递到 GPU。

async function initSprites( img, sprite_width = img.width, sprite_height = img.height ) 
  // you would have to make the sprite factory logic based on your actual spritesheet,
  // here Im using a simple fixed-size grid from wikimedia
  const sprites = [];
  for( let y = 0; y < img.height; y += sprite_height) 
    for( let x = 0; x < img.width; x += sprite_width ) 
      sprites.push(
        await createImageBitmap( img, x, y, sprite_width, sprite_height )
      );
    
  
  return sprites;

async function loadImage( url ) 
  var img = new Image();
  img.crossOrigin = 'anonymous';
  await new Promise( (res, rej) => 
    img.onload = (e) => res( img );
    img.onerror = rej;
    img.src = url;
   );
  return img;


async function init() 
  const url = "https://upload.wikimedia.org/wikipedia/commons/b/be/SpriteSheet.png";
  const FPS = 10;
  const sprite_width = 132;
  const sprite_height = 97;
  
  // we don't keep any reference to the Image
  const sprites = await initSprites( await loadImage( url ), sprite_width, sprite_height );
  
  const canvas = document.getElementById( 'canvas' );
  const ctx = canvas.getContext( '2d' );
  const frame_time = 1000 / FPS;
  let sprite_index = 0;
  let lastTime = performance.now();
  
  requestAnimationFrame( update );
  
  function update( t ) 
    if( t - lastTime > frame_time ) 
      lastTime = t;
      sprite_index = (sprite_index + 1) % sprites.length;
      draw();
    
    requestAnimationFrame( update );
  
  
  function draw() 
    ctx.clearRect( 0, 0, canvas.width, canvas.height );
    // we just reproduce the original spritesheet
    // but animated
    let inner_index = 0;
    for(let y = 0; y < 3; y++) 
      for( let x = 0; x < 4; x ++ ) 
        inner_index ++;
        const index = (sprite_index + inner_index) % sprites.length;
        const sprite = sprites[ index ];
        ctx.drawImage( sprite, x * sprite_width, y * sprite_height );
      
    
  


// for Safari and IE
monkeyPatchCreateImageBitmap();

init().catch( console.error );


// a very poor monkey patch, only allowing HTMLImageElement as source
// returns an HTMLCanvasElement
function monkeyPatchCreateImageBitmap() 
  if( typeof window.createImageBitmap === 'undefined' ) 
    window.createImageBitmap = monkeyPatch;
  
  function monkeyPatch( img, sx = 0 , sy = 0, sw = img.width, sh = img.height )
    const canvas = document.createElement( 'canvas' );
    canvas.width = sw;
    canvas.height = sh;
    canvas.getContext( '2d' )
      .drawImage( img, sx, sy, sw, sh, 0, 0, sw, sh );
    canvas.close = (e) => canvas.width = 0;
    return Promise.resolve( canvas );
  ;
&lt;canvas id="canvas" width="528" height="291"&gt;&lt;/canvas&gt;

【讨论】:

我的问题是sprite sheet图片过大的时候,会占用太多内存,导致动画看起来卡顿,有没有一些内存优化的练习? 即使使用 ImageBitmaps?之后,您的图像数据应该会被释放。此外,请确保只初始化您实际使用的精灵,例如,如果您制作多关卡游戏,则无需在 lvl1 加载 lvl99 老板。然后,可以使动画滞后的是移动内存中的数据,这就是这个答案的重点。大量内存被使用本身不应该导致滞后。但是,用于资产的内存越多,可用于逻辑的内存就越少,因此请务必在那里进行优化。最后,进一步优化资产的唯一方法是减小它们的大小。

以上是关于html5 canvas大sprite sheet图片动画性能优化的主要内容,如果未能解决你的问题,请参考以下文章

我应该使用 HTML5 Canvas 还是 CSS3 Sprites 来为游戏对象设置动画?

第513期Canvas 最佳实践(性能篇)

Spritesheet 没有为 HTML5 Canvas 设置动画

unity怎么把canvas做成小地图

从 sprite_sheet 坐标创建纹理

Sprite Sheet - 在旧 sprite 之上绘制新 sprite