如何优化圆画?

How to optimize circle draw?

本文关键字:优化 何优化      更新时间:2023-10-16

我想知道如何优化我的圆画方法。我寻求如何在将它们发送到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函数只是画一个四边形,所以画圆函数画100个四边形,这些四边形按cos,sin和半径移动。

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

我怎样才能用不同的方式画圆?

由于您明确要求优化生成顶点坐标的方法,因此我将为此提出一个解决方案。但是,查看一些基准测试测量(请参阅下面的演示链接(,我不相信这真的是任何性能问题的原因......

您可以使用旋转矩阵递归计算以 (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 次迭代,误差微不足道。

在您的评论中,您提到您正在使用OpenGL进行渲染(假设旧API(,因此使用单个GL_QUADS是您的问题。当你在做OpenGL时,调用你的圈子的每个单独的"像素"。这通常比渲染本身慢得多。有什么选择可以解决这个问题?

  1. 驻维也纳国际

    这是最好的选择,只需创建包含cos(a),sin(a)单位圆点的VBO并呈现为单个glDrawArray调用(应用变换矩阵将单位圆转换为所需的位置和半径(。这应该几乎和单个GL_QUADS呼叫一样快......(除非你每个圆圈得到太多点(,但你会失去厚度(除非组合 2 个圆圈和模板......这里:

    • 完整的GL+GLSL+VAO/VBO C++示例

    您可以找到如何在OpenGL中使用VAO/VBO。另请参阅有关如何打孔(厚度(的信息:

    • 我有一个OpenGL镶嵌球体,我想在上面切一个圆柱形孔
  2. GL_LINE_LOOP

    您可以使用粗线而不是矩形,这样您就可以将glVertex调用从每个"像素"的 4 个减少到 1 个。它看起来像这样:

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

    粗略的,因为我不知道color是如何组织的,颜色设置可能会改变。此外,glLineWidth也不能保证适用于任意厚度...

    如果您仍然想使用GL_QUADS那么至少将其转换为需要一半glVertex调用的GL_QUAD_STRIP...

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

    您甚至可以创建着色器,将圆的输入中心,厚度和半径(作为制服(作为输入中心,并通过在圆周围渲染单个QUAD框来使用它。 然后在片段着色器内部丢弃圆周之外的所有片段。像这样:

    • 如何在 glsl 上制作梯度球体?

实现#2是最简单的。 使用#1需要一些工作,但如果您知道如何使用VBO,则不会太多。#3也不太复杂,但如果您没有任何着色器经验,可能会造成问题......