Addressing Performance Challenges in Urban Rendering with ThreeJS

Issue:
I am facing significant performance problems while rendering a scene using Three JS. The main issue arises from rendering a large number of simple geometries (11,107).

(edit) Each building has a unique height based on elevation data, a distinct shape based on their outline, and a material selected from 5 options depending on the area they cover.

I have identified the problem in the initial scene provided below, while the second link offers context. Disabling the polygon buildings in the second link demonstrates the decrease in framerate caused by that layer.

Preview Image

  • View 3JS Render (Polygons Only)
  • View Entire Scene


Determining Polygon Attributes:

Each polygon has a uniform height but varies in shape based on the building footprint. Moreover, every building is assigned a color gradient relative to its area size (large=yellow, medium=red, small=purple). This was done in a geographic information system before being passed to ThreeJS (QGIS, with QGIStoTHREEJS plugin).


Possible Solutions Attempted:
My attempts have been focused on merging polygon geometry to reduce render calls. However, due to different colors for each polygon, applying appropriate materials and mesh has proved challenging. I'm struggling with the logic of managing this within the existing loops.


Relevant Code Snippet:
The complete source code can be found here, and a downloadable version of the code is available here. This snippet includes lines 1935 to 1987.
I've extracted only the relevant part of the ThreeJS source code related 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 represents one of the 11,000 geometries, A is an index (0 to 1106), h is a float, m is an integer (0 to 5) serving as an index for selecting one of the five color categories, polygons consists of coordinate values for building footprints, and zs indicates 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 color categories. the index serves as a reference for a specific geometry to retrieve its associated material.

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

We seek advice on optimizing the rendering process for these buildings without overwhelming draw calls. Any guidance would be greatly appreciated.

Answer №1

If you're experiencing performance issues from adding numerous extruded meshes to your scene, the culprit might be too many draw calls.

To optimize this, consider creating a single mesh which will only require one draw call. Utilize ExtrudeBufferGeometry instead of ExtrudeGeometry for more efficient results.

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 want to assign different colors to the shapes, you can either add vertex colors to your geometry or use groups if there are limited color variations. The best approach depends on your specific requirements.

Alternatively, it may be simplest in your scenario to have separate extruded meshes for each color.

For a demonstration, check out this fiddle showcasing how to populate a single BufferGeometry with various extruded shapes.

Compatible with three.js r.89

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Node.js: Promise chain abruptly stops after reaching a predefined limit without causing any errors

Currently, I am attempting to perform a straightforward operation in nodejs using promises. My task involves working with an array that consists of objects. These objects contain query parameters for a URL that I need to access through a GET request. As th ...

How to Use JQuery to Disable Controls or Background on an ASPX Page

I have implemented a code to display a dialog box for confirming a delete action. The code works fine, but the issue is that when the dialog box pops up, it doesn't disable any controls on the ASP.Net page. This allows me to interact with other contro ...

Contentful - Unfortunately, this does not meet the criteria for valid JSON

https://i.sstatic.net/CLsT1.jpg My content model includes a field that holds a JSON object. However, when I attempt to input an array into this field, I receive an error stating This is not valid JSON. https://i.sstatic.net/uOVK6.png In another screensh ...

An issue has arisen with NextJS Link where it is failing to populate an anchor tag

In my React + NextJS project, I am struggling to display a list of products similar to what you would find on an ecommerce category page. Each product is wrapped in a p tag and should link to its corresponding detail page using an anchor a tag. Although t ...

The dropdown menu in Svelte is malfunctioning when only one option is available

One issue I am facing in my Svelte application is with a dropdown menu that displays a list of players. I am trying to implement a search functionality where the dropdown menu should only show players that match the search query. However, I have encountere ...

Exploring the power of Next.js dynamic routes connected to a Firestore collection

Currently seeking a solution to create a dynamic route that will display each document in a Firestore collection using Server-side Rendering. For instance, if there is a document named foo, it would be accessible at test.com/foo under the [doc] page compo ...

Can you effectively leverage a prop interface in React Typescript by combining it with another prop?

Essentially, I am looking to create a dynamic connection between the line injectComponentProps: object and the prop interface of the injectComponent. For example, it is currently set as injectComponentProps: InjectedComponentProps, but I want this associat ...

How to effectively leverage useMediaQuery in material ui?

Upon completing the web application, I have made the decision to ensure it is mobile-friendly. Utilizing the material UI framework with react, I delved into the documentation but found myself uncertain about how to effectively implement it. Let me provide ...

Why does attempting to access an undefined property not trigger an error?

I am curious to know why var myVar = unDef; may trigger a ReferenceError, while var myObj = {}; var myVar = myObj.unDef; runs smoothly? It simply returns undefined without any runtime issues. Despite both not being defined. ...

Illustrating SVG links

I'm working on a basic svg animation project where I want to create a simple shape by animating a line under a menu link. The goal is to have a single line consisting of a total of 7 anchors, with the middle 3 anchors (each offset by 2) moving a few p ...

Using jQuery to insert PHP code into a <div> element

In our capstone project, I'm looking to implement a dropdown calendar feature. I initially attempted to use ajax within a dropdown Bootstrap 4, but encountered issues with collapsing. As an alternative, I am considering utilizing the include method. A ...

Toggle button visibility on ng-repeat item click

Hello everyone, I'm encountering an issue with displaying and hiding buttons in ng-repeat. <div class="row" ng-repeat="item in items"> <button type="button" ng-click="add()">+</button> <button type="button" ng-click="remo ...

Oops! There was an error: Uncaught promise rejection: TypeError - Unable to access 'subscribe' property of null object (Ionic Angular)

I encountered an issue in my angular ionic project. When I log in, the first page displays the error "ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'subscribe' of null." However, upon reloading the page, the error disappears ...

Accessing/Storing Pictures in MongoDB using Mongoose

I've been struggling with managing images in MongoDB for a while now. Everywhere I look suggests using GridFS because of the 16mb size limit per document. However, the documents I want to store are all <16mb each. Currently, I am storing it like th ...

Implement the maskmoney library in your input fields

In the form below, I am automatically adding inputs using a JavaScript function like this: $('.Preco1').maskMoney({ decimal: '.', thousands: ' ', precision: 2 }); $('.Preco1').focus(); $('#sub').maskMon ...

An error has occurred while trying to declare Symbol InputText during an Angular production build

Currently, I am facing an issue while trying to export the primeng modules from a file. During the test build, I do not encounter any errors. However, when I proceed with the production build, I come across the following error: ERROR in Symbol InputText de ...

Unintended repetition with fetch() for loading a texture

I've been experimenting with using the fetch statement to fetch a local image and incorporate it into my (three.js) project. However, I seem to have created an infinite loop and I can't figure out why. Since the project is quite large, I've ...

MongoDB has encountered an error while attempting to combine two disparate conditions

I need to run a MongoDB query using JavaScript. Specifically, I am looking to retrieve documents based on two different criteria. The first condition is as follows: $or: [ { "users.username": user.username }, { buyer: ...

What are some ways to avoid sorting parameters in AngularJS when making a GET request using $resource?

My resource is: angular.module('myApp.services') .factory('MyResource', ['$resource', function ($resource) { return $resource('http://example.org', {}, {}); }]); How I send a GET request: MyResourc ...

sending an array from Javascript to PHP

Utilizing JavaScript, I have successfully converted data from a file into an array. Now, my goal is to utilize input from a form to search through this array using PHP and present the results in a table. Despite browsing various solutions for similar probl ...