Thursday, January 7, 2021

Helicopters

Finally, a post that's not about procedural building interiors! I added helicopters to 3DWorld's procedural cities environment. It started out simple, and I just kept adding to it like I did with cars. For some reason, I can't stop myself from adding more features. This one was 90% fun and only 10% frustration though as I was able to get most of the features to work pretty quickly.

At first I added helipads to the roofs of some of the larger/taller city buildings. But what good are helipads without helicopters? I had to add them next. I started with one 3D model, but that wasn't enough, I had to make it support multiple models and ended up with two of them. Stationary helicopters aren't so interesting, let me make them fly. They can't just fly anywhere though, they need to choose an unused destination helipad on some other building in some other city. Right, they also need to slowly take off and land, and rotate into the correct orientation in the process. Okay, so now helicopters fly around, that's pretty cool. But wait, they're colliding with buildings! Let me add collision avoidance for buildings and terrain by increasing their altitude to avoid these objects.

That's better, but they need to cast shadows on the buildings and ground. Soft shadows. They need to self-shadow themselves as well. Okay, it was a lot of work to make shadow updates efficient. Oh, wait, the blades need to rotate too. (Or is propeller the correct term here?) The shadows need to show the rotating blades. There, that's more like it. That took, what, only two weeks to add and a few hundred lines of code?

Here's a screenshot of my first helicopter model sitting on the helipad on a building roof.

Helicopter resting on a helipad, casting a soft shadow.

Fortunately, helicopters were much easier to add than cars. Cars took me months of working an hour a day at night to finally complete them. There are several factors that account for the difference. First, I already had all of the 3D model loading and drawing systems for cars that I could reuse. Second, there are only 30 helicopters compared to ~4,000 moving cars, which means I don't have to worry much about the performance of drawing them and running their AI. Third, helicopter fly in 3D, so it's a lot easier to make them avoid colliding with stuff. But most of all, they don't have to use traffic lights or worry about traffic congestion or running people over. I was able to avoid the madness of handling traffic jams.

Helicopter flying above the terrain and buildings.

 

Helicopters follow a three segment path where they rise vertically while rotating into position, travel in a straight constant elevation line until they reach the destination, then descend to the helipad below. Once they land they will wait 30-60s before taking off again for a new destination helipad. I should probably make them accelerate and decelerate eventually, but I'm not sure how noticeable that would be. Their max altitude is computed as the max of two values: a fixed distance above the higher elevation helipad (source or destination), and a clearance height above the highest elevation obstacle. I chose to use the terrain and buildings as obstacles to avoid. I'm ignoring trees and smaller objects for now, and just assuming my min vertical clearance value is large enough to avoid colliding with tall trees.

I'm computing max terrain height by walking along the path to the destination and checking each heightmap value along the way. Building max height is computed by ray casting through the buildings and calculating the highest point of each building that the ray intersects. Note that any ray passing completely under a building is guaranteed to hit the terrain, so I don't have to handle this case. This is pretty close, but doesn't account for a helicopter clipping the side of a building. I would have to use a projected cylinder or multiple rays to handle that case. The simplest fix for buildings was to expand building bounding cubes by something a bit larger than the radius of the helicopter. I haven't come up with a good way to handle clipping something like the edge of a cliff in the terrain. I've never seen this happen in practice, so I'm ignoring that situation for now.

One feature I didn't add (yet) is collision avoidance between two helicopters. I added simple bounding sphere-sphere collision detection code, but it seems like waiting until a collision has occurred before reacting is far too late. Maybe it's okay to allow two cars to slightly collide and then move them apart a bit so that they "just touch". It doesn't work that way with helicopters. For one, they can't swerve or slam on the breaks to quickly change direction like cars can. Then there's the danger factor of near collisions. Maybe it's okay to stop your car a few inches before hitting the car in front, and no one will even notice. That's believable in a traffic simulation. However, with helicopters, coming within inches of colliding with some other helicopter mid-air is not cool. You're not going to simply scratch your bumper or dent your license plate if you're a bit off. I can't simply move the two helicopters slightly so that they're not intersecting for that frame and expect it to look believable.

Okay then, how does it work? Collisions must be predicted and avoided ahead of time. In real life it's probably on a time scale of tens of seconds to a few minutes depending on speed. Maybe I can get away with a few seconds of motion prediction in my case. Still, helicopters move pretty quickly compared to cars, and they can cover a lot of distance in a few seconds. They would have to look ahead and predict the paths of potentially colliding helicopters out to a relatively large radius in order to correct their velocity in time. That would result in some form of cylinder-cylinder intersection algorithm, which isn't the easiest to get right.

There's also the question of which helicopter moves first so that they don't actively move into each other's path. Do you ever find yourself trying to avoid walking into someone in the hallway, so you move to the left? Then that person moves to their right, and you're still on a collision course? So you move right instead, and they move left? Yeah, that's what I'm talking about. That game of chicken would be no fun to play in real life helicopters. I have no idea how pilots actually handle that sort of thing.

