Hey there!
I’m trying to create a shader for sprites that would allow me to change the color of their outlines (or set it invisible), have them react to lighting and be able to change their tints.
I’ve spent some time trying to combine a sprite outline shader with Unity’s default sprite/diffuse shader.
Right now it kind of works. Only problems are that if I set the output.alpha on the surface shader then the outlines are always visible and if the tint-property’s rgb values are anything other than 0,0,0, I start getting strange artifacts on the sprites. Any idea what am I doing wrong here?
Also note that I’m a complete idiot when it comes to shader coding, so if there are some horrible mistakes here, feel free to let me know!
Here’s the original outline shader:
Shader "Custom/SpriteOutline"
{
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Color ("Color", Color) = (1, 1, 1, 1)
}
SubShader {
Tags {"Queue"="Transparent" "RenderType"="Transparent"}
Cull Off
Blend One OneMinusSrcAlpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
struct v2f {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
v2f vert(appdata_base v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 _Color;
float4 _MainTex_TexelSize;
fixed4 frag(v2f i) : COLOR
{
half4 c = tex2D(_MainTex, i.uv);
c.rgb *= c.a;
half4 outlineC = _Color;
outlineC.a *= ceil(c.a);
outlineC.rgb *= outlineC.a;
fixed alpha_up = tex2D(_MainTex, i.uv + fixed2(0, _MainTex_TexelSize.y)).a;
fixed alpha_down = tex2D(_MainTex, i.uv - fixed2(0, _MainTex_TexelSize.y)).a;
fixed alpha_right = tex2D(_MainTex, i.uv + fixed2(_MainTex_TexelSize.x, 0)).a;
fixed alpha_left = tex2D(_MainTex, i.uv - fixed2(_MainTex_TexelSize.x, 0)).a;
return lerp(outlineC, c, ceil(alpha_up * alpha_down * alpha_right * alpha_left));
}
ENDCG
}
}
FallBack "Diffuse"
}
And of course Unity’s sprite/diffuse
Shader "Sprites/Diffuse"
{
Properties
{
[PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
[MaterialToggle] PixelSnap ("Pixel snap", Float) = 0
[HideInInspector] _RendererColor ("RendererColor", Color) = (1,1,1,1)
[HideInInspector] _Flip ("Flip", Vector) = (1,1,1,1)
[PerRendererData] _AlphaTex ("External Alpha", 2D) = "white" {}
[PerRendererData] _EnableExternalAlpha ("Enable External Alpha", 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 nolightmap nodynlightmap keepalpha noinstancing
#pragma multi_compile_local _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
#include "UnitySprites.cginc"
struct Input
{
float2 uv_MainTex;
fixed4 color;
};
void vert (inout appdata_full v, out Input o)
{
v.vertex = UnityFlipSprite(v.vertex, _Flip);
#if defined(PIXELSNAP_ON)
v.vertex = UnityPixelSnap (v.vertex);
#endif
UNITY_INITIALIZE_OUTPUT(Input, o);
o.color = v.color * _Color * _RendererColor;
}
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = SampleSpriteTexture (IN.uv_MainTex) * IN.color;
o.Albedo = c.rgb * c.a;
o.Alpha = c.a;
}
ENDCG
}
Fallback "Transparent/VertexLit"
}
And here is the combined result:
Shader "Custom/SpriteOutline"
{
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_Color ("Tint", Color) = (1, 1, 1, 1)
_Color2 ("Outline color", Color) = (1, 1, 1, 1)
}
SubShader {
Tags {"Queue"="Transparent" "RenderType"="Transparent"}
Cull Off
Blend One OneMinusSrcAlpha
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
struct v2f {
float4 pos : SV_POSITION;
half2 uv : TEXCOORD0;
};
v2f vert(appdata_base v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
return o;
}
fixed4 _Color2;
float4 _MainTex_TexelSize;
fixed4 frag(v2f i) : COLOR
{
half4 c = tex2D(_MainTex, i.uv);
c.rgb *= c.a;
half4 outlineC = _Color2;
outlineC.a *= ceil(c.a);
outlineC.rgb *= outlineC.a;
fixed alpha_up = tex2D(_MainTex, i.uv + fixed2(0, _MainTex_TexelSize.y)).a;
fixed alpha_down = tex2D(_MainTex, i.uv - fixed2(0, _MainTex_TexelSize.y)).a;
fixed alpha_right = tex2D(_MainTex, i.uv + fixed2(_MainTex_TexelSize.x, 0)).a;
fixed alpha_left = tex2D(_MainTex, i.uv - fixed2(_MainTex_TexelSize.x, 0)).a;
return lerp(outlineC, c, ceil(alpha_up * alpha_down * alpha_right * alpha_left));
}
ENDCG
}
//Surface shader starts here
Cull Off
Lighting Off
ZWrite Off
Blend One OneMinusSrcAlpha
CGPROGRAM
#pragma surface surf Lambert vertex:vert fragment:frag nofog nolightmap nodynlightmap keepalpha noinstancing
#pragma multi_compile_local _ PIXELSNAP_ON
#pragma multi_compile _ ETC1_EXTERNAL_ALPHA
#include "UnitySprites.cginc"
struct Input
{
float2 uv_MainTex;
fixed4 color;
};
void vert (inout appdata_full v, out Input o)
{
v.vertex = UnityFlipSprite(v.vertex, _Flip);
#if defined(PIXELSNAP_ON)
v.vertex = UnityPixelSnap (v.vertex);
#endif
UNITY_INITIALIZE_OUTPUT(Input, o);
o.color = v.color * _Color * _RendererColor;
}
void surf (Input IN, inout SurfaceOutput o)
{
fixed4 c = SampleSpriteTexture (IN.uv_MainTex) * IN.color;
o.Albedo = tex2D (_MainTex, IN.uv_MainTex) * IN.color;
//If I disable alpha then outlines are hidden properly, why?
//o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}