【问题标题】:How to optimize circle draw?如何优化画圆?
【发布时间】:2018-06-24 14:15:38
【问题描述】:

我想知道如何优化我的圆形绘制方法。在将顶点发送到 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)

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

【问题讨论】:

  • 优化?现在是不是太慢了?
  • 你在用windows吗?如果是这样添加标签。我希望我的代码是多系统的(不要怎么说它不同的方式),优化?现在是不是太慢了?是的。简单地说,当我画 100 个四边形 fps = ~3000/4000,现在画一个圆圈 fps = ~1500
  • C++ 没有内置的方法来绘制形状。您必须使用库。那你用的是什么库?
  • @user202729 我正在使用 opengl 绘制四边形。我没有添加 opengl 标记,因为我的示例不包含任何 opengl 调用。有什么不同的数学方程式可以用来画圆吗?也许毕达哥拉斯 - 我在某个地方看到过这样渲染的圆圈,但很久以前。

标签: c++ opengl geometry


【解决方案1】:

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

您可以使用 rotation matrices 递归计算以 (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;
    }
}

Live demo & simplistic comparison benchmark

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

【讨论】:

    【解决方案2】:

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

    1. VBO

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

      您可以了解 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,那么至少将其转为GL_QUAD_STRIP,这将需要一半的glVertex 调用...

      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 bbox 来使用它。然后在片段着色器内部丢弃圆圆周之外的所有片段。像这样的:

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

    【讨论】:

    • 我从未说过我正在使用 GL_QUADS。我在批处理渲染器中使用 GL_TRIANGLES。它看起来像:1. 创建 vao、vbo(没有数据)、ebo(有索引数据),2. 映射缓冲区,3. 将数据添加到缓冲区(使用 DrawRect()、FillRect()、DrawCircle()、 DrawLine(), 方法), 4. 取消映射缓冲区 5. 通过一次绘制调用绘制元素 您的 DrawCircle 方法显示了遗留问题。
    • 这个批处理渲染器非常高效。我可以渲染 100k 个带纹理的矩形精灵,同时具有 ~3k fps。也许几何着色器渲染器会更有效 - 我不知道,我还没有阅读几何着色器。
    • @IScream 几何着色器通常很慢并且不能在所有实现中正常工作。如果你在新的 GL 上,那么 #3 就是这样,因为它只需要一个 QUAD:每个圆的 VAO/VBO 甚至可以是静态的,带有放置矩阵和片段着色器其余的......顺便说一句,您应该将上次评论中的信息添加到您的问题中,因为它是一个重要的规范。
    猜你喜欢
    • 2020-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-26
    • 2014-10-14
    • 2011-11-15
    • 1970-01-01
    相关资源
    最近更新 更多