Terrain lighting & shadows, and the road towards it
People have been asking: why built-in shadows don’t work on Unity’s terrain? (here, here, …) Yes, right now (Unity 2.0.2) they don’t. Why — because we didn’t have time to make them work yet. Both terrain and built-in shadows are new features in Unity 2.0, and those two don’t happen to just work together.
Here’s a glimpse into what is needed to get it working.
The Plan, straight from our internal planning wiki, with some explanations added:
o) Convert all terrain code to use Intermediate Mode Render Calls. This makes all the points below possible, and makes things like Projectors or wireframe terrain rendering in scene view pretty much just work.
o) Update terrain shaders for proper rendering order and soft vegetation. Expand ShaderLab so that a shader pass can indicate it should only be executed when Soft Vegetation is on.
o) Proper vertex lights on terrain, implemented in a vertex shader. Currently terrain handles up to four directional lights; we need to expand it to handle point lights as well. I think we’ll treat spot lights (when in vertex lit mode) as point lights; as calculating multiple spot lights in a vertex shader is next to impossible because of instruction count limits. Spotlights are not very useful in vertex lighting mode, right?
o) Implemented pixel lit shaders for terrain. This will get light cookies, per-pixel lit terrain, and ability for terrain to receive shadows.
o) Add option somewhere for pixel or vertex lit terrain (defaults to vertex lit).
o) Proper vertex lights on trees. Very similar to «vertex lights on terrain» point above.
o) Make tree shaders able to cast shadows. Because trees are scaled & animated in a vertex shader, the shadow-casting shader needs to apply the same animation as well.
o) Make Projectors not affect grass / trees. How? We’ll expand ShaderLab so a any shader can say «no Projectors on me, thanks», and modify grass & tree shaders to do that.
o) Make projectors not affect terrain when game content is built with Unity 2.0. This is web player backwards compatibility — there are Unity games out there that use projectors and terrains, and they should continue functioning exactly like they did before. So Projectors should not affect terrain in this case.
o) Make tree shadows not
s*ck when they are squashed for billboarding. Trees are squashed into a planar shape when they approach billboards; this is to make the mesh-to-billboard transition less noticeable. However, this makes shadows from those trees look very wrong. Not sure how to handle this; I think we’ll just stop casting shadows when trees are starting to squash.
o) Redo Lightmapped terrain shaders:
- Use existing shaders for game content built with Unity 2.0. Again, existing web player games should work like they did before! For reference, in Unity 2.0.2 lightmapped terrain was not affected by any lights at all.
- Make new set of terrain shaders that follows the convention of other Lightmapped shaders: lightmap, plus additional lights on top. Whoa, hey, we need two new sets of those shaders: for vertex-lit terrain and for pixel-lit terrain!
o) Optimize the bejesus out of rendering internals. Why — because the old terrain rendering code was like this:
- Figure out which parts of terrain and vegetation are visible.
- Set terrain shader.
- For each terrain chunk: draw it’s mesh!
- Set tree shader.
- For each tree: draw it’s mesh!
- …similar for billboarded trees, grass, and so on.
This is all simple & nice. Of course this was exactly the problem why shadows, projectors and such didn’t work on terrain — everything was just rendered immediately. Now, the new terrain code just submits any visible parts of terrain as Intermediate Renderers, and the rendering code figures the rest out… But that means it’s going through generic rendering «pipeline», that has to figure out lighting, sort out objects, set shaders and their parameters, etc. The major overhead in switching to this «proper rendering» is the lost ability to do this:
- Set the shader.
- Draw lots and lots of objects with it.
The rendering code in Unity does sort objects by shader, and does ultimately figure out that they are using same shader, possibly same set of textures, even possibly same set of lights and same set of material parameters — but some time is spent just figuring out that nothing actually changed between the objects.
Well, the upside is that optimizing rendering internals makes everything else run somewhat faster as well!
(I am here right now… the points above are done, the points below are on the list)
o) Make hills/trees outside of view be able to cast shadows (how?)
o) Expand shaders of detail meshes so they can cast shadows.
o) Optimize terrain & tree vertex lit shaders. Right now they process four vertex lights, where each can be directional or point light. This is noticeably slower than old shaders, that only supported directional lights (point lights need more calculations). We need to write multiple combinations of those shaders, specialized for specific situations («one directional light», «two direcitonal lights», «two arbitrary lights», and so on).
o) Add option somewhere for terrain to not cast shadows.
o) Add option somewhere for trees to not cast shadows.
o) Add option somewhere for detail objects to not cast shadows.
o) (maybe, don’t know how to do) Option for grass to receive shadows.
o) Reduce memory allocations in terrain code. Because of intermediate rendering, some of temporary arrays are not actually needed anymore.
Whew, that was quite a list! I’m not sure I’m able to even read through all of that… well, back to work.