Thursday 5 March 2015

The Trouble with Mipmaps

In recent weeks I've been experimenting with developing content for the Google Cardboard VR system using Google's SDK plug-in for the Unity game engine.  For this job I've created a test room with some simple objects including chairs and a table.

For the table I decided to take advantage of symmetry to repeat texture elements, ensuring a good optical resolution across the full surface of the object.  This made sense, because the player would get close-up, and it added only a handful of extra polygons.

The table model (with fake shadow).  The table on the right illustrates the position of the polygon split.
As you can see, the table is split across the middle, allowing the UV map elements from one end to be flipped and overlaid over the other end.  Thus texturing one half of the table automatically textures the other half in mirror-image.  (I could have taken this further and cut it into quarters, but felt this would have made the symmetry-repetition too obvious to the eye.)

Optimising further, I therefore needed to texture only half of the table-top, a single leg, and one end/side panel.  The texture has baked-in ambient occlusion, too.

All well-and-good, but when I planted the table in the game engine a nasty seam appeared across the middle of the table, which got worse at certain viewing angles.


At first I thought this was a glitch from not sewing the UV sections properly.  However, after checking the original model and moving the camera away from the table, I realised that the cause was an old problem I'd first encountered last year: mipmaps.

For those not familiar with the concept, mipmaps (or MIP maps if you're a pedantic etymologist) are a tool used by game engines to reduce the 'cost' of drawing a 3D scene.  Applying a full-sized version of the texture is wasteful if the object is too far away to see its detail; therefore, the engine generates pre-rendered half-size versions of the texture which are used instead.

Original texture (256x256) with successive half-sized versions. Collectively, this set of textures takes up 33% more memory than the original texture. (Image adapted from Tom's Hardware Guide)
This illustrations indicates where different textures are used at different distances.
In principle mipmaps are a really useful tool for optimising performance.  However, they do have a nasty habit of killing sharp edges on textures.  I first encountered this when developing a modular road-building system for Unity.

On this section of road you can see where the central lines are blurred in the distance.  Far-away sections are textured with a lower-resolution image (i.e. black and white pixels averaged to grey).

In the case of the table, the "seam" edge of the tabletop was being merged with the black pixels nearby as the resolution decreased.

Room (distorted by VR camera) with table seam visible.

The easiest way to solve the problem is to disable mipmaps.  This can be done on Unity via the texture's settings.


However, disabling mipmaps means a greater cost when rendering.  On a mobile device that is a significant problem; after all, mipmaps were invented (and are enabled by default) for a reason.

In this case, thankfully, the answer was simple: to overlap the wood texture well beyond the seam edge to ensure that it averages to a wood colour even at lower resolutions.

Look!  Still using mipmaps but no seam, thanks to better UV texture mapping!
It just goes to show that it's good to be careful when matching texture patterns to UV maps: a couple of pixels overhang is not enough in critical situations -- a lesson learned the hard way.