Monday, June 26, 2017

Procedural City With Buildings

I would like to have large cities and other man-made objects in 3DWorld. Terrain, vegetation, clouds, and water look fine, but the scene needs more. My large array of museum building models from a previous post is interesting, but looks too repetitive, nothing like a real city.

I started looking into procedural city creation a few weeks ago. There are tons of articles and demos of generated cities online. Most of them seem to start with city planning, then add roads (often in a grid), and finally add buildings. This works fine with flat open land areas, but probably not so well in 3DWorld procedural scenes. It's not clear how this top-down approach works with water, mountains, cliffs, and other non-flat terrain features. So I gave up on planning and roads, and went straight to building generation.

I got some of my procedural building ideas from this post by Shamus Young on building generation for the Procedural City project. 3DWorld buildings need to look good both during the day and at night, which means they need textures. At some point I'll probably also need to add detail features such as railings, antennas, AC units, etc.

To simplify things, I have only two textures per building, one for the sides and another for the roof. The easiest textures to use are brick/stone walls and arrays of office building windows. I was able to find some good ones online. A few of them also have normal maps. I'm using per-material texture scales and custom texture mapping to make the windows and bricks/blocks appear to be a consistent size across textures and building shapes. The textures are very repetitive so that they tile properly across the building, which means they don't have any features that really stand out. No doors, etc.
Buildings using plain brick or stone wall textures don't even have windows. I may need to improve on these issues in the future. It would be nice to have a texture library of the same exterior building materials and style but a variety of tiles that can be interchanged. For example, blank walls, rows of windows, door(s), etc. These would need to tile properly so that a window next to a door looks seamless. Unfortunately, it's very difficult to find acceptable texture sets online that are both free and high quality.

I've added the following building types/shapes to 3DWorld, starting with the simplest and most common:
  1. Low brick buildings (rectangle, L, T, and U shapes) with 1-3 levels
  2. Office buildings of 1-8 stacked cubes of decreasing size, possibly with cut corners
  3. Round (cylindrical or ellipsoid) office towers, possibly with a flat side
  4. Polygon shaped office towers with 3-8 sides of 1 or 2 different side lengths
  5. Large buildings composed of multiple cubes of various heights attached together
  6. Large buildings composed of angled geometric pieces attached together
Here are some example screenshots showing how building generation has advanced over time. The first one was early in the development process, and the last few are the most recent.

Early cube buildings of types 1 and 2.

Mixed cube, cylinder, and polygon buildings of types 1-4.

I have a config file format that defines different building classes, which I call materials. Each class has a large number of parameters including frequency, textures, placement rules, size variables, shape probabilities, distribution parameters, etc. The width, height, aspect ratio, number of levels, number of splits, number of sides, allowed wall angles, rotation angles, altitude ranges, and others can all be user configured. The user assigns material parameters (with ranges) to the various building types and probabilities to control their ratios for each city. I asked the system to generate 100K buildings over a radius of several square miles, and it came back with around 60K buildings. Buildings over water and on high mountains are skipped, which means that buildings cover approximately 60% of the surface within the city radius. Generation time is about half a second using 4 CPU cores.

Buildings are packed together and checked so that they don't intersect each other. They're placed at the correct height value by querying the terrain height generation function, which is where most of the generation time is spent. This works correctly for both procedural terrain and height values read from a texture. Their bases are adjusted to remove gaps between the bottom of the buildings and the ground. Trees and plants are placed in the gaps between buildings. The scales aren't completely right though. Buildings are too small compared to the player and trees, which make trees look odd when placed near tall office skyscrapers. Buildings are probably too large compared to mountains and other terrain features though. All of these sizes are configurable in 3DWorld, so I'll have to work on this later.

Buildings are pretty well integrated into 3DWorld now. They're generated on-the-fly when needed each time the terrain changes. The algorithm can be re-seeded by the user to generate a new city. I've implemented shadows from and onto buildings, and building self-shadowing for both ground and tiled terrain mode. The shader used supports dynamic and indirect lighting, and fog. I've added player collision detection with buildings; you can even walk on a building's roof. I've mostly finished the line intersection tests for building ray tracing so that they appear correctly in overhead map view:

Overhead map view of buildings, showing that ray casting is working.

Rendering is efficient because it supports block-based distance and view frustum culling, back face culling, and dynamic level of detail (LOD) for complex buildings. Draw time is 2.7ms for around 5000 visible buildings such as in the screenshots below. I could probably use multiple threads for rendering, but it's much more complex and doesn't help much. I'm currently generating the vertex data (mostly quads) on the CPU and sending it to the GPU every frame. I could precompute this once and store it on the GPU, but that could take a lot of GPU memory for 60K buildings at full detail. Keep in mind that most buildings have flat faces, so the normals are different for the corner vertices, which means the vertex data can't be shared. The data I actually have to send to the GPU is very small compared to the full set, only the front faces of the nearby buildings within the view frustum. I'm guessing that's around 1-2% of the total number of vertices/triangles. Also, it's difficult to do culling and batching of buildings by texture if the data is stored on the GPU. If I eventually add transparency from glass windows, I'll need to sort back to front by depth, and that will also complicate things.

Here are several more images of my generated city. What you see is only a tiny fraction of the total city. It extends off into the distance out to a radius several times what the view/fog distance is. The player is free to walk or fly out to the edge of the city to explore it all. It's also possible to specify more than one city at different locations.

Mixed building types 1-5 on rock and grass terrain.

Cube buildings of types 1-2 among various types of trees. Trees are probably too large compared to the buildings.

Here are some screenshots showing close-ups of interesting building types. I believe the most complex building is composed of 192 triangles, though at most 128 are visible and drawn at once. Note that every one of the 60K buildings is unique. I could use instancing, but that doesn't seem to be required to get good performance.

Various buildings with a variety of shapes, of all types. The building in the bottom left is a cube with corners cut off.

Triangular building of type 4 with cut corners on a hillside. The trees are probably too large compared to the buildings.

Complex angled office building of type 6 on a snowy peak.

Maybe I should also place larger/higher buildings in the city center, and smaller buildings near the perimeter. That can actually be done using the config file, where I can define a different placement rectangle or circle radius for each building type. I can even create multiple cities using different center and radius values, or multiple districts within one city that each have a different mix of building types. It's worth experimenting to get a more consistent and realistic look to the city. ... Okay, here is what it looks like when I place the office towers in the city center:

City skyline: Tall office buildings in the city center, smaller brick and stone buildings in the outskirts.

Huge city in a flat area with a few sharp peaks in the distance.

The next step is to generate roads or some other system to tie the buildings together. I don't know quite how to do that yet, since it either requires flattening the terrain, or curving the roads to match it. This is particularly difficult because the terrain hasn't been generated at the time of building placement. It's generated in chunks when needed, whereas the entire city is created at once. It's also unclear whether the roads should form a uniform grid, or curve to follow the terrain contour. A grid road network is likely easier, though it requires the buildings to be oriented parallel to the grid axes rather than randomly rotated like they are now. There are a lot of choices with assorted trade-offs. It's will be interesting to see how all of this turns out.

No comments:

Post a Comment