VNC Devlog # 3: Ship pipeline

In the last few months I've been designing the pipeline for producing high quality spaceship models for my Indie Game Von Neumann's Catastrophe.

I've been trying to find the right balance between low-poly/low-fi and realism. I'm deliberately eschewing full-baking processes with dedicated million+ poly models that get baked into high-resolution normal maps for a few reasons:

  1. Complicated linear process that makes changes costly. I'm just a one-man team so anything that saves me time and effort is a win.

  2. High impact on the number of draw-calls: having a dedicated set of baked textures for every single ship (and the materials to use these textures effectively) not only costs a lot of memory, but it limits how many different ships can be on screen at any one time.

  3. Making variations or modularity is hard.

So instead of that I've decided to make my own pipeline to create hard-surface spaceships. I've created a custom Shader in both Unity and Blender that allows me to render a potentially infinite amount of different ships all from just one single Material and few textures. By using a UV unwrapping process called "using a trimsheet", I can select various parts of my models and give their surfaces pre-defined characteristics (albedo, metallicity, roughness/smoothness, emission) by sampling 2 small 128x128 textures: one for the base colour, and the other with 3 channel masks (i.e. RGB). I use one further texture, which is a normal map with a tiling panel-like structure. This texture is overlaid on the final result by using an additional UV-map on top of the default one.

Custom Shader in Blender. I managed to create the exact same shader in Unity's URP using Shadergraph.

Lightmaps

The one thing about space is that it always has a very stark lighting scheme (i.e. one very, very bright infinite distance pointlight). Many games rely on Screen Space Reflections or baked cubemaps to give rendered objects the appearance of being indirectly lit from all sides, but in space, unless you're flying very close to a celestial body, the reflection in most cases is almost 99% pure black. I could fix this by adding unrealistically colourful nebulae, but I'm making a hard-sci-fi game set in our galactic neighbourhood, so that just wouldn't do.

Instead, to make the dark side of my spaceships more interesting to look at, I added lots of emissive decals, like window lights. However, something about this seemed wrong when I looked at it, and then it hit me: the lights are not illuminating anything. So I fixed this:

Left: no bounce lights
Right: hand-painted bounce lights

There are a couple of approaches I could have taken to rectify this: the correct, hard way, or the artistic shortcut.

The hard way would involve me manually baking a lightmap texture for every single ship, which would ruin the reusability of my materials (since this lightmap would be unique for every single ship). It would be 100% physically accurate, but making changes later would be difficult and would require a lot of extra work.

The artistic way I devised involves using Vertex Colors. For every single point in a 3D model, we store not just its position (xyz) but can also give it a color value (rgb). In Blender you can author this information by literally painting your mesh's surface. The color between is vertex is interpolated, creating the appearance of a smooth gradient across the surface.

This way, I can manually paint in the editor which parts of a spaceship should be lit up by bounce lighting, and since this information is unique to the mesh, it allows me to keep using just a single material for all ships, space stations, etc. and yet each can look unique.

Unity Pipeline

I've made the exact same shader for both Unity and Blender so when I'm creating assets in one it'll look exactly the same as in the other. Every part of this ship is a separate mesh and I can even (re-)mix & match these components in Unity afterwards. (e.g. to create variants of ships)

When I import such a model into Unity I make sure every part is well-named using my own conventions. I then arrange the imported meshes in the Editor and group them by their LOD (Level Of Detail). Using a third party asset called Mesh Baker, I combine all the separate meshes into a single big one, or rather, between 3-4 big ones, one for each LOD.

The highest quality models have between 20-70 thousand triangles, while the lowest have less than 100.

Once this pipeline has been configured for a ship, almost all future changes will be automatically imported.