Of course, the first thing that daunted at me is the sheer amount of house meshes I will need, the amount of effort in making them and, worse, the eventual sense of "repetition" as you walk down the streets. And so, I thought - why not generate the cities using tiles? Most of the existing 3D tile engines I've seen (which there aren't many of) use a really big tile. NWN and Dungeon Siege both define a tile as an entire house or a structure. But I wanted a finer detail than that; houses, themselves, made of different tiles, arranged according to a tile-based, "floor plan." A throwback to old iosmetric games, but in full 3D!
- Easy and fast to create world
- Randomization and proceduarl generation
- Easy fading away / hiding "upper floors" to not obstruct player's view
- Easy to partition world into "inside" and "outside" tiles with "transition" tiles like doors or windows
- Low memory footprint of the layouts, meaning potentially huge world, or even single-thread loadless streaming that takes less than a frame!
- Purely procedural generation is too random (looks unrealistic!) - needs a lot of restrictions
- "Blocky" design with everything orthogonal to each other. Diagonal tiles are not enough to break this monotony.
- Making more complex patterns require specialized tiles, and sometimes a series of specialized tiles next to each other - cumbersome
- Poor handling of gradual vertical differences (want to build a house on an incline? forget about it!)
Regardless, I went to coding. Here is what my current test-implementation looks like, with randomized tiles:
Now, while I was at a point where I desperately needed to create more tiles for more variety, I started to doubt the viability of the tile engine, mainly due to technical issues. Consider that the typical, 4-story house I tested with has dimensions of 4x3x4, meaning it's made of 3*4*3 = 36 tiles, plus a roof. Rounding it up for extra details, a single house could be made of ~= 40 tiles = 40 meshes. Multiply that by a few tens of houses, and rendering all that is no small feat! A scene such as this:
Was enough to drop my framerate to 20fps, and that's without any other objects to render, and no real physics, AI or even game logic. I've spent the past few days experimenting, researching (what little there was) and realized that a 3D tile engine would neccessarily suffer from the following technical issues:
- Can't "LOD away" tiles, if you see 40, you have to render 40
- With a Single-Mesh house, it can be LODed to a single simple mesh with a single shader/material, thus drawn with just one draw call (not "40"), or taking tiny space in a dynamic batch buffer
- Unless you make lo-poly versions of each house, which defeats the point of tiling
Low poly tiles
- With so many tiles to draw, the only viable techniques are Instancing or Dynamic Batching
- Instancing - requires low poly (NVidia 2006 GDC talk - "no more than 100")
- Dynamic Batching - since you keep rebuilding the batch, it needs to be low-poly due to sheer size of tile copies (remember, each mesh is copied by as many times as tiles that use it!)
- With so many tiles, you end up with a lot of texture and shader switches;
- You have to pack several textures into a bigger texture => resulting tile textures are fairly low rez
- If you see 30 houses, that is 30*40 = 1200 instances which all need their own instance data (instead of just 30 with single mesh)!
- In my benchmark, with around 50 houses, I reached a point where, if I saw only half of them, it actually took longer to build the instance data than to render it!
- Meaning: if you only see half of all your tiles, it's faster to render all of them than to actually cull them. Culling is not efficient
- Impractical caching of per-frame instance data - due to fine granularity of your world, even small camera movements will lead to big fragmentation of your cached data
- Even if you did cache it, you would need to create different caches per every single mesh (since with instancing you cant group multiple meshes in a single vert. buffer)
- Even if you did use one huge static buffer, then the many texture/shader switches due to the amount of tiles would negatively affect performance even when you are seeing a small subset of them (i.e. staring away from city center).
And so, the only viable games are steep-angle isometric games (RPG or RTS with limited camera). Think NWN, Dungeon Siege, Starcraft 2 etc.
- Try to lower camera angle or go TPP and the poor culling performance and no viable caching will destroy your performance
- Try to go FPS and the player will quickly notice the low-poly tiles and low-rez textures
- Try to make a racing game and enjoy your 90 degree turns everywhere.