Tuesday, August 4, 2026

City Parks and Residential Grass

I didn't get a chance to write a July blog post for various reasons. I was busy with summer related activities and work (full time job) tasks. I also didn't add much new content the past month or two. It was mostly bug fixes and optimizations. I did make some progress with parks and residential areas. Most notably, I added proper grass blades to the city areas that were using grass textured ground. There were some smaller improvements such as adding ivy to house walls, adding park restroom skylights, improving the look of park creeks, and making people walk on park paths. I originally wasn't going to write a post on this because it was too small a topic, but I haven't done any major improvements since adding data centers.

This time around I'm going to show what I did and then go back to explain the details. Here's a video I recorded a few weeks ago showing the new look of city parks.

 

There are a few bugs visible in this video that I already fixed: grass in the walkway, wrong splash footstep sounds, woman stuck by a parked car near the end.

Grass

I've had proper fields of grass blades supported in 3DWorld's tiled/infinite terrain system for many years. I even wrote a blog post on the topic back in 2018 before I started working on procedural cities. This uses instanced blocks of randomly placed grass triangles that are curved using an OpenGL tessellation shader and move with the wind. I use a texture to mask off the area of the terrain that has grass and read heightmap values to translate grass to the correct elevation in the vertex shader. Distant grass is drawn with reduced triangle sets where pairs of neighboring grass blades are merged into a single larger triangle with summed area and average color.

All of these optimizations allow terrain and grass rendering to run at hundreds of FPS (frames per second) on dedicated graphics cards, though the framerate struggles to meet 30 FPS on integrated graphics. That's why I have both a config file option and keyboard shortcut to disable grass if needed. It defaults to on since 3DWorld targets PCs with dedicated GPUs from the last decade.

The challenge for grass is that cities are drawn as flat quads at fixed elevation and don't use the terrain system. The terrain mesh is masked off and not drawn in these areas. Instead, there are flat roads, sidewalks, concrete, and grass textures for parks and residential plots. What I need is to draw grass blades over these plots and parks, but without also drawing the terrain. I can do this by adding custom terrain weights that sum to a value greater than one, and use that in the terrain shader to disable the ground but still enable grass in the grass shader. This enables flowers as well, since they use the same set of textures and similar shaders.

I just have to set the heightmap values to match the height of the city, so that the grass is drawn at the correct elevation. Except it's not that simple, because parks have hills, ponds, and creeks at heights above or below the rest of the city. So I have to sample these height values from parks when creating the heightmap for each terrain tile. Technically, I only need to set heights for the hills because there's no grass at the bottom of ponds and creeks. But it's simpler if I can set the low points as well and use this for setting the player height rather than querying the park at each frame.

City park with grass around the path and a person walking.

The next problem is that I don't want grass everywhere inside parks and residential plots. It needs to be excluded from areas with houses, restrooms, driveways, swimming pools, park paths, creeks, and ponds. Those last few items are complex shapes that my simple cube intersection system can't easily handle. Paths are represented by a series of 100 points along a centerline and a constant width, which is definitely neither easy nor efficient to test blocks of grass against. This means grass needs to be placed using point + radius queries for each grid texel, where the query returns false (no grass) when the circular shape intersects any grass blocker object. This should guarantee no grass blades sprouting up in the middle of the sidewalk.

With enough effort, this does work. However, the terrain material mask is too low resolution to get good grass coverage. The 128x128 base texture represents areas of around 4 meters in size, which produces grass coverage that's too sparse around small objects such as park paths. Any small object masks off a 4m x 4m area where there's no grass. It's actually worse than that, because of the way grass density is smoothly interpolated across texels. If I want to have zero grass on driveways and sidewalks, I have to check even more than 4m away to guarantee the weight function drops to zero before hitting a grass blocker object.

City park with grass, a swing set, a hill on the right, and a restroom in the back.

