Wednesday, December 4, 2019

Procedural City: More Building Interiors

I started discussing generation of procedural building interiors in the previous post. I've since fixed various problems so that interior walls and doorways are correct and realistic in most cases. Some of the changes even required me to go back and redesign the building exterior geometry. For example, doors are placed after interior walls now so that they don't open up to the end of a wall. I'm also continuing to work on building interior geometry. I've made a lot of progress over the past few weeks, but there's plenty more work to be done before I'm happy with it.


Walls

I fixed most of the problems with wall placement. Walls rarely end in the middle of windows now. If this happens, the wall placement will be discarded and a new random placement selected. It can still occur for small sections of building walls (for example the interior of a U-shaped building) that have been clipped in a way that a partial window needed to be dropped. In this case, the other windows are spaced further apart to fill in the gap. This part of window assignment is done later in the drawing setup stage where the vertex data is extracted and stored. The wall generation algorithm doesn't know about this extra spacing of windows because it's too difficult to detect it this early in the flow. It uses the wrong window spacing for checking that a wall ends at a window.

I fixed the misalignment of walls at T-junctions where two adjacent parts of the building place walls that intersect each other. In addition, there was a similar case where two parts try to place walls at the same location between them and the doorway positions don't line up. [Where here "part" means a single geometric primitive that is attached to other primitives to form a building, which in the case of houses is always a cube.] This resulted in either a partial narrow doorway or no opening at all. Now the placement of walls and doors between parts is done in a separate step after all parts have placed their own interior walls. This guarantees that all walls that could intersect the part boundary walls are known and can be considered when placing the final set of walls.

Finally, I improved the placement of doorways. Long walls now have a chance of getting additional doorways that improve room connectivity, adding loops and hallways. A doorway can be placed if it  connects two rooms that were previously not connected by a doorway. I believe the current algorithm should guarantee that every story of the building is fully connected (walkable) so that every room can be reached from every other room through some path. I can't quite prove this, but so far I haven't seen any disconnected rooms in the dozens of buildings I've inspected. Larger rooms are often connected on all four sides to adjacent rooms.


Interior Doors

I added placeholder 2D quads (rectangles) representing doors between interior rooms of buildings. They don't have any width right now. In fact a single door is actually stretched to the entire height of the building, where the door texture is repeated for each story. The doors are all open for now. At some point I might have randomly closed doors, or even allow the player to open and close doors with an action key.

I might have to split up the doors into separate quads for each story/floor. On the other hand, maybe it's okay to use a single tall quad. The player can't see any floor other than the one they're on, so they won't know that they're opening all of the doors on each floor at the same time. Of course this will change when I add stairs (more on this in the next post).


Interior Objects

I started placing simple objects in rooms. At first it was just one cube per room, but now I have tables with up to four chairs around each of them. These are placed at random locations within most of the rooms with constraints that they stay within the room bounds and don't block any doorways. I haven't added textures yet, so all tables and chairs are unlit solid colors. The geometry is procedurally generated and very simple, consisting only of cubes.

There are around 600K total rooms in the secondary city buildings. That would translate to around 500K tables and 1M chairs. That's too much geometry to store in memory, so I've created a system to dynamically generate interior room geometry for nearby buildings and delete it when the player moves away from the buildings. This limits the geometry to only a few hundred out of 14K buildings at a time. I'm only storing cubes tagged with metadata for each object on the CPU, which is also needed for player collision detection (future work). Vertex data for each object is sent to the GPU when the buildings are visible but is not stored on the CPU side. This data is freed when buildings disappear from view.

Here is a screenshot of a building with placed room geometry. The camera is outside looking into a room. You can see a table, some chairs, walls, and two doors in the background.

Large house with doors and a table with chairs placed into most of the rooms, looking through the window from outside.

Interior Windows

