Saturday, February 15, 2014

Custom file templates for Unity+MonoDevelop

So after being rewriting the same code once and again and again from Unity's (and/or MonoDevelop's) templates just to start working on my own code, I decided to create my very own templates. Looking through the web I found a great addin by Robert 'Quazistax' Benko, open for everyone to play with. My needs and coding style are a bit different, and Unity has changed quite much since the day he wrote them (back in 2011).

You will notice there's an insane amount of comments in them. I'm a maniac when it comes to keep the code documented, and have been a long-time fan of Doxygen and similar documentation standards (.NET XML, JavaDoc...). By far most of the docs are copy-pasted from the official Unity docs, as it's nice to have a reminder of what does everything and their (many) caveats.

Most likely, I will update this addin from time to time as I see fit (probably to add recurring similar-but-different components, shaders, etc.)

Download

You can download the addin with the templates here.

Installation

On Windows, I've installed it in the folder Unity_Installation_Path/MonoDevelop/Addins. Just paste the folder contained in the package there and restart MonoDevelop.

On MacOSX I haven't tried yet. Quazistax states in his post that the installation folder is Contents/MacOS/lib/monodevelop/AddIns in the MonoDevelop application package.

For the details on the installation and how to solve certain (sporadic) problems refer to his original post.

How to use them

Under the file creation dialog in MonoDevelop, if the addin was successfully installed, you will find a new category to the left: Kencho\Unity\C#. Let me explain you what they're for and why.

  • C# class
    This is a simple minimalistic C# class. It's useful when you want to create your own types, particularly if you want them decoupled from Unity.
  • C# Attribute class
    To make your own C# attributes adding semantic meaning to your own types without adding coupling to Unity.
  • C# Exception class
    I think this is self-explanatory. Useful when you want to create your custom exception type to handle errors in runtime.
  • Unity MonoBehaviour (Full)
    A complete template for Unity components. This template will add handlers to all the messages a MonoBehaviour can receive. Most of the time you will only use a subset of them, so remove any handler that you won't need.
  • Unity MonoBehaviour (Advanced)
    This is a subset of the previous template. It only includes the handlers for commonly used messages, like the updating, enabling/disabling of the component, physics and user interaction (through the mouse). You'll want to use this template for the game object with a somewhat complex behavior.
  • Unity MonoBehaviour (Basic)
    An even more reduced subset of the previous template. Adds the most common operations (Awake, Start, Update and FixedUpdate), and is recommended for simplistic game objects, like particle systems or other non-interactive elements.
  • Unity MonoBehaviour (Very Basic)
    The most reduced subset of the MonoBehaviours. Even the documentation is reduced! The equivalent to the default Unity's "new script" template (using my style and encapsulating it into a namespace)
  • Unity Property attribute class
    It's a specialized attribute recognized by Unity (and thus, coupled to it). Useful to add custom semantic to MonoBehaviour properties (it's a new feature added with Unity 4 which also includes a few ones, like Range(min, max) to declare that a property should stay between two fixed values). Specially useful if used with a custom PropertyDrawer (see below).
  • [Editor] Unity Editor
    Base to create custom Unity editors (inspectors) for components. By default behaves like the default inspector. Classes based on this template can only be used in editor mode, and must be included in an Editor folder.
  • [Editor] Unity Editor Window
    To create custom editor windows, like utility windows or floating/panel windows to extend the functionality of the editor. Classes based on this template can only be used in editor mode, and must be included in an Editor folder.
  • [Editor] Unity Property Drawer
    This one comes in two variants. One is used to add a GUI to custom serializable classes that doesn't have an inspector themselves. The other is to add a GUI to properties tagged with a certain PropertyAttribute. It was introduced in Unity 4 and can read more about it here and here. Classes based on this template can only be used in editor mode, and must be included in an Editor folder.

Wednesday, January 15, 2014

Cel-shaded sprites in Unity3D 4.3 and higher

So a lot of people was requesting details or the actual implementation on how I got the cel-shading effect on the recently added Unity sprites as seen in this video:
Actually, it's not very different to any other cel-shaded materials in Unity. Given no such shader is provided by vanilla Unity, the very first thing to do is to create a new shader for this lighting model. I downloaded the sources for the built-in shaders and started working on my own shader from there.

The shader

Here's the final shader. I'll discuss the details of its implementation below.

CelShadingSpriteShader.shader

