Friday, April 10, 2026

Nature in Procedural Cities

I'm switching back to cities after working on buildings for a while so that I can add more nature elements. Specifically, I've added creeks to city parks and ivy to house walls. Both of these are pretty simple effects, but they took a surprising amount of time because they're so different from buildings. Natural/organic elements are generally more difficult than cube-shaped man made objects. I also feel like plants and water are easier for people to find flaws in since they're so common.

Creeks

One or more parks are added to both commercial and residential cities. Parks currently have one or two curving walkways and an optional pond. Ponds only occurred in around 50% of parks. I do like them, especially now that the water is reflective, so I increased this to more like 75% by doubling the number of placement iterations from 100 to 200.

Ponds are drawn in several passes using different textures and effects. The ground is formed by a cone shape to create sloped sides and a flat bottom, with a darker color circle on the bottom to add fake light attenuation with depth. (I didn't use the same water shaders I had in buildings because I didn't want the runtime overhead and complexity of switching shaders while drawing city objects.) Water is drawn as a partially transparent textured oval on top of the pond as the last step so that alpha blending works. This is done before drawing the park terrain quad, and has depth writing enabled, so that the ground isn't drawn over the pond. The result is a hole cut into the terrain where the interior geometry is visible.

I wanted to add creeks flowing across parks, under the walkways, and into these ponds. I reused the same sine wave path logic that I had been using for park paths to create the shape of the creek. That was easy, but drawing it proved to be more difficult. My first attempt was to do something similar to ponds with a sloped ground layer on the bottom and a partially transparent water surface drawn as a path on top. However, this presented some problems. First, it's not easy to create a sloped shape for a curved path that forms a hole free mesh and blends correctly with the existing geometry, including the edge of the pond. Second, the two water layers don't join nicely where the creek meets the pond because the intersection is an ellipse formed from straight sides. And third, the trick of using a depth write to mask off the terrain under the creek causes problems with the walkways, which should be drawn over the creek and not masked off.

After making several attempts to correctly render the creek, I gave up and decided that it had to be done with actual geometry rather than the depth masking approach. And the solution to joining the creek with the pond was to drawn them both as a single textured quad that covered the entire park. But this quad had to be drawn below the terrain height rather than above it so that it was under the park path. To do this, I needed to add proper heightmaps to parks instead of drawing them as single flat grass-textured quads.

Park Heightmaps

The general idea was to create a mesh as a 2D array of height values, similar to the way the tiled terrain was drawn. Except parks don't align to terrain tiles, they're not necessarily square, they don't use the same shader, and so on. Interestingly, the closest code I could find to draw this was the code I used to draw ... pillows! (Yes, the pillows placed on house beds are in fact 2D height maps using curve functions to shape them.) So I factored out the shared code and reused it when creating parks. I initially used a heightmap size of 128x128 to match the terrain tiles (compared to only 24x24 for pillows), but I later decided that 256x256 looked better. I needed at least several texels of creek width to get reasonable edges. These heightmaps are only drawn when the player is close to a park, and there's usually only one or two parks visible, so the added frame time is negligible. Distant parks continue to be drawn as flat planes with the creek skipped.

The park heightmap is initialized with all heights set to the park/city elevation. The pond is added by lowering height values using a X/Y weighted 2D sine function that produces an elliptical perimeter where the edges gradually slope down to the lowest point at the center. The creek is added by lowering the terrain along its path based on the distance to the creek center line. The code is similar to how I level the terrain under roads. I added custom vertex coloring so that grass at higher elevation remains green, while lower values transition through a brown dirt color to complete black at the deepest part of the pond. An example pond is shown below.

City pond drawn with the new park heightmap system, with city reflection.

Creeks need more work. They're shallower than ponds, which means the bottoms are brown dirt rather than black. That seems okay. But the banks are jagged rather than smooth because the creek is only a few height samples wide, which means the underlying 2D grid shows through. I initially attempted to fix this, but eventually gave up and accepted the fact that this makes it look more "natural".

I still had to handle the case where the creek passed under walkways and the sidewalk along the road where the park ended. I decided to add a cylindrical metal pipe to both locations with the same corrugated steel texture I was using for buildings. That looked acceptable, though the walkway was obviously a flat plane as it had zero thickness. So I added a bit of thickness by drawing the sides of walkways, which looked better even in areas where there was no creek. Maybe it's still too thin? The pipe doesn't align exactly with the creek and has water above it, but it's good enough for now.

If I turn around I can see the creek feeding the pond, which goes through a pipe and under the walkway.

Creek vs. walkway crossings only look good when they happen at nearly right angles. In some situations I saw creeks meet up with walkways and continue partially under them for some distance, which looked wrong. What orientation was the pipe supposed to be in? The fix was to check for intersections between the creek and walkway paths and calculate their angles. If the angle is outside the range of about (70, 110) degrees or the paths crossed multiple times within a small area, the creek is rejected. The placer makes up to 100 attempts to place a valid creek until it gives up. I've only seen one park with two very windy walkways where the creek can't be placed.

Residential park creek that crosses under walkways three times (two visible).

Another nice property of park heightmaps is that they should allow me to add other features such as hills in the future. The reason I haven't added hills yet is because pedestrians currently assume all of the city is a flat plane. They'll clip through the hill when walking near it. I may be able to fix this by adding pedestrian colliders for hills, but that's a task for some other day. Oh, and I'm sure I would have to fix the player collision for hills as well. And adjust park path, tree, bench, and picnic table placement - you get the idea. Like this:

