简体   繁体   中英

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?

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.

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:

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

  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:

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...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM