I've been working on various aspects of 3DWorld's procedural cities during the past month or so since my last blog post. I don't have a single topic that's large enough to write an entire post on. Just recently I did add a system for connecting the extended basements of houses together into huge underground mazes, but I don't have any good screenshots to show of this yet. So instead I'll review some of the improvements I've made to building exterior geometry. These are not in chronological order.
Scrolling Signs
First up we have scrolling company name signs. Signs were initially discussed in my February post. I did eventually get back to improving them, which was partially inspired by scrolling text I saw on some of the buildings in a video of someone else's project. Their effect looked very different from what I wanted to achieve, though it was the same high level idea. I applied the scrolling effect to the illuminated night time signs on the roofs of tall city office buildings, since these are the most visible to the player.
This took a surprising amount of work to get right. It was relatively easy to make the text move one character at a time to the right and wrap characters from the end back to the front. Smooth scrolling was much more difficult because it required updating my text drawing system to handle partial characters so that I could have the last character split across the last and first position as it wrapped around. This worked by duplicating one of the characters so that it was at both the beginning and the end, and then clipping the outer edges of each to the correct final size.
Then came the even more difficult task of making this work with my non-fixed width font. Some of the characters (such as 'i') are much narrower than others (such as 'M'). This means that the scrolling rate is different for each character, and narrow characters spend less time split across the ends than wide characters. I had to first render each character to a temporary buffer to determine the start and end positions within the sign display area. Then I was able to calculate a mapping from each character to the point in time where its left edge passes the scrolling point. From this I could take the game time, calculate the fractional component of the scrolling position from the period, and look up the time value to get the first character to draw. The delta time between the character appearing and the current time value was used to determine how far along that character the movement was, or the split position in the case of the last character.
This still doesn't work perfectly, but it's much better than my previous two attempts (not shown). Here's a video showing several scrolling signs. YouTube seems to have made this blurry and hard to see. I'll add a screenshot after the video, but you'll have to imagine how the text looks like when scrolling and just trust me that it works. Unless it's just me and others don't see a blurry video?
Still image of some scrolling neon company signs attached high up on tall office towers. Shown at night, with a bloom postprocessing effect applied. |
Exterior Trim
Houses were looking too plain, in particular the windows. What had previously been limiting my ability to add exterior details was the way in which building geometry is generated and drawn. I'm creating the exteriors of every building ahead of time and drawing them at large distances of over a mile because they're actually visible that far away. Even the tiny windows can be seen in the distance! This is especially true of glowing windows at night. I can get away with adding a ton of interior detail to buildings because both their generation and drawing can be deferred until the player is close enough to see these details through the windows. The difference in drawn distances for exterior vs. interior is user-configurable but generally as much as 5-10x.
The reason I can't draw exterior geometry as interior objects is because they use different lighting models and shaders. Exterior lighting consists of directional sun and/or moon light, hemispherical ambient lighting from the sky and ground, and a 2D grid of point lights from sources such as streetlights and headlights that are mostly active at night. Interior lighting is a combination of room-based point lights in a 3D grid, combined with optional indirect lighting computed in the background when entering buildings. If I were to draw building exterior objects as interior geometry, they would appear too dark because sun and moon light is not enabled and building interior room lights are on the other sides of the walls. You can actually see this problem in some of the earlier "Welcome" signs I placed above doors. These looked gray but were actually a bright white color with only a small amount of ambient lighting.
Wait, but what about other exterior objects? For example, I have some screenshots with house fire escapes. I cheated! Fire escapes are black, and the player can't tell that the lighting is wrong. Okay, what about those house number signs such as the one shown here? They're not black. And neither are the flags I added to houses. Well, these are actually objects that belong to the city itself, not the house they're attached to. They're drawn by the same system that draws street signs and manhole covers. This is why you only see them in cities and not in the randomly placed secondary buildings I have scattered around my world. Right, then what about gutters on houses? I don't remember ever discussing them in my blog, but maybe some of you noticed them on my recent house screenshots. Well, these are part of the exterior and are generated and batch drawn for the entire city at once. Their geometry is simple enough that they don't add much overhead compared to the exterior walls, roof, doors, etc. And skylights? My skylight drawing solution cheated as well by using an invisible room light above/outside the building pointing in the direction of the sun to light the top surface. No, up until now I didn't have a system to make detailed exterior lit objects work properly.
It's time to change that. Now the building drawing has an additional rendering pass for per-building objects that are placed on the exterior and drawn with sun/moon lighting. Yes, this adds some frame time overhead, but it's not very significant. The real cost was the complexity required in the control flow and a system to swap shaders while drawing spatial blocks of buildings. My solution is a bit ... odd, though, because these objects are still part of the building "interior" and are generated at the same time as the interior. Which means they don't have the same long draw distance as exteriors, which means they can't be too large, or they'll obviously pop in and out as the player's distance to the building changes.
The first step was to change the "Welcome" signs and other small items to use the new system. Then I realized the exterior faces of some of the windowing coverings had the same problem. I fixed blinds and bathroom glass block windows to draw their exterior surfaces as what I suppose I have to call "exterior interior geometry." I didn't realize how broken that looked until I fixed it! Finally, it's time to add new content. This includes proper 3D window frames, window pane separators for houses (apparently called muntins and mullions), window sills, and exterior wall trim. Window frames and sills were added to some styles of office buildings as well. Window style varies per wall/floor of each house.
I definitely needed to improve the look of windows because their exteriors were too plain and flat. The lighter window border I had was drawn with a low resolution alpha mask texture originally designed to be viewed from a distance. This didn't match with the detailed trim shown on the interior of the window. I did brighten the window border in the texture and doubled the resolution; that helped, but wasn't enough. Now the new windows with physical geometry look more 3D, detailed, and brighter:
Exterior of a large house with properly lit window frames, pane dividers, window sills, blinds, and signs. |
Next, I wanted to add some separation between the ground floor and upper floors of houses. Many houses use different materials for these, for example, brick or stone on the bottom and wood, siding, or stucco on the top. It's easier to start with a simple band around the perimeter of the house than jumping into a whole new system of combining multiple exterior wall materials on a single building. (I did in fact experiment with siding, though I haven't enabled it and I'm not ready to get into that topic yet.) A thin white band to match the windows is an easy place to start. Remember, I can't make anything too large because it's only visible when the player is close by. I'll also only enable this for 20% of buildings to make it a somewhat rare decoration. Well, rare may be too strong a term because it's not nearly as rare as houses that look like duplexes, triangular office buildings, and office buildings with overhangs. So I'll go with "uncommon" instead.
House with window trim and exterior wall trim at the top of the first floor that wraps all the way around. |
House trim is always white because I feel it makes them look clean and bright. Office buildings have multiple shades of trim from gray to black that are designed to improve contrast with the average brightness of their exterior walls. Here's an office building with dark glay-black trim.
"Dark Mode" office building. |
I'm still thinking of what else I can add to buildings. I can potentially include significant object detail since it's only drawn for nearby buildings. Drawing cubes around every window may seem like a lot of geometry, but it's far, far less than what's drawn for the building interiors.
Ceiling Fans
Did I say I was going to discuss exterior geometry? I thought I had a third topic, but I suppose I forgot the last topic in the process of writing that long section above. Having only two topics isn't enough, and I just realized that I haven't shown my ceiling fans yet.
The more animated and interactive objects in buildings the better, right? So how about one that's both: a ceiling fan that can spin and is controlled by the player. I even put a light on it. However, it did take a surprising amount of time to find a suitable 3D model, and I'm still not very happy with it. I had to have a model that satisfied the following constraints:
- Free. Because my goal with the 3DWorld project is to not spend any money on it, so that I can make it free and open source without "losing money."
- Not too high poly, so that I can spam these into every room if I want to.
- Mostly white in color so that I can potentially repaint it, though so far I haven't done this.
- Relatively wide and short so that it fits in the limited ceiling space of my houses without clipping through the player's or AI's heads.
- Two separate objects/meshes for the blade vs. base so that I can animate it to make it spin. Or, in the case of the model I ended up using, completely symmetric so that I can spin the entire model and the player doesn't know the base is also spinning. (Though in my case it's not perfectly symmetric and you can see the base rotating).
- Something that allows a light to be easily attached to the bottom and that can be set as emissive when on. This rules out any fans that come with fancy light fixtures or multiple lights.
- A model that loads properly in Assimp or my own OBJ reader. In particular, I had to rule out a few with missing embedded textures.
Out of the ~100 models I found online, only one really fit all requirements, and not very well at that. But it is what it is, a crappy ceiling fan is better than no fan at all. It's not that it's a bad model, it's just very simple and plain. Behold, we have the bedroom ceiling fan:
I guess this works? The room is a bit darker than I would like because the light is lower than the ceiling and the upward light is blocked by the fan itself. I'm sure it would look better with indirect lighting. What's that? You demand to see that screenshot as well? Fine, I'll go create another one. Here it is:
Room with ceiling fan, with indirect lighting! |
Yes, this definitely looks better. If only it didn't come with a framerate cost. I'm not sure why the base of the fan is black though. Maybe it's because it's halfway buried in the ceiling, or its lighting is blocked by an occluder from the fan that's too large, or the fact that it's spinning makes the lighting computation wrong. Ah, wait, I think it's because the normals are backwards on that top part of the fan.
Anyway, it's properly interactive. The player can switch the fan and light on and off together with that light switch on the left. And it's also gated by some breaker in the breaker box in the basement, if there is one. Maybe sometime in the future I can include these in gameplay somehow. Are they zombie distractions? A fun, crazy way to expand ball or duct tape physics? I guess you'll have to wait and see.
For text wrapping, I would have thought you would have used your UV texture system. Set the texture to wrapping, and then animate the mapping offset. Also, it appears from the videos that the text is scrolling the wrong way?
ReplyDeleteLove the added building details! I always thought that "pop in" could be made less obtrusive by a gradual dithered fade-in as you get closer. I also like Shamus' approach of having the detailed geometry slowly emerge out of the larger base geometry, which seems like it could be done in a geometry shader. Though at this point you might want to skip all of that and go straight to virtualized micropolygons and then never worry about LOD again.
On the topic of forgetting what points you were going to cover, you really should rough in an outline before you start writing the detailed paragraphs. I know the vast majority of people write from start to finish as you appear to do (and Shamus did as well) but it's so unnecessary! The advantage of having a plan in the form of a map, the ability to confidently reference future and past topics, the setup for unlimited fractal ramification (much in the same way that you generate building interiors in fact) to a desired level of detail, you get all of that by making a simple outline before starting to write the body text.
I'll add a low-poly multi-part ceiling fan to my to-do list. Surprising that no one has made one yet.
Signs are drawn as individual characters from a font texture atlas and don't wrap. Maybe I could wrap them if I rendered each sign to its own texture. I didn't use that approach because it seems inefficient to have that many unique textures, and more complex to manage them. Right now I can draw all sign text in two draw calls, one for regular text and one for emissive text. Maybe I could use a dynamic texture atlas where each sign is stacked vertically and can still scroll horizontally, though that would need to be as wide as the longest text string and would waste a lot of texture space.
DeleteI think you're right about text scrolling the wrong way. It doesn't really matter for short signs like this where the entire string is always visible, but if the text was longer it wouldn't be readable the way I have it. Hopefully I can fix it with a simple sign change.
For popping, it's a trade-off. I do use both dithering/fading (for trees) and emerging/growing (for grass). The cost is that rendering is more expensive, so the transition must occur earlier. It also doesn't work well when there are transparent objects in the way such as windows, and is somewhat incompatible with shadow casting. Maybe I can get away with it in some cases. Using a geometry shader would likely make things too slow on Nvidia and AMD cards since there are a ton of triangles to draw.
I sometimes write the outline first. Actually, I usually start by inserting some screenshots. In reality I'm using writing these posts at the same time I'm working on the code and I go back and update the text multiple times. I often find problems when looking at the screenshots or find solutions when explaining the problem, which then allows me to fix something. Also, doing this at the same time allows me to keep and post some of my failed attempt screenshots that would have otherwise not been saved. ... This is *not* one of those posts. This one was posted because I try to post something every month and got to the end of April without even thinking about it, so I threw something together that covered all the small topics I worked on in the past 1-2 months.
I spent hours looking for ceiling fans. It was frustrating because I found at least a dozen models that initially looked good. But some were missing their textures, or had other parts of the room mixed in with the object, or bad normals, or holes in the geometry, or were too tall, etc. Even the one I used isn't perfect because some of the triangles have the wrong winding order and bad lighting.