The first step to create a shape is to calculate its signed distance field (SDF). SDF is a function or texture where each value represents the shortest distance from a point to the nearest surface, with the sign indicating whether the point is inside (negative) or outside (positive) the shape.
For example, to create a circle, you can use the following code:
float circleSdf(float2 uv) {
return length(uv - .5) * 2;
}
Other shapes can be created similarly by defining their SDF functions. Here are some examples:
float triangleSdf(float2 uv) {
uv = (uv * 2 - 1) * 2;
return max(-uv.y * .5, uv.y * .5 + abs(uv.x) * 0.8660254038); // 0.8660254038 is height of equilateral triangle
}
float squareSdf(float2 uv) {
uv = uv * 2 - 1;
return max(abs(uv.x), abs(uv.y));
}
float rectangleSdf(float2 uv, float aspectRatio) {
uv = uv * 2 - 1;
return max(abs(uv.x * aspectRatio), abs(uv.y));
}
It is also possible to create polygon by defining number of sides. We use the polar coordinate system to calculate the distance from the center to the edge of the polygon:
#define TAU 6.28318530718
float polygonSdf(float2 uv, int sides) {
uv = uv * 2 - 1;
float angle = atan2(uv.x, uv.y) + TAU * .5;
float radius = length(uv);
float anglePerSide = TAU / float(sides);
return cos(floor(.5 + angle / anglePerSide) * anglePerSide - angle) * radius;
}
When we have the SDF for a shape, we can use it to determine the color of each pixel based on whether it is inside or outside the shape. For example, to fill the shape with a color, we can use the following code:
float sdf = circleSdf(uv);
float inside = step(sdf, size); // 1 when inside the shape, 0 when outside
float3 color = lerp(bgColor, shapeColor, inside);
We can also use the SDF to create outlines or borders by using a threshold value:
float outline(float sdf, float s, float width) {
float v = step(s, sdf + width * .5) - step(s, sdf - width * .5);
return saturate(v);
}
...
float sdf = circleSdf(uv);
float outlineColor = outline(sdf, .5, .02);