amiralizadehit

We are gonna add outline to our sprite objects, is there any way?
amiralizadehit
  • Posts: 2

Nate

There is no easy way to outline the whole skeleton. The best way to do it is using a shader effect if your game toolkit supports it. Eg, render the skeleton to an FBO, then draw the FBO using a shader that colors pixels that are translucent (aka an edge highlight).

BTW, you can use tint black to make a "flash" which is a solid color silhouette.
Attachments - Spine User Guide: Tint black
User avatar
Nate

Nate
  • Posts: 9983

Pharan

It's possible but not simple in Unity, unfortunately.
This is true for any multi-Sprite setup, not just Spine.

If you had to google for it, I'd go for "2d sprite outline unity"
You have to make sure to distinguish from 3D outlines, which are usually just the trick where they expand mesh vertices. That won't work for alpha blended sprites.

Here's one I found that works but doesn't have anti-aliasing. https://forum.unity.com/threads/free-open-source-outline-effect.314362/
Unity_2017-10-23_15-38-34.png
You do not have the required permissions to view the files attached to this post.
User avatar
Pharan
  • Posts: 5366

IndieDoroid

Wow awesome find!! This is pretty useful, thanks for the link Pharan!
User avatar
IndieDoroid
  • Posts: 162

yang4990

Pharan wrote:It's possible but not simple in Unity, unfortunately.
This is true for any multi-Sprite setup, not just Spine.

If you had to google for it, I'd go for "2d sprite outline unity"
You have to make sure to distinguish from 3D outlines, which are usually just the trick where they expand mesh vertices. That won't work for alpha blended sprites.

Here's one I found that works but doesn't have anti-aliasing. https://forum.unity.com/threads/free-open-source-outline-effect.314362/
Unity_2017-10-23_15-38-34.png
Great!!!!!!
yang4990
  • Posts: 3

NicolaSirago

Hi everyone, I know this is quite old post, but I'm just wondering if anyone has found a better solution on drawing outlines out of a spine asset.

The main issue of using the solution highlighted by Pharan is that the outlines of different sprites are not decently combined. Every asset (with outlines) should have a separate render texture, and even so, I saw that outline above outline fight each other.

So what I did was to add a gameObject behind the spine asset and set another material with a simple shader that draws an outline in the frag shader function.
so what's the problem here? It's about performance:

1. Even using the same material for all the assets there are 6 batches, maybe because I created a MaterialPropertyBlock for each asset.
2. The meshes are duplicated
3. The overdraw doubles

So here I propose the question again, does anyone have a better solution?

thanks in advance for any suggestions.
You do not have the required permissions to view the files attached to this post.
NicolaSirago
  • Posts: 16

Nate

Can you render multiple skeletons to a render texture, then apply an outline to that?
User avatar
Nate

Nate
  • Posts: 9983

NicolaSirago

Nate wrote:Can you render multiple skeletons to a render texture, then apply an outline to that?
yes but if I do so I will have an outline surrounding the shape of all the combined sprite assets.

And taking a snapshot of every sprite from the camera will generate the second image
You do not have the required permissions to view the files attached to this post.
NicolaSirago
  • Posts: 16

Nate

Ah I misread that you wanted that. When outlining each skeleton separately, you said they aren't "decently combined". What is it about that you don't like?
User avatar
Nate

Nate
  • Posts: 9983

NicolaSirago

Nate wrote:What is it about that you don't like
the result I get with the render texture is the last image I showed you.
What I want to achieve is the first one in the first post with the outlines sorted in the same order with the characters, but without duplicating meshes.

Now I'm digging in your Spine/sprite/unlit in order to understand how "Write to Depth" works and maybe using Stencil, but I'm not a shader expert and it's really overwhelming.
NicolaSirago
  • Posts: 16

Harald

NicolaSirago wrote:So what I did was to add a gameObject behind the spine asset and set another material with a simple shader that draws an outline in the frag shader function.
so what's the problem here? It's about performance:

1. Even using the same material for all the assets there are 6 batches, maybe because I created a MaterialPropertyBlock for each asset.
2. The meshes are duplicated
3. The overdraw doubles
If you have achieved the desired correct outline without using any render textures, you are already saving a lot performance wise.
Unless you activate depth write (write to the z buffer), you will have a hard time improving on any of the above three drawbacks, for the following reasons:

  1. Batch increase: Even when using only an additional pass in the shader at a single material, it will create a separate draw call for this pass. A separate pass is almost always required, if you need the normal Spine character rendered on top of the outline without outlines around each individual body part.
  2. The meshes are duplicated depending on what you mean by this: you could and should reuse the existing Mesh, you should not regenerate the identical mesh (which I assume you don't). The easiest way to achieve reuse of the same Mesh is to create a shader with an additional pass for the outline. You could duplicate your desired shader and then add a pass to it that creates the outline.
  3. The overdraw doubles. This will always be the case if you need correct outlines (and not outlines around e.g. every single arm or leg part), unless you enable depth write.
Now I'm digging in your Spine/sprite/unlit in order to understand how "Write to Depth" works and maybe using Stencil, but I'm not a shader expert and it's really overwhelming.
You could also have a look at the simpler shader Spine/Skeleton Lit ZWrite instead, which is shorter and less complex.

Anyway, you will face some drawbacks when using "write to depth" (ZWrite): When relying on the Z-buffer for sorting your parts, your outlines and border regions will not be alpha-blended nicely, but instead will show a stepping-effect (an aliasing effect similar to what you see in 3D games at geometry borders when disabling anti-aliasing).

Regarding the stencil buffer: since Unity uses the stencil buffer for sprite masks, modifying and reading it will have some side effects with existing masks. So we would not recommend using the stencil buffer. Apart from that, you will receive a joint outline of multiple Spine skeletons when using a single stencil buffer, which is not what you said you want.

So I would heavily recommend using your existing solution or moving it to a separate pass in a copy of an existing shader - this will result in the best visual result without any artifacts or degradation of quality.
User avatar
Harald

Harri
  • Posts: 2076

NicolaSirago

Harald wrote:[
thank you very much for the extended answer, so it seems I was on the right track, (apart from copying the mesh, yes I was doing that :shh: )

anyway, here the result I get so far:
obviously we can improve a lot, but it already does what we need
Shader "Spine/Special/Outline" {
Properties {
_Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
[NoScaleOffset] _MainTex ("Main Texture", 2D) = "black" {}
[Toggle(_STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
[HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
[Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default

[PerRendererData]_Threshold("threshold", Float) = 0.5
[PerRendererData] _Outline("Outline", Float) = 0
[PerRendererData] _OutlineColor("Outline Color", Color) = (1,0,0,1)

}
CGINCLUDE
#include "UnityCG.cginc"
ENDCG

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

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

Stencil {
Ref[_StencilRef]
Comp[_StencilComp]
Pass Keep
}

Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

sampler2D _MainTex;

fixed4 _MainTex_TexelSize;
fixed _Threshold;
fixed _Outline;
fixed4 _OutlineColor;

struct VertexInput {
fixed4 vertex : POSITION;
fixed2 uv : TEXCOORD0;
};

struct VertexOutput {
fixed4 pos : SV_POSITION;
fixed2 uv : TEXCOORD0;
};

VertexOutput vert(VertexInput v) {
VertexOutput o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}

fixed4 frag(VertexOutput i) : SV_Target{
//fixed4 texColor = tex2D(_MainTex, i.uv);
fixed4 texColor = fixed4(0,0,0,0);
if (_Outline > 0 && texColor.a <= _Threshold) {
// Get the neighbouring four pixels.
fixed pixelUp = tex2D(_MainTex, i.uv + fixed2(0, _MainTex_TexelSize.y*_Outline)).a;
fixed pixelDown = tex2D(_MainTex, i.uv - fixed2(0, _MainTex_TexelSize.y*_Outline)).a;
fixed pixelRight = tex2D(_MainTex, i.uv + fixed2(_MainTex_TexelSize.x*_Outline, 0)).a;
fixed pixelLeft = tex2D(_MainTex, i.uv - fixed2(_MainTex_TexelSize.x*_Outline, 0)).a;

if (pixelUp + pixelDown + pixelRight + pixelLeft > _Threshold)
{
texColor.rgba = _OutlineColor;
}
}

return texColor;
}
ENDCG
}

Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT

sampler2D _MainTex;


struct VertexInput {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
};

struct VertexOutput {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 vertexColor : COLOR;
};

VertexOutput vert (VertexInput v) {
VertexOutput o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
o.vertexColor = v.vertexColor;
return o;
}

float4 frag (VertexOutput i) : SV_Target {
float4 texColor = tex2D(_MainTex, i.uv);
#if defined(_STRAIGHT_ALPHA_INPUT)
texColor.rgb *= texColor.a;
#endif
return (texColor * i.vertexColor);
}
ENDCG
}

Pass {
Name "Caster"
Tags { "LightMode"="ShadowCaster" }
Offset 1, 1
ZWrite On
ZTest LEqual

Fog { Mode Off }
Cull Off
Lighting Off

CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#pragma fragmentoption ARB_precision_hint_fastest
sampler2D _MainTex;
fixed _Cutoff;

struct VertexOutput {
V2F_SHADOW_CASTER;
float4 uvAndAlpha : TEXCOORD1;
};

VertexOutput vert (appdata_base v, float4 vertexColor : COLOR) {
VertexOutput o;
o.uvAndAlpha = v.texcoord;
o.uvAndAlpha.a = vertexColor.a;
TRANSFER_SHADOW_CASTER(o)
return o;
}

float4 frag (VertexOutput i) : SV_Target {
fixed4 texcol = tex2D(_MainTex, i.uvAndAlpha.xy);
clip(texcol.a * i.uvAndAlpha.a - _Cutoff);
SHADOW_CASTER_FRAGMENT(i)
}
ENDCG
}
}
}
NicolaSirago
  • Posts: 16

Harald

Glad to hear that it helped, thanks for sharing the shader code!

What I forgot to add in the previous post:
Neighbourhood sampling based outline generation (as you posted above) comes with added cost for larger outline-width. If you only need one pixel wide outlines, that's perfectly fine of course.
In case you need variable-width outlines, distance fields could come in handy. Usually used for crisp font rendering, it is also used for outline rendering without the need for neighbourhood sampling. However, this requires custom prepared image data, which is not too trivial in combination with atlas generation, etc. Just mentioning for the sake fo completeness - your approach is perfectly valid and looks good.

You could also add some antialiasing/smoothing by not only testing against a threshold like this:
if (pixelUp + pixelDown + pixelRight + pixelLeft > _Threshold)
{
texColor.rgba = _OutlineColor;
}
but instead having a semi-transparent outline at corner pixels:
// _ThresholdStart = 0.5
// _ThresholdEnd = 2.0

fixed sum = pixelUp + pixelDown + pixelRight + pixelLeft;
fixed outlineAlpha = (sum - _ThresholdStart) / (_ThresholdEnd - _ThresholdStart);

texColor.rgba = lerp(texColor, _OutlineColor, outlineAlpha);
This way you will get smooth outlines.
User avatar
Harald

Harri
  • Posts: 2076

foriero

Will this be included in Unity runtime?
Founder & CEO Foriero s.r.o.
https://studio.foriero.com
User avatar
foriero
  • Posts: 335

Harald

After thinking about it, I came to the conclusion that I will add outline functionality to all Spine shaders.

I have created a ticket here:
[unity] Provide outline at shaders · #1531
User avatar
Harald

Harri
  • Posts: 2076

NicolaSirago

Harald wrote:After thinking about it, I came to the conclusion that I will add outline functionality to all Spine shaders.

I have created a ticket here:
[unity] Provide outline at shaders · #1531
WOW Thank you, Harald! I'm glad I dug up the issue.

My current goal is to keep the shader doing just a single pass, to improve vertex numbers and most of all keeping the dynamic batching capability. It seems quite hard to figure out.

What I want to do is to first isolate the vertices that enclose the animation, and then do the alpha test just for those pixels that are much closer to the external edges, but I don't understand yet how it's doable. :think: :think: :think:
NicolaSirago
  • Posts: 16

Harald

You're welcome, thanks for the input. The perfect solution (distance field based) would be a lot of work, but a good solution with some constraints might already help a lot of people.
NicolaSirago wrote:My current goal is to keep the shader doing just a single pass, to improve vertex numbers and most of all keeping the dynamic batching capability. It seems quite hard to figure out.
The problem with any single-pass solution will be to remove outlines at overlapping body parts, which is not trivial:

outlines-combined1.png

outlines-combined-hilight.png

outlines-combined2.png


With unchanged geometry, I can only think of a single pass solution using the depth buffer and custom different written depth values for outline (further behind at increased depth) and solid parts (in front at unmodified depth). Writing custom depth values in the pixel shader however comes with a performance penalty, which would most likely outweight the benefits.

The best single pass solution I could think of would be to modify the mesh generation in the skeleton to output the geometry twice in the same vertex buffer, once for the outline with modified z position (further behind), followed by the normal mesh. You would then most likely need to add vertex attributes that encode the isOutlineMeshVertex information, and then only draw the outline or inner part in the shader respectively.

At least I cannot currently think of an easy single pass solution. If anyone comes up with better ideas, I would love to hear them of course! :nerd:
You do not have the required permissions to view the files attached to this post.
User avatar
Harald

Harri
  • Posts: 2076

foriero

Hi, any progress on this?
Founder & CEO Foriero s.r.o.
https://studio.foriero.com
User avatar
foriero
  • Posts: 335

Harald

We have not yet started this task, but it will be in the near future.

---

Outline functionality has now been added to all spine-unity 3.8 and 3.9 shaders. New unitypackages are available for download here as usual:
Spine Unity Download

We will add an example scene and an announcement blog post for this feature soon. Until then, please consult the changelog, section Additions - Outline rendering functionality for all shaders on how to use this feature.
User avatar
Harald

Harri
  • Posts: 2076

Jamez0r

Woohoo!
User avatar
Jamez0r
  • Posts: 196

bantam

Addition of the outline shader helps us a lot thank you for adding it! I can't seem to get it working correctly at the moment. Enabling outline causes a spiky outline effect even at 8 outline. Any advice for getting it working? Using Skeleton Lit ZWrite but the effect is the same on all shaders.

Screen Shot 2019-12-09 at 1.47.42 AM.png


Export settings if thats where im going wrong, tried a bunch of settings to no effect:

Screen Shot 2019-12-09 at 2.11.29 AM.png


Unity 2019.2.14f1

Doesn't seem to make a difference if I use straight alpha or PMA
You do not have the required permissions to view the files attached to this post.
bantam
  • Posts: 2

Nate

The outline is only drawn within the mesh borders. When you see parts of the outline being cut, your mesh borders are too tightly wrapped around the opaque pixels of your images.

To fix this problem, move the mesh vertices further out to provide enough space. You may need to add more whitespace around the borders of your images so you can move the mesh vertices.

Also, if you see outline color appearing at borders of attachments, it is likely the outlines of nearby atlas image regions growing into your mesh:

You can add more padding via the atlas export settings to leave enough space for the neighbor outline. You may also consider disabling whitespace stripping instead of increasing the padding.
User avatar
Nate

Nate
  • Posts: 9983

buddhamon

I thought I might add this for someone who's just looking for a quick and simple outlining implementation via script!
Renderer renderer = mySkeletonAnimation.transform.GetComponent<Renderer>();
Shader newShader = Shader.Find("Spine/Outline/Skeleton Fill");
foreach (Material material in renderer.materials) material.shader = newShader;
If you want to change it back, simply replace the second line with
Shader newShader = Shader.Find("Spine/Skeleton Fill");
User avatar
buddhamon
  • Posts: 23

Nate

Thanks buddhamon!

Note the blog post mentioned above, before the blog post was posted, is here:
Blog: Outline shaders for spine-unity
User avatar
Nate

Nate
  • Posts: 9983

Harald

Thanks for sharing buddhamon! Please note that the spine-unity runtime also comes with an additional Outline Shaders example scene as described at the bottom of the blog post here:
Blog: Outline shaders for spine-unity: Outline shaders example scene
User avatar
Harald

Harri
  • Posts: 2076


Return to Unity