Programmer’s Weekly: So you like them shadows?

Written by: Tomas Sala

So you like them shadows?

As you might know Unity3D now supports realtime shadows on mobile devices, woot!  For those interested here’s the official announcement of 4.2: http://blogs.unity3d.com/2013/07/22/unity-4-2-has-arrived/

But, like us, your first response is probably: “That’s never going to fly in my mobile game, it’ll suck down frame-rate like nobody’s business!”, and you would be partly right. But it’s not the whole story. If used correctly and carefully it is possible to add a whole new layer of visual depth to your game.

First of we need to talk about what type of shadows unity3D allows on mobile devices. This blog post is not about shadow-maps, but about real-time shadows. This means shadows projected from a directional light onto your scene. Shadows that are rendered every frame, and thus dynamically respond to your scene and light..

Some basic rules

  • Only hard shadows are supported, (we can soften them up in the shader pass I’ll discuss later, but that’s not advisable processing wise)
  • Only 1 directional light is allowed in the scene.  
  • Basically, only low-resolution shadows are practical on most devices (even the nexus 7 (2nd gen, 2013) will take a substantial hit from medium resolution shadows)

Now a couple of main problems pop up, specific to most mobile games.

  • Most mobile games don’t use lights due to the added rendering cost, or are based on unlit atlassed textures, vertex colors or shaders optimized to work without light sources.
  • Draw Calls and batching, these related issues are a pain when doing mobile development. It just got a whole lot nastier with real-time shadows.

So in this week’s blog post we’ll be looking at solutions for each of these two problems.

Solution 1:  Differentiate between what needs to cast a shadow and what needs to receive one.

First off all the most basic rule is: Don’t use shadows where it’s not required, and know the difference between a receiver and a caster.

A shadow caster is an object that casts a shadow. You need to minimize the amount of these. The fewer casters, the fewer shadows and the faster the calculations are. This works down to detail. So if you have a building, make sure only the base structure casts a shadow. Don’t do the chimneys, windows, doors, and other details! They have no need to cast a shadow. Make sure they are turned of in the mesh render component in Unity3D.

shadow-caster

 

A shadow receiver is the object that receives the shadows. First of all, make sure that an object that receives shadows does NOT cast shadows. So separate ceilings from floors for instance, and have the floors be shadow receivers, but not the ceiling. (Or not even the walls. Remember, because you have only 1 directional light, it’s practical to make it a top-lit scene).

Now splitting up your objects might result in additional draw calls,but we’ll deal with those later.

Solution 2: Adding real-time shadows to an Unlit or custom-lit scene.  

So a basic trick transferred from the days of yore to modern mobile game development is the use of vertex colors. Vertex colors allow you to not just paint a model, but actually light it (with, for instance, a radiosity solution) and then save it or bake it into the vertex data of the model..

That will look something like this. A basic vertex colored lighting solution, merged with a custom shader.

vc_colors

This gives the Illusion of lights without using any. Add to this light-maps or pre-lighted textures and you get  a static object, that looks like its lit. Additionally, you can shape the visual style to be unrealistic, cartoony or anything else.

 

Now that your game artists have gone through all the effort to make something look nice without light just to save performance, it’s un-logical to simply add a light to have shadows. Basically a double whammy, the shadows need to be rendered, and the light needs to be calculated.

So why not just turn on shadows? 

Here at Little Chicken Game Company our artists write their own shaders in tools like the Strumpy Shader Editor. The disadvantage of this is that you export surface shaders. To turn on shadows, you need to create a shader that has an output to diffuse. This causes the shader to become lit: lighting your object and at the same time creating shadows. The moment you connect an input to emmisive to create an unlit shader, your shadows disappear. And we don’t want to light the object, we only want the shadows.

Solution: Add a renderpass to your unlit surface shader.

If you really want shadows, you’ll need to find a way to add shadows to your emmisive surface shader. (fragment shaders might be faster and simpler, but when you’re stuck to strumpy and don’t know how to code fragment shaders, this will do the trick)

So we’re going to add a shadow pass to an emmisive shader. I’ll be quick and just give the shader code for the second pass.

—————code————————————————————————————————–