Anyway, I'm sure I can solve this, but it's going to be somewhat complex. My solution is unlikely to work the first time. I'll have to try something, test it, fix it, and iterate until it works properly and is visually acceptable. Unfortunately, it seems to be nearly impossible to actually get two helicopters to collide. There are only 30 of them in something like 20 square miles of cities, and they have to collide in all three dimensions. Simply crossing each other's paths in the XY plane isn't good enough, they need to be at similar altitudes as well. Okay, maybe that's really a good thing. If helicopters are incredibly unlikely to collide, then maybe it will never actually happen and I can skip implementing collision avoidance completely? It's worth a try. It's one of those tasks where I probably won't add it until I witness a collision months from now.

Here's a screenshot showing two helicopters using two different models that are very close to each other. You can tell by the shadows they project on the road below that they're maybe 50 feet apart and headed somewhat towards each other. However, they're at different enough altitudes that they don't actually collide. The bottom of the helicopter on the right is about 20 feet above the top of the one on the left. This is the closest I've come to witnessing a mid-air collision. Sure, you wouldn't want to have your helicopters fly that close to each other in real life unless it's a stunt show, but that's not close enough to properly test collision avoidance. Also, good luck trying to capture this situation again. I don't remember how long I had to fly around before I found it.

Two helicopters nearly colliding mid-air. You can see their shadows cast on the road below.

The most time consuming step, by far, was making dynamic shadows from moving helicopters efficient. Everything else in 3DWorld's tiled terrain mode is static, which means shadow maps can be generated once and reused across frames. Yes, cars and people are dynamic, but I have no hope of ever having them all cast shadows while getting a reasonable framerate. I haven't even tried.

The big problem here is that this system was never intended to handle shadow updates every frame. The user won't even notice an extra 20ms generating shadows at startup, but adding 20ms to every frame would kill the framerate. There's a lot of stuff to draw into the shadow map: terrain, buildings, trees, parked cars, etc. - I mean, a real lot of objects. On top of that, there are many optimizations that are used for the main draw pass but haven't been implemented or enabled for the shadow pass:

  • Billboard drawing of objects such as trees are disabled because they make terrible looking shadows, so we draw the full model in the shadow pass.
  • Most objects don't have level-of-detail implemented or enabled for the shadow pass.
  • Occlusion culling has been disabled for shadows and wouldn't work well anyway when the sun is overhead because buildings don't tend to occlude too much of the city from that direction.
  • The shadow map is a higher resolution than the screen, which means more work for the rasterizer.

My first attempt at regenerating shadow maps every frame resulted in unplayable frame rates in the 20s or 30s. Then I realized I only had to regenerate shadow maps for regions around the helicopters. This brought the framerate up to around half of what it was without shadow updates, on average. A typical flyover would drop from 110 FPS to 55 FPS with shadows. I later realized that I could also skip updates when the shadow of the helicopter wasn't visible in the player's view frustum. I can get the shadow location by ray casting into the scene using the helicopter's center point and sun direction. The shadow will land on whatever point the ray intersects the terrain or a building. This helped in many situations, but the worst case when the player was looking down at the shadows was still 55 FPS.

I was able to slowly improve this by making various optimizations to specific types of objects that were drawn:

  • Tree low detail (LOD) drawing was broken (incorrectly disabled) for the shadow pass. This was easy to fix.
  • I was drawing every building in the shadow pass. Changing this code to iterate over building tiles and only drawing visible tiles (copy the code from the normal draw pass) helped a lot.
  • There was a performance bug where I was accidentally drawing traffic lights multiple times. The code was supposed to draw the set within each visible tile, but was drawing them all for each visible tile, for both the main and shadow pass.
  • I wasn't bothering to use view frustum culling of scenery objects such as plants and rocks. Enabling that helped a bit.

These changes together reduced the shadow pass frame time by nearly half, giving me ~71 FPS rather than ~55 FPS. This will help in other cases where the light source (sun or moon) moves as well, such as when the player changes the time-of-day or the dynamic day/night cycle is enabled by the user. The visual quality was nearly identical, and I improved non-shadow frame time for the main pass slightly in the process. That's the low hanging fruit, and good enough for now. I added a hotkey to enable and disable dynamic helicopter shadows to allow the user to select between quality and performance.

Here's a YouTube video of me flying around following helicopters as they cross the cities.


You may notice that the blades only rotate on one of the helicopter models. I had to split the model into two parts with the blades rotated differently from the model. This was accomplished by continuously increasing the rotation angle of the propeller while the helicopter was flying. I wanted to use smaller civilian helicopters, but most of the free 3D models I found online were of larger military helicopters. I'm not sure this type can land on typical rooftop helipads. The model I liked the most was in the 3DS file format. It's relatively easy to manually edit a text-based OBJ file model to identify and extract the propeller blades. It's not so easy to do that for a binary 3DS model where everything uses the same material. So for now only one of the two helicopters has rotating blades

I'm pretty happy with the state of helicopters for now. I may get back to them later and work on more shadow optimizations, add more models, improve collision avoidance, or add new types of destinations. I guess we'll have to wait and see.

Friday, January 1, 2021

Procedural Building Interiors: One More Pass?

I'm trying to finish up all the remaining additions I plan to make to procedural building interiors before I move on to other city items. This includes a few new objects and room types.

Interior Objects 

I've added quite a few new object types to building interiors: microwaves, computers, stacks of papers, boxes, horizontal and vertical window blinds, clothes washers, clothes dryers, paint cans, and rooftop solar panels. (Technically, solar panels aren't interior objects, but they're close enough.)

