Xelnath

It's probably not the most efficient thing ever, but it looks and feels cool!

EdgeShader_hdr_notonemap.png


If anyone's interested, I can point you at how to do it. :)
You do not have the required permissions to view the files attached to this post.
Xelnath
  • Posts: 408

Yugich3

Give me the script please!
I may give you a kidney in change!
Yugich3
  • Posts: 2

Xelnath

The exchange rate for a Kidney is $42,000. So yes, I will accept that ;D

It's pretty complex, but here you go:

OutlineShader.shader
/*
// Copyright (c) 2015 José Guerreiro. All rights reserved.
//
// MIT license, see http://www.opensource.org/licenses/mit-license.php
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
*/


Shader "Hidden/OutlineEffect"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_LineColor ("Line Color", Color) = (1,1,1,.5)

}
SubShader
{
Pass
{
Tags { "RenderType"="Opaque" }
LOD 200
ZTest Always
ZWrite Off
Cull Off

CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
#include "UnityCG.cginc"

sampler2D _MainTex;
sampler2D _OutlineSource;

struct v2f {
float4 position : SV_POSITION;
float2 uv : TEXCOORD0;
};

v2f vert(appdata_img v)
{
v2f o;
o.position = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = v.texcoord;

return o;
}

float _LineThicknessX;
float _LineThicknessY;
float _LineIntensity;
half4 _LineColor1;
half4 _LineColor2;
half4 _LineColor3;
int _FlipY;

half4 frag (v2f input) : COLOR
{
float2 uv = input.uv;
if (_FlipY == 1)
uv.y = 1 - uv.y;

half4 originalPixel = tex2D(_MainTex,input.uv);
half4 outlineSource = tex2D(_OutlineSource, uv);

float h = .95f;
half4 outline = 0;

half4 sample1 = tex2D(_OutlineSource, uv + float2(_LineThicknessX,0.0));
half4 sample2 = tex2D(_OutlineSource, uv + float2(-_LineThicknessX,0.0));
half4 sample3 = tex2D(_OutlineSource, uv + float2(.0,_LineThicknessY));
half4 sample4 = tex2D(_OutlineSource, uv + float2(.0,-_LineThicknessY));

if(outlineSource.a < h)
{
if(sample1.r > h || sample2.r > h || sample3.r > h || sample4.r > h)
outline = _LineColor1 * _LineIntensity;
else if(sample1.g > h || sample2.g > h || sample3.g > h || sample4.g > h)
outline = _LineColor2 * _LineIntensity;
else if(sample1.b > h || sample2.b > h || sample3.b > h || sample4.b > h)
outline = _LineColor3 * _LineIntensity;
}

//return outlineSource;
return originalPixel + outline;
}

ENDCG
}
}
FallBack "Diffuse"
}
OutlineBufferShader.shader
/*
// Copyright (c) 2015 José Guerreiro. All rights reserved.
//
// MIT license, see http://www.opensource.org/licenses/mit-license.php
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
*/


Shader "Hidden/OutlineBufferEffect" {
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_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
Blend One OneMinusSrcAlpha

CGPROGRAM
#pragma surface surf Lambert vertex:vert nofog keepalpha
#pragma multi_compile _ PIXELSNAP_ON

sampler2D _MainTex;
fixed4 _Color;

struct Input
{
float2 uv_MainTex;
fixed4 color;
};

void vert (inout appdata_full v, out Input o)
{
#if defined(PIXELSNAP_ON)
v.vertex = UnityPixelSnap (v.vertex);
#endif

UNITY_INITIALIZE_OUTPUT(Input, o);
o.color = v.color;
}

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

Fallback "Transparent/VertexLit"
}
OutlineEffect.cs

You will need to tweak this to suit your project.
/*
// Copyright (c) 2015 José Guerreiro. All rights reserved.
//
// MIT license, see http://www.opensource.org/licenses/mit-license.php
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
*/


using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Spine.Unity;

