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
    Mar 27 '18 at 16:58
  • \$\begingroup\$ do you need a simple fragment shader like this. \$\endgroup\$ Mar 27 '18 at 20:36
  • \$\begingroup\$ @DMGregory: Added a mock-up for reference. Thanks! \$\endgroup\$ Mar 28 '18 at 7:59

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\$ Mar 28 '18 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
    Mar 28 '18 at 14:06

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.