简体   繁体   English

在画布上绘制10,000个对象javascript

[英]draw 10,000 objects on canvas javascript

I need draw over 10,000 images (32x32 px) on canvas but over 2000 draws the performances is very bad. 我需要在画布上绘制超过10,000张图像(32x32像素),但超过2000画的表现非常糟糕。

this is a little example: 这是一个小例子:

object structure {position:0} 对象结构{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++;
}

with this code I traslate the images around of a coordinates. 使用这段代码我可以对坐标周围的图像进行扫描。

What do you recommend to improve performance? 您建议什么来提高性能?

update: 更新:

i try layering but the performances worsens 我尝试分层,但表现恶化

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

I can get you 10,000 but there are two main drawbacks. 我可以给你10,000,但有两个主要的缺点。

  1. You may notice the images don't respect transparency entirely, its possible to fix.. but that's beyond the scope of this answer. 您可能会注意到图像完全不尊重透明度,可以修复..但这超出了本答案的范围。

  2. You will have to use math to do any sort of transformations because the standard canvas transformation matrix can not be applied to ImageData 您将不得不使用数学进行任何类型的转换,因为标准画布转换矩阵无法应用于ImageData

Live Demo 现场演示

Explanation of the code and methods 代码和方法的说明

So to get the fastest performance possible with canvas and a large number of objects you need to use ImageData . 因此,要使用canvas和大量对象获得最快的性能,您需要使用ImageData This is accessing the canvas element on a per pixel level basically, and allows you to do all sorts of cool stuff. 这基本上是在每像素级别访问canvas元素,并允许你做各种很酷的东西。 I used two primary methods. 我使用了两种主要方法。

Also here is a nice tutorial that goes into it a bit to help get a better understanding. 这里还有一个很好的教程 ,有助于更好地理解它。

So what I did is first I created a temporary canvas for the image 所以我做的是首先我为图像创建了一个临时画布

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 Is the main piece which is in the rendering function Next是渲染功能中的主要部分

I'm just posting the relevant portion. 我只是张贴相关部分。

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

The main Take away from this is, if you need to draw a ridiculous number of objects on the canvas you can't use drawImage , pixel manipulation is your friend. 主要的是,如果你需要在画布上绘制一个荒谬的对象数量,你不能使用drawImage ,像素操作是你的朋友。

I think this is what you need. 我想就是你需要的。

Eric Rowell (creator of KineticJS) has done some stress tests here. Eric Rowell(KineticJS的创始人)在这里做了一些压力测试。

And he says this: 他说这个:

"Create 10 layers each containing 1000 shapes to create 10,000 shapes. This greatly improves performance because only 1,000 shapes will have to be drawn at a time when a circle is removed from a layer rather than all 10,000 shapes." “创建10个图层,每个图层包含1000个形状,以创建10,000个形状。这极大地提高了性能,因为在从图层中移除圆圈而不是所有10,000个形状时,只需要绘制1,000个形状。”

"Keep in mind that having too many layers can also slow down performance. I found that using 10 layers each made up of 1,000 shapes performs better than 20 layers with 500 shapes or 5 layers with 2,000 shapes." “请记住,有太多的层也会降低性能。我发现使用10个层,每个层由1,000个形状组成,表现优于20层,500个形状或5个层,2,000个形状。”

Update: You would need to run test cases in which the most optimized procedure would be for you. 更新:您需要运行测试用例,其中最优化的过程适合您。 Example: 10000 shapes can be achieved by either: 示例:可以通过以下任一方式实现10000个形状:

10000 shapes * 1 layer 10000个形状* 1层

5000 shapes * 2 layer 5000个形状* 2层

2500 shapes * 4 layer 2500个形状* 4层

Whichever works for you,choose that! 无论哪个适合您,请选择它! It depends upon your code. 这取决于你的代码。

If the images don't overlap, then the resulting image is 3200x3200 pixels which is more than most displays can display. 如果图像不重叠,则生成的图像为3200x3200像素,这比大多数显示器可显示的要多。 So you can try to get the bounding box of the transformed image and skip those which are outside the visible area (even though the canvas should already do that for you). 因此,您可以尝试获取已转换图像的边界框并跳过可见区域之外的那些(即使画布应该已经为您执行此操作)。

Another idea is to combine the small images into bigger ones and transform them together as a group. 另一个想法是将小图像组合成更大的图像并将它们组合在一起。

If you want to organize the images in a ring, then you can draw them once as a ring, save that as an image and then rotate the "ring image" instead of each individual image. 如果要将图像组织成环形,则可以将它们作为环形绘制一次,将其另存为图像,然后旋转“环形图像”而不是每个单独的图像。

Lastly, have a look at WebGL which might be more efficient than 2D canvas API. 最后,看看WebGL可能比2D canvas API更有效。

Here are some steps you can do to increase the performance: 以下是一些可以提高性能的步骤:

  • First get rid of the save / restore - they are very expensive calls and can be replaced with setTransform 首先摆脱save / restore - 它们是非常昂贵的调用,可以用setTransform替换
  • Unwind the loop to do more inside per iteration 展开循环以在每次迭代中执行更多操作
  • Cache all properties 缓存所有属性

FIDDLE 小提琴

Example with loop unwound for 4 iterations: 循环解开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

(don't expect real-time performance though). (不要指望实时表现)。

Although, it is perhaps a bit pointless drawing 2000 objects in such a small area. 虽然,在如此小的区域内绘制2000个物体可能有点毫无意义。 If you are after the effect I would suggest this approach instead: 如果你是在效果之后,我建议采用这种方法:

  • Create an off-screen canvas 创建一个离屏画布
  • Produce 5-8 frames with the method above and store them as images 使用上述方法生成5-8帧并将其存储为图像
  • Play back those 5-8 images as-is instead of doing all the calculations 按原样播放5-8张图像,而不是进行所有计算

If you need more fluid look simply produce more frames. 如果你需要更多流体外观,只需生产更多的框架。 You can store each frame in a single canvas based on cells which you use as a sprite-sheet later. 您可以根据稍后用作精灵表格的单元格将每个帧存储在单个画布中。 When drawing you must of course take care that current positions are static versus moving when actually animated. 在绘制时,当然必须注意当前位置是静态的而不是实际动画时的移动。 Rotation and the resulting position is another factor. 旋转和产生的位置是另一个因素。

After various tests, I have come to the following conclusions: 经过各种测试,我得出以下结论:

  • canvas does not have the capacity for this task. canvas没有此任务的容量。
  • Layered canvas only helps performance when static elements need not be constantly redrawn. 分层画布仅在不需要不断重绘静态元素时才有助于提高性能。
  • Add coordinates print limit helps a lot in rendering. 添加坐标打印限制有助于渲染。
  • alternatives to slow functions 慢功能的替代品
  • Do not print elements will ultimately hidden by another element with a higher z-index (working on it). 不打印元素最终会被具有更高z-index的其他元素隐藏(处理它)。

the end result is a small mix of all contributions. 最终结果是所有贡献的小混合。 but needs improvement. 但需要改进。

Tested with 30,000 objects and the performance is maintained at 60/fps. 测试了30,000个对象,性能保持在60 / fps。

http://jsfiddle.net/NGn29/1/ 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