tmp

Home About me Blogroll

Sunday, May 26, 2013

Outline in Unity with mesh transparency.

Here I found a shader for Unity to obtain an outline of a mesh.

 http://answers.unity3d.com/questions/141229/making-a-silhouette-outline-shader.html
 
This shader uses a pass to create a slightly bigger mesh behind the original one.
This is a good solution (at least in Unity), but only for convex/non transparent object. The fragments of the outline will indeed appear behind the mesh:
 We can remove the fragments behind the mesh modifying the depth buffer with a duplicated object.
The original object writes to the z-buffer, so the duplicated object (i.e. the one that act as an outline) will be partially culled by the original one.

In order to obtain this, we can use these shaders:

Transparent shader for the original object:
Shader "Outline/Transparent" {
  Properties {
    _color ("Color", Color) = (1,1,1,0.5)
  }

    SubShader {
    Tags {"Queue" = "Geometry+1" }
      Pass {
        Blend SrcAlpha OneMinusSrcAlpha
        Lighting On
        ZWrite On

        Material {
          Diffuse [_color]
        }
      }
    }
}
Outline shader for the outline, it will be applied to the duplicated object (Note: this is a mod of the shader quoted at the begin)
Shader "Outline/Outline" {
    Properties {
      _OutlineColor ("Outline Color", Color) = (0,0,0,1)
      _Outline ("Outline width", Range (.002, 0.03)) = .005
    }

    CGINCLUDE
    #include "UnityCG.cginc"

    struct appdata {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    };

    struct v2f {
    float4 pos : POSITION;
    float4 color : COLOR;
    };

    uniform float _Outline;
    uniform float4 _OutlineColor;

    v2f vert(appdata v) {
      // just make a copy of incoming vertex data but scaled according to normal direction
      v2f o;
      o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

      float3 norm = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
      float2 offset = TransformViewToProjection(norm.xy);

      o.pos.xy += offset * o.pos.z * _Outline;
      o.color = _OutlineColor;
      return o;
    }
    ENDCG

    SubShader {
    Tags {"Queue" = "Overlay"}

      Pass {
        Name "OUTLINE"
        Tags { "LightMode" = "Always" }
        Cull Front
        ZWrite On
        ZTest Less
        Blend SrcAlpha OneMinusSrcAlpha
        ColorMask RGB
        Offset 15,15

        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
          half4 frag(v2f i) :COLOR { return i.color; }
        ENDCG
      }
    }

    SubShader {
      Tags {"Queue" = "Overlay" }
      CGPROGRAM
      #pragma surface surf Lambert

      sampler2D _MainTex;
      fixed4 _Color;

      struct Input {
        float2 uv_MainTex;
      };

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

      Pass {
        Name "OUTLINE"
        Tags { "LightMode" = "Always" }
        Cull Front
        ZWrite On
        ColorMask RGB
        Blend SrcAlpha OneMinusSrcAlpha

        CGPROGRAM
        #pragma vertex vert
        #pragma exclude_renderers gles xbox360 ps3
        ENDCG
        SetTexture [_MainTex] { combine primary }
      }
    }

    Fallback "Diffuse"
}


The result is pretty good:



Finally, here it is a Unity script that automatically creates the outline effect when applied to an object:

using UnityEngine;
using System.Collections;

public class Outliner : MonoBehaviour {

  public Color meshColor = new Color(1f,1f,1f,0.5f);
  public Color outlineColor = new Color(1f,1f,0f,1f);

  // Use this for initialization
  public void Start () {

    // Set the transparent material to this object
    MeshRenderer meshRenderer = GetComponent();
    Material[] materials = meshRenderer.materials;
    int materialsNum = materials.Length;
    for(int i = 0; i < materialsNum; i++) {
      materials[i].shader = Shader.Find("Outline/Transparent");
      materials[i].SetColor("_color", meshColor);
    }

    // Create copy of this object, this will have the shader that makes the real outline
    GameObject outlineObj = new GameObject();
    outlineObj.transform.position = transform.position;
    outlineObj.transform.rotation = transform.rotation;
    outlineObj.AddComponent();
    outlineObj.AddComponent();
    Mesh mesh;
    mesh = (Mesh) Instantiate(GetComponent().mesh);
    outlineObj.GetComponent().mesh = mesh;

    outlineObj.transform.parent = this.transform;
    materials = new Material[materialsNum];
    for(int i = 0; i < materialsNum; i++) {
      materials[i] = new Material(Shader.Find("Outline/Outline"));
      materials[i].SetColor("_OutlineColor", outlineColor);
    }
    outlineObj.GetComponent().materials = materials;

  }

}

Tuesday, March 5, 2013

Unity3D tip #01 - in-game configurable input

Currently unity doesn't provide a way to configure the commands during the game. Once the player starts the program, the command are permanently linked to the keyboard and mouse buttons. For Facade we wanted to give the player the possibility to choose the keyboard configuration from the main menu, like in any other game. In order to do this, I've created a simple class that replaces the Input class that unity provides.
using UnityEngine;
using System.Collections;

public enum Command {
	FORWARD,
	LEFT,
	RIGHT,
	BACK,
	JUMP,
	QMENU,
	PAUSE,
	SWING_FORWARD,
	SWING_BACKWARD,
	ACTIVATE,
	CLOSE_TIP
}

