Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the most efficient way to display 4 million 2D squares in a browser?

My display has a resolution of 7680x4320 pixels. I want to display up to 4 million different colored squares. And I want to change the number of squares with a slider. If have currently two versions. One with canvas-fillRect which looks somethink like this:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

for (var i = 0; i < num_squares; i ++) {

   ctx.fillStyle = someColor;
   ctx.fillRect(pos_x, pos_y, pos_x + square_width, pos_y + square_height);

   // set pos_x and pos_y for next square
}

And one with webGL and three.js. Same loop, but I create a box geometry and a mesh for every square:

var geometry = new THREE.BoxGeometry( width_height, width_height, 0);

for (var i = 0; i < num_squares; i ++) {

   var material = new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } );
   material.emissive = new THREE.Color( Math.random(), Math.random(), Math.random() );

   var object = new THREE.Mesh( geometry, material );

}

They both work quite fine for a few thousand squares. The first version can do up to one million squares, but everything over a million is just awful slow. I want to update the color and the number of squares dynamically.

Does anyone has tips on how to be more efficient with three.js/ WebGL/ Canvas?

EDIT1: Second version: This is what I do at the beginning and when the slider has changed:

// Remove all objects from scene
            var obj, i;
            for ( i = scene.children.length - 1; i >= 0 ; i -- ) {
                obj = scene.children[ i ];
                if ( obj !== camera) {
                    scene.remove(obj);
                }
            }

            // Fill scene with new objects
            num_squares = gui_dat.squareNum;

            var window_pixel = window.innerWidth * window.innerHeight;
            var pixel_per_square = window_pixel / num_squares;
            var width_height = Math.floor(Math.sqrt(pixel_per_square));

            var geometry = new THREE.BoxGeometry( width_height, width_height, 0);

            var pos_x = width_height/2;
            var pos_y = width_height/2;

            for (var i = 0; i < num_squares; i ++) {

                //var object = new THREE.Mesh( geometry,  );

                var material = new THREE.Material()( { color: Math.random() * 0xffffff } );
                material.emissive = new THREE.Color( Math.random(), Math.random(), Math.random() );

                var object = new THREE.Mesh( geometry, material );

                object.position.x = pos_x;
                object.position.y = pos_y;

                pos_x += width_height;
                if (pos_x > window.innerWidth) {

                    pos_x = width_height/2;
                    pos_y += width_height;
                }

                scene.add( object );

            }
like image 467
Birdperson Avatar asked May 08 '15 14:05

Birdperson


2 Answers

The fastest way to draw squares is to use the gl.POINTS primitive and then setting gl_PointSize to the pixel size.

In three.js, gl.POINTS is wrapped inside the THREE.PointCloud object. You'll have to create a geometry object with one position for each point and pass that to the PointCloud constructor.

Here is an example of THREE.PointCloud in action: http://codepen.io/seanseansean/pen/EaBZEY

geometry = new THREE.Geometry();

for (i = 0; i < particleCount; i++) {

    var vertex = new THREE.Vector3();
    vertex.x = Math.random() * 2000 - 1000;
    vertex.y = Math.random() * 2000 - 1000;
    vertex.z = Math.random() * 2000 - 1000;

    geometry.vertices.push(vertex);
}

...

materials[i] = new THREE.PointCloudMaterial({size:size});
particles = new THREE.PointCloud(geometry, materials[i]);

I didn't dig through all the code but I've set the particle count to 2m and from my understanding, 5 point clouds are generated so 2m*5 = 10m particles and I'm getting around 30fps.

like image 146
Markus Avatar answered Nov 18 '22 23:11

Markus


The highest number of individual points I've seen so far was with potree.

http://potree.org/, https://github.com/potree

Try some demo, I was able to observe 5 millions of points in 3D at 20-30fps. I believe this is also current technological limit.

I didn't test potree on my own, so I cant say much about this tech. But there is data convertor and viewer (threejs based) so should only figure out how to convert the data.

Briefly about your question

The best way handle large data is group them as quad-tree (2d) or oct-tree (3d). This will allow you to not bother program with part that is too far from camera or not visible at all.

On the other hand, program doesnt like when you do too many webgl calls. Try to understand it like this, you want to do create ~60 images each second. But each time you set some parameter for GPU, program must do some sync. Spliting data means you will need to do more setup so tree must not be too detialed.

Last thing, someone said:

You'll probably want to pass an array of values as one of the shader uniforms

I dont suggest it, bad idea. Texture lookup is quite fast, but attributes are always faster. If we are talking about 4M points, you cant afford reading data from uniforms.

Sorry I cant help you with the code, I could do it without threejs, Im not threejs expert :)

like image 2
Entity Black Avatar answered Nov 19 '22 01:11

Entity Black