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. 

15 comments:

  1. "I also feel like plants and water are easier for people to find flaws in since they're so common." This is an excellent point. Everything you're doing with Procedural World is super hard to get to look right because they are a part of everyday human experience. Organic shapes are inherently fractal so they are difficult to depict discretely. Plus we are tuned to pick up on "healthy" environments by our evolutionary heritage. That's part of why I've taken so long to comment on these latest couple posts. It's clear you've taken a great deal of effort, but the visuals are still uncanny. The parking garage makes me feel like a parking garage, but the parks make me feel like it's some sort of alien VR zoo trying to convince me to be comfortable.

    ReplyDelete
    Replies
    1. Parks are difficult because they're done with the city system rather than the terrain system. The terrain system was designed for large open areas with denser vegetation and more variety, but it doesn't have the level of controlled placement that I need for parks. Plus the park area is a single square block and difficult to work with due to size limits. Parks were an afterthought. I'm sure I'll improve them in the future. I already made some improvements such as fixing the hills.

      Delete
  2. I like the heightmap addition to the streams, plus culverts. I feel like it just needs to be much deeper, wider, larger. Like, here is a park near where I live, and it has much bigger features: https://maps.app.goo.gl/XANtTMvg6q4w6bdo6
    And since you're generating this all parametrically, money is no object. You could make all the parks like the best public spaces ever constructed!
    https://www.bing.com/images/search?q=The+Getty+Museum+Water+features

    ReplyDelete
    Replies
    1. The problem is the limited size of one city block. There's only so much I can fit in. A large creek going into a small pond looks out of place. I could do a better job if I moved parks outside the city road grid or removed some roads to merge multiple blocks together. I do have some park fountains.

      Delete
    2. There was a bug in the creek handling of park heightmaps that always made them one height sample/pixel wide. That's fixed and now they look much better (and smoother!)

      Delete
  3. The dense hedges look really good! Very convincing. Could you make arbitrary topiary using this method?

    ReplyDelete
    Replies
    1. I can use other leaf textures for different plant species. I have proper hedges in the other (non-infinite) world mode with actual branches, but I haven't integrated that system into cities yet.

      Delete
  4. I feel like the dense hedge instancing method could be used for grass as well (close to the player) to get away from the astroturf-meets-compost-heap look of the current grass. For more distant grass, maybe you could do a vertical smear on the depth buffer? I feel like there must be a lot of resources out there about making convincing lightweight grass.

    ReplyDelete
    Replies
    1. The grass found in parks and residential yards is a flat texture. Blending the depth buffer probably wouldn't do anything useful, especially since I have non-grass geometry in that same depth buffer that would get blended. Do you mean blend the color buffer or texture?

      I have real 3D grass as part of the terrain system outside cities. The problem isn't performance but the lack of exact placement. If I add that grass everywhere, it will come up through the sidewalk and be under the water. I do have a 2D grass coverage texture that works well enough with large rectangular buildings, but it's too low resolution for curvy, sidewalks, creeks, and ponds. I would get an ugly rectangular stair step shape at the edges. I can't place individual blades of grass like I do with some small scenes because it would require millions of blades and would be too slow. I'm sure there's some solution I haven't thought of. One more line item in my todo list.

      Delete
  5. For the ivy growing code, one of the things that ivy often does is arc out from the surface its growing on. The branches that leave the wall catch more light, but can be overshadowed by higher ivy, causing a much more bushy appearance and pushing the ivy to keep expanding. For the dense ivy near the base, you could probably do the hedge method of adding bulk geometry with leaves instanced on the surface and doing individual branches on the edges, and sticking out from the bulk to add some dimensionality.

    ReplyDelete
    Replies
    1. I can add some ivy sticking out, though I need to be careful with clipping through objects placed next to walls. Adding ivy like hedges will result in leaves clipping through themselves and through the wall. I don't like the look of that and have mostly avoided the problem so far. It seems like less of a problem for hedges because they have those smaller feathery leaves rather than large flat leaves.

      Delete
  6. To continue the thought of my previous comment, you don't often see ivy growing on the top of a wall because the ivy doesn't "want" to grow on surfaces. It only grows on surfaces to get above the other plants. As soon as it hits the top of the wall, it's going to grow straight up, bend over under gravity, and make a "spray" of ivy.

    ReplyDelete
    Replies
    1. I used the grape vines on the fence and wall of my back yard for reference, since they look sort of like ivy. They do run along the top of the wall. However, now that I think about it, that's probably because I trim the branches that stick out/up. Maybe I need a random variable controlling how maintained the ivy is that's set per-yard.

      Delete
    2. Ahh, yes! The ivy looks very aggressively pruned.

      Delete