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 once 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 curves 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 keepout 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 applies 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.















































