Perspective Wireframe (using baked Barycentric coordinates)

I would like to achieve a “perspective” Wireframe using baked Barycentric coordinates (without a geometry pass). Ie a wireframe where the thickness is a constant with reference to the world space (right side of the reference image).

Most of the references I’ve found use fwidth or ddx & ddy to scale the wireframe relative to the screen space. This means that the wireframe looks thicker when the object is further away (left side of the reference image).

An example of such a shader is:

From Optimal way of rendering lines by @bgolus

Shader "Wireframe using Baked Barycentric Coordinates"
{
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _Width ("Line Width (Pixels)", Range(1, 50)) = 2
    }
    SubShader {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        Blend SrcAlpha OneMinusSrcAlpha
        Cull Off ZWrite Off
        Pass {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            struct v2f {
                float4 pos : SV_Position;
                float3 coord : TEXCOORD0;
            };
            half4 _Color;
            float _Width;
            void vert (appdata_full v
                , out v2f o
                // , uint vid : SV_VertexID
                )
            {
                o.pos = UnityObjectToClipPos(v.vertex);
                o.coord = v.color.xyz;
                // hack to get barycentric coords on the default plane mesh
                // vid += (uint)round(v.vertex.z + 1000);
                // uint colIndex = vid % 3;
                // o.coord = float3(colIndex == 0, colIndex == 1, colIndex == 2);
            }
            half4 frag (v2f i) : SV_Target
            {
                float3 coordScale = fwidth(i.coord);
                // more accurate alternative to fwidth
                // float3 coordScale = sqrt(pow(ddx(i.coord), 2) + pow(ddy(i.coord), 2));
                float3 scaledCoord = i.coord / coordScale;
                float dist = min(scaledCoord.x, min(scaledCoord.y, scaledCoord.z));
                float halfWidth = _Width * 0.5;
                float wire = smoothstep(halfWidth + 0.5, halfWidth - 0.5, dist);
                return half4(_Color.rgb, _Color.a * wire);
            }
            ENDCG
        }
    }
}

How can I modify such a shader so that the Wireframe thickness is constant with reference to the world space (right side of the reference image) please?

The easiest solution is to change the line width to a 0.0 to 1.0 range and not scale the barycentrics by the derivatives (fwidth or ddx & ddy). Something like this:

        // property
        _Width ("Line Width", Range(0, 1)) = 0.1

            half4 frag (v2f i) : SV_Target
            {
                // more accurate alternative to fwidth
                float3 coordScale = sqrt(pow(ddx(i.coord), 2) + pow(ddy(i.coord), 2));

                float3 scaledCoord = (i.coord - _Width * 0.5) / coordScale;
                float dist = min(scaledCoord.x, min(scaledCoord.y, scaledCoord.z));
                float wire = smoothstep(0.5, -0.5, dist);

                return half4(_Color.rgb, _Color.a * wire);
            }

However this doesn’t actually scale them by the world scale, rather doesn’t scale them at all. The derivatives are still used, but just to do anti-aliasing. The final line width is purely dependent on the scale of the triangle relative to the fractional cutoff. This is easy to solve with a geometry shader as you can trivially get the size of the triangle. For a fragment shader it’s a little harder.

1 Like

@bgolus thanks, I’ll give it a try.

Hi @jmsgreen , did you succeed in a perspective wireframe shader based on barycentric coordinates? Can you share the code?