簡體   English   中英

在畫布上繪制10,000個對象javascript

[英]draw 10,000 objects on canvas javascript

我需要在畫布上繪制超過10,000張圖像(32x32像素),但超過2000畫的表現非常糟糕。

這是一個小例子:

對象結構{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++;
}

使用這段代碼我可以對坐標周圍的圖像進行掃描。

您建議什么來提高性能?

更新:

我嘗試分層,但表現惡化

http://jsfiddle.net/72nCX/3/

我可以給你10,000,但有兩個主要的缺點。

  1. 您可能會注意到圖像完全不尊重透明度,可以修復..但這超出了本答案的范圍。

  2. 您將不得不使用數學進行任何類型的轉換,因為標准畫布轉換矩陣無法應用於ImageData

現場演示

代碼和方法的說明

因此,要使用canvas和大量對象獲得最快的性能,您需要使用ImageData 這基本上是在每像素級別訪問canvas元素,並允許你做各種很酷的東西。 我使用了兩種主要方法。

這里還有一個很好的教程 ,有助於更好地理解它。

所以我做的是首先我為圖像創建了一個臨時畫布

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

Next是渲染功能中的主要部分

我只是張貼相關部分。

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

主要的是,如果你需要在畫布上繪制一個荒謬的對象數量,你不能使用drawImage ,像素操作是你的朋友。

我想就是你需要的。

Eric Rowell(KineticJS的創始人)在這里做了一些壓力測試。

他說這個:

“創建10個圖層,每個圖層包含1000個形狀,以創建10,000個形狀。這極大地提高了性能,因為在從圖層中移除圓圈而不是所有10,000個形狀時,只需要繪制1,000個形狀。”

“請記住,有太多的層也會降低性能。我發現使用10個層,每個層由1,000個形狀組成,表現優於20層,500個形狀或5個層,2,000個形狀。”

更新:您需要運行測試用例,其中最優化的過程適合您。 示例:可以通過以下任一方式實現10000個形狀:

10000個形狀* 1層

5000個形狀* 2層

2500個形狀* 4層

無論哪個適合您,請選擇它! 這取決於你的代碼。

如果圖像不重疊,則生成的圖像為3200x3200像素,這比大多數顯示器可顯示的要多。 因此,您可以嘗試獲取已轉換圖像的邊界框並跳過可見區域之外的那些(即使畫布應該已經為您執行此操作)。

另一個想法是將小圖像組合成更大的圖像並將它們組合在一起。

如果要將圖像組織成環形,則可以將它們作為環形繪制一次,將其另存為圖像,然后旋轉“環形圖像”而不是每個單獨的圖像。

最后,看看WebGL可能比2D canvas API更有效。

以下是一些可以提高性能的步驟:

  • 首先擺脫save / restore - 它們是非常昂貴的調用,可以用setTransform替換
  • 展開循環以在每次迭代中執行更多操作
  • 緩存所有屬性

小提琴

循環解開4次迭代的示例:

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

(不要指望實時表現)。

雖然,在如此小的區域內繪制2000個物體可能有點毫無意義。 如果你是在效果之后,我建議采用這種方法:

  • 創建一個離屏畫布
  • 使用上述方法生成5-8幀並將其存儲為圖像
  • 按原樣播放5-8張圖像,而不是進行所有計算

如果你需要更多流體外觀,只需生產更多的框架。 您可以根據稍后用作精靈表格的單元格將每個幀存儲在單個畫布中。 在繪制時,當然必須注意當前位置是靜態的而不是實際動畫時的移動。 旋轉和產生的位置是另一個因素。

經過各種測試,我得出以下結論:

  • canvas沒有此任務的容量。
  • 分層畫布僅在不需要不斷重繪靜態元素時才有助於提高性能。
  • 添加坐標打印限制有助於渲染。
  • 慢功能的替代品
  • 不打印元素最終會被具有更高z-index的其他元素隱藏(處理它)。

最終結果是所有貢獻的小混合。 但需要改進。

測試了30,000個對象,性能保持在60 / fps。

http://jsfiddle.net/NGn29/1/

        var banPrint = true;
        for(nOverlap = nObject; nOverlap < objects.length; nOverlap++){
            if(
                objects[nOverlap].position == objects[nObject].position
                && nOverlap != nObject
            ){
                banPrint = false;
                break;
            }
        }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM