Monday, April 30, 2018

Building Window Generation

I'm continuing to work on 3DWorld's procedural cities, in particular the buildings. I want to make them look more interesting and realistic. Some of the building textures are plain brick or concrete block. There are no windows, doors, or other features. I've disabled these in many of my city screenshots because they look out of place next to the office buildings and other types that do have windows. It's time to fix this.

I decided to generate windows for buildings. Windows are just drawn as a texture in a postprocessing pass on top of the original geometry for the day time scenes. I draw the geometry of these buildings a second time, which applies the windows as decals using an alpha mask to let the bricks show through non-window areas. However, at night, building lights come on and the windows are emissive. These are drawn in yet a third pass with a different shader. I'll describe this more later.

I began by adding uniform 2D grids of windows to the previously windowless brick and concrete block buildings. I scaled the windows to approximately match the vertical floor spacing of the office buildings that already had windows in their textures. It makes sense that the various building types would be at the same scale. I made the center of windows a dark gray with a light gray window frame around them. There is only one window texture, and it contains exactly one window. The texture is generated based on user-specified config files that control window width, height, x-space, y-space, and border width. Window spacing and grayscale color can also be specified per-building material to help them match the surface textures with good contrast. Here are two screenshots of buildings with windows added. Note that I also added sloped roofs to some of the larger rectangular buildings.

Brick and concrete block buildings with generated windows in a uniform grid.

More brick and block buildings with generated windows. Window colors have been adjusted for good contrast.

The next step was making these windows look better at night. Previously, buildings were all a uniform grayish color in the dark. Car headlights and city streetlights definitely improved the look of the city, but buildings were still dull and boring. Night lighting really made buildings come to life.

This part was inspired by Shamus Young's Pixel City project from 2009. Apparently, this project has been restarted. I'm not going for the same look as Pixel City though. I want cities to look good in daytime, night time, and in between. I want them to be viewed from street level instead of just from above. I haven't added the window shape/color variation, lighted signs, and other detail lighting and geometry yet. I don't think my city lights look as good as Pixel City, but I'm still working on them.

I started by adding lights to some of the windows that were generated by the procedure above. These include smaller brick and block buildings that were placed outside the main city. I had them shoved out of the way to keep the windowless buildings out of my screenshots. There are no roads, cars, or streetlights out here. It's easier to experiment with lighting with the other light sources turned off anyway. Here is how it turned out.

Buildings with generated windows lit up at night in shades of white, yellow, and blue.

Each building has a single window light color that's randomly generated to be in the bright yellow to light blue spectrum. It's a little difficult to see unless you click on the image to zoom in. Too much color variations looks unnatural. This allows for various light styles including incandescent, fluorescent, and LED. It's the same technique I used for car headlights. In addition, each building has a random number of lit windows, between 10% and 50% of the total.

You might wonder how I'm able to light a random subset of the windows efficiently when there's only a single texture. All of the magic is in the fragment shader. We have access to two important parameters here: the texture UV coordinates and the per-building emissive light color. Texture coordinates are generated by scaling the building {x,y,z} vertex positions in world space. This ensures that textures align correctly across building faces that are split into multiple quads/triangles. Unfortunately, this also causes windows to sometimes wrap around the corners of buildings. I can't think of an easy fix for this.

The window texture contains a single window + frame within the texture's UV range of [0.0, 0.0] to [1.0, 1.0]. We can split the texture coordinates into an integer and a floating-point fractional component using the fract() GLSL function. The fractional components tell us where this texel is within the window. If it's around 0.5, the texel is near the center of the window. If it's close to 0.0, it's on the bottom/left, and if it's close to 1.0, it's near the top/right. The integer components give us the world space index of the window (u=x or y, v=z). We therefore have two pieces of data here that can be used to add variation to windows:
  • The window color, which is per-building
  • The integer texture coordinate (window ID), which is per-window
These values can be used as seeds in a pseudorandom number generator within the shader. The window color is hashed to produce a lit window density value between 10% and 50%. This will determine what percentage of windows in the current building are lit. The integer texture coordinate can be used to generate a random number to compare against this lit threshold. Together, these variables will determine which windows are lit and which ones are dark. Fragments from dark windows are discarded (not drawn) by the shader. Since all fragments (pixels) of a window have the same window ID, they will all be either lit or dark.

Let's move on to the inner city, which is full of tall office buildings that already include windows in their textures. I decided to create a larger city this time. The city is a single flat rectangular area of several square km with a total of 3700 buildings. Each building uses one of 12 unique side textures. Here is how the city looks in the day time. I get around 92 FPS with this view. All windows are part of the original building textures; none of them are generated by the new algorithm.

A large city that stretches to the horizon. These office buildings all have windows as part of their 12 different texture maps.

Here is how it looks at night, before I made any modifications. The buildings are the same bland grayish colors as the brick and stone buildings. All the texture detail is muted by the dark lighting, except at ground level where the streetlights and headlights illuminate it. The moon is out here. Without the moon, the scene is even darker gray.

A large city at night. The cars and streetlights produce light, but building windows are all dark. This will have to be fixed.

Here is the same scene with window lights enabled. I think they look pretty good for medium to far distance buildings. This addition makes the scene look much more interesting. Note that distant building lights are obscured by fog, allowing them to blend into the background. This helps to hide aliasing for sub-pixel windows. Also, I moved the moon below the horizon for this image so that the lights stand out more.

The same city as above, but with lit windows added to the office buildings. Looks much more interesting.

This screenshot shows just how large the city is. There are thousands of buildings stretching to the horizon.

Smaller city with cars, headlights, streetlights, and building window lights.

Here are some images showing how window lights look at a street level view. I don't think the nearby windows look very good because they aren't perfectly aligned with the building textures. I spent a long time finding the optimal values of texture xscale, yscale, xoffset, and yoffset to use with each of the 12 building textures. This helped, but didn't really work for buildings with nonuniform window size or placement. Some textures have large gaps between every few floors. Some have different window size/spacing/placement on some floors. Others have groups of 2-4 windows closely spaced together with larger gaps between the groups. It's just too difficult and time consuming to manually align all of the light rectangles with the actual windows for each texture. I did the best I could in one night of work.

Street view showing city lights. Nearby lit windows don't look as good here.

Street view again. These buildings have better alignment of window lights because windows are spaced out in a regular grid.

One hack to help with this problem is to fade nearby window lights to transparent by adjusting their alpha values based on distance to the camera. It's one line of shader code. This improves the screenshots, but the fade looks a bit odd when the player is moving.

Street view with nearby window lights faded out to transparent. See the mostly faded lighting on the left side building.

Here is a screenshot showing buildings placed on the hilly landscape. There are no roads, cars, streetlights, or traffic lights here. The foreground buildings are office towers, and there are some brick/stone buildings in the background. This is what the placement algorithm produces when roads are disabled.

Buildings lit up at night. These buildings are scattered over the scene at various elevations, with no roads.

I added an experimental bloom effect for city building windows. It works well enough for mid distance windows. Bloom applies to headlights, streetlights, and traffic lights as well as windows. I tried using a two pass x/y separable Gaussian blur, but the x and y blur directions were obvious. This technique works better with round or spherical light sources than it does with square light sources. I had to switch to using a more expensive single pass 2D Gaussian blur for the bloom effect. A 17x17 kernel size produces pretty good results with little change in frame rate. I suspect this is because the GPU is spending so much time in the vertex shader drawing car models. The bloom shader can be found here. The following two screenshots show city lights with bloom. Note how bright it makes the car headlights appear. Nearby buildings have lights that are fading out and therefore have less bloom.

Street view of city night time lights with bloom effect applied to make lights appear to be brighter.

City lights with bloom effect, shown from above.

Enabling all of these effects drops the frame rate to 58 FPS for a street level view facing into the heart of the city. This includes terrain, grass, 3700 buildings, roads, 4000 cars, traffic lights, streetlights, 1024 dynamic spot light sources, 40 dynamic shadow maps, a city lights pass, and a large kernel radius bloom filter. Oh, and also water with reflections, even though it's occluded behind the buildings. I'm sure I can optimize this if needed.

That's it for now. I think the results are good, a huge improvement to the look of night time cities. I still need to work on some lighting effects. I would like to add a better bloom effect to make windows look brighter, especially in the distance. It would be great if I could find a way to improve window lights for nearby buildings. I would also like to come up with some system for adding detail to the windows: shadows, furniture, etc. I'm not sure if I can generate these details in the shader using the window ID. Maybe, I'll have to experiment with this.

No comments:

Post a Comment