I spent quite some time trying to get interior windows to work right. The exterior walls of buildings are zero width with brick/stone textures on the outside and plaster textures on the inside. This is done by drawing the front and back faces in two passes using different textures. When the player is outside the building looking in, any face viewed through a window is an interior face and all other faces are exterior. (Note that the previous post still showed brick/stone textures on the insides of buildings.) The entire wall of each building face is a single quad. Windows are cut into it using a depth pre-pass when viewed from the outside and a stencil test when viewed from the inside. I had to implement the inside view a different way because the depth test approach caused artifacts when looking through multiple levels of windows, which wasn't possible until I added interior windows. For example, when looking from the inside of a building out the window into the window of the neighboring building.

The approach I used works because the player can only be inside one building at a time. That building is drawn first using different OpenGL draw modes from the other buildings. Then the other building windows and walls are drawn. It only takes, let me count ... 8 draw passes using different states to get this right.

Here is a screenshot of a room with table and chairs, this time viewed from inside. You can look out the windows and see into other buildings, which also have tables and chairs. I haven't added window panes and frames to interior windows yet. It's quite tricky to get that extra (9th?) pass in there. I also haven't figured out how to let the player look through more than two buildings (a path of interior => exterior => interior windows).

Inside a room of a house with a door, table, and chairs, looking outside at other buildings.

Okay, that solid color geometry and lack of directional lighting on the interior just looks too bad. After adding that screenshot above, I went into the code and enabled 20% diffuse lighting. It's not actually correct, because the sun doesn't shine into the rooms. What I really need is indirect lighting, or light fixtures on the ceilings with shadows. I have no idea how to add either of those for this many buildings in realtime, so that will have to wait until later. I could probably add up to a thousand static spotlights or point lights on the ceilings, but I can't add the shadows. I expect unshadowed room lights to look worse than no lights at all because you would see them shining through walls. I think this lighting looks a bit better than in the previous screenshot even if it's wrong. I also added a simple wood texture for the tables and chairs. Yes, even the chair seats.

Room with some diffuse interior lighting and tables with chairs using a wood texture.

Another view of a room interior.

After adding this screenshot, I went back and added proper support for multiple interior object textures. Now the seat cushions use a ... marble texture? I guess you'll have to wait until the next post to see them though. I can't go using all of my good screenshots on this post.

There's one more problem with these windows. This trick doesn't work when looking out a window into a different window of the same building.This can happen for L, T, H, and U-shaped buildings when the player is in one wing looking across a courtyard at another. The problem here is that the view ray will pass through an interior window, then an exterior window, then another interior window. There's no way to draw both interior windows before the exterior window without splitting the building into two parts that are drawn in different passes. It's the same limitation as alpha with blending. I'm not tracking enough info to draw individual parts of a building at this time. So instead I draw the exterior walls into the stencil buffer to remove the interior windows that look out at the exterior of the other part of the building.

Here is a wireframe view of the edge of the city showing just how much geometry there currently is. The tables and chairs are easy to see. The grass, of course, is the densest and still takes the most draw time. Adding building interior geometry only dropped the framerate from 125 FPS to 120 FPS.

Wireframe view of the edge of the procedural city showing all of the building walls, ceilings, tables, and chairs.

That's all for now. The next step is to connect floors together vertically using stairs and elevators. I've already started working on stairs. It seems like a fun project. There are lots of things that can go wrong, so this step may require going back and rewriting other parts of building generation again.

4 comments:

  1. You are so amazing! Where did you learn all these materials from? I've been trying to learn graphics programming on my own, but I have never got far with this. Your blogs are very enjoyable to read. :D

    ReplyDelete
    Replies
    1. Thanks! I learned most of this by myself by working on the 3DWorld project. It helps to read a lot of papers and presentations and experiment with a lot of graphics projects.

      Delete
    2. Thanks for the tips, and please keep writing blogs :D.
      My name is Trinh Vo by the way, and I also went to UC Berkeley and graduated in 2009.

      Delete