My solution was to create a higher resolution 1024x1024 weights texture to use in place of the 128x128 lower resolution texture for any terrain tiles that overlap a city area with grass. This results in higher resolution textures embedded within the 2D array of normal textures. Cities are typically around 1.5 terrain tiles in size, resulting in 2x2, 2x3, or 3x3 blocks of higher resolution textures. The good news is that there's usually only one city close enough to show grass at a time, which limits the memory needed to around 36MB (3x3 tiles * 4 one-byte weight values * 1024x1024 texels). The borders of the texture need special care to interpolate edge values so that they meet with the lower resolution texture edges seamlessly. This was accomplished by using an image upscaling operation for areas outside the city bounds where normal heightmap terrain is drawn. All of this took a long time to get right.

Much of that time was spent optimizing the system so that it can make up to 1M (1024x1024) queries into the city object system to determine the grass coverage value at each texel. This required many individual optimizations to get the worst case under the 16ms (~1/60s) frame time target. This included hierarchical queries, custom acceleration structures, caching/testing common grass blockers, and using multiple threads. In the end I was able to get the worst case time down to around 10ms, which is fast enough that there's no noticeable lag when creating new weight textures. This data is cached per city and only regenerated when the player moves far across the map.

City park with a creek and pond, and a woman walking along the path.

The final results look pretty good to me. It's not perfect. There are still some jagged edges of grass blade clusters along curved surfaces such as creeks and park paths. It's not that obvious because of how the grass geometry blends into the ground texture when looking down at a steep angle. I did put some effort into tuning the colors and texture scales so that these two features look very similar in the distance. The grass texture is a good representation of a field of grass viewed top down.

One missing feature is that I'm not removing grass at the base of trees. This is primarily because the tree occlusion map is the same lower 128x128 resolution as the terrain heightmap. If I use this for grass, the keep-out area around tree trunks is too blocky. I didn't want to increase the tree occlusion map resolution because it's only one component of a larger texture that stores other terrain properties such as ambient occlusion and shadow data. Increasing the resolution of the entire texture would take too much time and memory. 

Park Improvements

I've made a few other park improvements. Park restrooms have improved interiors with high ceilings and beams. Some of them even have skylights. They're now connected to the city electrical wires.

Fountains now have a water surface draw that occludes the grass under it. That means I don't need to use them as grass blockers. The water is reflective just like ponds and creeks and uses the same shader. I used a similar approach when drawing fountains inside malls.

The heightmap used for creeks and ponds has been improved so that creeks are wider and have smoother edges. I added rocks along the sides of creeks. Park path and creek crossings look a bit cleaner as well.

I modified the pedestrian path finding so that they prefer to follow park paths that go in the same general direction as their destination. I added logic for them to avoid walking in creeks, and to use park path crossings when they must cross a creek. This turned out to be too complex for the basic city path finding system, so I had to switch to using a grid-based approach like I use with building backrooms. This system is explained in a previous blog post. I had to make some adjustments to the end points of creeks to pull them back from the edge of the park so that there's more room for people to walk next to the sidewalks. This is important for pedestrians passing by each other.

Here is a recent overhead view of a park showing the grass, walkways, creek, and pond. 

Crossing park paths and creek with rocks.

Residential Yards

The screenshots above showed off the new look of parks. The same grass improvements apply to the yards of houses in residential cities. The only difference is that the set of blockers changes to include houses, driveways, walkways, and swimming pools. I think the results are quite nice compared to earlier screenshots. Adding vegetation is always an improvement.

A residential neighborhood with grass yards.

I added house driveways a while back. While I was working on grass, I thought it would be good to add walkways to the front doors of houses so that people don't have to walk through the grass. This also neatly solves the problem of grass clipping through the small step under exterior doors, because I can make walkways grass occluders just like driveways. I placed the mailbox next to the walkway (if there is one) rather than the driveway. Even with the higher grass map resolution, grass can still only be placed at a precision of around half to one meter square. (There are some constant factors so it's not an exact fraction of the 4m tile spacing.) This does leave some empty space at the edges of the walkway and driveway.

House with driveway + basketball hoop and walkway + mailbox. A digital alarm clock can be seen in the room on the right.

