简体   繁体   English

在html5 Canvas中提高10,000个粒子的性能

[英]Increase performance for 10,000 particles in html5 Canvas

I have two JS Fiddles, both with 10,000 snow flakes moving around but with two different approaches. 我有两个JS小提琴,两个都有10,000片雪花四处移动,但有两种不同的方法。

The first fiddle: http://jsfiddle.net/6eypdhjp/ 第一个小提琴: http//jsfiddle.net/6eypdhjp/

Uses fillRect with a 4 by 4 white square, providing roughly 60 frames per second @ 10,000 snow flakes. 使用带有4 x 4白色正方形的fillRect ,每秒提供大约60帧@ 10,000个雪花。

So i wondered if I could improve this and found a bit of information on HTML5Rocks' website regarding canvas performance. 所以我想知道我是否可以改进这一点并在HTML5Rocks的网站上找到关于画布性能的一些信息。 One such suggestion was to pre-render the snow flakes to canvases and then draw the canvases using drawImage. 这样的建议之一是将雪花预先渲染到画布上,然后使用drawImage绘制画布。

The suggestion is here http://www.html5rocks.com/en/tutorials/canvas/performance/ , namely under the title Pre-render to an off-screen canvas . 建议在http://www.html5rocks.com/en/tutorials/canvas/performance/ ,即标题预渲染到离屏画布 Use ctrl+f to find that section. 使用ctrl + f查找该部分。

So i tried their suggestion with this fiddle: http://jsfiddle.net/r973sr7c/ 所以我用这个小提琴尝试了他们的建议: http//jsfiddle.net/r973sr7c/

How ever, i get about 3 frames per second @ 10,000 snow flakes. 但是,我每秒得到大约3帧@ 10,000个雪花。 Which is very odd given jsPerf even shows a performance boost here using the same method http://jsperf.com/render-vs-prerender 考虑到jsPerf甚至在这里使用相同的方法http://jsperf.com/render-vs-prerender来提高性能,这是非常奇怪的

The code I used for pre-rendering is here: 我用于预渲染的代码在这里:

//snowflake particles
var mp = 10000; //max particles
var particles = [];
for(var i = 0; i < mp; i++) {
    var m_canvas        = document.createElement('canvas');
        m_canvas.width  = 4;
        m_canvas.height = 4;
    var tmp             = m_canvas.getContext("2d");
        tmp.fillStyle   = "rgba(255,255,255,0.8)";
        tmp.fillRect(0,0,4,4);

    particles.push({
        x  : Math.random()*canvas.width, //x-coordinate
        y  : Math.random()*canvas.height, //y-coordinate
        r  : Math.random()*4+1, //radius
        d  : Math.random()*mp, //density
        img: m_canvas //tiny canvas
    })
}   
//Lets draw the flakes
function draw()    {   
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for(var i = 0; i < particles.length; i++)       {
        var flake = particles[i];
        ctx.drawImage(flake.img, flake.x,flake.y);    
    }
}

So I wondered why am i getting such horrendous frame rate? 所以我想知道为什么我会得到如此可怕的帧速率? And is there any better way to get higher particle counts moving on screen whilst maintaining 60 frames per second? 是否有更好的方法可以在屏幕上移动更高的粒子数,同时保持每秒60帧?

Best frame rates are achieved by drawing pre-rendered images (or pre-rendered canvases). 通过绘制预渲染的图像(或预渲染的画布)可获得最佳帧速率。

You could refactor your code to: 您可以将代码重构为:

  • Create about 2-3 offscreen (in-memory) canvases each with 1/3 of your particles drawn on them 创建大约2-3个屏幕外(内存中)画布,每个画布上都绘制有1/3的粒子
  • Assign each canvas a fallrate and a driftrate. 为每个画布分配一个下降和一个漂移。
  • In each animation frame, draw each offscreen canvas (with an offset according to its own fallrate & driftrate) onto the on-screen canvas. 在每个动画帧中,将每个屏幕外画布(根据其自身的降低和漂移的偏移量)绘制到屏幕画布上。

The result should be about 60 frames-per-second. 结果应该是大约60帧/秒。