[RequireComponent(typeof(Camera))]
public class OutlineEffect : MonoBehaviour
{
public List<SkeletonRenderer> spineCharacters = new List<SkeletonRenderer>();
public List<Renderer> outlineRenderers = new List<Renderer>();
public List<int> outlineRendererColors = new List<int>();
public List<Renderer> eraseRenderers = new List<Renderer>();

public float lineThickness = 4f;
public float lineIntensity = .5f;

public Color lineColor1 = Color.red;
public Color lineColor2 = Color.green;
public Color lineColor3 = Color.blue;
public bool flipY = false;

private Material outline1Material;
private Material outline2Material;
private Material outline3Material;
private Material outlineEraseMaterial;
private Shader outlineShader;
private Shader outlineBufferShader;
private Shader outlineBufferShaderSpine;
private Material outlineShaderMaterial;
private RenderTexture renderTexture;
private Camera _camera;

Dictionary<Material, Material> materialMap = new Dictionary<Material, Material>();
List<Material[]> originalMaterials = new List<Material[]>();
int[] originalLayers = new int[1];
Material[] originalEraseMaterials = new Material[1];
int[] originalEraseLayers = new int[1];

void OnEnable()
{
CreateMaterialsIfNeeded();
}

void OnDisable()
{
DestroyMaterials();

if( _camera)
{
DestroyImmediate( _camera.gameObject);
_camera = null;
}
}

Material GetMaterialFromID(int ID)
{
if (ID == 0)
return outline1Material;
else if (ID == 1)
return outline2Material;
else
return outline3Material;
}

Material CreateMaterial(Color emissionColor)
{
Material m = new Material(outlineBufferShader);
m.SetColor("_Color", emissionColor);
m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
m.SetInt("_ZWrite", 0);
m.DisableKeyword("_ALPHATEST_ON");
m.EnableKeyword("_ALPHABLEND_ON");
m.DisableKeyword("_ALPHAPREMULTIPLY_ON");
m.renderQueue = 3000;
return m;
}

void Start ()
{
spineCharacters = new List<SkeletonRenderer>(Object.FindObjectsOfType<SkeletonRenderer>());
if (spineCharacters != null && spineCharacters.Count != 0)
{
foreach ( SkeletonRenderer sr in spineCharacters )
{
outlineRenderers.Add(sr.GetComponent<MeshRenderer>());
/* Good vs Evil
BaseUnit b = sr.GetComponentInParent<BaseUnit>();
if ( b[PS.Faction] == Faction.Player)
outlineRendererColors.Add(0);
else
outlineRendererColors.Add(1);
*/

}

}

CreateMaterialsIfNeeded();
}

void OnPreCull()
{
if (outlineRenderers != null && outlineRenderers.Count != 0)
{
Camera camera = GetComponent<Camera>();

int width = camera.pixelWidth;
int height = camera.pixelHeight;
renderTexture = RenderTexture.GetTemporary(width, height, 16, RenderTextureFormat.Default);

if (_camera == null)
{
GameObject cameraGameObject = new GameObject("OutlineCamera");
cameraGameObject.hideFlags = HideFlags.HideAndDontSave;
_camera = cameraGameObject.AddComponent<Camera>();
}

_camera.CopyFrom(camera);
_camera.renderingPath = RenderingPath.Forward;
_camera.enabled = false;
_camera.backgroundColor = new Color(0.0f, 0.0f, 0.0f, 0.0f);
_camera.clearFlags = CameraClearFlags.SolidColor;
_camera.cullingMask = LayerMask.GetMask("Outline");

if (outlineRenderers != null)
{
originalLayers = new int[outlineRenderers.Count];
for (int i = 0; i < outlineRenderers.Count; i++)
{
if (outlineRenderers[i] != null)
{
if ( i >= originalMaterials.Count )
originalMaterials.Add(outlineRenderers[i].sharedMaterials);
else
originalMaterials[i] = outlineRenderers[i].sharedMaterials;

originalLayers[i] = outlineRenderers[i].gameObject.layer;

Material[] newMats = outlineRenderers[i].sharedMaterials;
for (int j = 0; j < originalMaterials[i].Length; j++)
{
Material subMaterial = null;
if ( materialMap.TryGetValue(originalMaterials[i][j], out subMaterial) )
{
newMats[j] = subMaterial;
}
else
{
Debug.Log("Lol, you need to make a new texture~");
}
}
outlineRenderers[i].sharedMaterials = newMats;
outlineRenderers[i].gameObject.layer = LayerMask.NameToLayer("Outline");
}
}
}
if (eraseRenderers != null)
{
originalEraseMaterials = new Material[eraseRenderers.Count];
originalEraseLayers = new int[eraseRenderers.Count];
for (int i = 0; i < eraseRenderers.Count; i++)
{
if (eraseRenderers[i] != null)
{
originalEraseMaterials[i] = eraseRenderers[i].sharedMaterial;
originalEraseLayers[i] = eraseRenderers[i].gameObject.layer;

eraseRenderers[i].sharedMaterial = outlineEraseMaterial;
eraseRenderers[i].gameObject.layer = LayerMask.NameToLayer("Outline");
}
}
}

_camera.targetTexture = renderTexture;
_camera.Render();

if (outlineRenderers != null)
{
for (int i = 0; i < outlineRenderers.Count; i++)
{
if (outlineRenderers[i] != null)
{
outlineRenderers[i].sharedMaterials = originalMaterials[i];
outlineRenderers[i].gameObject.layer = originalLayers[i];
}
}
}
if (eraseRenderers != null)
{
for (int i = 0; i < eraseRenderers.Count; i++)
{
if (eraseRenderers[i] != null)
{
eraseRenderers[i].sharedMaterial = originalEraseMaterials[i];
eraseRenderers[i].gameObject.layer = originalEraseLayers[i];
}
}
}
}
}

void OnRenderImage( RenderTexture source, RenderTexture destination)
{
CreateMaterialsIfNeeded();
UpdateMaterialsPublicProperties();

outlineShaderMaterial.SetTexture("_OutlineSource", renderTexture);
Graphics.Blit(source, destination, outlineShaderMaterial);
RenderTexture.ReleaseTemporary(renderTexture);
}

private void CreateMaterialsIfNeeded()
{
if(outlineShader == null)
outlineShader = Resources.Load<Shader>("OutlineEffect/OutlineShader");
if (outlineBufferShader == null)
outlineBufferShader = Resources.Load<Shader>("OutlineEffect/OutlineBufferShader");
if(outlineShaderMaterial == null)
{
outlineShaderMaterial = new Material(outlineShader);
outlineShaderMaterial.hideFlags = HideFlags.HideAndDontSave;
UpdateMaterialsPublicProperties();
}
if(outlineEraseMaterial == null)
outlineEraseMaterial = CreateMaterial(new Color(0, 0, 0, 0));
if(outline1Material == null)
outline1Material = CreateMaterial(new Color(1, 0, 0, 0));
if(outline2Material == null)
outline2Material = CreateMaterial(new Color(0, 1, 0, 0));
if (outline3Material == null)
outline3Material = CreateMaterial(new Color(0, 0, 1, 0));


if (spineCharacters != null && spineCharacters.Count != 0)
{
SkeletonRenderer sr = spineCharacters[0];
var mr = sr.GetComponent<MeshRenderer>();
for( int i = 0; i < mr.sharedMaterials.Length; i++ )
{
var mat = mr.sharedMaterials[i];
Material newMat = null;
if (outlineRendererColors != null && outlineRendererColors.Contains(0))

newMat = new Material(GetMaterialFromID(outlineRendererColors[0]));
else
newMat = new Material(outline1Material);
#ifdef UNITY_4
var texture = mat.GetTexture(0);
newMat.SetTexture(0,texture);
materialMap[mat] = newMat;
#endif
#ifdef UNITY_5
newMat.mainTexture = original.mainTexture;
#endif
}
}


}

private void DestroyMaterials()
{
DestroyImmediate(outlineShaderMaterial);
DestroyImmediate(outlineEraseMaterial);
DestroyImmediate(outline1Material);
DestroyImmediate(outline2Material);
DestroyImmediate(outline3Material);
outlineShader = null;
outlineBufferShader = null;
outlineShaderMaterial = null;
outlineEraseMaterial = null;
outline1Material = null;
outline2Material = null;
outline3Material = null;
}

private void UpdateMaterialsPublicProperties()
{
if(outlineShaderMaterial)
{
outlineShaderMaterial.SetFloat("_LineThicknessX", lineThickness / 1000);
outlineShaderMaterial.SetFloat("_LineThicknessY", (lineThickness * 2) / 1000);
outlineShaderMaterial.SetFloat("_LineIntensity", lineIntensity);
outlineShaderMaterial.SetColor("_LineColor1", lineColor1);
outlineShaderMaterial.SetColor("_LineColor2", lineColor2);
outlineShaderMaterial.SetColor("_LineColor3", lineColor3);
if(flipY)
outlineShaderMaterial.SetInt("_FlipY", 1);
else
outlineShaderMaterial.SetInt("_FlipY", 0);
}
}
}
Attach the MonoBehaviour to your main camera, create a new Layer called "Outline".
Then set your camera to HDR mode if you want it to glow instead of just outline. Maybe some bloom if you feel like it.