One of the other smaller items I've added is digital alarm clocks on the night stands in bedrooms. They use the same drawing logic as the digital clocks found on office building walls. The time is updated dynamically based on the current real time.

Wall Ivy 

I worked on improving residential city ivy between some of the grass-related changes. Ivy is far simpler and easier to work with compared to grass because it's a completely separate system that doesn't have to interact with the terrain. I'm free to do this however I want.

Each yard now has a random "maintenance" score that determines how well maintained it is. Yards with low maintenance have ivy that climbs over walls and hangs off the sides. I added a weight parameter to ivy branches that pulls them down into a curved shape as they grow longer while unsupported. This gives unmaintained ivy a more natural and wild look.

Ivy growing on a wall and branching out away from the edge. This is from an older screenshot before I added grass.

Ivy can now be found growing up the walls of some houses. I initially added the entire wall just like the edges of walls separating yards, but that looked wrong because ivy grew over windows. I went back and rewrote this part so that the ivy system accepts a list of areas to avoid, which are filled in with the windows along that wall of the house. Now the ivy will use the space between, above, and below windows to grow.

Ivy growing on two exterior walls of a house.

Here's another example with a different house.

Ivy growing along the wall of a house, between the windows.

This system has a lot of potential. I can in theory add arbitrary surfaces for ivy to grow on with exclusion regions, provided they're all axis aligned rectangles. It shouldn't be too difficult to extend this to other shapes that support point and line intersection queries.

That's all for this post. I don't have anything else planned at this time. I'm distracted by tasks and work and will get back to 3DWorld sometime later. I do have some items on my Trello board such as caves.

