Barking Mouse Studio

Developer Blog

Jul 7

Using Unity Lightmap Data in Shaders

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;
    #else
        return (8.0 * color.a) * color.rgb;
    #endif
}

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.

Recently, we needed to dynamically add a BoxCollider on each level which sounds easy enough but ended up being kinda tricky*. The game object didn’t have it’s own mesh, so the collider needed to auto-size based on its children and Unity doesn’t do that automatically. We ended up using Unity’s Bounds to describe the size of our collider, and discovered a few tricks.

Here’s a Gist of the complete code (only 10 loc, woot!): https://gist.github.com/jimfleming/5855904

First we create the collider on our parent transform (whatever contains the child meshes):

BoxCollider collider = gameObject.AddComponent<BoxCollider>();

The BoxCollider has two main properties we’re interested in: center, a Vector3, represents the center of the box, measured in the object’s local space. size, also a Vector3, represents the size of the box, and also measured in the object’s local space.

With a collider we need to set its size and center properties, keeping in mind that these should be in the parent transforms local space.

To do this we’ll use Bounds which is an abstract representation of a box and has the same size and center properties of our BoxCollider but, unlike the collider, they don’t need to be in a particular space (local or world). Bounds is handy since it includes an Encapsulate function which will grow the bounds to include other bounds.

So, we initialize a couple of Bounds. The first will be what we’ll size our parent collider to. The second will represent the bounds of a child as we iterate through each of them:

Bounds parentBounds = new Bounds(Vector3.zero, Vector3.zero);
Bounds childBounds = new Bounds(Vector3.zero, Vector3.zero);

Now we need to iterate through our children’s meshes. The easiest way to do this is by getting the children’s MeshFilter components:

MeshFilter[] meshFilters = transform.GetComponentsInChildren<MeshFilter>();
foreach (MeshFilter meshFilter in meshFilters) {
    // ...
}

For the center property of a child’s bounds we’re going to use use the mesh filter renderer bound’s center. We do this since renderer bounds are in world space and we need to know where the child piece is located relative to the parent. If they were in the child’s local space, we wouldn’t know the bounds relationship to the parent. We convert this position to the parent transform’s local space (the space they’ll need to be in for our collider):

childBounds.center = transform.InverseTransformPoint(meshFilter.renderer.bounds.center);

Then we use the child’s mesh bounds for size which is in the child’s local space:

childBounds.size = meshFilter.sharedMesh.bounds.size;

And finally expand our parent’s bounds to include the child’s:

parentBounds.Encapsulate(temp);

Now that our bounds are set up we can use them to size and center our BoxCollider:

collider.size = bounds.size;
collider.center = bounds.center;

And that’s it! Now we have a box collider sized to fix our level, including each of its children pieces.

*Being kinda tricky is also why the game that we thought would take 3 months has taken a year…

I made a video of the new help dialogue and the new turn undos in action.

Now, when you undo a turn the toy resets to its original position. It’s a small change, but the effect is better then I expected it to be. Seeing the turn undo while the whole toy spins around is kinda cool.

The toy rotation code is pretty simple:

private IEnumerator EasedUndoRotation() {
  rotateSet = false;
  initialRotation = transform.localRotation;
  initialPosition = transform.localPosition;

  float time = 0;
  while (time < duration) {
    time += Time.deltaTime;
    transform.localRotation = Quaternion.Slerp(initialRotation, 
            Quaternion.identity, time / duration);
    transform.localPosition = Vector3.Slerp(initialPosition, 
            Vector3.zero, time / duration);
    yield return null;
  }

  transform.localRotation = Quaternion.identity;
  transform.localPosition = Vector3.zero;
  rotateSet = true;
}

(Source: vimeo.com)

I added another level to chapter two today. Once I have all the assets, levels take about 1 - 1.5 hours to setup and test. I’m manually setting all the rotations in unity. We’re using our own custom level editor. For all future games, I really really need to automate the process.

I spent the morning using quick time to take screen capture videos while I solved all the levels. It&#8217;s pretty easy if you have a mac.
1. With the game running full screen in Unity, open Quicktime Player 
2. Select File -&gt; New Screen Recording
3. Click the record button
4. Click the screen one more time to record full screen
5. Record till the level is solved (or I goof up) and press stop
6. Upload to my vimeo account

I spent the morning using quick time to take screen capture videos while I solved all the levels. It’s pretty easy if you have a mac.

1. With the game running full screen in Unity, open Quicktime Player 

2. Select File -> New Screen Recording

3. Click the record button

4. Click the screen one more time to record full screen

5. Record till the level is solved (or I goof up) and press stop

6. Upload to my vimeo account

I wish I had older video, but this is the very first I took of the game. It was taken back in January right after the first time I was able to play through a level successfully.

Most recent game play video.  Improved sound and rotations.