Issues creating fixed-width lines using Geometry shader in view space

Hi, I’m attempting to create lines of a fixed width. I am creating a mesh using the Lines topology, and then using a geometry shader to create a quad from each line. My goal is to create lines that are of a constant width, ignoring perspective. Here’s what I’ve got so far:

Shader "Unlit/ThickLine"
{
    Properties
    {
        _Color("Main Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma target 5.0
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geo
           
            #include "UnityCG.cginc"

            struct v2g
            {
                float4 vertex : SV_POSITION;
            };

            struct g2f
            {
                float4 pos: POSITION;
            };

            fixed4 _Color;
           
            v2g vert (float4 position : POSITION)
            {
                v2g o;
                o.vertex = position;
                return o;
            }

            [maxvertexcount(4)]
            void geo(line v2g v[2], inout TriangleStream<g2f> ts)
            {
                float weight = 1;

                float4 p1 = mul(UNITY_MATRIX_MVP, v[0].vertex);
                float4 p2 = mul(UNITY_MATRIX_MVP, v[1].vertex);

                float4 dir = normalize(p2 - p1);
                float4 perp = float4(dir.y, -dir.x, 0, 0);

                float4 v1_top = p1 + perp * weight;
                float4 v1_bot = p1 - perp * weight;

                float4 v2_top = p2 + perp * weight;
                float4 v2_bot = p2 - perp * weight;

                g2f o1;
                o1.pos = v1_top;
                ts.Append(o1);

                g2f o2;
                o2.pos = v1_bot;
                ts.Append(o2);

                g2f o3;
                o3.pos = v2_top;
                ts.Append(o3);

                g2f o4;
                o4.pos = v2_bot;
                ts.Append(o4);
            }
           
            fixed4 frag (g2f i) : SV_Target
            {
                return _Color;
            }
            ENDCG
        }
    }
}

Here’s what I think I’m doing: I’m converting both the line points into view space. From there I get the direction from one point to another, then the direction perpendicular to this. Now I can build the four points of the quad to represent this line. Since I’m doing the offsets in view space, this should create a line of constant width. Here’s what I’m getting:

(With the camera right beside the starting point).

I’m not sure what I’m doing wrong, but it looks like the offsets are being applied in world space…but I’m not sure how since I clearly transform the vertices beforehand.

Thanks for any help,
Erik

So it turns out I was a bit off on how view space works—I was assuming perspective distortion had already been applied and I could operate on the vertices as if they were already on a 2D plane (which doesn’t seem to be the case).

My current resource is this article about drawing lines in OpenGL. I’m trying to implement the Screen-Space Projected Lines section (except as a geometry shader), which should be pretty easy, but in some simple tests I’ve been doing it looks like the w coordinate is always 1, making it difficult to correct for the perspective divide…based on what I’ve read here in the OpenGL manual the w coordinate should be the perspective divide at the end of the vertex shader, but this doesn’t seem to be the case. Does anyone have any ideas?

1 Like

Was able to figure it out! Trick was to multiply the offset by each individual vertex’s w coordinate. Here’s the geometry shader, pretty simple:

            [maxvertexcount(4)]
            void geo(line v2g v[2], inout TriangleStream<g2f> ts)
            {
                g2f g[4];

                float4 p1 = mul(UNITY_MATRIX_MVP, v[0].vertex);
                float4 p2 = mul(UNITY_MATRIX_MVP, v[1].vertex);

                float2 dir = normalize(p2.xy - p1.xy);
                float2 normal = float2(-dir.y, dir.x);

                float4 offset1 = float4(normal * p1.w * _Thickness / 2.0f, 0, 0);
                float4 offset2 = float4(normal * p2.w * _Thickness / 2.0f, 0, 0);

                float4 o1 = p1 + offset1;
                float4 o2 = p1 - offset1;
                float4 o3 = p2 + offset2;
                float4 o4 = p2 - offset2;

                g[0].pos = o1;
                g[1].pos = o2;
                g[2].pos = o3;
                g[3].pos = o4;

                ts.Append(g[0]);
                ts.Append(g[1]);
                ts.Append(g[2]);
                ts.Append(g[3]);
            }

2 Likes

Hello.
I was able to do what I wanted to do with your shader! Thank you very much.

However, I noticed that some line displays became thin and disappeared depending on the camera viewpoint.
Fixing the 10th line of the shader this way, it worked for me.

float2 dir = normalize(p2.xy /p2.w - p1.xy /p1.w);
1 Like

Vertical lines are wider because screen aspect ratio is stretching clip space as well. Fix:
offset.x *= _ScreenParams.y/_ScreenParams.x;