Shader "Sprites/CelShadingSpriteShader"
{
 Properties
 {
  [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
  _NormalsTex ("Sprite Normals", 2D) = "bump" {}
  _CelRamp ("Cel shading ramp", 2D) = "white" {}
  _Color ("Tint", Color) = (1,1,1,1)
  [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
 }

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

  Cull Off
  Lighting On
  ZWrite Off
  Fog { Mode Off }
  Blend SrcAlpha OneMinusSrcAlpha

  CGPROGRAM
  #pragma surface surf CustomLambert alpha vertex:vert
  #pragma multi_compile DUMMY PIXELSNAP_ON

  sampler2D _MainTex;
  sampler2D _NormalsTex;
  sampler2D _CelRamp;
  fixed4 _Color;

  struct Input
  {
   float2 uv_MainTex;
   fixed4 color;
  };
  
  half4 LightingCustomLambert (SurfaceOutput s, half3 lightDir, half3 viewDir, half atten) {
   half NdotL = dot (s.Normal, lightDir);
   half4 c;
   c.rgb = (s.Albedo * _LightColor0.rgb * (tex2D (_CelRamp, half2 (NdotL * 0.5 + 0.5, 0)))) * (atten * 2);
   c.a = s.Alpha;
   return c;
  }
  
  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);
   v.tangent = float4(-1, 0, 0, 1);
   
   UNITY_INITIALIZE_OUTPUT(Input, o);
   o.color = _Color * v.color;
  }

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

 Fallback "Transparent/VertexLit"
}

First of all, we'll need to add new parameters for the shader, that will allow us to pass a normal map and a toon ramp for the shading.

_NormalsTex ("Sprite Normals", 2D) = "bump" {}
_CelRamp ("Cel shading ramp", 2D) = "white" {}

Also, in order for the lighting calculations to be passed to our shader, lighting must be enabled:

Lighting On

The actual surface shader is pretty much copied from the surface shader lighting examples provided by Unity. I opted by specifying a custom Lambertian lighting model where the lighting values are read from the provided toon ramp instead of procedurally computed. Note in the shader preprocessor directives I also specified I'd use my own vertex shader function. This vertex shader function is almost identical to the one in the default sprite shader, though in this case, normal and tangent values for each vertex have to be provided by this function, as Unity's sprite implementation doesn't provide them; normals are pointing towards camera and tangents are towards left. These vectors are in object-space, and are required for the lighting calculations and normals unpacking.

v.normal = float3(0,0,-1);
v.tangent = float4(-1, 0, 0, 1);

Last, but not least, we also extract the surface shader normal component from the normal map in the surface shader:

o.Normal = UnpackNormal (tex2D (_NormalsTex, IN.uv_MainTex));

That would be everything about the shader itself. Let's go onto using the shader in a sprite.

Using the shader

Unfortunately for us, Unity doesn't allow to provide a per-sprite value other than its main texture and its color. There's a way to "override" this and manually add the normal map and the toon ramp per-renderable instead of per-material, but last time I tried, it wasn't being serialized. Hence why in our shader definition _NormalsTex and _CelRamp aren't preceded by the new [PerRenderable] annotation. So, for now, we have to manually create one material for each normals map. Nothing difficult here: new material, use our Sprites/CelShadingSpriteShader shader, and pass the normals and toon ramp textures. Then, we just need to tell the sprite renderer to use this material instead of the default one and we're ready to go.

But, I've hand-drawn my sprites and don't know how to do a normals map for them!

Fear not. I've also done a small tool to generate this map in case you're an artist and don't know how a normals map works.

The sprite normals map generation tool

This is a basic tool to generate a sprite normals map from two "lighting" textures. Whereas the sprite base texture should contain the flat colors (and its details) but no lighting information, these two textures should contain only the lighting information for the sprite; one of them with the horizontal lighting, and the other with the vertical lighting. In most programs like Photoshop or GIMP these three textures would normally be drawn in three different layers: one for the base colors, and each of the other two blended on it using "add" or "screen" as the blending function. However, while normally an artist would paint the lighting only on the lit side, in this case the unlit side must also be shaded.

For instance: the horizontal lighting map is painted like the sun is at the left. Pixels that are completely facing towards the sun (left) are painted in white. Pixels completely opposite to the sun (facing right) are painted in black. Pixels facing towards the artist (neither facing left nor right), in 50% gray, and the rest, blended in between. The vertical lighting map is done the same way, as if the sun is right above the sprite.

Now, the tool itself:

SpriteNormalsToolWindow.cs

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using System;

namespace Editor {
 public class SpriteNormalsToolWindow : EditorWindow {
  [MenuItem ("Window/Sprite utils/Normal map generator")]
  public static void ShowWindow () {
   EditorWindow win = EditorWindow.GetWindow<SpriteNormalsToolWindow> ("Sprite normals tool", true);
   win.minSize = new Vector2 (100, 100);
  }
  
  public Texture2D horizontalNormals = null;
  public Texture2D VerticalNormals = null;
  public Texture2D OutputTexture = null;
 
  public void OnGUI () {
   EditorGUILayout.BeginVertical ();
   {
    horizontalNormals = EditorGUILayout.ObjectField ("Horizontal normals", horizontalNormals, typeof (Texture2D), true) as Texture2D;
    VerticalNormals = EditorGUILayout.ObjectField ("Vertical normals", VerticalNormals, typeof (Texture2D), true) as Texture2D;
    EditorGUILayout.Space ();
    OutputTexture = EditorGUILayout.ObjectField ("Output texture", OutputTexture, typeof (Texture2D), true) as Texture2D;
    if (horizontalNormals == null || VerticalNormals == null)
     GUI.enabled = false;
    if (GUILayout.Button ("Generate normals"))
     GenerateNormalsMap (horizontalNormals, VerticalNormals);
    GUI.enabled = true;
   }
   EditorGUILayout.EndVertical ();
  }
  