-- 22 Oct 2015, 21:07 --

If you make some improvements to this shader, please share with us :)
Xelnath
  • Posts: 408

devis.rossini

Hi,

very interesting! We are trying to do something similar, but we have to draw oultines for many objects, that are created and destroyed during runtime.

Is there a way to avoid to use a global script like your, that must reference any skeleton?

Thanks a lot,

Devis
devis.rossini
  • Posts: 6

Xelnath

Add or remove things to the global script object. Create a simple script that just adds the object when the script is enabled and removes it from the list when disabled.

e.g. (Pseudo code)
MyHelper : MonoBehaviour
{
void OnEnable()
{
var OutlineEffObj = GameObject.Find("MyOutlineCamera").GetComponent<OutlineEffect>()
OutlineEffObj.Add(this);
}

... same thing, but disable.
}


This is important, because if you don't do it, then you'll be doing separate rendering passes per object - which will consume even more performance and look bad.

-- 12 Nov 2015, 18:56 --

Edit:

Alternatively, if you would like to pay me to write the described script, I'd be happy to work out terms.
Xelnath
  • Posts: 408

linghunchongxin

en. i think you design has a problem when 2 spine character overlap,like thie below

linghunchongxin
  • Posts: 8

Xelnath

No, that's intended. :)
Xelnath
  • Posts: 408