public enum Axis {
	HORIZONTAL,
	VERTICAL
}

public class FInputManager : MonoBehaviour {

	static private KeyCode[] commandsArray;

	static public void SetCommands(KeyCode[] commands) {
		commandsArray = commands;	
	}
	
	static public bool GetCommand(Command com) {
		return Input.GetKey(commandsArray[(int)com]);
	}
	
	static public bool GetCommandDown(Command com) {
		return Input.GetKeyDown(commandsArray[(int)com]);
	}
	
	static public bool GetCommandUp(Command com) {
		return Input.GetKeyUp(commandsArray[(int)com]);
	}
	
	static public float GetAxis(Axis axis) {
		switch (axis) {
		case Axis.HORIZONTAL: 
			return (float)( -System.Convert.ToInt32(Input.GetKey(commandsArray[(int)Command.LEFT])) + 
							 System.Convert.ToInt32(Input.GetKey(commandsArray[(int)Command.RIGHT])));
		case Axis.VERTICAL: 
			return (float)( System.Convert.ToInt32(Input.GetKey(commandsArray[(int)Command.FORWARD])) + 
							 -System.Convert.ToInt32(Input.GetKey(commandsArray[(int)Command.BACK])));
		}
		return 0;
	}
	
	static public KeyCode GetKeyCode(Command com) {
		return commandsArray[(int)com];
	}
	
}
Instead of using strings like unity does, this class uses only an enum, thus it avoid allocations of pointless strings. The mapping between a Command and a unity KeyCode is done through the array commandsArray. This can be set through the SetCommands method (but look out from the out-of-bound errors ;) ). Now let see how the menu and the config file works.
private List> buttonsEntries;

// --- inside the menu loop --

if (buttonsEntriesNeedsRefresh) {
	buttonsEntriesNeedsRefresh = false;
	LoadButtonsEntry();
}

// The user is inserting the new key for a command. 
// Capture it and save it the right button entry
if (waitingForKey) {
	Event e = Event.current;
	if (e.isKey) {
		waitingForKey = false;
		buttonsEntries[entryToUpdateIndex].Item2 = e.keyCode;
	}
}

int entryIndex = 0;

GUILayout.BeginHorizontal();
if (GUILayout.Button("Save")) {
	buttonsEntriesNeedsRefresh = true;
	SaveButtonsEntry();
	UpdateInputManager();
	menuState = MenuState.MAIN;
}
if (GUILayout.Button("Discard Changes")) {
	buttonsEntriesNeedsRefresh = true;
	menuState = MenuState.MAIN;
}
if (GUILayout.Button("Load default configuration")) {
	buttonsEntriesNeedsRefresh = true;
	CreateDefaultConfig();
	menuState = MenuState.MAIN;
}
GUILayout.EndHorizontal();

buttonConfigScroolPosition = GUILayout.BeginScrollView(
	buttonConfigScroolPosition, false, true);

foreach (Tuple entry in buttonsEntries) {
	GUILayout.BeginHorizontal();
	GUILayout.Label(entry.Item1, GUILayout.Width(buttonWidth));
	if (GUILayout.Button(entry.Item2.ToString(), GUILayout.Width(100))) {
                // The player clicked this button, so the button to be updated 
                //is the button specified by entryIndex
		entryToUpdateIndex = entryIndex;
                // From now on we will wait for the user to press a new key.
		waitingForKey = true;
	}
	entryIndex++;
	GUILayout.EndHorizontal();
	}
}
Once the player press the 'Save' button, the methods SaveButtonsEntry() and UpdateInputManager() are called.
private void SaveButtonsEntry() {
	System.IO.File.WriteAllText(buttonsConfig, string.Empty);	
	FileInfo buttonsConfigFileInfo = new FileInfo(buttonsConfig);
	StreamWriter writer = buttonsConfigFileInfo.AppendText();

	foreach (Tuple entry in buttonsEntries) {
                // We want a KISS config file, nothing fancy
                // The first field is the name of the command,
                // the second is the string representation of the KeyCode value.
		writer.WriteLine(entry.Item1 + '=' + entry.Item2.ToString());
	}
	
//		writer.Flush();
	writer.Close();
}

private void UpdateInputManager() {
	System.Array commands = System.Enum.GetValues(typeof(Command));
	KeyCode[] newCommands = new KeyCode[commands.Length];
	int i = 0;
        // Search each possible value of the Command enum inside the config file
	foreach (var com in commands) {
		bool commandFound = false;
		foreach (var entry in buttonsEntries) {
                        // Again, keep it simple, nothing fancy
			if (entry.Item1.ToUpper().Replace(' ','_') == com.ToString()) {
				newCommands[i] = entry.Item2;
				commandFound = true;
				break;
			}
		}
		if (!commandFound) {
			Debug.LogError("Unable to find command '" + com + "' in the buttons configuration file");
		}
		i++;
	}
	FInputManager.SetCommands(newCommands);  // <--- data-blogger-escaped-inputmanager.="" data-blogger-escaped-pre="" data-blogger-escaped-the="" data-blogger-escaped-update="">


It is pretty easy to achieve this in C#: We can get the string representation of each enum value, thus we don't need to do any mapping between string and integers values.