Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to optimize circle draw?

I am wondering how could I optimize my circle draw method. I seek how to generate as quickly as possible the vertices, before sending them to opengl.

void DrawCircle(float x, float y, float radius, Color color, float thickness)
{
    int numOfPoints = 100;
    for (float i = 0; i < numOfPoints; ++i) {
        float pi2 = 6.28318530718;
        float angle = i / numOfPoints * pi2;

        FillRect(
        cos(angle) * radius + x,
        sin(angle) * radius + y, 
        thickness, 
        thickness, 
        color);
    }
}

FillRect function simply draws a quad, so DrawCircle function draws 100 quads which are moved by cos, sin and radius.

FillRect(float x, float y, float w, float h, Color color)

How could I draw circle different way?

like image 225
IScream Avatar asked Dec 03 '25 10:12

IScream


2 Answers

Since you explicitly asked for ways to optimize your method generating the vertices coordinates, I'll propose a solution for that. However looking at a few benchmarks measurements (see demo link below), I'm not convinced this is really the cause of any performance issue...

You can compute vertices on a circle centered at (0,0) recursively using rotation matrices:

// Init
X(0) = radius;
Y(0) = 0;
// Loop body
X(n+1) = cos(a) * X(n) - sin(a) * Y(n);
Y(n+1) = sin(a) * X(n) + cos(a) * Y(n);

This replaces cos and sin computations inside the loop with only a few floats multiplications, additions & substractions, which are usually faster.

void DrawCircle(float x, float y, float radius, Color color, float thickness) {
    int numOfPoints = 100;
    float pi2 = 6.28318530718;
    float fSize = numOfPoints;
    float alpha = 1 / fSize * pi2;
    // matrix coefficients
    const float cosA = cos(alpha);
    const float sinA = sin(alpha);
    // initial values
    float curX = radius;
    float curY = 0;
    for (size_t i = 0; i < numOfPoints; ++i) {
        FillRect(curX + x, curY + y, thickness, thickness, color);
        // recurrence formula
        float ncurX = cosA * curX - sinA * curY;
        curY =        sinA * curX + cosA * curY;
        curX = ncurX;
    }
}

Live demo & simplistic comparison benchmark

The drawback of using exclusively recursion is that you accumulate tiny computations errors over each iteration. As the demo shows, the error is insignificant for 100 iterations.

like image 69
Vincent Saulue-Laborde Avatar answered Dec 05 '25 00:12

Vincent Saulue-Laborde


In your comments you mention you are using OpenGL for rendering (assuming old API) so using individual GL_QUADS is your problem. As you are doing OpenGL calls for each separate "pixel" of your circle. That is usually much slower then the rendering itself. What options are there to solve this?

  1. VBO

    this is your best bet just create VBO holding the cos(a),sin(a) unit circle points and render as single glDrawArray call (applying transform matrices to transform unit circle to desired position and radius). That should be almost as fast as single GL_QUADS call... (unless you got too many points per circle) but you will lose the thickness (unless combining 2 circles and stencil...). Here:

    • complete GL+GLSL+VAO/VBO C++ example

    you can find how VAO/VBO are used in OpenGL. Also see this on how to make holes (for the thickness):

    • I have an OpenGL Tessellated Sphere and I want to cut a cylindrical hole in it
  2. GL_LINE_LOOP

    you can use thick lines instead of rectangles that way you decrease glVertex calls from 4 to 1 per "pixel". It would look something like this:

    void DrawCircle(float x, float y, float radius, Color color, float thickness)
        {
        const int numOfPoints = 100;
        const float pi2=6.28318530718; // = 2.0*M_PI
        const float da=pi2/numOfPoints;
        float a;
        glColor3f(color.r,color.g,color.b);
        glLineWidth(thickness/2.0);
        glBegin(GL_LINE_LOOP);
        for (a=0;a<pi2;a+=da) glVertex2f(cos(a)*radius + x, sin(a) * radius + y); 
        glEnd();
        glLineWidth(1.0);
        }
    

    of coarse as I do not know how the color is organized the color settings might change. Also glLineWidth is not guaranteed to work for arbitrary thickness ...

    If you want still to use GL_QUADS then at least turn it to GL_QUAD_STRIP which will need half of the glVertex calls...

    void DrawCircle(float x, float y, float radius, Color color, float thickness)
        {
        const int numOfPoints = 100;
        const float pi2=6.28318530718; // = 2.0*M_PI
        const float da=pi2/numOfPoints;
        float a,r0=radius-0.5*thickness,r1=radius+0.5*thickness,c,s;
        int e;
        glColor3f(color.r,color.g,color.b);
        glBegin(GL_QUAD_STRIP);
        for (e=1,a=0.0;e;a+=da) 
          {
          if (a>=pi2) { e=0; a=pi2; }
          c=cos(a); s=sin(a);
          glVertex2f(c*r0 + x, s * r0 + y); 
          glVertex2f(c*r1 + x, s * r1 + y); 
          }
        glEnd();
        }
    
  3. Shaders

    you can even create shader that takes as input center,thickness and radius of your circle (as uniforms) and using it by rendering single QUAD bbox around circle. then inside fragment shader discard all fragments outside your circle circumference. Something like this:

    • How can i make gradient sphere on glsl?

Implementing #2 is the easiest. with #1 is some work needed but not too much if you know how to use VBO. #3 is also not too complicated but if you do not have any experience with shaders that might pose a problem...

like image 28
Spektre Avatar answered Dec 04 '25 23:12

Spektre



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!