Not quite right Line drawn in a pixel shader function.

Im going to post the code for the problem i have i suppose this is purely a math problem.

I decided to make a line drawing algorithm purely on the shader in a function as part of a desire for ways to visualize variables on the shader itself.
Now i made a line drawing function and it works but not quite right to say it more or less draws kind of a ray instead of a line and it artifacts if the line is horizontal.

The reason i believe is how i compare the conditional using the slope from a to b and a to the current pixel which introduces a larger and larger amount of error with distance.

but i really can’t see any other way to do it.

so the question is how do i make a nice even line between two points or how to determine the distance of a point from the line itself not the two points that make up the line or how to conditionally test properly.

float4 FuncLineDraw(float2 a, float2 b, float2 pos, float4 col)
{
    float2 p = pos;
    float2 AtoB = b - a; 
    float2 BtoA = a - b;  
    float2 PtoA = a - p; 
    float2 PtoB = b - p; 
    float dotPAtoBA = dot(PtoA, BtoA);
    float dotPBtoAB = dot(PtoB, AtoB);
    // find if the point is between the other two points.
    float isWithinPlanes = sign(dotPAtoBA * dotPBtoAB +0.0001f);

    // find the slope and compare the slopes a to b and a to p.
    float2 slopeAtoB = (b - a).x / (b - a).y;
    float2 slopeAtoPos = (p - a).x / (p - a).y;
    float diffpositive = abs(slopeAtoB - slopeAtoPos);
    //
    // here is the problem the <.01 is the conditional but it is a constant.
    // however the angle from a or the slope is not constant with distance ?
    //
    // how to solve this?
    //
    if (diffpositive < .01f && isWithinPlanes > 0.0f)
    {
        // color fall off
        float distAtoB = distance(b, a);
        float distAtoThisPos = distance(p, a);
        // plot color falloff though if im in here im on the line or close to it.
        col.r = 1.0f * distAtoThisPos / distAtoB;
        col.g = 1.0f - col.r;
        col.b = 1.0f;
    }      
    return col;
}

Back in my QB/asm days, I used to use this as my go-to for line drawing…

… However, I’m not quite sure how to adapt this to a shader since it relies on a for-loop, once you’ve calculated the appropriate steps of the line. In a shader, it’s already iterating over every pixel for you. I think the only other thing I might try is, for each pixel, calculate the perpendicular intersection point of the line, checking to see if it’s within the bounds of the endpoints and below since distance threshold.

I’m sorry, I’m really just trying to spit out ideas and be helpful. I’ve done a few shaders but I’m really not well versed. Good luck!

Ya bresenhams wont work on the shader as it is iterative. While based on a error term its meant to operate without divisions so its fast but its a stepping algorithm.

The shader however being a parralel processor each point has to calculate for itself if it is directly between the two points on the line. Which means calculating the nearest distance to the line which means finding the cross to the AB point vectors and the intersection on the AB line of P-> to get C on the line then calculating the distance C to P. p being the position a b the two points c the found point on the line and n the normal from p to c … further constricting valid position between the points though that parts easy and done already. The other stuff is easier said then done though even harder on the shader were the error info is terrible.

I did a thing on shader toy for drawing lines, when I get home I’ll have a look at how I did it.

I would just calculate the shortest distance between the point and the line. If the distance is smaller than some threshold, you’re on the line.

float4 FuncLineDraw(float2 a, float2 b, float2 pos, float4 col)
{
    float2 AtoB = b - a; 
    float2 AtoP = pos - a;

    float2 AtoBnorm = normalize(AtoB);
    float2 linePerp = float2(AtoBnorm.y, -AtoBnorm.x);
    float dist = abs(dot(AtoP, linePerp));

    if (dist < lineThickness) 
    {
         // we're somewhere on the infinite line, but not necessarily btw. A and B
         float lineLength = length(AtoB);
         float len = dot(AtoP, AtoBnorm);
         if (len>=0 && len<=lineLength)
         {
               // we're between point A and B
         }
    }

Ah thanks markus saw this a little late, well i also wanted the if’s out anyways.
It was a pain in the ass to get this to work though.

This is the result in a shader i can just do this to draw a line for whatever purpose.

// draw the line.
col.rgba += DrawLine(position, abitraryLineStart, abitraryLineEnd, linethickness, lineColor);

Here is the function i came up with below is the usage of it in a pixel shader.

// This is the soft edge hlsl line draw it is a straight function.
// Conditional if's are removed dunno if sign and stuff counts as a if.
// The returned value is to be added to the color pixels out of bounds will return 0's.
float4 DrawLine(float2 curpixel, float2 a, float2 b, float lineThickness, float4 col)
{
    float2 p = curpixel;
    float t = lineThickness;
    float2 c = b - a;
    float2 n = normalize(float2(-c.y, +c.x)); // normalized cross ab
    float2 i = -(n * dot(n, p - a) * 2.0f); // inflected perp normal
    float invdist2line = 1.0f - saturate((i.x * i.x + i.y * i.y) / (t * t)); // inverted distance of point to line.
    return col * invdist2line * saturate(sign(dot(a - p, -c) * dot(b - p, c) + 0.001f)); // + 0.0001f  determine point is within segment.
}

Here is the pixel shader calling it.

 //__________________________________________________
 // (Tech)  Line draw.
 //__________________________________________________
 //
 float4 PsExperimental(float4 position : SV_Position, float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
 {
    // get the color from the texture.
    float4 col = tex2D(TextureSampler, texCoord) * color;
    col.a = 1.0f;

    // setup to draw a line.
    float2 abitraryLineStart = float2(300.0f, 200.0f);
    float2 abitraryLineEnd = ForceLocation; // fo now this is were the mouse is.
    float linethickness = 10.0f;
    float4 lineColor = float4(0.99f, 0.99f, 0.99f, 1.0f);

    // draw the line.
    col.rgba += DrawLine(position, abitraryLineStart, abitraryLineEnd, linethickness, lineColor);
    return col;
}

Resulting Output …

Note this version has soft edges if you want a solid line drawn then you can change this bit of code in the function to add a sign call around the distance.

  float invdist2line = sign(1.0f - saturate((i.x * i.x + i.y * i.y) / (t * t))); // distance of point to line.

I want this for sort of pesuedo debuging info to draw points lines and even value bars to show the values of variables in a sort of janky way.

I was actually considering to try to make the shader draw numbers thru a function for debug info i can sort of see how to do it just dunno if its possible it would get pretty big.

Not that you need it now, but this is a fragment that draws grid lines in a shader.

I got text on the shader too its fat as a cow but it works.

1 Like

lol, cool, are you drawing text as a set of lines? If so, would it be better to pass a long texture with all your chars in and then just sample the char you want from it when you need it?

are you drawing text as a set of lines? If so,

would it be better to
pass a long texture with all your chars in and then just sample the char
you want from it when you need it?

Yes and maybe.

I wanted to see it work as a pure function so it doesn’t interfere with other shaders to say it’s additive to them.

So the idea is you are doing something in your shader then you need to see a value. You insert the function just before your shader returns and pass it the current pixel color and it returns the original color. It only alters a color if applicable to drawing a characters line otherwise it returns the color that would have been written. I think the other way would be less reliable in the end.

This has limitations of course it can’t really output values for things that are changing per position unless there was some clever scheme to do it.

But for static or global values per frame on the shader it will work. Things like input variables projection matrices ect.