This technique trades increased memory usage to achieve maximum frame rates. 该技术交换增加的内存使用量以实现最大帧速率。

Here's example code and a Demo: 这是示例代码和演示:

  var canvas=document.getElementById("canvas"); var ctx=canvas.getContext("2d"); var cw=canvas.width; var ch=canvas.height; var mp=10000; var particles=[]; var panels=[]; var panelCount=2; var pp=panelCount-.01; var maxFallrate=2; var minOffsetX=-parseInt(cw*.25); var maxOffsetX=0; // create all particles for(var i=0;i<mp;i++){ particles.push({ x: Math.random()*cw*1.5, //x-coordinate y: Math.random()*ch, //y-coordinate r: 1, //radius panel: parseInt(Math.random()*pp) // panel==0 thru panelCount }) } // create a canvas for each panel var drift=.25; for(var p=0;p<panelCount;p++){ var c=document.createElement('canvas'); c.width=cw*1.5; c.height=ch*2; var offX=(drift<0)?minOffsetX:maxOffsetX; panels.push({ canvas:c, ctx:c.getContext('2d'), offsetX:offX, offsetY:-ch, fallrate:2+Math.random()*(maxFallrate-1), driftrate:drift }); // change to opposite drift direction for next panel drift=-drift; } // pre-render all particles // on the specified panel canvases for(var i=0;i<particles.length;i++){ var p=particles[i]; var cctx=panels[p.panel].ctx; cctx.fillStyle='white'; cctx.fillRect(px,py,1,1); } // duplicate the top half of each canvas // onto the bottom half of the same canvas for(var p=0;p<panelCount;p++){ panels[p].ctx.drawImage(panels[p].canvas,0,ch); } // begin animating drawStartTime=performance.now(); requestAnimationFrame(animate); function draw(time){ ctx.clearRect(0,0,cw,ch); for(var i=0;i<panels.length;i++){ var panel=panels[i]; ctx.drawImage(panel.canvas,panel.offsetX,panel.offsetY); } } function animate(time){ for(var i=0;i<panels.length;i++){ var p=panels[i]; p.offsetX+=p.driftrate; if(p.offsetX<minOffsetX || p.offsetX>maxOffsetX){ p.driftrate*=-1; p.offsetX+=p.driftrate; } p.offsetY+=p.fallrate; if(p.offsetY>=0){p.offsetY=-ch;} draw(time); } requestAnimationFrame(animate); } 
 body{ background-color:#6b92b9; padding:10px; } #canvas{border:1px solid red;} 
 <canvas id="canvas" width=300 height=300></canvas> 

I don't think you want to create a new canvas element every time. 我不认为你每次都想创建一个新的canvas元素。 Doing so causes a huge performance drain. 这样做会导致巨大的性能消耗。

When I moved this code out of the for loop, the performance instantly improved. 当我将此代码移出for循环时,性能立即得到提高。 I think doing so will allow you to optimize your code to achieve the intended behavior: 我认为这样做可以使您优化代码以实现预期的行为:

var m_canvas = document.createElement('canvas');
    m_canvas.width = 4;
    m_canvas.height = 4;
var tmp = m_canvas.getContext("2d");
    tmp.fillStyle = "rgba(255,255,255,0.8)";
    tmp.fillRect(0, 0, 4, 4);

Check out this revised JSFiddle . 看看这个修改过的JSFiddle

Hope this helped! 希望这有帮助!

You would pre-render elements you paint many times. 您可以预先渲染多次绘制的元素。

Say for example you have a landscape where you paint bushes (of same form) on various locations during a game scroll. 比如说你有一个风景,你可以在游戏滚动过程中在各个位置绘制灌木丛(相同形式)。 The the use of memory canvas would be ok. 使用内存画布就可以了。

For your code you should try to divide your flakes into for example 10 sizes. 对于您的代码,您应该尝试将薄片分成10个尺寸。 Thus create 10 memory canvases. 从而创建10个内存画布。 Then paint these into random possitions. 然后将这些画成随机的。

In other words you copy 10 canvases 1.000 times. 换句话说,您复制了10张画布1.000次。 Not 10.000 canvases 10.000 times. 不是10.000画布10.000倍。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM