Performance when handling large number of lights in a city game

Hello, I am currently developing a procedurally generated game. Houses and terrain are all procedurally generated. I am looking forward to add lights to the scene but if I add a large number of lights (30+) the frame drops or the scene stops to render entirely.

I have tried to use the LOD class to enable or disable lights that are far from the viewer but whenever I start moving and a new light is enabled or disabled, I got a huge framedrop (maybe the shaders needs recompiling? I don’t really know)

const lod = new LOD();
lod.addLevel(new PointLight(), 0);
lod.addLevel(new Object3D(), 50);
this.add(lod)

Question: How can I handle a large number of lights? For example, I would like to have at least one or two SpotLight for each house. What approach do you recomend?

Images for ilustration purposes:

1 Like

It’s not possible to handle the resulting amount of lights in three.js (and probably with forward rendering in general). Deferred shading is a typical approach to render many lights in a scene without a significant performance-hit. However, WebGLRenderer does not support deferred shading. You might want to give the experimental WebGLDeferredRenderer a shot:

https://threejs.org/examples/webgldeferred_animation.html

However, I would choose a different approach and only use real lights in the focus area of the player. Lights which are far away can sometimes be represented as simple 3D objects. Or consider to use static lighting by baking it into a map.

2 Likes

Thanks! I was afraid of these being the only options. About Baking I don’t think it would be possible since all the geometry is calculated in real time and it would be pretty hard for me to implement my own baking algorithm.

About using the WebGLDeferredRenderer: I have tried it like so:

this._renderer = new WebGLRenderer({canvas});
this._deferredRenderer = new WebGLDeferredRenderer({renderer: this._renderer});
this._deferredRenderer.forwardRendering = false;

// and then (on resize)
this._deferredRenderer.setSize( window.innerWidth, window.innerHeight);

// And inside the render loop:
this._deferredRenderer.render(this._world, Game.visibleCamera);

If I switch the forwardRendering option to true, it shows the scene perfectly, but If I set it to false, it shows only the background colour of the scene, but it doesn’t show anything at all.

Can you please point me what I am doing wrong?

EDIT: I’ve tried debuging it with Spector.js chrome extension and it tells me No frames with gl commands detected. but the render loop is being executed.

Update: I deactivated the scene background color and switched from WebGL2 to WebGL1. It seems the deferredRenderer is not sampling correctly the textures. Could it be?

Can you please share your entire project as a git repository? Or even better, try to reproduce the issue with the official example (which works actually fine):

https://threejs.org/examples/webgldeferred_animation.html

Of course. I have reproduced the issue with the official example adding a texture to the walls.

Here is the issue with the link to the code:

Hey @jhm.ciberman,

there are alternatives, as always. I ran into a similar problem in the past, as @Mugen87 stated - you can’t have more than a handful of lights in a standard forward rendering (which is how WebGLRenderer works), it would say 3 is already a stretch, as each light adds fragment shader overhead for each material as well as each light being added or removed causes all shaders to be re-compiled. It’s a bad idea all around.

Deferred rendering in three.js is pretty bad. What can one do? Well,

  • you can be okay with the deferred renderer in three.js
  • you can write your own renderer or switch to a different library that supports deferred rendering
  • you can fake lighting with lightmaps (as @Mugen87 suggested)
  • you can fake lighting with geometry and sprites. Things like light-beams and shiny glares create a good illusion of light

I use a mix of last two in my game to pretty good effect.

good luck

4 Likes