如何在OpenGL中高效地绘制大量多边形?

4

我目前正在使用OpenGL来绘制几千个多边形,但速度非常慢。绘制8000个多边形大约需要100毫秒。

以下是我的设置信息:

  • I have each object in my 3d field set as a collection of 2d polygon planes, so a square would consist of 6, 4-vertex planes. So I do have access to each individual plane.
  • I cannot guarantee that each polygon will have the same number of vertices
  • I'm currently drawing as follows:

    for(allPlanes){
      glBegin(GL_POLYGON);
       for(allPointsInThePlane){
           glVertex(pointX,pointY,pointZ);
        }
      glEnd();
    }
    

这比我预期的要慢得多,我已经考虑使用glDrawElements()来代替,并将多边形平面分解成三角形,然后使用trianglefans或strips进行绘制。

我只是想寻求一些关于最有效的方法,或者对我绘图方法的任何批评建议。


5
你正在进行大量的OpenGL调用。每个调用都有一定的开销,这会使你的程序变慢。你必须将所有数据存储在缓冲区中,并使用少量的调用来渲染所有内容。请参考@genpfault的答案。 - lvella
3个回答

9

三角剖分一切并将三角形扔进一个大的VA/VBO

编辑:GLUtesselator包装器:

struct TessContext
{
    ~TessContext()
    {
        for( size_t i = 0; i < combined.size(); ++i )
        {
            delete[] combined[i];
        }
    }

    vector< Eigen::Vector2d > pts;
    vector< GLdouble* > combined;
};

#define APIENTRY __stdcall

void APIENTRY tess_begin( GLenum type ) {}
void APIENTRY tess_edgeFlag( GLboolean flag ) {}
void APIENTRY tess_end() {}

void APIENTRY tess_vertex( void *data, TessContext* ctx )
{
    GLdouble* coord = (GLdouble*)data;
    ctx->pts.push_back( Eigen::Vector2d( coord[0], coord[1] ) );
}

void APIENTRY tess_combine( GLdouble coords[3], void *vertex_data[4], GLfloat weight[4], void **outData, TessContext* ctx )
{
    GLdouble* newVert = new GLdouble[3];
    ctx->combined.push_back( newVert );

    newVert[0] = coords[0];
    newVert[1] = coords[1];
    newVert[2] = coords[2];
    *outData = newVert;
}

template< typename Vec >
vector< Vec > Triangulate
    ( 
    const vector< Vec >& aSimplePolygon
    )
{
    vector< GLdouble > coords;
    for( size_t i = 0; i < aSimplePolygon.size(); ++i )
    {
        coords.push_back( aSimplePolygon[i].x() );
        coords.push_back( aSimplePolygon[i].y() );
        coords.push_back( 0 );
    }

    GLUtesselator* tess = gluNewTess();
    //gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD );
    //gluTessProperty( tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO );

    gluTessCallback( tess, GLU_TESS_BEGIN,          (GLvoid (APIENTRY *)())    tess_begin      );
    gluTessCallback( tess, GLU_TESS_EDGE_FLAG,      (GLvoid (APIENTRY *)())    tess_edgeFlag   );
    gluTessCallback( tess, GLU_TESS_VERTEX_DATA,    (GLvoid (APIENTRY *)())    tess_vertex     );
    gluTessCallback( tess, GLU_TESS_END,            (GLvoid (APIENTRY *)())    tess_end        );
    gluTessCallback( tess, GLU_TESS_COMBINE_DATA,   (GLvoid (APIENTRY *)())    tess_combine    );
    gluTessNormal( tess, 0.0, 0.0, 1.0 );

    TessContext ctx;

    gluTessBeginPolygon( tess, &ctx );
    gluTessBeginContour( tess );

    for( size_t i = 0; i < aSimplePolygon.size(); ++i )
    {
        gluTessVertex( tess, &coords[i*3], &coords[i*3] );
    }

    gluTessEndContour( tess );
    gluTessEndPolygon( tess );

    gluDeleteTess(tess);

    vector< Vec > ret( ctx.pts.size() );
    for( size_t i = 0; i < ret.size(); ++i )
    {
        ret[i].x() = ctx.pts[i].x();
        ret[i].y() = ctx.pts[i].y();
    }

    return ret;
}

使用 Eigen,但没有用于任何有趣的事情。


6
glDrawArrays()glDrawElements()(或glDrawRangeElements())是首选的方法,也是唯一未被弃用的方法。立即模式(您的示例)是最慢且最不受欢迎的方法,主要用于OpenGL教程和调试(根据我的经验)。还有显示列表,这是OpenGL的“宏”,仅比使用立即模式进行绘制高一级,以及顶点缓冲对象(VBO)

除了立即模式之外,其他任何方法都应足够快速满足您的需求。


@aid,我在尝试使用glDrawArrays()函数时遇到了麻烦。我创建了一个浮点数数组,用作每个平面的顶点。我启用了GL_VERTEX_ARRAY,然后为每个数组分配了一个glVerterxPointer(3,GL_FLOAT,0,plane#array)。现在,我有每个平面的顶点指针。然后,我调用glDrawArrays(GL_TRIANGLE_FAN,0, ???)。我不确定最后一个参数该怎么办,它应该是我制作的顶点指针的总数吗? - Dark
第三个参数是要绘制的顶点数。 - aib

2
使用三角剖分(它是镶嵌的一种特殊情况)计算点并绘制它们是首选方式(您使用了已弃用的立即模式)。
当前的图形架构使用着色器。
因此,您应该一次性处理所有顶点,将其保存到数据结构中并将其发送到GPU,而不是每次在处理器和GPU之间发送少量顶点:
- 一次性绘制它(使用glDraw*()函数)。
这样做更快,因为:
- 整个数组只计算一次并保留在数据结构中,以便可以重复使用。 - 然后将数据完全发送到GPU内存,可以在其中执行进一步操作,而无需进行任何额外的数据传输和相关开销(通过使用可编程着色器)。

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