DEAD SECRET makes careful use of light mapping to control the mood and tone of each room. Careful manipulation of light and darkness was one of our key tasks in building the game, and Art Director Mike spent almost as long on lighting our scenes as he did building them. We structured our lights and light maps very carefully to produce subtle lighting and also maximize rendering efficiency. In the end we were pretty happy with the result. Then we upgraded the project to Unity 5.

You may have read about other developers who spent a lot of time and money on upgrading to Unity 5, mostly because of changes to the lighting system. Unity 5 completely replaces the light mapping system used in previous versions (Autodesk’s Beast) with a new lighting system (Geomerics Enlighten) that specializes in realtime global illumination. We had heard horror stories from other developers who attempted the transition of large projects to Unity 5, and so we waited, hoping that the issues would be worked out in time. By all accounts the Unity team spent 2015 burning the midnight oil to fix bugs and improve workflows in particles, physics, performance, and lighting. But, over a year since the release of Unity 5, transitioning a large project to Enlighten is still a pretty brutal experience. Here’s how we did it.

DEAD SECRET Lighting Under Unity 4

Dead Secret’s scene is organized around the following constraints:

Scene loading is unacceptably slow, especially on mobile platforms. Therefore the entire game must be implemented within a single scene, which we’ll load up once at startup. Because scene loading is slow we need to be able to instantaneously swap light maps in order to implement a transition from daytime to nighttime.

Performance is highly dependent on batching, and to maintain maximum batching efficiency we want a small number of very large light map textures.

Almost all lights are static, but we also have a few realtime, moving lights as well (e.g. a flashlight). Almost all geometry is static.

Some lights should cast both into light maps and create dynamic shadows for non-lightmapped objects (a “Mixed” light).

Given those requirements, our Unity 4 implementation in Dead Secret looked like this:

A single scene, full of static geometry with tons of lights, all set to Baked and sorted into buckets of Daytime Lights, Nighttime Lights, or Both.

One or two important lights in each scene set to Mixed for real-time shadow casting.

A custom culling system (described here) that turned lights on and off depending on where the player was standing.

A complicated Beast settings XML file (authored via the excellent Lightmapping Extended tool) for daytime light settings, another for nighttime settings.

An editor script that could set the Daytime ambient light color, move the correct Beast.xml file to the proper place in the file system, turn on the right set of lights, kick off a light map bake and then, when it was finished, move all the generated light map textures into a different folder and kick off another bake for Night.

A runtime script that could, in a single frame, change active lights between day and night sets, swap out the textures being used for light mapping (via LightmapSettings), set the proper LightProbes, and change the ambient light.

This gave us pretty good results. We got our huge scene down to seven 4096×4096 light maps, which accommodated our batching requirements. We could dynamically swap between day and night and see the lights, ambient, and light maps instantly change. Because almost everything was static and baked the runtime cost was low enough for us to hit 60 fps in VR on mobile platforms. It looked good and we were pretty happy with it.

Though the final results were good, rendering light maps in this way had two major problems in Unity 4.

First, rendering light maps was slow. Like, really slow. 20 to 30 hours on my work machine to render both day and night maps. This wouldn’t have been so bad except that when light mapping completes the scene file is modified. Since the entire game is implemented in one scene, nobody on the team could do any work while light mapping was running.

Second, having 14 4096×4096 textures in your game (along with everything else) was too much for Unity 4’s 4 GB of addressable memory. As a 32-bit application, the large light maps caused the Unity editor to crash all the time. Now, a 4096 texture uncompressed with mip maps is about 85 mb, and with 14 of these you’re talking about over a gig of memory. Still, it was annoying. To continue working we had to drop the resolution of the light maps to 1024 and then write a command line build script that resized the textures, made a build, and then sized them back down, all without every initializing the graphics system to avoid extra memory overhead.

With those caveats aside, lighting in Unity 4 worked well. We shipped on Gear VR in October 2015 based on Unity 4. But, for PC and upcoming PS4 releases, we knew we needed to finally ditch Unity 4 and move on to 5.

Unity 5 Lighting Woes

The good news was that, other than lighting, almost everything about our project worked without modification under Unity 5. We had a couple of scripts that needed modification, and the transition exposed a few race conditions in the game that hadn’t manifested earlier. But DEAD SECRET was playable under Unity 5 after just a day or two of work.

Lighting, on the other hand, was pretty busted. Over the course of several months we worked to recreate the lighting quality we had in Unity 4 using Enlighten, and the road was not easy. Along the way I filed more bugs against Unity than I have in the five years of Unity game development. Not only is Unity 5 lighting different than its predecessor, it’s still a work in progress. The main challenges we face under Unity 5 are:

Light map rendering is, for our scene, about 5x slower than Unity 4 was for the same scene. That’s almost a week of render time on my work machine. We bought a new computer just to bake light maps.

than Unity 4 was for the same scene. That’s almost a week of render time on my work machine. We bought a new computer just to bake light maps. Mixed lights do not work properly (case #750836). To cast dynamic shadows against light mapped surfaces in DEAD SECRET we end up lighting all of the geometry twice at rather enormous frame time cost. We can only get away with it because the game is so efficient in other areas.

properly (case #750836). To cast dynamic shadows against light mapped surfaces in DEAD SECRET we end up lighting all of the geometry twice at rather enormous frame time cost. We can only get away with it because the game is so efficient in other areas. Though light map information is no longer stored in the scene (good!), it’s now stored in an opaque structure called LightingData, which overrides scene parameters and limits the control we have over our scene (bad!). LightingData only stores information relevant to the last bake. In particular, it stores which lights were applied to the lightmap and which were not. This as a number of bad side-effects: Changing a light from baked to realtime has no immediate effect (case #758744, closed as “by design”). This means you can’t see what lights will look like, even in real time, without kicking off another bake (which, as above, is hours or days of your life gone). You can no longer create multiple sets of light maps from different sets of lights in the same scene. Rendering multiple light map passes causes information about which lights were used in the bake, now stored only in LightingData, to be lost. This means that even if you swap light map textures at runtime, some of your lights will behave as unbaked realtime lights because they don’t know that they were accounted for in a previous bake. To work around this we actually have to bake lights in three passes now: once for Day, once for Night, and once with all lights on just to generate a LightingData struct that works. This also means we can’t see what our lighting looks like in the scene view any longer. Light.alreadyLightmapped, which ostensibly serves to control which lights were baked into the current set of light maps, is overridden by LightingData, making it useless.

“Ambient” light isn’t actually ambient in Unity 5 (case #753023). In Unity 4, ambient light is just a color modification applied to all pixels, which allows you to control the minimum darkness of a scene. In Unity 5, ambient behaves as if there is glowing sphere around the outside of the world, with light emitting from it equally across its surface. The result is that ambient light is occluded by geometry: if you make a box and put the camera inside it, it will be absolute black regardless of the ambient light color or intensity. This significantly changed the look of DEAD SECRET, and we struggled for months to undo it. In the end the solution was to hack old-school ambient back into the standard shader. It’s dumb: the standard shader supports all kinds of different lighting modes, controlled by #ifdefs, and adding support for “legacy” ambient is only a one- or two-line change. Unity could easily support old-style ambient the way it supports other lighting modes. When I asked them about it I was told, “old ambient was a hack, that’s not how lighting really works,” which I thought was a pretty ignorant answer. I don’t care how lighting “really works,” I care about realizing the art style my art director has selected. I care about compatibility with years of development spent in Unity 4. For new projects there are some advantages to the new ambient lighting scheme, but failing to support the old system cost us several months of dev time.

(case #753023). In Unity 4, ambient light is just a color modification applied to all pixels, which allows you to control the minimum darkness of a scene. In Unity 5, ambient behaves as if there is glowing sphere around the outside of the world, with light emitting from it equally across its surface. The result is that ambient light is occluded by geometry: if you make a box and put the camera inside it, it will be absolute black regardless of the ambient light color or intensity. This significantly changed the look of DEAD SECRET, and we struggled for months to undo it. In the end the solution was to hack old-school ambient back into the standard shader. It’s dumb: the standard shader supports all kinds of different lighting modes, controlled by #ifdefs, and adding support for “legacy” ambient is only a one- or two-line change. Unity could easily support old-style ambient the way it supports other lighting modes. When I asked them about it I was told, “old ambient was a hack, that’s not how lighting really works,” which I thought was a pretty ignorant answer. I don’t care how lighting “really works,” I care about realizing the art style my art director has selected. I care about compatibility with years of development spent in Unity 4. For new projects there are some advantages to the new ambient lighting scheme, but failing to support the old system cost us several months of dev time. Lighting is just sort of generally busted in Unity 5. I filed bugs about light mapping overwritting finalgbuffer shader output when rendering in deferred (case #757945), that LightmapEditorSettings.resolution has changed meaning from “baked resolution” to “indirect resolution” (case #753022), which caused our baking tools to set insanely high indirect resolution values and hang the light mapper. There used to be a way to toggle light maps on and off in the scene view, but that’s gone now. The errors that the light mapping tool generate only make sense if you happen to be an expert in what the heck Enlighten does. Do you know what it means when there’s a “light transport” error? Or what is happening when it sits on the “clustering” phase for 36 hours straight? I’m sure there are experts out there who get this, but it’s certainly not documented and the errors themselves don’t give a whole lot of hints.

On the upside, Unity 5 is a 64-bit app and doesn’t crash because of large light map textures any more. But our lighting takes longer, took us months to figure out the proper setup for, and it looks significantly worse than the Unity 4 build of the same scene. The realtime GI features of Enlighten look nice but as a mobile and VR developer, I have no use for them today and am unlikely to have any use for them at any time in the next few years. Therefore my conclusion is that the move from Beast to Enlighten has been, for developers like ourselves, a disaster.

I do think that Unity understands that the situation isn’t good. I’ve been told that mixed lights are expected to work again in Unity 5.4. I’ll find out if that’s true when 5.4 comes out of beta and becomes the stable branch. A new light mapper was announced at GDC this year, and the demo they showed was impressive. But since there was no hint of a release date I expect it won’t be usable for at least a year. Going forward, we won’t need to do the Unity 4 -> Unity 5 transition ever again (and, per this experience, the cost/benefit of upgrading our other old games is deeply negative, so those games are effectively deprecated). New games written against Unity 5 (including our next super-secret mobile VR project) should be easier to manage. Maybe one day Unity scene loading on mobile will get fast enough that I can actually use multiple scenes without significant loads between them, which would ease the burden put on the light mapping system.

Speaking with other developers, a bunch of folks have similar issues with Unity 5’s new approach to lighting. Some are doing their light baking outside of Unity, and a few have gone so far as to implement their own light mappers. Folks using the realtime GI stuff also have complaints, although theirs are different. I suspect the lighting team at Unity is under significant pressure from a bunch of different sources, and I don’t envy that position. Here’s hoping it the situation improves soon.