Scanny
Scanny

Reputation: 3

ThreeJS: Simple City Performance Issue

Problem:
I have encountered major issues with performance with regard to a scene rendered with Three JS. The problem concerns rendering a large number of simple geometries (11,107).

(edit) Each building has a unique height based on elevation data, a unique shape based on their outline, and a material of 5 possible options depending on the size of the area they take up.

I have isolated the issue in the first scene below, the second link provides context. Turning off the polygon buildings in the second link illustrates the drop in framerate that the layer is causing.

Preview Image


How polygon attributes were determined:

Each polygon has a uniform height, but unique shape based upon a building footprint. Furthermore, each building is assigned a colour from a gradient relative to the size of its area (large yellow, mid red small purple). As a reference, this was achieved in a geographic information system before being passed to ThreeJS (QGIS, with QGIStoTHREEJS plugin).


Attempted Solutions:
I have focused on trying to merge the polygons geometry to reduce the number of render calls. However, because each polygon has an assigned colour I have faced issues applying the appropriate materials and mesh. I am struggling with the logic of how to do this in context of the two loops in operation.


Relevant Code Snippet:
The full source can be found here, and a download of the working code is available here. This snippet is from lines 1935 to 1987.
I have trimmed the ThreeJS source to what is relevant to my issue.

    ///////////////// WIP /////////////////
Q3D.PolygonLayer.prototype.build = function(parent) {
    var materials = this.materials,
        project = this.project;

    if (this.objType == "Extruded") {


        // (3) Function for creating the individual building polygons 
        var createSubObject = function(f, polygon, z) {
            var shape = new THREE.Shape(Q3D.Utils.arrayToVec2Array(polygon[0]));
            for (var i = 1, l = polygon.length; i < l; i++) {
                shape.holes.push(new THREE.Path(Q3D.Utils.arrayToVec2Array(polygon[i])));
            }

            // Where the problems start...

            // Here each geometry is created turned into a mesh with its unqiue material
            var geom = new THREE.ExtrudeGeometry(shape, {
                bevelEnabled: false,
                amount: f.h
            });
            var mesh = new THREE.Mesh(geom, materials[f.m].m);
            mesh.position.z = z;
            return mesh;

            //I am not sure how I can merge each polygons geometry with the others whilst allowing each polygon to hold onto its unique colouring...

        };

        // (2) Function for creating polygon layer
        var createObject = function(f) {
            if (f.polygons.length == 1) { // TRUE for building polygons
                return createSubObject(f, f.polygons[0], f.zs[0]); // calls function to create each building

            }
        };
    }


    // (1) function for manufacturing each layer
    this.f.forEach(function(f, fid) {
        f.objs = []; // create array of objects
        var obj = createObject(f); // call polygon creation method
        obj.userData.layerId = this.index;
        obj.userData.featureId = fid;
        this.addObject(obj);
        f.objs.push(obj);
    }, this);

    if (parent) parent.add(this.objectGroup);
};

///////////////// END OF WIP /////////////////



Edit: The structure of each geometry is as follows(f).

GEOMETRY
f[A] = {h, m, polygons, zs};
Given that f is one of the 11,000 geometries, A is an index (0 to 1106), h is a float, m is an int (0 to 5) that acts as an index for accessing one of the five colour categories, polygons is a list of coordinate for building footprint edges, and zs is the extrusion height.

e.g.
f[11106] = {h:0.0302738130622,m:1,polygons:[[[[-23.0863540568,-1.57556646762],[-23.1968547585,-1.56551240558],[-23.1928481251,-1.49924919288],[-23.0803253841,-1.50930323633],[-23.0863540568,-1.57556646762]]]],zs:[0.0695352124961]};

MATERIAL
There are five colour categories. the index acts as a reference for a given geometry to find its associated material.

e.g.
f[11106].m points to
m[1] = {c:0xcb4778,type:0};

There must be someone who knows a way of rendering these buildings without hammering out so many draw calls . Any help would be immensely appreciated.

Upvotes: 0

Views: 767

Answers (1)

WestLangley
WestLangley

Reputation: 104843

You are adding thousands of extruded meshes to your scene and this is resulting in performance issues due to too many draw calls.

One solution is to create a single mesh, which will result in a single draw call. You can use ExtrudeGeometry to do this, but ExtrudeBufferGeometry is more efficient.

var shapes = [];
shapes.push( shape_1 );
...
shapes.push( shape_n );

var geometry = new THREE.ExtrudeBufferGeometry( shapes, extrudeSettings );

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

scene.add( mesh );

If you need to apply different colors to the shapes, one option is to add vertex colors to your geometry. Another option, if there are not too many different colors, is add groups to your geometry. Which one is best depends on your use case.

EDIT: Actually, in your case, it may be easiest to just have a separate extruded mesh for each color.

EDIT: Here is a fiddle that shows how to populate a single BufferGeometry with various extruded shapes.

three.js r.89

Upvotes: 3

Related Questions