【问题标题】:Increase performance for 10,000 particles in html5 Canvas提高 html5 Canvas 中 10,000 个粒子的性能
【发布时间】:2014-12-24 00:54:11
【问题描述】:

我有两个 JS Fiddles,都有 10,000 片雪花在四处移动,但有两种不同的方法。

第一把小提琴:http://jsfiddle.net/6eypdhjp/

fillRect 与 4 x 4 白色正方形一起使用,在 10,000 个雪花下提供大约 60 帧/秒。

所以我想知道是否可以改进这一点,并在 HTML5Rocks 的网站上找到了一些关于画布性能的信息。其中一个建议是将雪花预渲染到画布上,然后使用 drawImage 绘制画布。

这里的建议是 http://www.html5rocks.com/en/tutorials/canvas/performance/,即标题为Pre-render to an off-screen canvas。使用 ctrl+f 找到该部分。

所以我用这个小提琴尝试了他们的建议:http://jsfiddle.net/r973sr7c/

然而,我在 10,000 片雪花下每秒获得大约 3 帧。这很奇怪,因为 jsPerf 甚至使用相同的方法在此处显示了性能提升 http://jsperf.com/render-vs-prerender

我用于预渲染的代码在这里:

//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);    
    }
}

所以我想知道为什么我会得到如此可怕的帧速率?还有什么更好的方法可以让更多的粒子在屏幕上移动,同时保持每秒 60 帧?

【问题讨论】:

  • 我可能是错的,但是创建 1000 个 canvas 元素对我来说并不合适。 document.createElement('canvas') 应该运行 1000 次吗?
  • @JamesTaylor 好吧,这就是我解释 html5Rock 示例的方式。我可能会误解?
  • 如果你汇集并创建了 15 种粒子,然后只使用 15 个画布进行预渲染,这是有道理的 - 否则,不要预渲染
  • @topheman 所以预渲染只在某些情况下?
  • 不,但在 html5rocks 示例中,他们在画布上预渲染了 1 张图像,然后重复使用该相同的图像作为指针来创建他们的场景。你应该做同样的事情(我怀疑你需要 10 000 种粒子,如果只有半径在 1 和 4 之间变化)你的例子的陷阱是你随机改变颜色,所以很难统一粒子

标签: javascript html canvas


【解决方案1】:

通过绘制预渲染图像(或预渲染画布)实现最佳帧速率。

您可以将代码重构为:

  • 创建大约 2-3 个屏幕外(内存中)画布,每个画布上绘制 1/3 的粒子
  • 为每个画布分配一个下降率和一个漂移率。
  • 在每个动画帧中,将每个屏幕外画布(根据其自身的下降率和漂移率进行偏移)绘制到屏幕上的画布上。

结果应该是大约每秒 60 帧。

这种技术以增加内存使用量来实现最大帧速率。

这是示例代码和演示:

        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(p.x,p.y,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;}
&lt;canvas id="canvas" width=300 height=300&gt;&lt;/canvas&gt;

【讨论】:

  • 有趣,虽然如果我将帧速率提高,雪移动得更快会很奇怪 =/ 我想看看我能推多远,所以我将 RAF 切换回 setTimeout
  • 您可以针对不同的帧速率调整fallrate 属性,这样您的雪就会随心所欲地飘落。干杯!
【解决方案2】:

我认为您不想每次都创建一个新的canvas 元素。这样做会导致巨大的性能消耗。

当我将此代码移出 for 循环时,性能立即得到改善。我认为这样做可以让您优化代码以实现预期的行为:

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);

查看this revised JSFiddle

希望这有帮助!

【讨论】:

  • 啊,这是一个改进,尽管性能仍然比非预渲染小提琴示例差。
  • 嗯,你是对的。编辑的一个和他们的例子看起来很相似,虽然一个使用ctx.drawImage(flake.img, flake.x, flake.y);,另一个在draw()函数中使用ctx.fillRect(p.x,p.y,4,4);。这也可能与它有关。
【解决方案3】:

你会预渲染你多次绘制的元素。

例如,假设您有一个景观,您在游戏滚动期间在不同位置绘制灌木(形状相同)。使用内存画布就可以了。

对于您的代码,您应该尝试将薄片分成例如 10 种尺寸。从而创建 10 个记忆画布。然后将它们绘制到随机位置。

换句话说,您复制 10 幅画布 1.000 次。不是 10.000 幅画布 10.000 次。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-02
    • 1970-01-01
    • 1970-01-01
    • 2015-02-21
    • 2016-05-21
    • 2012-04-17
    相关资源
    最近更新 更多