Monday 20 October 2014

2D Lights and Normal Maps with Unity 4.6 UGUI

Hello world!

So I recently tried out the new Unity 4.6 beta. That's actually an understatement since I'm now using it fulltime for development of 6x Mass Production!

It's really good! If anyone knows or has used NGUI they'll know just how complete a UI system that is. They also might know that the main developer behind NGUI was involved in development of the Unity GUI system, and it shows! The coding and component style is very similar! I've decided to switch to uGUI completely now as a result. Slight risk on my part since it might be in beta for a while but 6x Mass Production is going to be in beta for a while so I'm going to run with it.

But! The reason I post here now it to show off something cool that was far easier in UGUI than NGUI, shader tricks! Well, at least this shader trick!



So there are no 3D elements in the game, just 2D elements, a shader, one directional light and some point lights. This is nothing new, but I'm so stoked that this worked out so well that I wanted to share what I did. I did briefly try to get this working with the Unity 2D sprite component... when I say briefly I mean 10 minutes, didn't quite get it but I'm sure it's possible too!

Also note that I might be butchering terminology, leave a comment if anything bugs you in this regard ^_^

Anyway, for UGUI:

Step 1: You'll need your texture and a bump map

So this texture is already shaded, the artist didn't know I'd be using dynamic lights

5 minutes to hack this up in Photoshop

You'll want to make sure that the normal map is setup correctly:
The important part is to turn on the "Create from Grayscale" option. If you don't know, basically the "Create from Grayscale" option turns the grey heightmap into a normal map



Step 2: Create the custom shader. This following code is almost exactly the same as the standard Unity Diffuse Sprite shader, besides 3 lines

Shader "Custom/Sprites/Bumped"
{
 Properties
 {
  [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
  _BumpMap ("Normalmap", 2D) = "bump" {}
  _Color ("Tint", Color) = (1,1,1,1)
  [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
 }

 SubShader
 {
  Tags
  { 
   "Queue"="Transparent" 
   "IgnoreProjector"="True" 
   "RenderType"="Transparent" 
   "PreviewType"="Plane"
   "CanUseSpriteAtlas"="True"
  }

  Cull Off
  Lighting Off
  ZWrite Off
  Fog { Mode Off }
  Blend One OneMinusSrcAlpha

  CGPROGRAM
  #pragma surface surf Lambert vertex:vert
  #pragma multi_compile DUMMY PIXELSNAP_ON

  sampler2D _MainTex;
  sampler2D _BumpMap;
  fixed4 _Color;

  struct Input
  {
   float2 uv_MainTex;
   float2 uv_BumpMap;
   fixed4 color;
  };
  
  void vert (inout appdata_full v, out Input o)
  {
   #if defined(PIXELSNAP_ON) && !defined(SHADER_API_FLASH)
   v.vertex = UnityPixelSnap (v.vertex);
   #endif
   v.normal = float3(0,0,-1);
   
   UNITY_INITIALIZE_OUTPUT(Input, o);
   o.color = v.color * _Color;
  }

  void surf (Input IN, inout SurfaceOutput o)
  {
   fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * IN.color;
   o.Albedo = c.rgb * c.a;
   o.Alpha = c.a;
   o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
  }
  ENDCG
 }

Fallback "Transparent/VertexLit"
}

Sorry for the lack of syntax highlighting, couldn't get blogger to play nice :/


Step 3: Create a bump map material with your normal/bump map
You'll see that I use "Custom/Sprites/Bumped" this is the shader I pasted above

Step 4: Create a Unity Image component with your image and custom bump map material



Step 5: Add lights, add more sprites make them do things and BOOM!




Ok, so this isn't an all encompassing tutorial or anything but I thought I'd share because I thought it looked cool and really wasn't too much effort to set up! There are some issues that might need addressing:

  1. Since we are using lights we are now constrained by the limitations that the light system imposes on us. So only a limited number of point lights (the default is 4 but you can set the number in the Unity quality settings)
  2. Extra draw calls! A direct result of using more lights means that more draw calls are needed to draw each light's contribution. This is usually fine for standalone but I suspect mobile might struggle a bit.
  3. If you are working from a sprite sheet and using unity to cut it up this method might not be workable. At least if it is I've not tried to make it work. Crux of the matter is that Unity's sprite atlasing parts and the normal mapping texture parts don't integrate nicely right now. Only tried for liek 10 minutes to get it to work so maybe someone has a smart solution out there. With NGUI I was able to map a normal map sheet to the image sheet with a similar shader, wonder if there's something similar I can do with UGUI...


That said, look at this shit! No 3D elements and it's looking pretty cool... well I think so anyway!

Oh, on another note! Keep an eye out, I'll have a playable version of 6x Mass Production for people to play soon, light effects and all! I should be working on the level design but I kinda meandered along this interesting light effect, game seems lifeless without it now :p