3
\$\begingroup\$

Most Outline Shaders or Tutorials found online, are Shaders which are sharp edge-outlines. Like here:

enter image description here

Even though a lot of games are already using soft outlines like here:enter image description here

with a nice gradienty- blurry effect.

Creating a Outline is basically easy (as described here: http://wiki.unity3d.com/index.php/Silhouette-Outlined_Diffuse)

Also ive seen that wanted effect in 2D: https://www.assetstore.unity3d.com/en/#!/content/84126

But how do they do it in 3D?

Ive found a useful step-by-step guide here: (https://willweissman.wordpress.com/tutorials/shaders/unity-shaderlab-object-outlines/) . But the first step is already not working. (rendering the 3d object in as pure white to a rendertexture).

\$\endgroup\$
  • 6
    \$\begingroup\$ Anytime you're tempted to type the phrase "not working," please immediately backspace it and replace it with a detailed description of the symptoms. There are millions of ways something could not work, including crashing, compile-time or run-time errors, blank screens / output, output that's mirrored, output that flickers or stutters, output that is distorted or discoloured, or any other imaginable way the output could differ from the target. \$\endgroup\$ – DMGregory Jul 12 '17 at 13:33
  • \$\begingroup\$ The answer is almost certainly "blur." That is, the outline is rendered in post processing and blurred before being drawn to the screen. Doing that is going to be hard; I've never managed to write a post processing effect correctly. \$\endgroup\$ – Draco18s no longer trusts SE Jul 12 '17 at 16:01
  • 2
    \$\begingroup\$ I will not post a complete answer but this link contains a solution that works great xroft666.blogspot.bg/2015/07/glow-highlighting-in-unity.html \$\endgroup\$ – Uri Popov Jul 13 '17 at 10:01
1
\$\begingroup\$

Sooo after keeping myself busy finding all my mistakes about the tutorial ive made in this: https://willweissman.wordpress.com/tutorials/shaders/unity-shaderlab-object-outlines/)

I would like to provide a very simple solution- where you dont even have to understand shaders at all.

  1. Create a new Camera Gameobject which only Renders things you want in a RenderTexture. That Rendertexture will later have the Outline. (Set the Culling Mask to a new "Outline" layer. Set the Layer of the Objects you want the outline for to the same layer.) enter image description here Also create a new RenderTexture and put it inside "TargetTexture" of your camera.

  2. Put this script inside that camera gameobject

    public class OutlineEffect : MonoBehaviour
    {
        public bool Gauss;
    
        public Material WhiteMaker;
        public Material PostOutline;
        public Material GaussianBlur;
    
        private Camera cam;
    
        void Start()
        {
            cam = GetComponent<Camera>();
        }
    
        void OnRenderImage(RenderTexture source, RenderTexture destination)
        {
            RenderTexture TempRT = new RenderTexture(cam.pixelWidth, cam.pixelHeight, 24);
            RenderTexture whiteRT = new RenderTexture(TempRT.width, TempRT.height, TempRT.depth);
    
            Graphics.Blit(source, whiteRT, WhiteMaker);
            if (Gauss)
            {
                Graphics.Blit(whiteRT, TempRT, PostOutline);
                Graphics.Blit(TempRT, destination, GaussianBlur);
            }
            else
            {
                Graphics.Blit(whiteRT, destination, PostOutline);
            }
        }
    }
    
  3. put these shaders inside a material with the same name and put those materials inside that script in step 2.

WHITEMAKER SHADER

Shader "WhiteMaker" {
    Properties
    {
        _MainTex("Main Texture 1", 2D) = "white" {}
    }

    SubShader
    {
        Tags{ "Queue" = "Transparent" }
        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha 
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            half _SampleTime;

            v2f_img vert(appdata_base v)
            {
                v2f_img o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = v.texcoord.xy;
                return o;
            }
            float4 frag(v2f_img i) : COLOR
            {
                // Surface
                float4 main = tex2D(_MainTex, i.uv);
                if (main.a == 0)
                {
                    return float4(0, 0, 0, 0);
                }
                else
                {

                    return float4(1, 1, 1, 1);
                }
                return main;
            }
                ENDCG
            }
    }
} 

POST OUTLINE SHADER

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

'UnityObjectToClipPos(*)'

Shader "Custom/Post Outline"
{
    Properties
    {
        _MainTex("Main Texture",2D) = "black"{}
    _SceneTex("Scene Texture",2D) = "black"{}
    }
        SubShader
    {
        Pass
    {
        CGPROGRAM

        sampler2D _MainTex;
    float4 _MainTex_ST;

    //<SamplerName>_TexelSize is a float2 that says how much screen space a texel occupies.
    float2 _MainTex_TexelSize;

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
    struct appdata
    {
        float4 pos : POSITION;
        float2 uv : TEXCOORD0;
    };

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

    v2f vert(appdata v)
    {
        v2f o;
        o.pos = UnityObjectToClipPos(v.pos);
        o.uv = o.pos.xy / 2 + 0.5;
        return o;
    }


    half frag(v2f i) : COLOR
    {
        //arbitrary number of iterations for now
        int NumberOfIterations = 20;

    //split texel size into smaller words
    float TX_x = _MainTex_TexelSize.x;

    //and a final intensity that increments based on surrounding intensities.
    float ColorIntensityInRadius;

    //for every iteration we need to do horizontally
    for (int k = 0; k<NumberOfIterations; k += 1)
    {
        //increase our output color by the pixels in the area
        ColorIntensityInRadius += tex2D(
            _MainTex,
            i.uv.xy + float2
            (
            (k - NumberOfIterations / 2)*TX_x,
                0
                )
        ).r / NumberOfIterations;
    }

    //output some intensity of teal
    return ColorIntensityInRadius;
    }

        ENDCG

    }
        //end pass    

        GrabPass{}

        Pass
    {
        CGPROGRAM

        sampler2D _MainTex;
    sampler2D _SceneTex;

    float4 _MainTex_ST;


    //we need to declare a sampler2D by the name of "_GrabTexture" that Unity can write to during GrabPass{}
    sampler2D _GrabTexture;

    //<SamplerName>_TexelSize is a float2 that says how much screen space a texel occupies.
    float2 _GrabTexture_TexelSize;

    #pragma vertex vert
    #pragma fragment frag
    #include "UnityCG.cginc"
    struct appdata
    {
        float4 pos : POSITION;
        float2 uv : TEXCOORD0;
    };


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

    v2f vert(appdata v)
    {
        v2f o;

        //Despite the fact that we are only drawing a quad to the screen, Unity requires us to multiply vertices by our MVP matrix, presumably to keep things working when inexperienced people try copying code from other shaders.
        o.pos = UnityObjectToClipPos(v.pos);

        //Also, we need to fix the UVs to match our screen space coordinates. There is a Unity define for this that should normally be used.
            o.uv = o.pos.xy / 2 + 0.5;
        o.uv = TRANSFORM_TEX(v.uv, _MainTex);

        return o;
    }


    half4 frag(v2f i) : COLOR
    {
        //arbitrary number of iterations for now
        int NumberOfIterations = 20;

    //split texel size into smaller words
    float TX_y = _GrabTexture_TexelSize.y;

    //and a final intensity that increments based on surrounding intensities.
    half ColorIntensityInRadius = 0;

    //if something already exists underneath the fragment (in the original texture), discard the fragment.
    if (tex2D(_MainTex,i.uv.xy).r>0)
    {
        discard;
        return tex2D(_SceneTex,float2(i.uv.x,1 - i.uv.y));
    }

    //for every iteration we need to do vertically
    for (int j = 0; j<NumberOfIterations; j += 1)
    {
        //increase our output color by the pixels in the area
        ColorIntensityInRadius += tex2D(
            _GrabTexture,
            float2(i.uv.x,1 - i.uv.y) + float2
            (
                0,
                (j - NumberOfIterations / 2)*TX_y
                )
        ).r / NumberOfIterations;
    }


    //this is alpha blending, but we can't use HW blending unless we make a third pass, so this is probably cheaper.
    half4 outcolor = ColorIntensityInRadius*half4(0,1,1,1) * 2 + (1 - ColorIntensityInRadius)*tex2D(_SceneTex,float2(i.uv.x,1 - i.uv.y));
    return outcolor;
    }

        ENDCG

    }
        //end pass    
    }
        //end subshader
}
//end shader
^
[POST OUTLINE SHADER ENDS HERE]

GAUSSIAN BLUR SHADER

Shader "Blur" 
{
    Properties
    {
        _MainTex("Base (RGB)", 2D) = "white" { }
    }

    SubShader
    {
        ZTest Always Cull Off ZWrite Off Fog{ Mode Off }

        Pass
        {
            CGPROGRAM

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

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;
            float step_w;
            float step_h;

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

            float4 _MainTex_ST;
            float4 _MainTex_ST_TexelSize;

            v2f vert(appdata_base v) 
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : COLOR
            {
                step_w = _MainTex_TexelSize.x;
                step_h = _MainTex_TexelSize.y;

                float2 offset[25] = 
                {
                    float2(-step_w*2.0, -step_h*2.0), float2(-step_w, -step_h*2.0),  float2(0.0, -step_h*2.0), float2(step_w, -step_h*2.0), float2(step_w*2.0, -step_h*2.0),
                    float2(-step_w*2.0, -step_h),     float2(-step_w, -step_h),      float2(0.0, -step_h),     float2(step_w, -step_h),     float2(step_w*2.0, -step_h),
                    float2(-step_w*2.0, 0.0),         float2(-step_w, 0.0),          float2(0.0, 0.0),         float2(step_w, 0.0),         float2(step_w*2.0, 0.0),
                    float2(-step_w*2.0, step_h),      float2(-step_w, step_h),       float2(0.0, step_h),      float2(step_w, step_h),      float2(step_w*2.0, step_h),
                    float2(-step_w*2.0, step_h*2.0),  float2(-step_w, step_h*2.0),   float2(0.0, step_h*2.0),  float2(step_w, step_h * 20),   float2(step_w*2.0, step_h*2.0)
                };

                float kernel[25] = 
                {

                    0.003765,    0.015019,    0.023792,    0.015019,    0.003765,
                    0.015019,    0.059912,    0.094907,    0.059912,    0.015019,
                    0.023792,    0.094907,    0.150342,    0.094907,    0.023792,
                    0.015019,    0.059912,    0.094907,    0.059912,    0.015019,
                    0.003765,    0.015019,    0.023792,    0.015019,    0.003765
                };

                float4 sum = float4(0.0, 0.0, 0.0, 0.0);

                for (int j = 0; j < 25; j++) 
                {
                    float4 tmp = tex2D(_MainTex, i.uv + offset[j]);
                    sum += tmp * kernel[j];
                }

                return sum;
            }

        ENDCG //Shader End
        }

    }

}
  1. create a new "raw image" inside a UI canvas. Put the Rendertexture inside that Rawimage. And set the Camera Height and Width as the image Height and Width. enter image description here

  2. Its done! You can check the "Gauss" bool true or false- depends on which you prefer. Also you can change the color of the raw image

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.