All these toon shaders I have been seeing render an duplicated mesh behind it and extrude the normals of the vectors to make it look like it has a border, but it feels a bit hacky to me.

Basically I am trying to create an outline shader for a plane so these toon shaders do not really work.

It's probably due to my lack of deep knowledge about shaders in particular but I am really confused and would like someone to explain. I'm also a bit worried that rendering an object twice would hit the performance.

Just a border/outline around the plane.

Edit: Added picture for reference. (Thanks for the edit Alexandre!)

  • 2
    \$\begingroup\$ When you say "outline shader for a plane," do you mean you just need to draw a stroked rectangle? If so, an outlining shader might not be the simplest route to this effect. Try showing us a mock-up of how this should look in your game, and we can suggest methods to achieve it. \$\endgroup\$
    – DMGregory
    Commented Mar 27, 2018 at 16:58
  • \$\begingroup\$ do you need a simple fragment shader like this. \$\endgroup\$ Commented Mar 27, 2018 at 20:36
  • \$\begingroup\$ @DMGregory: Added a mock-up for reference. Thanks! \$\endgroup\$
    – Patrick
    Commented Mar 28, 2018 at 7:59

1 Answer 1


If all we need is an outline around a rectangle, we can compute this analytically with the math of signed distance fields. :)

v2f vert (appdata v)
    v2f o;
    // Slightly enlarge our quad, so we have a margin around it to draw the outline.
    float expand = 1.1f;
    v.vertex.xyz *= expand;
    o.vertex = UnityObjectToClipPos(v.vertex);
    // If we want to get fancy, we could compute the expansion 
    // dynamically based on line thickness & view angle, but I'm lazy)

    // Expand the texture coordinate space by the same margin, symmetrically.
    o.uv = (v.uv - 0.5f) * expand + 0.5f;
    return o;

fixed4 frag (v2f i) : SV_Target
    // Texcoord distance from the center of the quad.
    float2 fromCenter = abs(i.uv - 0.5f);
    // Signed distance from the horizontal & vertical edges.
    float2 fromEdge = fromCenter - 0.5f;

    // Use screenspace derivatives to convert to pixel distances.
    fromEdge.x /= length(float2(ddx(i.uv.x), ddy(i.uv.x)));
    fromEdge.y /= length(float2(ddx(i.uv.y), ddy(i.uv.y)));

    // Compute a nicely rounded distance from the edge.
    float distance = abs(min(max(fromEdge.x,fromEdge.y), 0.0f) + length(max(fromEdge, 0.0f)));

    // Sample our texture for the interior.
    fixed4 col = tex2D(_MainTex, i.uv);
    // Clip out the part of the texture outside our original 0...1 UV space.
    col.a *= step(max(fromCenter.x, fromCenter.y), 0.5f);

    // Blend in our outline within a controllable thickness of the edge.
    col = lerp(col, _Color, saturate(_Thickness - distance));

    return col;

Sample render

This gives us an outline of constant pixel thickness with nicely rounded corners and antialiased edges (the green box above - the orange is Unity's selection outline, showing how we've expanded the quad relative to its otherwise identical twin below)

  • \$\begingroup\$ Awesome, thank you very much. This helps me a lot! If I'd want to compute the expansion, what would be needed? You don't have to program it, just pointing me to some resources would be an immensive help! :) \$\endgroup\$
    – Patrick
    Commented Mar 28, 2018 at 13:27
  • \$\begingroup\$ Honestly, I'm not sure how I'd approach that without diving in to actually work on it. There are a few funny cases to handle, like if your camera is allowed to get very close to the plane of the quad — then the outline needs to protrude out of plane. You might need a little skirt of geometry to do this well. \$\endgroup\$
    – DMGregory
    Commented Mar 28, 2018 at 14:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .