Unity Lightmaps

Creating a shader in Unity that receives lightmap data is harder than you would think… Here are three tips.

Code: https://gist.github.com/jimfleming/5937437

1. Surface shaders won’t work

The first thing to know is that you must use a vertex/fragment shader and not a surface shader. Unity’s ShaderLab syntax will also work correctly, albeit slightly differently.

This is because, in Unity, surface shaders are “expanded” into vertex/fragment shaders. When this happens the necessary lightmapping variables are defined “for you”. Because of this, either you get an error for not defining them when you try to access them (during the expansion step) or you get an error for redefining them (during the final shader compile step).

2. Unity’s unity_LightmapST is misnamed

Another detail is that you must use the correct variables that Unity sets aside for passing in lightmap data:

sampler2D unity_Lightmap;
float4 unity_LightmapST;

That second variable holds the lightmaps atlas data. Its name is important because its named incorrectly. Normally variables post-fixed with

_ST will be populated by Unity with the associated shader property’s atlas. The lightmap atlas, however, leaves out the underscore. This is problematic since one of the useful transformation functions (

TRANSFORM_TEX defined in UnityCG.cginc) expects the underscore:

#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

Because of this you need to write this part by hand, as such:

o.uv1 = i.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;

Note, with many shaders in Unity it is helpful to include this line which makes some helper functions available for working with lightmaps:

#include "UnityCG.cginc"

3. Use the decoder function Unity provides

The next important step is to “decode” the lightmap data correctly. The varies by platform but Unity provides a handy function to do this for you:

// Decodes lightmaps:
// - doubleLDR encoded on GLES
// - RGBM encoded with range [0;8] on other platforms using surface shaders
inline fixed3 DecodeLightmap(fixed4 color) {
    #if defined(SHADER_API_GLES) && defined(SHADER_API_MOBILE)
        return 2.0 * color.rgb;
        return (8.0 * color.a) * color.rgb;

This function takes the color value of the lightmap pixel in our fragment shader and modifies it according to the platform.

That’s it for now. Hope it helped.