First, I added microwave ovens to some kitchen counters as gray cubes with a microwave texture on the front. This was another one of my daughter's suggestions. A random counter and position are selected, and the microwave is placed at that location if it doesn't intersect any other objects on the counter. I suppose that completes the kitchen, for now. Here's an example screenshot.

This kitchen has a microwave oven in the corner. That completes the basic kitchen setup: table, stove, refrigerator, sink, and microwave.

Up to this point, some desks had computer monitors and keyboards on them, but there was no computer. Fortunately, it's easy to create a simple computer tower by pasting a computer front panel image onto the front of a gray-black cube. Yes, that looks pretty close to the case of the computer I'm using to write 3DWorld and this blog post. I can place these computers under the desk off to one side so that they don't block the chair and can't intersect with anything else placed on top of the desk. Now the office computer desks look more complete.

A computer tower has been placed under desks with monitors, to go with the keyboards.

Some desks have computers, some have books, and others are empty. Empty desks don't look too interesting. Let's add a messy pile of papers on top of some of them. I decided on a mix of mostly white/cream paper with a few yellow pages mixed in. They can be rotated at odd angles and may overlap each other. Objects such as books and bottles can be placed over them. So far I haven't attempted to add any text to these papers; that seems like a major effort that can be done later when I work up the courage to attempt it.

Some desks have a collection of papers scattered on them. The papers are currently colored but blank.

As a final addition to house interiors, I added a chance of placing some boxes on the floor of some room types. Living rooms, dining rooms, bedrooms, and offices can have one or two boxes, while storage rooms can have up to eight boxes. Now I can populate home offices with computers, books, stacks of papers, bottles, and boxes. Here's a representative screenshot of a low-tech (computer-less) home office.

Example home office with a book, papers, bookcase, boxes on the floor, and ... bottle of beer.

Blinds

The next big topic to address was window coverings, specifically for bedroom windows. I originally wanted to add cloth curtains, but I couldn't quite figure out how to add the material with its smooth wavy curves in an efficient way. There are dozens of windows in each house, and I can't add thousands of polygons for every one. So instead I decided to add straight segmented horizontal blinds similar to the ones I have on some of the windows of my house. It was easy to find a free blinds texture with normal map online. Then I had to apply that to a single cube for each blind, which is very cheap to add to every bedroom window.

I used my random number generator to make some rooms have open blinds and others have closed blinds. Then I added a bit of extra randomness per-window so that they were at slightly different heights, which looked more natural. Most blinds are left open to match the previous look of the rooms. Note that open vs. closed blinds don't actually affect the amount of indirect sun/moon/sky light that contributes to the ambient lighting of the room. Here's an example of a room with blinds that are mostly closed.

A bedroom with horizontal blinds on the windows, mostly closed.

Here is a different bedroom with open blinds. You can see that the texture is squished into the top part of the window such that the total number of slats is the same whether the blinds are open or closed. This is to simulate the rotation of the slats as the cords are used to open and close them. I might have to go back and try to find a better solution to the texture aliasing problem when the slats are too close together. I really like the pattern the normal map makes with the lamp light source in this next image.

The lamp makes an interesting pattern of light on the open horizontal blinds of this bedroom.

I can also swap the direction and create vertical blinds for some of the bedrooms. These are a bit different because they start at either side of the window and close in to the center, rather than hanging/extending from the top. Here is how mostly-closed vertical blinds look in the same room as the previous screenshot. The camera is further away from the lamp, near the back wall.

Vertical blinds in a different bedroom, one partially opened and two closed.

That's pretty good for now. I might still add curtains at some point, or maybe add more textures and variety to blinds. I still need to add cloth material to the tops and sides of canopy beds as well.

 

Storage Rooms

Now we move on to office buildings, in particular storage rooms. Since these rooms are only added to the ground floor of larger buildings, there tend to only be a few of them per office building compared to the hundreds of office rooms. Therefore I can add more storage room items than I can get away with for offices. I wanted to make sure I had enough item variety to make these rooms look interesting.

I showed storage room crates, shelves, and bottles in the previous post. Since then I've added cardboard boxes, computers, keyboards, and paint cans. These are all typical items you would find on office building storage room shelves. Computers and keyboards were easy to add because I already had them placed on/under office desks. Cardboard boxes are simple cubes, while paint cans are single cylinders. These shapes are easy to work with, but the process of finding appropriate free textures online was a lot more work. This is especially true for boxes, where I had to manually separate out the top, bottom, and side textures from a texture atlas in an image editor. Here is the end result: a room full of shelves stocked with lots of different items.

This storage room has boxes, two different crate types, and a chair on the floor. It also has boxes, crates, bottles, computers, keyboards, and paint cans packed onto shelves against the walls.

Note that none of the items on shelves are actually stored in memory on the CPU side, and don't have collision detection enabled. The player and "building people" collide with the shelf bounding cubes themselves and can't get onto the shelves to collide with individual items. Also, storage rooms don't have windows, so I don't have to worry about the player viewing their interiors from outside the building. The shelf contents are generated on-the-fly when the player enters the building, converted to raw vertex data, and sent to the GPU. I can add quite a few items because only a few storage rooms are in GPU memory at any given time.

 

Solar Panels

