Dashed lines for UMG Border widget

Hey guys,

I’m trying to recreate the following for a border UMG widget using a shader:

So far so good, we got this ingame with a UMG border widget drawing as box:

UE4Editor_20200212_001410.png

Using this material:


Any shader legends out there able to help me take it further and render dashed lines?

Reviving this. Did you ever find a solution?

I’m trying to create a dotted circle material.

would also like to know

If anyone is still wondering how to achieve something like this, I managed to get it working properly in UE 5.7 using a fully procedural UI material.

The setup is shown in the pictures below. These values worked well for me, but they can easily be tweaked as needed.

The code inside the “Custom” node is:

float2 dx = ddx(UV);
float2 dy = ddy(UV);

float uvPerPixelX = max(length(dx), 0.000001);
float uvPerPixelY = max(length(dy), 0.000001);
float aspect = uvPerPixelY / uvPerPixelX;

float2 p = (UV - 0.5) * float2(aspect, 1.0);

float2 halfSize = float2(0.5 * aspect, 0.5);

float r = Radius;
float t = Thickness;

float2 innerHalf = halfSize - t;
float2 outerHalf = halfSize;

float2 qOuter = abs(p) - (outerHalf - r);
float dOuter = length(max(qOuter, 0.0)) + min(max(qOuter.x, qOuter.y), 0.0) - r;

float2 qInner = abs(p) - (innerHalf - r);
float dInner = length(max(qInner, 0.0)) + min(max(qInner.x, qInner.y), 0.0) - max(r - t, 0.001);

float aa = max(fwidth(dOuter), 0.001);

float border = smoothstep(aa, -aa, dOuter) * smoothstep(-aa, aa, dInner);

// Clean circulating path-coordinat
float straightW = max((halfSize.x - r) * 2.0, 0.001);
float straightH = max((halfSize.y - r) * 2.0, 0.001);
float cornerLen = 1.5707963 * r;

float perimeter = 2.0 * straightW + 2.0 * straightH + 4.0 * cornerLen;

float s = 0.0;

float x = p.x;
float y = p.y;

float rightX = halfSize.x - r;
float leftX = -halfSize.x + r;
float topY = halfSize.y - r;
float botY = -halfSize.y + r;

// Top
if (y >= topY && x >= leftX && x <= rightX)
{
s = x - leftX;
}
// Corner top right
else if (x > rightX && y > topY)
{
float2 c = float2(rightX, topY);
float2 v = normalize(p - c);
float a = atan2(v.y, v.x);
s = straightW + (1.5707963 - a) * r;
}
// Right
else if (x >= rightX && y <= topY && y >= botY)
{
s = straightW + cornerLen + (topY - y);
}
// Corner bottom right
else if (x > rightX && y < botY)
{
float2 c = float2(rightX, botY);
float2 v = normalize(p - c);
float a = atan2(v.y, v.x);
s = straightW + cornerLen + straightH + (-a) * r;
}
// Bottom
else if (y <= botY && x <= rightX && x >= leftX)
{
s = straightW + cornerLen + straightH + cornerLen + (rightX - x);
}
// Corner bottom left
else if (x < leftX && y < botY)
{
float2 c = float2(leftX, botY);
float2 v = normalize(p - c);
float a = atan2(v.y, v.x);
s = straightW + cornerLen + straightH + cornerLen + straightW + (-1.5707963 - a) * r;
}
// Left
else if (x <= leftX && y >= botY && y <= topY)
{
s = straightW + cornerLen + straightH + cornerLen + straightW + cornerLen + (y - botY);
}
// Corner top left
else
{
float2 c = float2(leftX, topY);
float2 v = normalize(p - c);
float a = atan2(v.y, v.x);
s = straightW + cornerLen + straightH + cornerLen + straightW + cornerLen + straightH + (3.1415926 - a) * r;
}

// Round dash count so that start and end cleanly fit together
float dashCount = max(round(perimeter / DashSpacing), 1.0);
float fixedSpacing = perimeter / dashCount;

// Half offset so that the Seam isn’t right in a dash
float seamOffset = fixedSpacing * 0.5;

float dash = frac((s + seamOffset) / fixedSpacing);
float dashMask = step(dash, DashFill);

return border * dashMask;

Screenshot 2026-05-07 135254