16 comments:

  1. Typolice!
    "usually only once city" should be "one"
    "along curves surfaces" should be "curved"
    "grass improvements applies to the yards" should be "apply"

    ReplyDelete
  2. The 3D grass looks much better than the flat ground texture. I would adjust the ground to be darker and much lower contrast, so it doesn't stand out as much. If there's a way to add variation to the individual blades, that would help a lot too. I do admire how the grass catches shadows!
    The length is strange though. For un-maintained grass, I would expect it to be much taller, uneven and clumpy, and many even taller stalks tipped with seed clusters. For mowed grass, I would expect it to be shorter and more dense, with square clipped-off blades from the mower instead of the fresh new-grown blades that taper to a point.

    ReplyDelete
    Replies
    1. I don't think the lower contrast ground texture looks as good when viewed from high above. I suppose I could experiment with the texture colors.

      It's difficult to customize the shape of each blade because it's drawn as uniform instanced triangles whose vertices are moved by the grass shader to create the curved shape. I can't easily select a different shape for different areas without adding significant complexity to the system. The only configurable inputs are constant length, color mask texture, and density mask texture. In fact the grass at the edge of the city may be drawn in the same draw call as the grass outside the city if they fall within the same terrain tile.

      Delete
  3. Now that you have the grass system working, do you think you could add reeds to the edges of creeks, and ponds? The bare muddy shores look so sickly and sad. A few cattails and bulrushes could really spruce things up! And then you would have the system for your wilderness zones as well.

    ReplyDelete
    Replies
    1. I did have some plants at the edges of ponds, but I must have removed them at some point. I think it was because they didn't look like plants that would be found there. I don't think these would be dense enough to require something like the grass system. I only need to add a new plant type for that, and then place some of these plants along ponds. Creating plants with code is very time consuming though, and there's not enough random variation if I use a 3D model. Maybe eventually...

      Delete
  4. It's still super strange to see giant portrait windows looking into a bathroom. Can't the poor inhabitants at least get you to render that portion of the scene at 1/16 resolution and then scale it up for a pixelated mosaicking effect?

    ReplyDelete
    Replies
    1. This is only a problem for corner bathrooms. I do have glass block windows that I use for most bathrooms that only have windows along one wall. There's some drawing problem where the player viewing from outside => glass block => inside => glass block doesn't render correctly. Some problem with the alpha blending/depth/stencil passes. I'll have to see if I can fix that, or maybe it did get fixed at some point, then I can put the glass block windows there.

      Delete
    2. I enabled glass block windows for corner bathrooms. It looks somewhat odd, but appears to work, so I suppose I can leave it enabled.

      I don't think I can easily render just the interior/bathroom and then draw it as a texture for the window. All objects inside a building with the same material are part of the same draw call, so I can't draw the interior of houses at a finer granularity than per-building. I would need to use the bounds of each window, or the set of windows along each wall, as a scissor test. But since the windows themselves aren't physical objects but are a repeating texture, there's no clean way to restrict it to only the windows associated with the bathroom vs. all the windows on the wall.

      Plus, if you had several floors of stacked bathrooms, it would be expensive to draw the scene separately for each one. Or multiple buildings with visible bathrooms. I can't cache the texture because it would change each frame with the camera perspective as the player moves. I can't select only the closest building/bathroom because then there would be obvious popping as the selected one changed.

      I may be able to mask off the windows with bathrooms using the stencil mask or another render target, and blur it per-pixel inside the mask as postprocessing. But that wouldn't work if there was something like a tree between the camera and the window as it would blur the tree as well.

      I did spend quite a while thinking about this, but I never solved it. I'm sure it would be easy to do in a tech demo of just one room of one building with one window. But making it scale to many buildings with many rooms and multiple windows each, without much effect on the framerate? No idea!

      Delete
  5. The ivy arcing away from the walls is a great improvement! I think scaling the leaves up to double their current size (and randomizing the sizes a bit as well) would help to sell them better. I feel like they should also be darker. Would it be helpful if I submit some PRs with my recommended "improvements"? I tried that a few years ago (5344 commits ago it looks like) but IIRC you preferred the hobby element to the open-source commit triage at the time. Let me know your preference. I'd be glad to help in some small ways where I can.

    ReplyDelete
    Replies
    1. If I make the leaves larger then they're more likely to clip through each other and through the wall, which looks bad. It's actually pretty difficult to determine where a collection of rotated quads will intersect each other, at least in an efficient way. There are different leaf sizes. The ones near the ends of the branches are smaller.

      If you want to look at the code, start with ivy_builder_t::add_leaves_to_branch() in city_plants.cpp. Maybe add some randomness to lsz_scale. That's pretty easy for me to do though, almost certainly the easiest change of you four comments.

      Delete
    2. The leaves were already randomly resized from -20% to +20%. I changed the upper bound to +40%. But there was a bug affecting leaf size for the hanging vines. The leaf direction is derived from the cross product of the branch dir with the wall normal. This is always magnitude 1 for ivy along the wall, since the vectors are orthogonal. But for hanging ivy, I need to normalize the cross product, otherwise the leaves are too short. This definitely improves the look.

      Delete
    3. I'm glad you found the bug! Improvement is good.
      I'd like to draw your attention to a pattern of thinking that has been gnawing Niddhägger-like at the roots of this project for basically ever. In the simplest possible terms, you are letting your left-brain tyrannize you. This sentence is a perfect example:
      "If I make the leaves larger then they're more likely to clip through each other and through the wall, which looks bad."
      It only looks "bad" to the delineation-oriented "this or that" left brain. If you are trying to do a per-leaf NASA-qualified simulation then this matters. If you want the ivy to look right, then the overall impression of the ivy matters much more.
      I mean, just look at this:
      https://cdn.80.lv/api/upload/content/55/5e67420791cb1.jpg
      There are alpha-card intersections all over the place! Is it technically "unrealistic"? Sure. But it looks great, and at the end of the day that's the job of graphics.

      But maybe I don't know what I'm talking about. If I'm right then intersecting alpha-cards would be used in almost every open-world or fantasy game: WoW, Guild Wars 2, Elder Scrolls, many Unreal and Unity titles, mobile games, etc. People would make hair cards on characters. There would be particle-like debris, hanging moss, ferns, and ground clutter in countless games, all crisscrossing each other. Even some modern high-end titles would still use the technique for distant LODs or specific stylized assets because it is extremely cheap.
      I poke fun, but we both know it's all true. The “unrealistic” intersections are the entire point: they let a handful of textured planes (or tris for even faster rendering) create the visual complexity of hundreds of individual leaves while staying friendly to real-time rendering budgets and sorting like you have in this project. Instead of making the cards smaller and smaller, you should be making them bigger and bigger. Put whole clusters of ivy leaves on a card, procedurally jitter the UVs (or render whole clusters to a texture at runtime) if you need the variation, and make some beautiful ivy!
      https://gamesartist.co.uk/wp-content/uploads/2025/09/HighPolyIvy_Final-1024x576.jpg

      I've heard time and again from you that you can't make this or that improvement because it would result in "unrealistic" outcomes. This is the left brain talking, and as long as you give that aspect of you veto power over the trade-offs you will always be shackled to the details at the expense of the whole.

      I love procgen, and I want you to do well and for this very cool project to succeed. I'm not saying you have to agree with me, but I'm certain you would get much better results much faster from a healthy balance between right and left brain thinking. As it is, your left brain is shooting down most of the progress you could be making.

      Delete
    4. Thanks for your feedback. But keep in mind that 3DWorld isn't a game, it's a world generator/simulator. If it was a game, I would have released something on Steam in the past 10+ years I've been working on the project. As a generator, the point is to get the details right to make a realistic environment. Why do you think I put so much effort into connecting everything correctly with pipes? Do you think a casual player would notice that? Of course not! But I see those things when playing games. I don't know what's wrong with me, but things like intersecting leaves really bother me if I can get close to them in a game. Leaves don't intersect in the real world!

      Yes, maybe you disagree on this. Maybe most people do. But these are the goals of my project. I'm not making just another game, I'm making something different. This is a hobby and is meant to be fun and interesting. I'm crazy enough that I find writing code to determine leaf quad intersections fun and interesting. I would much prefer this over creating a leaf texture in an image editor or creating a plant in a 3D modeler. I'm terrible at artistic things like that.

      Your example tree image looks good. But that's because it was likely created by hand and is placed in a known environment, possibly where the player can't view a leaf so close that it fills their screen. This approach is very common in games, but also time consuming to get right. The artist can tweak the lighting, rotate the leaf cards so that they meet the branch at the correct point, fix the bad looking intersections, etc. In my case the plants are all uniquely generated. The player can go anywhere in the world and get right up to them, and sometimes interact with them. And it has a full day/night cycle so the lighting is unknown. If I'm not careful I get intersections that look bad on *some* plant, and it really stands out to me. I can't release something to anyone else without fixing that.

      I have looked at several procedural ivy projects. Most of these have cards/quads for each leaf. I would say it's common in procgen for people to feel that adding multiple leaves per card only looks good when they can be hand placed. The trick to getting good performance is to only show what's visible, using proper LODs, which is actually easier than working with a full 3D model of the plant because it can be simplified and culled at the triangle level. The 3DWorld trees, ivy, new cat tails, etc. - all the vegetation in the city scene except for grass - add negligible frame time.

      Also, fun fact: 3DWorld started as a tree simulator back in my 2001 intro to graphics course, and won the best project award. The tree was generated in such detail that I could simulate leaves moving in the wind, snow interaction, and individual raindrops hitting, sliding down, and falling off leaves. I want to keep this detail because that's what differentiates it from all the other engines/games/tools. Very few projects that I'm aware of generate/simulate things to this level of detail.

      Delete
  6. Hi, I would like to know if this engine can be used to make actual full games? Like a 2010 AAA quality game. Because this looks very promising... especially considering that it's completely open source...

    ReplyDelete
    Replies
    1. Probably, but it wouldn't be easy. I don't have a scene editor and the engine isn't really separated from the game with a clean API. Plus there's no documentation. I assume someone could make a game with enough effort if they were willing to modify the code. I already have a first person shooter game, space combat game, and open world exploration system that could be turned into a game. They're built in/part of the code. Not AAA quality though.

      Delete