The next addition takes us outside the building and onto the roof. I've added rooftop solar panels to some of the houses. The most complex part of this task was deciding which roof section was the best place to add the panel. The hard requirements are that the panel can't be shadowed by another part of the house and can't intersect any other part of the roof, including the chimney. I excluded smaller roof sections such as garage/shed roofs and porch roofs as well as any triangular roof polygons. This left the larger rectangular areas as valid placements. I ordered them by height so that the upper roof sections (which can't be shadowed by another part of the house) have a higher priority. Then I iterated over them, shrunk them by a fixed amount, and checked for intersections with another part of the house. After numerous iterations, I finally got it working. Here is the result.

Solar panels have been placed on the roofs of some houses in the optimal locations.

I did run into some problems with z-fighting (flickering) when viewing solar panels of distant houses because they were too thin, which placed them too close to the roof. I made them thicker until the problem went away. But now the player can sometimes see the inside surface of the panel when standing below the house and looking up at the roof. I guess the best solution is to draw the sides as well as the top surface quad, making the panels properly 3D. I suppose that looks better overall.

Rooftop solar panels look more 3D when I add their sides.

That's all of the interior objects I had on my list, other than the washer and dryer mentioned in the section below. I think that's pretty good for building interiors. I want to go back and work on cities next, possibly fire hydrants, street signs, etc.


Room Types

I have most of the common house rooms assigned now: bedrooms, bathrooms, kitchens, living rooms, dining rooms, offices/studies, and hallways. I even have a few special room types such as libraries, entryways, and rooms containing only stairs that I call "stairs". And of course there are garages and sheds. The only two common rooms that I'm missing are laundry rooms and storage rooms, so I've added these. All of the currently unassigned rooms in houses are assigned to one of these. The first unassigned ground floor room is made into the laundry room, and any remaining rooms are assigned to storage. Since most houses don't have unassigned rooms at this point, the majority have no dedicated laundry or storage rooms. If I want to change that I have to go back and re-assign some rooms, most likely smaller sized bedrooms.

House storage rooms are filled with boxes and possibly some of the other items found in office building storage rooms. They're usually very small; otherwise a bed, table, or desk would have been placed in them and they would have been made into some other room type by this point.

Laundry rooms have a washer and dryer, assuming both will fit. If only one can fit, then it's the washer. The placer prefers to place both against the same wall if possible. I wasn't able to find any free matching washer and dryer 3D models online, so I had to use a mismatched pair. Here's an example of a laundry room.

A laundry room with (mismatched) clothes washer and dryer.

Next up is helicopters. This post is already pretty long, and helicopters aren't related to building interiors, so I'll save that topic for next time.

Tuesday, November 17, 2020

Procedural Buildings: Improvements Based on Feedback

Yes, I'm still working on procedural building interiors. It seems like there are an infinite number of things to add and fix. This set of improvements to interiors is mostly the result of feedback and suggestions from my nine year old daughter and Paul Spooner. A few of these were old TODO items as well that I finally got a chance to work on. I'll list them here in the approximate order I that implemented them, along with lots of screenshots.

Lamps with Ambient Lighting

I didn't really like the look of the dark walls next to the lamps in the screenshots of my previous post. There should be some diffuse light passing through the lampshade to reach the wall, and also indirect light reflected from the ceiling and top of the dresser or nightstand. The easiest fix was to add a small radius unshadowed light source inside the lampshade and hope that it was small enough that it didn't bleed through the wall onto objects in the adjacent room. Here is a screenshot of that third light source. It looks a bit odd due to the lack of a smooth transition between the top/middle/bottom lights, but I guess it's better than having no middle light.

Lamps now have a small unshadowed light source inside the lampshade that makes it appear to diffusely illuminate the wall around the lamp.

Bed Posts and Canopy Beds

So far my beds have used simple and plain geometry, with single cube bottom, legs, headboard, and foot board. I decided to add bed posts to some of them. Bed posts are randomly selected to be either square or cylindrical. Here is a bed with square posts.

Bed with square bed posts and a lamp on a nightstand next to it.

I took this a bit further and added top bars to some of the beds with posts, making them into canopy beds. So far I haven't added any canopy material, so they're bare wood. Cloth for things like canopies or window curtains is difficult to add. There are two problems. First, what geometry do I use? Cubes won't work here. This cloth needs to be curved into folds to make it look realistic, which would require a large number of triangles. Maybe that's okay for canopies, but there are far too many windows in a house to add high polygon count curtains to each one.

The second problem is that real cloth transmits some amount of light. It's not quite the same as making the material semi-transparent with an alpha value between 0.0 and 1.0. Instead, they need some sort of two-sided lighting model that includes transmission through the back/lit side, similar to tree leaves. This may require a custom shader. I'll have to get back to this problem later. For now, the canopy beds have no canopy and the windows have no curtains.

A large canopy bed that casts interesting shadows. I haven't added the canopy cloth/material yet.

Storage Room Shelves with Boxes

Storage rooms still looked a bit empty to me. I needed to add objects to the shelves, but what? I guess the only objects I currently have that are intended for storage rooms are the same wooden crates I placed on the floors. I decided to add some smaller crates randomly placed on the shelves. Some shelves have none, and others are full of them. I also added an occasional random office chair in the corner of the room (not pictured below).

