asad asad - 1 year ago 63
C++ Question

line-width for ellipse is not constant

I am drawing hollow ellipse using opengl. I calculate vertices in c++ code using standard ellipse formula. In fragment shader i just assign color to each fragment. The ellipse that i see on the screen has thinner line width on the sharper curves as compared to that where curve is not that sharp. So question is, how to make line-width consistent across the entire parameter of ellipse? Please see the image below:
not worried about jagged edges at this point but it would be great if someone can point out technique to do anti-aliasing

C++ code :

std::vector<float> BCCircleHelper::GetCircleLine(float centerX, float centerY, float radiusX, float radiusY, float lineWidth, int32_t segmentCount)
auto vertexCount = (segmentCount + 1) * 2;
auto floatCount = vertexCount * 3;

std::vector<float> array(floatCount);
const std::vector<float>& data = GetCircleData (segmentCount);
float halfWidth = lineWidth * 0.5f;

for (int32_t i = 0; i < segmentCount + 1; ++i)
float sin = data [i * 2];
float cos = data [i * 2 + 1];

array [i * 6 + 0] = centerX + sin * (radiusX - halfWidth);
array [i * 6 + 1] = centerY + cos * (radiusY - halfWidth);

array [i * 6 + 3] = centerX + sin * (radiusX + halfWidth);
array [i * 6 + 4] = centerY + cos * (radiusY + halfWidth);

array [i * 6 + 2] = 0;
array [i * 6 + 5] = 0;
return std::move(array);

const std::vector<float>& BCCircleHelper::GetCircleData(int32_t segmentCount)
int32_t floatCount = (segmentCount + 1) * 2;
float segmentAngle = static_cast<float>(M_PI * 2) / segmentCount;

std::vector<float> array(floatCount);

for (int32_t i = 0; i < segmentCount + 1; ++i)
array[i * 2 + 0] = sin(segmentAngle * i);
array[i * 2 + 1] = cos(segmentAngle * i);

return array;

Aiming this:enter image description here

Answer Source

The problem is likely that your fragments are basically line segments radiating from the center of the ellipse.

If you draw a line, from the center of the ellipse through the ellipse you've drawn, at any point on the perimeter, you could probably convince yourself that the distance covered by that red line is in fact the width that you're after (roughly, since you're working at low spatial resolution; somewhat pixelated). But since this is an ellipse, that distance is not perpendicular to the path being traced. And that's the problem. This works great for circles, because a ray from the center is always perpendicular to the circle. But for these flattened ellipses, it's very oblique!

How to fix it? Can you draw circles at each point on the ellipse, instead of line segments?

If not, you might need to recalculate what it means to be that thick when measured at that oblique angle - it's no longer your line width, may require some calculus, and a bit more trigonometry.

Ok, so a vector tangent to the curve described by

c(i) = (a * cos(i), b * sin(i))


c'(i) = (- a * sin(i), b * cos(i))

(note that this is not a unit vector). The perpendicular to this is

c'perp = (b * cos(i), a * sin(i))

You should be able to convince yourself that this is true by computing their dot product.

Lets calculate the magnitude of c'perp, and call it k for now:

k = sqrt(b * b * cos(i) * cos(i) + a * a * sin(i) * sin(i))

So we go out to a point on the ellipse (c(i)) and we want to draw a segement that's perpendicular to the curve - that means we want to add on a scaled version of c'perp. The scaling is to divide by the magnitude (k), and then multiply by half your line width. So the two end points are:

P1 = c(i) + halfWidth * c'perp / k
P2 = c(i) - halfWidth * c'perp / k

I haven't tested this, but I'm pretty sure it's close. Here's the geometry you're working with:

enter image description here

-- Edit:

So the values for P1 and P2 that I give above are end-points of a line-segment that's perpendicular to the ellipse. If you really wanted to continue with just altering the radiusX and radiusY values the way you were doing, you could do this. You just need to figure out what the 'Not w' length is at each angle, and use half of this value in place of halfWidth in radiusX +/- halfWidth and radiusY +/- halfwidth. I leave that bit of geometry as an exercise for the reader.