help with blurring outline shader

hello there, i hope someone would be very kind and help me out here.
i have created this outline shader and i am trying to figure out how to make an softer blend from one color to the outline color.
below you can see my current shader and another pic of what i am trying to achieve :slight_smile:
thanks guys :slight_smile:
this is my current outline


this is the look i am trying to get
and here is my shader:

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// Upgrade NOTE: replaced '_Projector' with 'unity_Projector'
// Upgrade NOTE: replaced '_ProjectorClip' with 'unity_ProjectorClip'

Shader "Projector/Outline" {
    Properties {
        _ShadowTex ("Cookie", 2D) = "gray" {}
        _FalloffTex ("FallOff", 2D) = "white" {}

    //     _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
    _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
    _Color ("Main color", color) = (1,1,1,1)
    _Stroke ("Stroke Alpha",range(0,1)) = 0.1
    _Strokecolor ("Stroke Color",color) = (1,1,1,1)
    }
    Subshader {
        Tags {"Queue"="Transparent"}
        Pass {
            ZWrite Off
            ColorMask RGB
            Blend DstColor Zero
            Offset -1, -1

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "UnityCG.cginc"
           
            struct v2f {
                float4 uvShadow : TEXCOORD0;
                float4 uvFalloff : TEXCOORD1;
                UNITY_FOG_COORDS(2)
                float4 pos : SV_POSITION;
            };
           
            float4x4 unity_Projector;
            float4x4 unity_ProjectorClip;
           
            v2f vert (float4 vertex : POSITION)
            {
                v2f o;
                o.pos = UnityObjectToClipPos (vertex);
                o.uvShadow = mul (unity_Projector, vertex);
                o.uvFalloff = mul (unity_ProjectorClip, vertex);
                UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }
           
            sampler2D _ShadowTex;
            sampler2D _FalloffTex;

             sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Cutoff;
            half4 _Color;
            fixed _Stroke;
            half4 _Strokecolor;
           
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 texS = tex2Dproj (_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
                texS.a = 1.0-texS.a;

                fixed4 texF = tex2Dproj (_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff));
                fixed4 res = lerp(fixed4(1,1,1,0), texS, texF.a);
                 clip(res.a - _Cutoff);
                UNITY_APPLY_FOG_COLOR(i.fogCoord, res, res.a);

                if(res.a < _Stroke)
                {
                    res = _Strokecolor;
                    res.a = _Strokecolor.a;
                }
                else
                {
                    res = _Color;
                    res.a = _Color;
                }
                fixed4 comb = lerp(_Strokecolor, _Color, fixed4(1,1,1,1));
                fixed4 res2 = (comb + res ) * fixed4 (1,1,1,1);
                return res2;
            }
            ENDCG
        }
    }
}

Don’t use an if statement here. Instead lerp between _strokecolor and _color. Something like this:

res = lerp(_Strokecolor, _Color, smoothstep(_Stroke - 0.05, _Stroke + 0.05, res.a));

2 Likes

Hey man, thank you so much !
it worked quite well and i managed to get the look i needed :slight_smile:

hey everyone :slight_smile:
well i got my outline working but this produced a new problem :frowning:
to make it more clear what i am working on, i´m doing some fog of war mechanics.
i have a camera that only sees depth and produce a black and white image, which goes to an rendertexture that projects the discovered areas and the none discovered. this works well but i also need the gray tone (which is what the outline produces) so that if my units move away from a spot, they will have discovered the area but it gets greyed out.

my problem now, is that if i save the FOW map and load it up again, my shader only returns white but still keep it´s outline. i have very limited shader experience and i don´t know if it´s because it gets confused when i recreate the rendertexture ?
anyway here´s my script that saves the rendertexture and loads it up again. and the modified shader.

this is the pic of how the FOW looks like before saving it


and this is the pic after i have loaded the saved texture to the rendertarget

and finally my shader code.

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

// Upgrade NOTE: replaced '_Projector' with 'unity_Projector'
// Upgrade NOTE: replaced '_ProjectorClip' with 'unity_ProjectorClip'

Shader "Projector/Outline" {
    Properties {
        _ShadowTex ("RenderTexture", 2D) = "gray" {}
        _FalloffTex ("FallOff", 2D) = "white" {}

    //     _MainTex ("Base (RGB) Trans (A)", 2D) = "white" {}
    _Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
    _Color ("Main color", color) = (1,1,1,1)
    _Stroke ("Stroke Alpha",range(0,1)) = 0.1
    _Strokecolor ("Stroke Color",color) = (1,1,1,1)
    }
    Subshader {
        Tags {"Queue"="Transparent"}
        Pass {
            ZWrite Off
            ColorMask RGB
            Blend DstColor Zero
            Offset -1, -1

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog
            #include "UnityCG.cginc"
          
            struct v2f {
                float4 uvShadow : TEXCOORD0;
                float4 uvFalloff : TEXCOORD1;
                UNITY_FOG_COORDS(2)
                float4 pos : SV_POSITION;
            };
          
            float4x4 unity_Projector;
            float4x4 unity_ProjectorClip;
          
            v2f vert (float4 vertex : POSITION)
            {
                v2f o;
                o.pos = UnityObjectToClipPos (vertex);
                o.uvShadow = mul (unity_Projector, vertex);
                o.uvFalloff = mul (unity_ProjectorClip, vertex);
                UNITY_TRANSFER_FOG(o,o.pos);
                return o;
            }
          
            sampler2D _ShadowTex;
            sampler2D _FalloffTex;

             sampler2D _MainTex;
            float4 _MainTex_ST;
            fixed _Cutoff;
            half4 _Color;
            fixed _Stroke;
            half4 _Strokecolor;
          
            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 texS = tex2Dproj (_ShadowTex, UNITY_PROJ_COORD(i.uvShadow));
                texS.a = 1.0-texS.a;

                fixed4 texF = tex2Dproj (_FalloffTex, UNITY_PROJ_COORD(i.uvFalloff));
                fixed4 res = lerp(fixed4(1,1,1,0), texS, texF.a);
              

                  fixed4 res1 = lerp(fixed4(1,1,1,0),_Strokecolor,smoothstep(_Stroke - 0.1, _Stroke + 0.1, res.a));
                fixed4 res2 = lerp(res1,_Strokecolor,smoothstep(_Stroke -0.1, _Stroke +0.1, res.a));

                UNITY_APPLY_FOG_COLOR(i.fogCoord, res1, texF.a);

            //    return res2;
            //    return res1;
            return res1;
            }
            ENDCG
        }
    }
}