Storage room with shelves on all walls and crates on the shelves and floor.

I would like to add cardboard boxes, but so far I haven't been able to create a convincing cardboard material. The don't really look like proper boxes if I make them a uniform tan color. I tried to find good box textures online, ones that had various box-like symbols, text, tape, etc. The problem here is that boxes generally don't look right if I use the same texture on all six cube sides. Do I need three different textures, one for the top and bottom, one for the sides, and another for the ends? Maybe, it's tough to justify adding three new textures and three new materials just for cardboard boxes. Maybe adding plastic storage bins would be easier.

I could also just add random room objects to the shelves: books, lamps, bottles (see below), pieces of wood, etc. I don't know how that would look. None of those are really items you see stacked on storage room shelves. Do I need to create several new objects that only appear on shelves in storage rooms?

Walled Stairs with Railings

Most office buildings have enclosed stairs. I hear that's related to fire safety, which I suppose makes sense. I changed my office building stairs so that they at least have walls on the sides. Walls are skipped when the stairs are up against an existing exterior wall of the building. There's still no stairwell door, but this is an improvement. Railings are connected to the walls of the stairs and continue to have vertical support poles when they fit. U-shaped stairs don't have vertical poles due to the complexity of adding them at the correct height. Here is an example of office stairs in a main hallway leading down to an open elevator. The dark spot on the ground is my shadow.

Office building walled stairs with railings. There's an open elevator at the bottom of the stairs.

Computer Keyboards

I added computer monitors to some desks a few weeks ago using the same 3D model as the TV. That's good, but monitors aren't usually placed by themselves (except for in the focus rooms where I work). Monitors are usually connected to a computer, and the computer is often connected to a keyboard and mouse. Keyboards are easy to draw, so I added them first. There are no cords, computers, or mice yet.

Desks with computer monitors now also have a computer keyboard. (That Windows desktop is a screenshot of my home computer's desktop.)

Maybe someone was using the monitor and keyboard with a laptop that's no longer on the desk. ... In that case, then why is the monitor showing a desktop? I don't know, I suppose the computer could be hidden behind the desk somewhere. I'll have to add one under the desk later. Maybe then I can add some desktop computers and keyboards to those storage shelves. I may even be able to get away with making them a single textured cube so that they're basically free to draw.

Colored House Walls

Paul pointed out that the contrast between building walls and trim is too low. The easiest way to fix that is to change the wall color, the trim color, or both. I've left some houses with white walls, but made others have light blue, green, pink, and peach colored walls. Some of these are shown in the screenshots below.

Railings with Balusters and Stair Overhangs

My railings and stairs were looking a bit plain. I was originally worried about having too many triangles for detailed stairs, so I tried to keep them minimal. I've made a lot of optimizations since then, including adding a class of "small" objects and materials that are only generated and drawn when the player is close to the building. This is intended for high detail objects with curved surfaces that can't easily be seen from a distance. This form of level-of-detail is what allows me to add so many different objects to thousands of buildings and still draw everything in realtime.

I split stairs into top and bottom cube parts, where the top is slightly darker in color and overhangs a bit. This makes it easier to tell where the tops of the individual steps are. I also added vertical cylinders for railing balusters to keep the little kids (or careless adults) from falling off the sides of the stairs. I placed one on each stair, which looks pretty good. I put another cylinder along the bottoms so that I can skip adding end cap circles to each baluster. These railings work as proper collision objects for the player as well, to avoid the frustration of unintentionally falling all the way to the bottom of the stairs when making a wrong move trying to reach the top floor.

Stairs with railings, balusters, and overhanging steps in a room with light green walls.
 

Dark Trim for Office Buildings 

I wanted to keep office building walls white because that's the most common color. My real life office (which I haven't been to since March) does have some colored walls, but they're mostly white. Instead of changing the wall colors to get good contrast, I'll change the trim color to dark gray. There, that looks much better.

Office buildings now have dark gray trim to contrast with the light colored walls, ceilings, and floors.

Maybe I should add trim along the ceilings as well like I have in houses? I haven't quite gotten the angled ceiling trim to meet properly at outside corners of hallways. You can see this on the corners of closets in houses as well, though it's difficult to spot. I think for now I'll add ceiling trim to smaller office buildings, but leave it out of larger buildings with intersecting hallways. That should help with polygon count as well since the largest buildings can have over a thousand rooms.

Reception Desks in Office Lobbies

Most large office buildings don't have their front doors open to a long hallway with rooms on each side. The doors open to a lobby instead. What better object to place in the lobby than a nice big reception desk. I can put one at each end of the hall/lobby to handle both doors.

I originally wanted to use the desk model I created in my earlier (hand made, not procedural) office building scene with the rounded corners created from cylinders. I attempted that, but I couldn't get the textures to line up using the texture coordinate system I implemented for building interiors. There's just no easy/automatic way to remove texture seams from the intersection of a cube and a cylinder. Oh well, I guess I'll just have to make the top surface out of cubes and settle for square corners for now.

An office building lobby reception desk.
 

Office Building Libraries

3DWorld already places libraries with up to eight bookcases on an upper floor of some large houses. I can reuse that code to turn offices into small libraries for office buildings as well. I need to make sure to limit the number of libraries, because all these books really add up for thousand room office buildings. This was an easy change and adds some variety to offices. Here's an example with a rectangular table. Some libraries have round tables.