linghunchongxin

en. but it's no satisfy my demand, do you have some ideas to solve this problem when 2 charaters overlap but one before another one?
linghunchongxin
  • Posts: 8

Xelnath

I guess you could do a separate render pass per character.

-- 25 Mar 2016, 00:47 --

Found the issue with Unity 5.3.4

You need to change newMat.SexTexture(0, texture);

to

newMat.mainTexture = original.mainTexture;
Xelnath
  • Posts: 408

Sir_Felolis

I'm getting an error on OutlineEffect.cs saying that "original" doesn't exist in the current context. I've no idea where do add it or what to assign it to. Help please!
Sir_Felolis
  • Posts: 2

Majicpanda

https://www.assetstore.unity3d.com/en/#!/content/42566

How are they doing it in this package? It looks like it's possibly an internal "outline" which would be a bit easier to calculate I assume? This might be acceptable to some degree if it's a lot easier to do.
Majicpanda
  • Posts: 197

derekh

Xelnath wrote:I guess you could do a separate render pass per character.

-- 25 Mar 2016, 00:47 --

Found the issue with Unity 5.3.4

You need to change newMat.SexTexture(0, texture);

to

newMat.mainTexture = original.mainTexture;
There is another error with Unity 5.3.4 :
error CS0103: The name `original' does not exist in the current context.
And I don't know how to fix it.Pls help me out. :'( Waiting for your reply.Thanks.

PS.There should be a ' } ' at the end of function Start() and ‘using Spine.Unity;’ at the beginning of OutlineEffect.cs.(with Spine-Unity runtime 3.0)
derekh
  • Posts: 1

Xelnath

Okay, I have made the
}
and using "Spine.Unit" changes for future players.

To do more proper management, I would need to setup a public github, but that takes some time andI would need to solicit some donations in order to make up for lost time updating this.
Xelnath
  • Posts: 408

sysper

Very cool!
sysper
  • Posts: 9

SullyTheStrange

Hey, this has been serving me awesomely for a while now (thanks!), but for some reason the outline is appearing upside down on Mac. I found this in my Googling, but I'm not sure where to put this into the script. Any idea?

http://docs.unity3d.com/Manual/SL-PlatformDifferences.html
// On non-GL when AA is used, the main texture and scene depth texture
// will come out in different vertical orientations.
// So flip sampling of the texture when that is the case (main texture
// texel size will have negative Y).

#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
uv.y = 1-uv.y;
#endif
SullyTheStrange
  • Posts: 53

Xelnath

Probably in the primary shader.

I am working on a mac this week, I'll take a look.
Xelnath
  • Posts: 408

SullyTheStrange

Did you get a chance to look into it? :)
SullyTheStrange
  • Posts: 53

Xelnath

No, I didn't. I'm tracking down other more basic bugs right now :(
Xelnath
  • Posts: 408

SullyTheStrange

No worries, let me know if you ever do.
SullyTheStrange
  • Posts: 53

Oruji

Hi, trying to make this piece of code work with my project.

This line returns false:

if(materialMap.TryGetValue(originalMaterials[i][j], out subMaterial))

And I don't see any other reference to materialMap in the code.

I get the error message: "Lol, you need to make a new texture~".

Any idea?
Oruji
  • Posts: 4


Return to Unity