First attempt at adding a hill to parks. See, there are problems with the tree already.
 

The terrain doesn't cast shadows because there's not much below it to shadow. I actually didn't even think about shadows until writing this post. Maybe the water passing under walkways should be shadowed, but it's not very noticeable. I would probably need to add shadows if I place hills in parks. Update: After writing this, I did go back and change walkways to cast shadows over creeks. It doesn't make much of a difference.

 

Hedges

Next, I was inspired by some procedural ivy posts on Reddit and decided to add ivy to the concrete walls separating the yards of some houses in residential cities. I started by copying the class that draws the leaves of hedges near the player, since it's similar to how I want to handle ivy. Have I shown hedges in this blog yet? I don't remember, I'm up to 190 posts! Maybe not, so I'll go over this briefly now.

There are three types of residential plot dividers: hedges, walls, and fences. Well, technically there's also "none" for some blocks. Fences can be either wooden or chain link. Plot dividers are currently all represented as boxes with five of the sides drawn (excluding the bottom). Oh, four for chain link fences since they're thin and have no tops. Hedges don't look particularly convincing as simple textured boxes, so I added a randomly generated cluster of 40K leaves around the perimeter of the shape to give them some volume and improved shadows. Leaves use the same texture as the flat quads so that they blend together well. There's a single instance of this "leaf shell" that's translated, rotated, and scaled to fit each hedges box. They're only drawn for hedges close to the player as an optimization.

Hedges added to residential yards, drawn as textured boxes with additional random leaves with random orientations added using the same texture.


Ivy on Walls

I factored out a base class for vegetation drawing that could be used with both hedges and ivy. Note that I wasn't able to reuse the existing system I had for drawing terrain plants for a number of reasons: different shaders, placement on objects rather than terrain, generated with cities rather than tiles, can't easily use instancing for ivy, etc. I felt that I had to create a new system here that fit into the city drawing framework.

Each residential city has its own ivy manager class. Since cities are spaced out over the terrain, only the nearest visible city needs to draw its ivy, which means I can clear any ivy from the previous city when the player moves to a new one. A city has multiple blocks, each block has multiple residential plots, some of which have walls, with two sides, with multiple ivy plants. Except ivy is only placed on a subset of the possible wall faces for performance reasons. I suppose that's realistic as well, since most walls aren't covered in ivy. I walk down the object hierarchy and randomly skip some of the nodes when placing plants.

Ivy is generated per-wall. All walls within the nearby player view area are evaluated and any necessary ivy geometry is created and stored in a map using the wall index as a key. Each individual ivy plant is unique and customized to fit its wall and any surrounding plants that were generated earlier. Ivy is represented as a set of branches formed from connected series of cylinder segments and randomly chosen split points. A root location is selected as the starting point for each plant, and a branch is constructed using a random path. 4-8 additional branches are generated from a randomly selected point along a previous branch. Each new branch uses a radius slightly smaller than the branch is was split from. Consecutive branch cylinders are joined cleanly by merging their vertices at shared path points, similar to how I draw tree branches. Each branch end with a cone that forms a point.

I added a constraint that branches must always move upward to reach toward the light. That's not always true with real ivy, since it tends to sag and fall over time. But I found that rule made the results look cleaner, lead to fewer branch intersections, and avoiding the need to check for collisions with the ground. The only exception is when a branch is split from the bottom side of an existing branch that's somewhat horizontal. In that case it will still have an upward bias that will take effect over time as new cylinders are placed. This rule tends to produce ivy in a somewhat inverted triangular shape that's narrow at the base and fans out to a wide top.

Leaves are added along branches on alternating sides with a random rotation away from the wall. The rotation reduces the chance of two nearby leaves overlapping. Each leaf is drawn as a flat quad with two triangles. Leaf size is random within a small range, except for the ends of branches, where smaller leaves are added. Branches check for intersections with other branches of the same vine, while leaves check for overlaps with other leaves. I allow two nearby ivy plants to intersect because it's difficult and expensive to check for this. Leaves are allowed to overlap slightly since their textures don't cover the entire quad. Branches are also constrained to stay within the bounds of the wall, leaving some area open at the ends where the wall meets other walls, fences, or hedges. Leaves can only extend outside the wall bounds at the top.

Multiple ivy plants growing up walls that enclose the backyard of a house.

The final step was to allow ivy to wrap onto the top of the wall. This works by effectively rotating the placement plane by 90 degrees to the top surface and continuing branches that extend off the top of the wall. Some ivy plants don't extend high enough to wrap along the top of the wall. Since the branches bend at sharp angles close to 90 degrees at the edges of the wall, I draw an extra sphere to connect vertical to horizontal branches. All of this took me quite a few attempts to get right.

Ivy modified to grow over the tops of walls.

I did also consider having ivy wrap around the sides of walls, but that's quite a bit more difficult. Some walls are adjacent to other walls, so I would at least need to track which wall ends are open. And I'm generating ivy on both sides of the wall independently, which means I don't know where the existing ivy is on the other side when a branch wraps around the wall. So I haven't yet added this behavior.

Another future improvement is to add proper transmissive lighting for ivy leaves. I already use this approach for tree leaves. This works by calculating an attenuated lighting value for the side of the leaf facing away from the sun rather than leaving it dark, to simulate light passing (scattering) through the leaf. I haven't added this because the shader that supports it doesn't have some of the other features I want for cities.