Tuesday, April 10, 2018

Procedural City Update: Lights and Parking Lots

I'm continuing to work on 3DWorld's procedural city. In the last post, I added cars. In this post, I'll describe some of the other features I've added to improve the look and realism of cities. Here is a summary of the main accomplishments, listed in the order in which I implemented them:
  • Ambient Occlusion under cars (added as an update in the previous post)
  • Dynamic lighting for car headlights on roads, buildings, and other cars
  • Parking lots full of parked cars
  • Occlusion culling of cars using buildings
  • City streetlights
That's three (two and two halves) of my future work list from the last post. Pretty good progress so far.

Ambient occlusion was already shown in the previous post. It's a pretty simple trick using a partially transparent gray texture under each car, so there's not much to say about it. I would love to add car shadows, but unfortunately I still haven't figured out how to do that efficiently. I'll keep it on the todo list for now.

There's nothing to show for occlusion culling. If done correctly, the player will never see it. It's an optimization where cars that are behind nearby buildings don't need to be drawn. I do this by selecting all visible buildings (in the view frustum) within two city blocks of the player. Then, for each car, I generate rays from the top 4 corners of its bounding cube to the player camera. If all rays intersect any one building, the car is occluded by the building and doesn't need to be drawn. This is only needed for nearby, high detail car models. Since buildings are mostly convex, and rarely have overhangs, this tends to work correctly. Occlusion culling improves frame rates, especially now that I've added parked cars. This optimization increases in-city framerate from 50-60FPS to 80-90 FPS. Yes, most of the time is spent drawing car models. Everything else can be drawn at 200 FPS.

I'll describe what I did for parking lots, headlights, and streetlights below.


Parking Lots

I found something to fill in the empty space between buildings - parking lots! What's a big city without parking lots? Each parking lot is between 12 and 28 spaces wide and between 1 and 3 rows long, as constrained by config file parameters. They're placed wherever there's enough space between a road and a set of buildings. Each parking lot is populated by cars using a random density between 0% and 75%. This results in about the same number of parked cars as there are moving cars on the roads. The parking lots are randomly oriented with randomly selected car directions. I left the cars all pointing in the same way so that the spatial sorting algorithm would magically draw them back to front to make alpha blending of transparent windows work. Here is what a parking lot with 12x3 parking spaces looks like.

Parking lots partially filled with cars. These cars are inactive/static and their headlights are always off.

These parked cars don't take much extra CPU time and memory because they're static instances with no update logic. I haven't implemented car parking behaviors, and I'm not really planning to add that. However, parked cars do take extra GPU resources to draw. This is why I had to add occlusion culling using nearby buildings. Parking lots are only generated when buildings are enabled. They're guaranteed to have padding between streets and buildings so that cars have space to enter and exit.


Car Headlights

It was actually pretty easy to add dynamic lighting support to the city scene. I used the same lighting system as I did for gameplay mode as explained in a previous blog post. Each nearby, visible car has two headlights that automatically come on in the dark. Car headlights make a huge difference in night time scenes and dramatically improve realism. Headlights illuminate the road surface, buildings, and other cars. There are currently no headlight shadows. Here are two screenshots showing car headlights with no buildings.

A row of cars stopped at a light, shining headlights on each other. Buildings have been disabled.

Cars with headlights traveling on roads. Buildings have been disabled.

I've modeled headlights as spotlights (cones) pointing slightly downward, with smooth lighting falloff at the edges of the beam. This is very similar to the "flashlight" in gameplay mode. Colors vary randomly from yellow to blue-white to simulate different lighting technologies (incandescent vs. halogen vs. LEDs). Here are three more images, this time showing buildings.

Cars with headlights driving in a city at night.

Cars with high beams in the city, shown from the point of view of a car. Maybe headlights aim too high?

Cars with headlights driving through an intersection.

The headlights are probably too bright and pointed too much upward so that they shine into the windows of the office buildings. This would probably drive people crazy! I left the settings that way just to make the lighting easier to see; I've switched to "low beams" after taking these screenshots.


City Streetlights

Parking lots and headlights are great, but there are still too many dark, open areas. I decided that I can fill some of the space with streetlights along the roads that come on at night. Each city block has four streetlights, one along each side. They're offset from each other and spaced out evenly along roads to produce somewhat uniform light coverage. I used the same dynamic lighting system as car headlights. Streetlights are also spotlights, but with a 180 degree field of view and pointing down.

Technically, streetlights aren't dynamic because they don't move. However, there are thousands of them, and I can't have them all on at the same time. This would hurt the frame rate too much. Instead, I select and enable only a few hundred of the most influential lights sorted by (radius/distance) that are within the camera's view frustum. The upper limit is 1024 lights combined across car headlights (two per car) and streetlights. Here are some screenshots.

Closeup of a streetlight shining on an empty road in the city.

Streetlights and cars illuminate the roads and buildings at night. Yes, these buildings have no windows or doors.

I haven't implemented shadows for streetlights yet.

These next two images show just how many lights there are in the city. Currently, only nearby cities have lights enabled. Maybe I should add a slider to control streetlight color?

Streetlights and headlights in the city, shown from above.

Streetlights light up the entire city at night. There are about a thousand lights in this scene, half of them dynamic.

All that's left is to add lights in the office buildings. I'm not sure how difficult that is. Would it be possible to have the fragment shader somehow detect windows and add emissive light inside their bounds? I currently don't really know how to do this efficiently, but I guess we'll see in the next post. Oh, and I probably need to add windows to those all-brick buildings, or use different textures.

One more thing: 3DWorld is now on GitHub! Here is the project page. Now you can actually go look at the code, though it may not be easy to read.

4 comments:

  1. Ooh, looking nice! Maybe you could do an ambient light with a vertical intensity gradient in the distance to simulate distant street lights? If you look at areal photos of big cities, the individual lights tend to drown out into a nearly uniform glow.

    ReplyDelete
    Replies
    1. You mean like city light pollution? I suppose I can try to add that, maybe as some sort of shader uniform.

      Delete
    2. Uh, yeah, kind of a light pollution effect. I more meant that you could add more street lights, and then use a gradient ambient light instead of using the individual cones in the distance.

      Delete
    3. So far I haven't figured out how to add ambient lights. There are some problems I need to resolve. First, I can't quite add a shader uniform for the height/Z range of lights because there are multiple cities at different elevations, and the buildings and objects are generally drawn in the same draw call. I don't have any sort of cube ambient light area, or any way to specifically set custom ambient regions. If it was confined to a cube it would have a sharp cutoff, while I probably need a gradual cutoff.

      Also, I probably don't want to add this effect for the city blocks that have lit streetlights. I don't have any way within the city/buildings code to determine how far out the streetlights are drawn to. It's not a fixed draw distance, it's the closest ~1000 lights. But there are different types/intensities of lights, and the selection system uses light size in weighting, so it's not a clean transition across all lights. Rather they tend to get sparser further away from the player.

      Lights extend outward pretty far though, probably some 5-10 Km. So this effect would only be for distant buildings that start to become occluded by fog.

      Delete