and finally my script that saves and loads my FOW map

using UnityEngine;
using System.Collections;
using System.IO;
public class SaveTexture : MonoBehaviour {

    public RenderTexture unFillTexture;
    public Rect grabRect;
    private Texture2D test;
    public    string filepath;
    Camera cam;
    public    int ScreenWidth,ScreenHeight ;
    public bool Save,load;
    public Texture2D loadtex;
    public GameObject matte,FogPojector;
//    public Material projectoroutline,projectorMulti;
    void Start () {

        cam =GetComponent <Camera>();
        ScreenWidth = (int)grabRect.width;
        ScreenHeight = (int)grabRect.height;
        matte.SetActive (false);
        filepath = GameManager.Instance.filepath;


    }
    void Update()
    {
        if(Save)
        {
           
            StartCoroutine ("SaveFog");
        }
        else
        {
           
            StopCoroutine ("SaveFog");
        }

        if(load)
        {
            StartCoroutine ("LoadFog");
        }
        else
        {
            StopCoroutine ("LoadFog");
        }
    }

    IEnumerator SaveFog(){

        yield return new WaitForEndOfFrame ();
        matte.SetActive (true);

        RenderTexture rt = new RenderTexture(ScreenWidth, ScreenHeight, 24);
        GetComponent<Camera>().targetTexture = rt;
        Texture2D screenShot = new Texture2D(ScreenWidth, ScreenHeight, TextureFormat.ARGB32, false);
        GetComponent<Camera>().Render();
        RenderTexture.active = rt;
        screenShot.ReadPixels(new Rect(0, 0, ScreenWidth, ScreenHeight), 0, 0);
        GetComponent<Camera>().targetTexture = null;
        RenderTexture.active = null; // JC: added to avoid errors
        Destroy(rt);
    //    GetComponent<Camera>().targetTexture = unFillTexture;
        byte[] bytes = screenShot.EncodeToPNG();

        File.WriteAllBytes (filepath + "/fogmap.png",bytes);
        print (filepath + "/fogmap.png");
        matte.SetActive (false);
        Save = false;
        GameManager.Instance.FOWMap = true;
    //    gameObject.SetActive (false);
        StopCoroutine ("SaveFog");


    }

    IEnumerator LoadFog(){

        yield return new WaitForEndOfFrame ();
        //FogPojector.GetComponent <Projector>().material = projectorMulti;

        Texture2D tex = null;
        //loadtex = unFillTexture;
        byte[] filedata;

        if (File.Exists (filepath + "/fogmap.png"))
        {
            filedata = File.ReadAllBytes (filepath + "/fogmap.png");
            tex = new Texture2D (2, 2);
            tex.LoadImage (filedata);
            loadtex = tex;
        //    loadtex.alphaIsTransparency = true;
            Graphics.Blit (loadtex,unFillTexture);


       

        }
        yield return new WaitForEndOfFrame ();
        load = false;


        if(load ==false)
        {
            gameObject.SetActive (false);
        }

    }


}

i am 95 percent sure it´s a shader problem because the rendertexture works fine with other shaders. hope you guys can help me out :slight_smile:

Looks like you lerp from white to the stroke color for res1. Then you lerp from the stroke color to res1 for res2.

Pretty sure res2 needs to lerp from res1 to black.

HI, thank you for your reply :slight_smile:
well if you look at the shader, i´m actually not returning anything from res2
but i tried your idea with lerping from res1 to black but that produced the same bug. maybe it´s a alpha issue ?

hey there, i have been doing some testing and it seems like the problem lies in the saved texture. the reason why my black fog disappears is because my texture gets loaded up as an simple RGB with no alpha. i tested with another texure and found that in order to use the rgb for alpha masking is to put the alpha source to grayscale.
unfortunately it can only be done in the inspector. does anyone know another way to copy the rgb values of a texture to the Alpha channel ?

If you mean in c# just set the alpha value of the pixel the same way you’re setting the RGB values.

If you mean in the shader just set the alpha of your output to any value you want. i.e. “res1.a = whatever” looks like you maybe want “res1.a = texF.a”.

hey man, thank you for your help :slight_smile:
i tried both in C# and in the shader, but both methods didn´t work out :frowning:
i have moved on to try another method which actually seem to work. instead of saving out a texture i instead use a tile based system instead. since i am already using a grid system to create floors and buildings, i managed to tweak it to use for my FOW.
my only issue now, i that i tried to make it interesting by creating a Hexagon grid for my fow tiles. unfortunately if i use my functions i use to get a tile based on a vector, my affected hex tile is in a offset.
So right now i am trying to figure out how to get an hex tile´s position based on a vector 3 position in world space. maybe i should close this post and make a new post somewhere else ?