Making a procedural skyscraper city generator with Three.js and WebGL2

Farauanu Emanuel
Nov 5 · 6 min read

I always loved making small 2D games using HTML5 Canvas, mainly because I enjoyed coding all the actions in plain JavaScript. With time though, I realised that it could be very time consuming to build games from scratch in 2D, not to mention 3D.

Then I took the Computer Graphics course at my university. For the first practical, we had to build a simple scene in Three.js and make some straightforward vertex and fragment shaders in WebGL2 for our objects. I took that as a challenge to advance my skills. So instead of making a simple scene, I decided to write a procedural skyscraper city generator. My inspiration was a project I came across last year that impressed me.

Mostly out of excitement, I chose to publish my project, as I think there were some exciting challenges worth discussing in an article.

Before delving into the code, here is the final product:

A short introduction to Three.js

If you ever need to render a 3D scene in your browser, most of the modern ones use WebGL2 for that. One can always try and write all of the WebGL code in JavaScript by themselves, but it can get quite painful. By following the tutorials from this helpful website, you will quite soon realise that the amount of code necessary can be ridiculous. Having to write your code for inverting matrices can be quite painstaking for example.

Here comes Three.js into play. It is a fantastic JavaScript 3D framework which takes care of all the clunky code behind the scenes, letting its users work with a clear and usable API. It even has its materials for objects, which are ready to use shaders, giving different classic looks to your objects.

However, we will write our shaders in WebGL2 for Three.js to use, as this was one of my goals for this project. I will not cover all of the steps for creating the 3D scene and all that, as there are plenty of online materials covering how to do that. I will mainly focus on the shaders and the procedural city generation.

The basics

The type of city I am trying to generate is a grid-like one, a large patch of ground divided into squares by intersecting streets. Each of these squares will then contain four skyscrapers with random dimension.

Before starting with the code, I will mention that the website is meant to be served by a server. The simplest method to do this is to use a Python server. All that needs to be done is calling the following command in CMD (or your terminal of choice):

If you have python installed, this will serve an index.html file at http://localhost:8000/, allowing you to view your website and load files from JavaScript. We need this to load the shader files since we are going to store them outside of our main website code. It is also possible to store them inside the HTML file or the JavaScript code, but it can look quite ugly/clunky. For the full structure of the project and for playing around with it, you can take a look here.

There are also a few basic functions which I wrote to help me with some of the tasks:

There are two last important observations before moving on. If you don’t see any rendering taking place try updating or switching your browser to Edge, Firefox or Chrome. Also, sometimes the files you are loading might get cached. The effect of is will be that you won’t see your scene updating when you change your code. The easiest way to fix this is by using incognito mode on your browser, as that disables caching.

Generating the grid

First, we generate a base with cells for holding our skyscrapers:

I decided to think of my grid of cells as a matrix, each cell having a position in the matrix. Therefore, my first step was to create an array of 2D indices to serve as parameters for the mathematical function that assigns positions to my cells in the 3D space. Each cell is a Group, allowing me to add skyscrapers into it using local coordinates, relative to the corner of the cell.

A small but important observation is that all non-declared variables in the code snippets are constants declared in an upper scope. You can take a look at main.js in the project for a clearer image (I will also attach the whole file at the end).

The next step is to create shaders for our city, as we need those before creating any 3D objects.

Here is the vertex shader, which takes care of the positions of the objects in the rendered scene:

And here is the fragment shader, which handles the colour of the objects:

We bind both of these to a Three.js ShaderMaterial:

Of course, some explanations are due here. Firstly, there are several ways shaders can receive variables. One way is through uniforms, variables which have a constant value for all vertices that are passed to the shaders. There are attributes, variables specific to each vertex such as modelMatrix or position. The aforementioned examples are provided automatically by Three.js when we create a ShaderMaterial, but we can also add attributes of our own if we want to. The last type of variables are varyings, which are variables passed from the vertex shader to the fragment shader.

All we do in the vertex shader is to compute the world normal, the position of the given vertex in the final scene (gl_Position), and the distance from our vertex to the camera. The normal and the distance are then passed as varyings to the fragment shader.

In the fragment shader, we add the appropriate lighting amount to the colour of our vertex, based on the angle between the light and the normal. All that is happening is converting the angle of the light to a 3D position, then taking the dot product of the normal and the vector from our vertex to the light to compute the light intensity. We also darken the objects based on their distance from the camera, for a fog-like effect.

We can now use ShaderMaterial to create our city. First, we create the geometries for our base, including the streets:

One problem I encountered when trying to create the 3D objects was that I was providing the angle values in degrees instead of radians, obtaining really weird results. Three.js expects all of the values for rotations to be provided in radians.

We can now proceed to generate the skyscrapers. The first step I took was to divide each cell into four smaller regions with a minimum size:

Basically, I am creating two dividers which have a minimum distance from the edges of the cell, enforcing this way the minimum size of each region.

The next step is to create a skyscraper with random height and colour, given its base dimensions:

We can now proceed to fill the cells with the skyscrapers:

The code above generates four skyscrapers in a given cell based on the randomly generated dividers and a margin which is enforced between the buildings so that they don’t look glued together in the final scene.

All that is left to do is filling each cell with skyscrapers:

Putting all of the code above into a Three.js scene, with some extra code for the camera positioning and all that (see main.js) should lead to a fully generated city. A lifeless city sadly. It is time to add the lights.

Adding cars (red fuzzy dots)

Given that I wanted to create a few hundred of these lights in my city and that my laptop only has a pathetic integrated GPU, I couldn’t afford to make the cars actual lights and compute the amount of light they scatter around the world. Instead, I settled down to just draw each car as a fuzzy circle texture which I made in GIMP. For that, I needed two new shaders.

Here is the new vertex shader:

And here is the fragment shader for using textures:

Using textures with Three.js is actually quite easy, as the library does all of the background work for us. The uv variable used in the vertex shader represents UV mapping coordinates. UV mapping is a process that maps 2D coordinates to 3D coordinates, and all of the maths here is being taken care of by Three.js

Here is how we define our ShaderMaterial for the moving cars such that it loads our image-based texture:

We now write a function that takes a street index and an orientation parameter to generate our circle at a random position on the given street and in the correct lane:

We can now randomly generate four collections of moving lights, one for each possible NESW orientation:

We then add all of the lights in each collection to our scene and write a function for moving them in their direction to be called with each new frame:

We’ve now gone through all of the essential code. Putting all of the above into an init function and sprinkling some of the Three.js API magic on top to create the scene, camera, WebGL renderer and controls will result in the demo above. Here is a link to the final JS file to see how to merge everything.

Farauanu Emanuel

Written by

Computer Science and Philosophy Student at St Anne’s College, University of Oxford.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade