Tuesday, May 28, 2013

Cubemapped Environment Probes: Source Engine-style cubemap implementation in Unity 4 Pro

I wanted a static baked-in solution for doing cubemap-based reflections in Unity. Using cubemaps instead of (or with) traditional Blinn-Phong specular is great for games if (a) the light sources / environments stay static, and if (b) the player's camera will frequently be close enough to see small surface details. If that sounds like your game, maybe baked cubemaps are the way to go for you.

So my workflow looks like this: you put "env_cubemaps" around your level, bake the cubemaps using a Unity Editor command, and then put an "env_cubemap_catcher" on your models that'll look around for the nearest cubemap and use that for reflections. I also added an optional line-of-sight check, because you'd probably want a character to ignore cubemaps on the other side of a wall, but the raycasts do end up making it more expensive.

There's a lot more optimization you could do with this (cache the current cubemap and check that first, so you can ignore anything further away, etc.) or even new features to add (real-time cubemaps that auto-bake themselves, etc.) but I think the system holds up okay and is good enough to share.

NOTE: Because I'm using a built-in Camera.RenderToCubemap() function, this script will work only in Unity Pro. There's a way to get around this for Unity Free though, by manually setting up 6 camera angles and taking a series of Application.CaptureScreenshot(), then cropping selectively with Texture2D.GetPixels() / Texture2D.SetPixels(), all while taking field of view / distortion into account, etc. That sounds like a lot of work, so I didn't do it this way, but it's certainly possible.

But let's assume you have Unity 4 Pro. Here's what to do:
  • Put the EnvCubemap component on an empty GameObject in your scene. You'll probably want to make this a prefab, then duplicate it a bunch and put them all in differently-lit areas.
  • Put the EnvCubemapBake script in an /Editor/ folder anywhere in your /Assets/ folder. Then, in the menu, go to Cubemaps >> Bake All Static Cubemaps.
  • Put the EnvCubemapCatcher script on your Mesh Renderer or Skinned Mesh Renderer. It'll grab a list of all EnvCubemaps in the scene, then every now and then it'll go through the list and pick out the closest one, and pass the cubemap into its material.