  private void GenerateNormalsMap (Texture2D horizontals, Texture2D verticals) {
   // Create the asset if not existing yet
   if (OutputTexture == null) {
    OutputTexture = new Texture2D (Mathf.Max (horizontals.width, verticals.width), Mathf.Max (horizontals.height, verticals.height), TextureFormat.RGBA32, false);
   }
   
   // Actual map generation
   Color[] outputPixels = new Color[OutputTexture.width * OutputTexture.height];
   float uNormalized = 0.0f, vNormalized = 0.0f;
   Color ch, cv;
   float r, g, b;
   Vector2 xyNormal;
   Vector3 normal;
   for (int v = 0; v < OutputTexture.height; ++v) {
    vNormalized = (float )v / OutputTexture.height;
    for (int u = 0; u < OutputTexture.width; ++u) {
     uNormalized = (float )u / OutputTexture.width;
     ch = horizontals.GetPixelBilinear (uNormalized, vNormalized);
     r = ch.r * 2.0f - 1.0f;
     cv = verticals.GetPixelBilinear (uNormalized, vNormalized);
     g = cv.g * 2.0f - 1.0f;
     xyNormal = Vector2.ClampMagnitude (new Vector2 (r, g), 0.999f);
     r = xyNormal.x;
     g = xyNormal.y;
     b = (float )Math.Sqrt (1.0f - (double )(r * r) - (double )(g * g)); // z = sqrt (1 - x^2 - y^2)
     normal = new Vector3 (r, g, b).normalized;
     outputPixels[u + v * OutputTexture.width] = new Color (normal.x * 0.5f + 0.5f, normal.y * 0.5f + 0.5f, normal.z * 0.5f + 0.5f, 1.0f);
    }
   }
   OutputTexture.SetPixels (outputPixels);
   OutputTexture.Apply ();
   
   File.WriteAllBytes (Path.GetDirectoryName (Application.dataPath) + "/Assets/SpriteNormals.png", OutputTexture.EncodeToPNG ());
   AssetDatabase.ImportAsset ("Assets/SpriteNormals.png");
   OutputTexture = AssetDatabase.LoadMainAssetAtPath ("Assets/SpriteNormals.png") as Texture2D;
  }
 }
}

Remember you must save this file in an editor subfolder inside the assets folder.

The tool will ask for a horizontal and a vertical lighting maps, and if no output texture is provided, will create one by itself.

How this tool works is quite simple. It maps the horizontals onto the red channel and the verticals onto the green channel. Normal maps contain the (normalized) normal vectors packed into color components (each vector component x, y and z range from -1 to 1, and are halved and shifted so they range between 0 and 1, for the r, g and b color components). The z component of these vectors can be reconstructed from the other two using some basic algebra and the pythagorean theorem:

sqrt (x^2 + y^2 + z^2) = 1;
z = sqrt (1 - x^2 - y^2)

Now, access the tool via "Window->Sprite utils->Normal map generator" and drop your lighting maps on their respective fields in the tool. I forgot to mention that, in order for the textures to be queried by the tool, both must be imported with the "Read/Write Enabled" flag enabled (advanced mode). Clicking the "Generate normals" button will create by default a new texture "SpriteNormals.png". This new texture must be imported as a normal map (not a regular texture), and if you plan to alter it later using this tool (for instance, making changes to the lighting maps), the "Read/Write Enabled" flag must be enabled for this one too (though can be disabled if no changes will be made later).

This is it so far. No big deal once you get the idea, as you can see. However, there are some caveats that must be considered.

The cons

These points have already been discussed in this entry at Unity Answers. Some of them have already been covered above. The rest...

  • As the normal maps can't be defined at each sprite, normal maps must be pre-packed manually and, as the texture coordinates between the spite textures and its normals must correlate directly in texture space, we must pack the sprites ourselves. In other words: Unity's built-in sprites atlassing won't work. Lay out the different sprites in a single sheet manually and use that texture to define the sprite fragments in Unity (using the three layers method I talked above is extremely useful for this!).
  • Sprites in Unity aren't meant to be lit, and there seems to be a bug in the lighting calculations. When using scales other than +/-(1, 1, 1) sometimes the sprites are detected as "out of light range" and the light contribution is discarded.
  • User Jessy pointed out that, if batching kicks in for several sprites, each sprites' object space is replaced by a new batch object space. This normally isn't a problem as long as no rotation or non-uniform scaling happens, but if a rotated or mirrored sprite gets batched, its tangents will be corrupted (as they're defined in the shader, not the geometry), and hence its lighting will be wrong too. So far I've had no problems mixing mirrored and non-mirrored sprites (they aren't batched together), but can't ensure you'll be lucky too.

Closing words

You can download the assets here. You're free to use them at will in your own projects, though it would be nice if instead of redistributing/copy-pasting them you would link this post (dropping a note for me to see how you used it or a mention in your game would be über-cool too! :D)

Hope you found it useful!