如何优化圆形绘制?

3

我想知道如何优化我的圆绘制方法。我需要尽可能快地生成顶点,然后将它们发送到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函数简单地绘制四边形,而DrawCircle函数则绘制100个四边形,这些四边形通过cos、sin和半径进行移动。

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

我该如何用不同的方法画圆形?


2
优化?现在速度太慢了吗? - user202729
1
C++ 没有内置的绘制图形的方法。你必须使用一个库。那么你正在使用哪个库? - user202729
避免 XY 问题。 - user202729
如果您正在使用OpenGL...请参考这个或者这个 - user202729
你为什么要坚持生成数百个矩形呢?更好的选择#1:使用三角扇。更好的选择#2:使用单个正方形定义圆,并使用片段着色器绘制它。 - spectras
显示剩余11条评论
2个回答

3

既然您明确要求优化生成顶点坐标的方法,我将提出解决方案。但是,通过查看一些基准测试测量结果(请参见下面的演示链接),我不确定这是否真正是任何性能问题的原因...

您可以使用旋转矩阵在以(0,0)为中心的圆上递归计算顶点:

// 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);

这将在循环内用几个浮点数乘法、加法和减法来替换cossin计算,通常速度更快。

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;
    }
}

实时演示和简单比较基准

仅使用递归的缺点是在每次迭代中会累积小的计算误差。正如演示所示,对于100次迭代,误差是微不足道的。


2

在您的评论中提到您正在使用OpenGL进行渲染(假设是旧API),因此使用单独的GL_QUADS是您的问题。因为您正在对圆形的每个单独的“像素”进行OpenGL调用。这通常比渲染本身慢得多。有哪些选项可以解决这个问题?

  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:

实现#2是最容易的。使用#1需要一些工作,但如果你知道如何使用VBO,那么也不会太麻烦。 #3 也不太复杂,但如果你没有任何着色器方面的经验,可能会遇到问题...


我从未说过我使用GL_QUADS。我正在使用GL_TRIANGLES与我的批处理渲染器。它看起来像这样:
  1. 创建vao,vbo(没有数据),ebo(带有索引数据),
  2. 映射一个缓冲区,
  3. 向缓冲区添加数据(使用DrawRect(),FillRect(),DrawCircle(),DrawLine()方法),
  4. 取消映射缓冲区
  5. 一次绘制调用绘制元素 你的DrawCircle方法显示了遗留问题。
- IScream
这个批处理渲染器非常高效。我可以在大约3k fps的情况下渲染100k个带纹理的矩形精灵。也许几何着色器渲染器会更有效 - 我不知道,因为我还没有阅读关于几何着色器的内容。 - IScream
@IScream 几何着色器通常很慢,并且在所有实现上都无法正常工作。如果您使用新的GL,则**#3是一种方法,因为它只需要单个QUAD:每个圆形的VAO/VBO**,甚至可以使用放置矩阵静态化,然后由片段着色器完成其余部分.... 另外,您应该将上次评论中的信息添加到问题中,因为这是一个重要的规格。 - Spektre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接