A library inside an office building with many bookcases and a rectangular table in the center.

Bathroom Showers

My daughter asked me why there are no showers in the bathrooms of houses. That's a great suggestion! I can create a simple shower out of mostly cubes. I found a nice bathroom tile texture to use and got to work. My showers are always placed in an empty corner of the bathroom against two interior (windowless) walls. The back and floor are made of tile, with a shiny metal frame and two transparent glass panes on the outside. One of these panes is the door. One of the walls has the shower head, and there's a drain in the tile floor. That's a minimal design, but it works well enough.

A shower placed in the corner of a bathroom with light blue walls.

It took some effort to update the material drawing system so that I could draw transparent objects last, which is required for proper alpha blending. I did eventually get everything to work properly, even when viewed through a mirror.

This shower has an additional door handle, which I later realized was missing in the previous version.

A shower (with door handle added) placed in the corner of a bathroom with peach walls.

I also used that same tile texture for kitchen counter backsplashes. I only added these to interior walls because the windows on exterior walls are large enough that there's no exposed wall space for a backsplash.

Kitchen counter with a tile backsplash added to non-window wall sections.

Improved Light Culling

I was having lots of problems with room lighting, both fragment shader performance issues and light leaking though walls. I already have a system in place that performs ray casts in various directions to determine the bounds of each room light. This allowed me to do some light culling on the CPU side. Rays that hit room walls mean the light is confined to the room containing it, while rays that exit through doorways allow the light to influence other rooms. This lets me to use large light sources while constraining their areas of influence to only a few rooms. I store the computed bounding cube of each light in a texture and use it inside the fragment shader for light source culling.

It took me a few months to finally add fragment shader support for this. I was originally worried it would be very complex to pull this off, and would hurt framerate. In reality it only took about two hours, worked the first time, and actually improved draw time. I guess the ability to early exit out of lighting computations more than offsets the dynamic branching cost in the fragment shader.

This change allowed me to increase the radius/brightness and angle of upward pointing, unshadowed room lights without having to worry about light bleeding through walls into adjacent rooms. You can compare the two shower screenshots above and see that the lower one has more light on the upper walls. I was then able to go and re-tweak other lighting parameters to remove some of the annoying light leaking and unwanted shadows. This really makes a big difference when moving around the city as well because there is far less "popping" from shadow maps being switched on and off.

Bottles and Wine Racks

Paul suggested I add wine racks. I'm not sure how common wine racks are, and I definitely think I'm missing some other household objects that are far more common. I do have a very small wine rack built into a kitchen counter in my house. I guess I can add wine racks, but first I have to add wine bottles. Fortunately, a bottle is pretty easy to make by gluing spheres and cylinders together. I already have support for this. Here is a green bottle with a white cap and label. It's not a wine bottle, but that's fine, I can add other types of bottles on top of tables, desks, and kitchen counters.

A bottle of Sprite, 7Up, or maybe beer on a table.

Once I had a bottle, I could combine those with a modified bookcase to get a wine rack. Real wine racks normally are angled or have extra pieces so that the bottles can't roll around, but this is good enough for now. Maybe I can reuse this geometry for storage cubes later. I added wine racks to 75% of dining rooms, at most one per house. Here is one example.

A wine rack full of wine placed in a dining room with light green walls.

Wait! I can add these bottles to my storage room shelves as well. Now they sort of look like supermarket shelves where customers put their returned items back in the wrong place, or shelves in a recycling center. Or maybe the janitor is an alcoholic. Or the employees had a party down here.

Bottles next to boxes on storage room shelves. This room also has a spare chair against the wall.

Sunday, October 18, 2020

Adding Content to Procedural Buildings

Now that mirrors have been completed, it's time to get back to adding content to the interior and exterior of buildings. Maybe after I've added enough object variety these will start looking like real buildings.

First up, I've added fences around the yards of some houses. These are very simple wooden fences with vertical posts and two horizontal slats. Some go around only one side of the house, and others enclose a section of the yard with a gap for a gate. Fences are only added to L-shaped houses that have an area of yard interior to their bounding cubes.

House with a simple wooden fence around the front yard, and an opening where a gate should be.

That's it for exteriors. Moving onto interiors, some first floor windowless rooms of office buildings have been made into storage rooms containing stacks of crates and boxes. I would like to add some sort of shelves on the walls, but I haven't gotten to that yet.

Storage room with no windows and two different types of crates stacked up along the walls and back.

While writing this post I decided that shelves should be easy to add, so here they are. Some walls have 2-4 shelves stacked vertically and held up by regularly spaced metal brackets. Here is an example of a storage room with 4 stacked shelves along three walls. That looks much more like a storage room to me. Now I just need to figure out what objects to add to the shelves. More crates/boxes?

Storage room containing various crates and empty shelves along three walls.

I've updated office swivel chairs to a model that has textures and is more interesting than the uniform black color of the previous chair model. In addition, I replaced some of the simple cube chairs with these chair models for desks that have computer monitors in houses.

A cubicle with the new office chair 3D model, complete with textures.

Office chair at an office desk and a computer monitor that's turned on.

