Wednesday, January 22, 2020

Procedural City: Building Interiors and Lighting

I took a break from 3DWorld development in the end of December and beginning of January, but now I'm back working on procedural cities. Previous code was buggy, inefficient, and messy. I went back in and fixed many problems, mostly on the graphics/drawing side. I also cleaned up the code and added various optimizations to get the frame rate back above 80 FPS even with all of the building lights. Here is a list of some of the changes:
  • Player collision detection with building interior geometry (walls, floor, stairs, furniture)
  • Exterior building doors now open to allow the player to enter and exit (no more flying/walking through walls)
  • Tweaking of furniture and stairs placement to guarantee all buildings rooms are connected and walkable
  • Added a player flashlight, which works both inside and outside of buildings
  • Players can now look out a window, through a courtyard, and into another part of the same building (inside => outside => inside transition)
  • Multiple light shapes (rectangle, cylinder) and colors (white, blue-white, yellow-white)
  • Improvement to both the quality and performance of interior room lights
  • Improved elevator geometry

Window Drawing

Let me being with the inside => outside => inside transition. This is the case where the player is, for example, looking out a window of an L-shaped building and into another window on the other part of the L. This would seem to be simple, but remember that windows are actually drawn as alpha mask cuts in building walls by the fragment shader. They're not real geometry. I had to figure out how to remove both the interior wall of the current building section and the exterior wall of the other part of the building based on their respective set of windows. I need to have two different sets of window masks, one for the inside walls and another for the outside walls.

I can't use the depth buffer trick that I was originally using because that only works with a single mask (since there's only one depth buffer). I also can't easily use a stencil test because that would require two stencil buffers. So instead I had to use the depth buffer for the closer window and stencil testing for the further window. This required adding even more draw passes for buildings:
  • Shadow map passes (for shadows)
  • Depth pre-pass for building interiors (optional optimization)
  • Interior building geometry
  • Stencil buffer write for nearby windows (back face of building exteriors)
  • Interior building walls of front/near part, using stencil test to cut out windows
  • Interior building walls of back/far part with no stencil test
  • Doors in proper opened/closed state
  • Depth pass for distant windows (front face of building exteriors)
  • Building exterior geometry
That's, what, 9 draw passes? But it works! The most difficult part is splitting up building walls into front/near parts and back/far parts. I can't easily use the vertex data that was sent to the GPU and stored in VBOs for multiple reasons. The front and back parts aren't necessarily stored in contiguous blocks, and individual walls (quads) may need to be split. I ended up excluding the current building's exterior wall geometry from drawing, which required tracking the offset of each building inside the VBO data. Then I split the building into front and back parts based on the player's position on the CPU each frame. This is only one building, so it's not a lot of data, but the code is very complex.

I eventually got it working. Here is what you see when looking out the window of this U-shaped building. It looks pretty simple but this same view looked completely wrong before these changes. There was either no window or the window looked completely through the other part of the building.

Looking out a window, through the courtyard, and into another part of the same building.

I'll state it again that building windows are added with shader magic, not using geometry. This is an optimization, but makes it much more difficult to control window appearance and alignment. (I can't even count how many times I had to go back and tweak something related to window alignment.) It also makes viewing building interiors very complex. However, I can increase the number of windows to an insane value with no performance impact. Here are some buildings with 10x the number of windows in each dimension (100x the total).

Building with 10x window density viewed from the outside to demonstrate no per-window draw overhead.

Note that the number of floors and rooms has remained the same. Only the number of windows has changed. Each room now has walls full of tiny windows. Shown from inside a building:

Building with 10x window density shown from the inside. It would be possible to make a fine mesh screen.

Exterior walls currently have zero thickness. This allows me to use the same geometry for the interior and exterior sides of walls. However, it looks a bit odd when viewed up close because the window frame is missing. The problem is that there's no easy way to draw a frame. Where does the geometry that goes around the edges of the window come from? It's not in the vertex data, and the shader that adds the window cutouts can't add additional geometry. This is currently an unsolved problem.


New Features

I've added various new features to improve the look of building interiors. For example, elevators now have doors that are sometimes closed.

This elevator door is closed. Some of them are open. Eventually they may be dynamic.

I haven't figured out how to make working elevators that the player can use yet. Eventually I would like to add elevator buttons and have the doors open so that the player can enter and exit on a different floor. I don't think it's particularly difficult; after all, I do have a working elevator in my other office building scene. That one was manually created. I just haven't gotten to this yet.

Houses now have cylindrical room lights, compared to the rectangular lights used in office buildings. In addition, room lights can have different colors. Office buildings are more blue colored for fluorescent lights. Houses have yellow tinted incandescent bulbs. Other buildings continue to have white lights. This adds a bit of variety to the buildings.

Room of a house with a yellow tinted cylindrical light that casts shadows.

I've added a flashlight that the player can use. It's very similar to the game mode flashlight I've shown before and uses much of the same code. Using it requires changing scene lighting parameters, so I haven't quite got this working well yet. Here is an image showing the flashlight in use.

The player can switch a flashlight on and off both inside and outside of buildings.

Update: I fixed it. Now there are two light models with different falloff, one for the flashlight and one for room lights, streetlights, and car headlights. Here's an updated screenshot of the same location showing a flashlight with a tighter beam and sharper falloff. This time it's at night!

Flashlight used at night with a tighter beam radius.

Player collision detection with interior building geometry is now working. This includes interior and exterior walls, floors, stairs, tables, and chairs. The player can enter and exit a building through doors and walk up and down stairs. Each room and each floor should be traversable. I haven't included collision detection for interior doors because they tend to get in the way and block access to rooms when open. The system is set up to make it easy to add new object types that include collision detection.

I also allowed the player to enter and leave buildings through exterior doors. The doors don't technically open, but they do turn transparent so that only the frame is drawn. It's not that difficult to animate them, that's just another detail I haven't gotten to. Exterior doors allow the player to pass through doorways but not walls or windows. You no longer need to disable collision detection to enter and exit buildings.

This building's front door is open! Doors automatically "open" when the player is near (where "open" means they're drawn as transparent) so that the player can walk through. The door frame is still shown.

Videos

Here is a progression of videos I made while I worked on fixing various problems and improving the visuals. They show the progress I've made over the past few weeks.

I got collision detection with building interiors (walls, floors, stairs, furniture) mostly working early on, then took awhile to perfect it. Here is an early video showing me walking around inside a building.


Here is a newer video showing the flashlight and walking through building doors. I didn't have doors that automatically opened when recording this video. That was one of the last features I added.


Here is an updated video where I fixed some of the problems with the flashlight size/falloff and made doors that actually open to allow the player to enter and exit. It's definitely an improvement.



That's it for this post. I'll have more procedural city content to show in a few weeks. Future work may include adding more types of objects to building interiors: furniture, lamps, objects on the tables, etc. Maybe I can allow the player to push some items around. Maybe I can even place models of people inside buildings. There's still a lot of work to do with room placement in office buildings, better furniture placement, working elevators, animated doors, etc.

At some point I may also try to get building interiors working with larger city office towers. The difficulty with these is that the window placement is irregular and varies per building type/texture. In addition, some of the building shapes are curved and placed at odd angles. It's not a simple as adding windows to flat rectangular walls with simple brick and stone textures. It doesn't look quite right if only the rectangular buildings have transparent windows and interiors. I'm not sure how I can use shader trickery to automatically place windows in a regular grid pattern on the exterior walls of these building types. I tried this earlier, but it doesn't look very good.