Binary Nightmare #4
Ghost in the Machine
One of the more interesting elements of programming is bug hunting. If something is wrong then you could say that there is a problem somewhere in the codebase. Finding out who is responsible for that particular bug can be a time consuming affair, along with establishing the exact cause of that bug.
We recently did some upgrades to Mimis, in order to support future art blog posts, from our perspective things were on track. An additional loading staging was added to allow for asset bundle support, giving us greater flexibility than our initial system gave, along with better memory usage and greater flexibility. This change allows a single version of Mimis to be used across the entire site, opposed to unique instances per page.
We try to keep our version of Unity as up to date as possible, due to the WebGL support being in it’s infancy. Unity Technologies released a relatively large update in December, this took WebGL out of preview status into a fully supported build target. We updated, and the result was that there were some relatively large changes to the way Unity now dealt with loading, very much improved but we quickly found that some work needed to be done with our website code to allow for the support we needed.
So we opted to not rush the changes and ensure that the system was solid before pushing forwards. Lots of changes later, along with some lovely illnesses due to the time of year. It was all up and running, stable and functional - The exception being that the results didn’t look right at all, the normal mapping was artifact central.
Borked, but Why?
Much head scratching and a little confusion which resulted in the entire codebase, web and C# script being combed for possible locations of bugs, along with some shader re-writing in order to isolate the normal mapping results.
As you can see - things look bad, and the data is clearly incorrect, eventually after some google fu I established the problem to actually be engine side - Unity had shipped with a bug, and there wasn’t much I could do about it, and it was specific to WebGL. The asset bundling wasn’t packaging normal maps correctly, and without the source code for the engine there were few options available to us.
- Unique builds per scene
- Remove normal mapping
- Use and earlier version of Unity, and roll-back all changes.
- Wait for a proper fix from Unity Technologies.
Rolling back our changes risked human error and would take us back to a less efficient result, only to then reintroduce the changes at a later date. Unique builds per scene would increase our server space usage, and basically not provide a flexible long term solution, and removing the normal mapping all together would have defeated the point of displaying the artwork in engine as close to our intended results and would require an update and changes later.
We opted to wait, the work we’d done was functional and better for the long term results, requiring much less work, and thus less prone to human error, and mean whatever we did deliver would be correct.
Mimis is now fully functional, there are still some small updates, but we will now be able to deliver content regularly through the use of this tool.
While we were waiting...
Whilst we were waiting on a fix for unity, I made some shader changes that greatly improve the quality of the normal maps, and reduced compression artifacts that are introduced by DXT1 compression on a standard RGB texture. These things occur as a result of how DXT compression works via approximating colours based off of interpolation of R,G, and B channels.
There is a well known technique which has been around for some years, that via the use of a little clever mathematics, and texture munging ensures that the results suffer from less artifacts. It’s entirely dependant upon a few simple principles.
DXT5 compression allows for better precision in both the green and alpha channel. By changing the texture, moving the Red channel into the Alpha channel, and leaving the Green channel in place then blanking out the blue channel to reconstructed in the shader the compression algorithm results in much better results as there is no shifting in the interpolation.
Reconstructing the blue channel in the shader isn’t difficult - as the guys over at Unity technologies have already done this for you! If you’re working natively you’re fine but on mobile platforms and WebGL, you’ll have to either undef it or alter the shaders.
If you’re inclined to add the changes manually it’s a very simple bit of code.
normal.xy = packednormal.wy * 2 - 1;
normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
That’s it, we live in an era now where the square root can easily be chewed through by modern graphics hardware. So go ahead, have better looking normal maps.