I went back and made a second attempt at potted plants. The pots had looked good, but I wasn't able to get the alpha blending to work on the leaves. This time I switch to a second (more expensive) shader that has alpha testing and alpha blending enabled. Now the plant leaves are drawn properly. Pots come in a variety of different colors, and there are five different plant/leaf textures. One or two potted plants can be added to a living room, and one plant can be added to some other room types such as dining rooms.

A potted plant in the corner of a room. Plants come in various sizes, aspect ratios, pot colors, and leaf textures.

Now that bedrooms have dressers and nightstands, I can place smaller objects on top of these. What objects should I add? Well, I have lamps on some of the dressers and nightstands in the bedrooms of my real life house. Lamps are a good place to start.

A lamp has been placed on the nightstand to the left in this bedroom.

I haven't made the lamp a light source yet. Light should be partially transmitted through the lampshade, which isn't something I can currently do. I suppose if I wanted to attempt this, I would need two shadow maps, one pointed up and the other pointed down. ... Okay, I can't resist. I added those two light sources for lamps. There's no indirect lighting, and no transmission through the lampshade, but the shadows on the ceiling look too good not to enable this. I also managed to get the light to cast a faint upward unshadowed area to sort of simulate light passing through the lampshade.

A lamp placed in an otherwise dark room at night, with shadow maps, shining on the ceiling and floor.

Two lamps can be see turned on in adjacent rooms at night.

I think it would look better if the lampshade was lit, but I'm not sure how to actually do that. Use two different lamp models, one with an emissive lampshade for the lamps that are turned on? How about just setting the emissive color before drawing the lamp if it's turned on, and reset it after the draw? Yes, that last one seems to work. Here is a new lamp screenshot with an approximation to diffuse light transmission through the lampshade.

Now the lampshade is emissive when the lamp is turned on.

Monday, October 5, 2020

Mirror Reflections and Player Models Gone Wrong

This is just a short post to show off some of the interesting and amusing mistakes I made while working on the previous mirror reflection post. Well, they're not all mistakes, a few were just experiments I decided to do.

Here is a screenshot that's similar to what I saw when disabling (or, um, incorrectly implementing) the reflection operation. The mirror is an image of the player's view of the mirror itself. Since it's updated in realtime, each frame takes another image of the previous image, zoomed out a bit based on the camera distance from the mirror. This stacks up some number of reflection images on top of each other, which will converge to something like this when the player stops moving.

The original bug looked a bit different from this where the mirror was a smaller recursive zoomed in copy of the mirror itself where the mirror-in-mirror converged to a tiny point in the middle of the screen. Unfortunately, I never saved a screenshot of the original problem and I can't seem to get the code back into a state that looks like that. Like this.

Here is what happens when I make the same "mistake" but also draw the player model in the reflection image. Rather than the mirror, we see the inside of the player model's head, since that's where the "camera" is. It looks like someone stretched the skin from a man's head over a canvas and hung it on the wall like a picture. I have no idea what's up with the eyes.


Did you ever try to look at something on your face very closely in a mirror, and you lean in so close that the front of your face goes through the mirror's surface and you're looking through your head? Yeah, I hate it when that happens to me.


Yeah, I know, that's not realistic. You can't see through your face when looking closely into a mirror! In the real world, ... there's no back face culling. You'll see inside your head instead, like this:


If I move a bit closer so that my entire face is through the mirror plane, all I can see is the back of my head. Yeah, that's right. That's the inside surface of my hat. No brain in there!


Okay, what if I draw the player model even when reflections aren't enabled? Did you ever wonder what it would look like if you had only one eye? And it was inside the center of your head rather than on the surface? And your head was empty? Well, wonder no more, here is what it would look like:

That kind of looks like the inside view of an oversized rubber Halloween mask. Except there are no eye holes cut out. Is that how people feel when inside those big mascot costumes? I tried to remove the eyes so that you can see through the eye holes, but unfortunately the face model is a single mesh using a single texture. That's a shame, it could have made a fun gameplay mode to only allow the player to see through tiny eye holes. Who needs peripheral vision, right?

Friday, October 2, 2020

Building Mirror Reflections

This time my post is not about objects I've added to building interiors. Well, technically that's not true. I have added mirrors to office and house bathrooms, but most of the content isn't about mirror placement because that was a trivial task. This post is about how I implemented mirror reflections, which also required me to finally add a player model.

I started by copying parts of my existing water plane reflection code. This was all oriented in the Z (up) direction, while I needed mirrors oriented in X or Y to place on walls. That required changing all of my code for reflection matrices and the camera frustum to work with different reflection directions, but fortunately I only had to handle the axis aligned cases.

Then I had to deal with the mirror changing the winding direction of all polygons in the scene, which affects the front vs. back face culling tests. I had to hunt down various face direction tests in the building drawing code and invert them in the mirror reflection case. I also had to fix problems with polygon bias direction in some of the passes. At first I didn't quite understand what was going on and fixed it using trial-and-error to flip signs.

The next task was to avoid drawing the other side of the wall behind the mirror. Mirror reflections work by moving the camera to the other side of the mirror and negating the view direction. This effectively puts the reflection camera outside the bathroom looking in through the back side of the mirror. But there's a wall behind the mirror, so the camera will be looking at the other side of the wall! I had to add a custom clip plane at the mirror surface to remove everything behind it, which would have been in front of the back of the mirror for the reflected camera.