Pass
{
Blend DstColor Zero
Fog
{ Mode Off
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#pragma fragmentoption ARB_precision_hint_fastest
#include “UnityCG.cginc“
#include “AutoLight.cginc“
struct appdata
{
fixed4 vertex : POSITION;
fixed4 color : COLOR;
};

struct v2f

{
fixed4 pos : SV_POSITION;
fixed4 color : TEXCOORD0;
LIGHTING_COORDS(1, 2)
};
v2f vert (appdata v)
{
v2f o;
o.pos = mul( UNITY_MATRIX_MVP, v.vertex);
o.color = 1;
TRANSFER_VERTEX_TO_FRAGMENT(o)
return o;
}
fixed4 frag(v2f i) : COLOR

{

fixed atten = LIGHT_ATTENUATION(i);

fixed4 c = i.color;
c.rgb *= atten;
return c;
}
ENDCG
}

—————code ends——————————————————————————————-

 Just paste this after the ENDCG of your regular shader code, in the un-compiled unity3d shader. Make sure you use  FallBack “VertexLit” at the end of your shader. (FallBack “VertexLit” will also enable shadows in your fragment shaders, it’s way easier)

Example, here I’ve only used this additional shading pass on the ground surface. On nothing else. All the shading and colors you see are not created by the directional light, but by the shader, combining fresnel effects, ramps and vertex colors. And finally a shadow pass.

sample_shadows

 

Solution 3:  Batching and drawcalls.

So we now have two things: shadows in our unlit scene and a selection of objects that cast shadows, and an even more limited number of objects that receive shadows.

Doing this separation tightly, will already decrease your drawcalls substantially compared to blindly turning on your directional light with shadows.

But there is an additional problem, materials that are still shadow capable are not batched. In the documentation it states that objects that cast or receive shadows are not batched. But the problem goes deeper, any material capable of casting shadows (or possibly, that is also used for shadows) is not batched!  So in Oberon’s Court I made the mistake of having one solid black material that could cast shadows.

This shader:

—————code————————————————————————————————–

Shader “Oberonscourt/Color”

{

Properties {
_Color (“Color“, Color) = (1,1,1)
}

SubShader {
Color [_Color]
Pass {}
}
Fallback “ VertexLit“, 1
}

—————code ends——————————————————————————————-

Now I stupidly assumed that using this shader on an object that did NOT cast or receive shadows would allow the object to be batched. This is not true! Even objects that do not cast or receive shadows but have shadows enabled in the shader will not batch. (so it seems, I could be wrong, but I’ve got the drawcalls to back it up)

So the final trick was to take any object that receives or casts no shadows, and make sure it had a material that had no shadow capabilities. In the sample case I removed the fallback vertexlit code from the shader

To optimize even further I changed the shader on materials that where on a shadow caster, but did not need to cast a shadow to a non-shadowed version of the same shader.  Practically this means the eyes, the mouth and other small props that where part of the skinned mesh. These are part of a shadow-casting mesh, but now no longer cast any shadows, and reduce the drawcalls. This is especially true for characters with many small un-skinned sub-parts.

Its probably more logical that any material with a shadow capable shader that is also used in an object that does NOT cast or receive shadow(so used also in non shadow, and shadowed objects). Causes the instance of the non casting material to be NOT batched.. (A hunch),,  The solution is the same no matter what the cause. Do not reuse a material you’ve used on a shadow caster or receiver, on an object that is not casting or receiving a shadow. Otherwise the non casting/receiving object will not be batched.

 

Conclusion

Having done these steps, and making sure that only a few objects actually cast shadows, I was able to create the environment for the game and have it run on most android 4.1+devices.  An added advantage by skipping the lighting and keeping the shadow reception shader unlit, I can now turn off the directional light and shadows, and it will look exactly the same (without shadows).  Which is great for an ingame settings menu for instance.

Do remember that shadows are now possible and performance can be maintained, but still if you expect anything less than a doubling of your drawcalls, you will be disappointed.

To finish it off here’s a side by side of with and without real-time shadows. I hope the above solutions and workflow will help you out in implementing shadows on mobile devices.

without-shadows


Cheers,
Tomas

207,048 total views, 64 views today

3 thoughts on “Programmer’s Weekly: So you like them shadows?

  1. Adding the shadows as a second pass also decreases performance a bit, because every object that uses it has to be rendered twice, and they won’t bath correctly.

    So you might not have to double your draw call, since the shadow pass only needs to render the objects that actually cast a shadow (from the light’s point of view). In some games actually use separate simplified geometry to cast shadows (one example I know of is Battlefront 2, on the Zero Game Engine).

    All that said, the number of draw calls is only really important for performance on the CPU. If the GPU is the bottleneck, you need to look into different optimizations. A relatively easy optimization is to convert strumpy shaders to handwritten ones. Strumpy tends to add a lot of unnecessary code. For example, when multiplying a vector with a single channel of a vector, it does stuff like this:

    float4 split0 = float4(originalVec.x, originalVec.y, originalVec.z, 1.0);
    float4 result = someVec4 * float(split0.y, split0.y, split.0, split0.y);

    in stead of:

    result = someVec4 * originalVec.y;

    Also: be careful when using the the code for the shadow pass posted above. It contains one ‘}’ too many, and the (”) characters should be (“) characters in stead (subtle difference probably caused by the blog software)

    • Thanks for the added info, Laurens!

      The extra bracket has been removed, but the quotes are still an issue. We’ll fix a code tag soon!

    • Nice one pointing out the copy paste error in the shader code. And indeed the second pass is a dirty edit. The best thing for the shader I made was to recode it to a simpler fragment shader. I’ve looked at shader forge, which looks like it will allow you to export to a fragment shader for your basic stuff.

      But since a lot of unity3d users and artist rely on strumpy, this should hopefully help them out..
      Regarding the strumpy optimization, Michiel Frankfort here at Little Chicken wrote a strumpy shader optimization tool that strips out much of the redundant code from your strumpy shader. Perhaps I can convince him to share the code for this in a future post..

      Cheers and thanks for the feedback,
      tomas

Leave a Reply to Little Chicken Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>