2D outline with texture sprite in multiple invading neighbor frame

Hello. Right now I have an 2D outline shader working pretty fine. This is done in the common way to sample the same texture multiple times giving an offset on different directions.
The problem is, as the texture has been cut out inside unity, the technique is applied to the neighbors frames as well, going inside the current frame when the outline is too big or the free space not too much, or both.
I have searched for solution and read that probably texel size (with some calculations) could help me here. I also read in the post here that I could detect when a texture is going outside of the normal [0,1] uv values and just discard it.

So, here are my questions:

  • First of all, how sprite renderer works in unity? it seems to be handling all its properties automatically. For example, I haven’t set anything related to flip and the flip works. But most important, the shader looks to hold the entire texture but the sprite is just showing the assigned frame. Yes this is because it is cut out, but, when is this done during the render process?
  • Second, directly related to the above question. If the “frame selection” stuff is done automatically, is there actually a way to say: “Hey, discard all the “frames” (this through the normal pixel work of course) that are outside of the current uv sample”?. I ask this because maybe that part of the flow is done AFTER the entire custom shader, so the “extra frames” are never outside of the uv.

Image with the problem is exaggerated because I can’t show the real sprite, but mine is failing with a pretty small outline. That’s why I need the solution
To show the problem I am using the outline from 1 side so it is easier to see the problem

Thanks a lot for anyone who can, at least, let me know if I this is possible or I would need to go and search for another feature or change all the spritesheets.

Good day!

Shader "Unlit/outlineShaderTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _outlineThickness("Outline Thickness", Range(0, 1)) = 0
        _outlineColor ("Outline Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "Queue"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

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

            sampler2D _MainTex;
            float _outlineThickness;
            float4 _outlineColor;

            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos (v.vertex);
                o.uv = v.uv;
              
                return o;
            }

            fixed4 CreateABorder(float2 uv, float4 mainTextureSampler, sampler2D mainTextureWithoutSampling, float xOfsset, float yoffset){
                uv.x += xOfsset;
                uv.y += yoffset;
                float4 border = saturate(tex2D(mainTextureWithoutSampling, uv).aaaa - mainTextureSampler.aaaa);

                return border;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float2 uv = i.uv ;
                sampler2D mainTex = _MainTex;
                fixed4 mainTexture = tex2D(mainTex, uv);

                // CREATE OUTLINE BORDERS --------------------------------------------------------
                fixed4 bottomOutlineTexture = CreateABorder(uv, mainTexture, mainTex, 0, _outlineThickness);
                fixed4 topOutlineTexture = CreateABorder(uv, mainTexture, mainTex, 0, -_outlineThickness);
                fixed4 leftOutlineTexture = CreateABorder(uv, mainTexture, mainTex, -_outlineThickness, 0);
                fixed4 rightOutlineTexture = CreateABorder(uv, mainTexture, mainTex, _outlineThickness, 0);
                ////-----------------------------------------------------------------------------

                float4 outlineBaseColor = _outlineColor;

                // ADDING BORDERS ALL TOGETHER ---------------------------------------------------
                float4 finalOutlineColor = saturate(
                    bottomOutlineTexture +
                    topOutlineTexture +
                    leftOutlineTexture +
                    rightOutlineTexture);
                // ------------------------------------------------------------------------------
                finalOutlineColor *= outlineBaseColor;

                float4 finalTextureColor = mainTexture + finalOutlineColor;

                return finalTextureColor;
            }
            ENDCG
        }
    }
}

9862785--1421010--outline normal.PNG
9862785--1421016--outline problem.PNG
9862785--1421019--material has whole texture.PNG
9862785--1421022--Character.1.png

Unity’s Sprites work by setting the UVs of the rendered mesh to the specific sprite you want within the current sprite atlas. Either you imported in an image with multiple sprites in it, or optionally Unity can pack multiple sprites that were imported separately into a single texture atlas for you.

Your outline here is using the raw UVs for the offset, which isn’t a great option here. UVs are normalized to be a 0.0 to 1.0 range where (0.0, 0.0) is the bottom left and (1.0, 1.0) is the top right. The resolution of the image, or the number or position of the sprites within the texture don’t change this. So if you want a 1 texel outline you’d potentially need a different “outline thickness” for every sprite texture being used.

What you probably want is to take into account the resolution of the texture currently being used, and then specify the outline width in texels.

Add this to your shader after sampler2D _MainTex;

float4 _MainTex_TexelSize;

That value holds the values (1 / textureWidth, 1 / textureHeight, textureWidth, textureHeight).

Then when you’re calling the CreateABorder() function, multiply the thickness value by _MainTex_Texel.x or _MainTex_Texel.y depending on if it’s supposed to be a horizontal or vertical offset respectively.