Finally, I had to handle the case where multiple mirrors were visible in the same office building bathroom. This produced a reflection of the opposite mirror, which didn't work correctly because the opposite mirror wasn't drawn as it was outside of the player's view frustum. After various failed attempts, I decided to simply limit bathrooms to having a single mirror when reflections were enabled. That's good enough for now.

After all these steps (and more), I finally had it working:

Realtime reflection in a mirror in the men's room.

Unfortunately, it was pretty slow. My first implementation nearly doubled the frame time when a mirror was visible. Fixing this required some combination of selective drawing, object occlusion culling, screen space stencil testing, and visible surface determination. At first I drew the reflection at half screen resolution, which helped. That's why you can see a bit of blurriness if you fullscreen these images.

One of the easiest optimizations was to check if the mirror was in a windowless room, and if so, only draw the building the player is in. None of the other buildings can be seen (unless a window is visible across the hallway, in the reflection of the doorway). Some of the other outdoor scene content can similarly be ignored. This was easy to do, but didn't help very much because I was limited by the fragment shader lighting calculations rather than the vertex shader or OpenGL driver overhead.

Next, I had to determine when a mirror was visible to the player. If it's not visible then there's no reason to create the reflection image. First, it must be within the camera's view frustum. Then I added a check if the player was actually inside the room containing the mirror. That almost worked, except for the case where the player was in the adjacent hallway looking in through an open door. So I included the connected hallway in the set of rooms for which the mirror needs to be drawn. But the hallway could run the entire length of the building, while the mirror is only visible for a small subset of the hall. I added a check for distance to the room/doorway based on the room size and hallway width. That mostly worked, but there were still a few situations when it either drew the mirror unnecessarily, or failed to draw it when some part was visible. My final solution was to trace a number of horizontal rays from the camera to points on the mirror's surface and skip drawing it only if all rays hit a building interior wall. That solution worked pretty well, so I left it at that.

The last optimization was to render only the interior part of the mirror rather than the entire screen. When the player is relatively far from the mirror, this makes a considerable difference. The mirror only occupies a small fraction of the pixels in the images above and below. I used a stencil test for this. The mirror was drawn in the stencil buffer, and the downstream room draw calls all tested the stencil buffer for each pixel/fragment. That optimization was surprisingly simple and effective compared to the ray intersection optimization. With that change in place, I was able to go back to rendering the reflection at full screen resolution.

I also finally added a model for the player. I used whatever human person model is specified first in the config text file. It happens to be the model of an old man. If you look closely, he (we the player?) even casts a shadow on the wall behind him. The player now always casts a proper shadow, even if not in a room with a mirror. The legs of the shadows are even animated when the player walks.

A reflection of the player model in a bathroom mirror that also casts a shadow. Oops, I think this is the women's room because there are no urinals!

But really the player model can be anything specified in the config file. For example, it can be a car. Here I had to fly up toward the ceiling to show off my new "player" model because it's so low to the ground.

The player as a car, reflected in the bathroom mirror. Who says a car can't use the bathroom?

What other fun model can I use? Let me see. How about this one. Can you guess what it is from only its shadow cast on the bathroom floor?

The player's shadow. What do you suppose the player is?

I'm sure you guessed it was a toilet. What else were you expecting, right? After all, we are in a bathroom.

That's okay, I'm beautiful on the inside ... as long as you flush me first.

Next, I added mirrors to bathrooms in houses. This was more difficult because these bathrooms usually have windows that can be seen in the mirror. That means I have to draw the outdoor part of the scene with terrain, vegetation, exteriors of other buildings, etc. This was challenging because I was creating the reflection texture from within the building drawing code by clearing the depth and color buffers before drawing buildings. Some of the outdoor objects that had previously been drawn into the frame buffer were cleared in the process. It wasn't as easy as drawing the reflection to a different buffer (FBO or PBO) because I needed a color buffer, depth buffer, and stencil buffer. I also couldn't easily move the reflection creation to somewhere else in the drawing control flow because it depended on the room lights setup code at the top of the draw function. So far I haven't come up with a good solution to this problem.

In addition, my logic to swap front vs. back faces interfered with the multiple rendering passes used to cut windows and doors into exterior walls, so I had to go back and rework that. Eventually I was able to get some of this to work. Here is a screenshot of a small mirror above the sink in a house bathroom. This one was drawn at full screen resolution.

Small mirror above the bathroom sink. It's on a medicine cabinet and spaced somewhat away from the wall, though it's hard to tell from this angle.

I have 3DWorld drawing window cutouts of the current building so that the exteriors and windows of other buildings can be seen through them in mirror reflections. I don't have the interiors of other buildings or terrain, vegetation, or roads working yet. I'm not sure how to do that in a clean and efficient way. Moving the reflection texture creation out of the building drawing loop and higher in the control flow would partially fix this, but it may be more complexity than I'm willing to add at this time. For now we'll have to avoid looking into the mirror in such a way that an exterior window can be seen.

Also note that bathroom mirrors are only added to interior walls to avoid having them overlap windows cut into exterior walls. I could check for window intersections like I do with some other room objects, but I believe most of them would intersect. This probably wouldn't be worth the trouble. Maybe I should also avoid putting mirrors on walls opposite exterior walls to work around the other problems.