2

我使用 OpenGL 渲染 2D 地图,在此过程中我需要渲染具有大量顶点(100,000+)的填充多边形。为此,我使用 glu tessellator 将多边形细分为三角形,并使用 VBO 渲染三角形。

多边形渲染成功。问题是镶嵌过程非常缓慢。对于一些带顶点的图表,在我的笔记本电脑(i5-3230M 2.6GHz,8G RAM)上500,000需要将近2 分钟。这对我的申请是不可接受的。

有没有比 glu tessellator 更快的其他曲面细分算法?

还是我做错了?

下面两张图是渲染结果

glPolygonMode(GL_FRONT, GL_LINE)

打开线框的多边形 仔细看看

编辑: 地图数据是静态的,原始多边形数据是经纬度格式。我已经将镶嵌的多边形数据(那些三角形)保存在单独的文件中。

为了更清楚(与问题没有直接关系),为了在屏幕上渲染,需要一个投影来将 LL 格式转换为屏幕坐标。

问题是用户可能要安装数千个图表(将在其中完成镶嵌)。尽管曲面细分只会运行一次,但仍然需要很长时间。

4

2 回答 2

2

地图是静态的还是动态的?

对于静态地图

为什么不将镶嵌多边形存储在某个文件中并且不再对其进行镶嵌...

对于动态地图

使用不需要镶嵌凸多边形的不同渲染方法可能会更快,如下所示:

  1. 清晰的屏幕与岛色
  2. 渲染岛屿轮廓

    没有填充的原语GL_LINE_LOOP根本不需要镶嵌。

  3. 填水

    只需从任何多边形外的点开始,然后用水填充地图。如果洪水填充编码正确(没有递归并用线条而不是像素填充),那么它应该只需要几个 [ms]。这种方法的问题是您需要访问渲染的内容,因此您至少需要 2 次渲染。在GPU上实现洪水填充也不是一件容易的事。

    还有一些替代方法,例如在 CPU 端存储边缘点并在CPU预先计算水填充。在这种情况下,您需要为图像的每条扫描线提供坐标列表,这些坐标将包含每个土地的起点和终点。然后只需填补单个渲染通道中的空白......xy

    这应该很容易在RT中呈现

[编辑] 增长填充测试演示

是否对您的数据进行了一些迭代增长的测试。数据集存在一些问题,例如您的多边形重叠,这可能只是孔洞,但由于我没有填充颜色信息,而只有对象ID,所以很难说。反正也可以修。这里使用我上面提到的方法(动态地图)进行小型 win 32 VCL/OpenGL/SW演示:

  • Win32 Demo使用慢速下载,免费,无需注册,只需输入代码即可。

它是 Win32 单机版,无需使用 OpenGL+VCL 安装

  • 鼠标滚轮缩放
  • Shift+鼠标滚轮选择不同的多边形
  • 鼠标+左键平移

可以修复的问题很少,但作为概念证明,它运行良好。我将您的 ASCII 映射编译为二进制形式(因此加载速度更快,但形式相同,只是多边形数,然后是每个多边形的点数,点 x,y 为 64 位双精度数。计数为 32 位整数)

我正在使用我自己的 OpenGL 引擎(我无法共享),因此您需要对这些内容进行编码(例如 OpenGL、FBO和 Texture init/set/usage)。无论如何,这里是这个VCL应用程序的C++代码:

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include <math.h>
#pragma hdrstop
#include "win_main.h"
#include "gl/OpenGL3D_double.cpp"
#include "performance.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
// VCL
TMain *Main;
// OpenGL
OpenGLtime tim;
OpenGLscreen scr;
OpenGL_FBO fbo;
GLuint txr_map=-1;
// miscel
int pn=0;                       // vertex count
double px0,px1,py0,py1;         // bbox
double mx,my;                   // mouse
double view[16],iview[16];      // direct and inverse Modelview matrix
double zoom=1.0,dzoom=1.1,viewx=0.0,viewy=0.0;  // view
int index=0;                    // selected polygon
bool _redraw=true;
DWORD cl_water=0xFFEE9040;
DWORD cl_land =0xFF70A0B0;
DWORD cl_edge =0xFF000000;
DWORD cl_sel  =0xFF00FFFF;
AnsiString tcpu,tgpu;
// map
List< List<double> > polygon;   // loaded polygons
      List<double>   water;     // points with water from last frame
//---------------------------------------------------------------------------
void view_compute()
    {
    double x,y;
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();
    x=divide(1.0,px1-px0)*scr.aspect;
    y=divide(1.0,py1-py0)*scr._aspect;
    if (x>y) x=y;
    x*=zoom;
    glTranslated(viewx,viewy,0.0);
    glScaled(x,x,1.0);
    glTranslated(-0.5*(px0+px1),-0.5*(py0+py1),0.0);
    glGetDoublev(GL_MODELVIEW_MATRIX,view);
    glPopMatrix();
    matrix_inv(iview,view);
    }
//---------------------------------------------------------------------------
void map_load_csv(AnsiString filename)
    {
    BYTE *dat;
    AnsiString lin,s,s0;
    int ix,i,l,hnd,siz,adr;
    double x,y;
    List< AnsiString > id;

         id.allocate(128);      id.num=0;
    polygon.allocate(128); polygon.num=0;

    hnd=FileOpen(filename,fmOpenRead); if (hnd<0) return;
    siz=FileSeek(hnd,0,2);
        FileSeek(hnd,0,0);
    dat=new BYTE[siz]; if (dat==NULL) { FileClose(hnd); return; }
    siz=FileRead(hnd,dat,siz);
    FileClose(hnd);

    adr=0; txt_load_lin(dat,siz,adr,true);
    for (ix=-1,s0="";adr<siz;)
        {
        lin=txt_load_lin(dat,siz,adr,true);
        if (lin=="") continue;
        i=1; l=lin.Length();
        s=str_load_str(lin,i,true); s=s.SubString(2,s.Length()-2);
        if (s0!=s)
            {
            for (ix=0;ix<id.num;ix++) if (id[ix]==s) break;
            if (ix>=id.num)
                {
                ix=id.num;
                id.add(s);
                polygon.add();
                polygon[ix].allocate(256);
                polygon[ix].num=0;
                }
            s0=s;
            }
        s=str_load_str(lin,i,true); s=s.SubString(2,s.Length()-2); x=str2flt(s);
        s=str_load_str(lin,i,true); s=s.SubString(2,s.Length()-2); y=str2flt(s);
        polygon[ix].add(x);
        polygon[ix].add(y);
        }
    }
//---------------------------------------------------------------------------
void map_save_bin(AnsiString filename)
    {
    int hnd,i;
    hnd=FileCreate(filename); if (hnd<0) return;
    FileWrite(hnd,&polygon.num,4);
    for (i=0;i<polygon.num;i++)
        {
        FileWrite(hnd,&polygon[i].num,4);
        FileWrite(hnd,polygon[i].dat,polygon[i].num*8);
        }
    FileClose(hnd);
    }
//---------------------------------------------------------------------------
void map_load_bin(AnsiString filename)
    {
    int hnd,i,n,m;
    hnd=FileOpen(filename,fmOpenRead); if (hnd<0) return;
    FileRead(hnd,&n,4);
    polygon.allocate(n); polygon.num=n;
    for (i=0;i<n;i++)
        {
        FileRead(hnd,&m,4);
        polygon[i].allocate(m); polygon[i].num=m;
        FileRead(hnd,polygon[i].dat,m*8);
        }
    FileClose(hnd);
    }
//---------------------------------------------------------------------------
void map_bbox()
    {
    int ix,i,n;
    double *p,a;
    pn=0;
    px0=px1=polygon[0][0];
    py0=py1=polygon[0][1];
    for (ix=0;ix<polygon.num;ix++)
        {
        p=polygon[ix].dat;
        n=polygon[ix].num; pn+=n>>1;
        for (i=0;i<n;i+=2)
            {
            a=*p; p++; if (px0>a) px0=a; if (px1<a) px1=a;
            a=*p; p++; if (py0>a) py0=a; if (py1<a) py1=a;
            }
        }
    }
//---------------------------------------------------------------------------
void map_draw()
    {
    int ix,i,n;
    double *p,a;
//  glLineWidth(2.0);
    for (ix=0;ix<polygon.num;ix++)
        {
        p=polygon[ix].dat;
        n=polygon[ix].num;
        if (ix==index) glColor4ubv((BYTE*)&cl_sel);
         else          glColor4ubv((BYTE*)&cl_edge);
        glBegin(GL_LINE_LOOP);
        for (i=0;i<n;i+=2,p+=2) glVertex2dv(p);
        glEnd();
        }
//  glLineWidth(1.0);
    }
//---------------------------------------------------------------------------
void TMain::draw()
    {
    tbeg();
    tim.tbeg();

    // [ render outline to texture ]
    fbo.bind(scr);
    glClearColor(divide((cl_land)&255,255),divide((cl_land>>8)&255,255),divide((cl_land>>16)&255,255),1.0);
    scr.cls();
    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixd(view);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (water.num)  // water start points for grow fill
        {
        // add water around txr border
        glBegin(GL_POINTS);
        glColor4ubv((BYTE*)&cl_water);
        for (int i=0;i<water.num;i+=2)
         glVertex2dv(water.dat+i);
        glEnd();
        }

    map_draw();
    scr.exe();

    fbo.unbind(scr);

    // [ copy GL texture to CPU image ]
    scr.txrs.txr_ld(txr_map);
    // [ create ScanLines for direct pixel access pyx[y][x] ]
    int e,x,y,xs,ys; DWORD **pyx,*p,c0,c1; double a[3];
    xs=scr.txrs.txr.xs;                     // texture resolution (rounded up to power of 2)
    ys=scr.txrs.txr.ys;
    pyx=new DWORD*[ys];
    p=(DWORD*)scr.txrs.txr.txr;             // CPU image pixel data
    for (y=0;y<ys;y++,p+=xs) pyx[y]=p;      // scan line pointers

    // [ Grow Fill water ]
    c0=rgb2bgr(cl_land);
    c1=rgb2bgr(cl_water);
    if (water.num==0)   // first frame view must be set so water is on all borders
        {
        // add water around txr border
        for (x=   1,y=0;y<ys;y++) pyx[y][x]=c1;
        for (x=xs-2,y=0;y<ys;y++) pyx[y][x]=c1;
        for (y=   1,x=0;x<xs;x++) pyx[y][x]=c1;
        for (y=ys-2,x=0;x<xs;x++) pyx[y][x]=c1;
        }

    for (e=1;e;)                            // grow it
    for (e=0,y=1;y<ys-1;y++)
     for (   x=1;x<xs-1;x++)
      if (pyx[y][x]==c0)
       if ((pyx[y-1][x]==c1)
         ||(pyx[y+1][x]==c1)
         ||(pyx[y][x-1]==c1)
         ||(pyx[y][x+1]==c1)) { e=1; pyx[y][x]=c1; }

    // create water start points for next frame
    water.num=0;
    e=4;    // step
    for (y=1;y<ys-2;y+=e)
     for (x=1;x<xs-2;x+=e)
       if ((pyx[y-1][x-1]==c1) // enough water around (x,y)?
         &&(pyx[y-1][x  ]==c1)
         &&(pyx[y-1][x+1]==c1)
         &&(pyx[y  ][x-1]==c1)
         &&(pyx[y  ][x  ]==c1)
         &&(pyx[y  ][x+1]==c1)
         &&(pyx[y+1][x-1]==c1)
         &&(pyx[y+1][x  ]==c1)
         &&(pyx[y+1][x+1]==c1))
            {
            // convert pixel(x,y) -> World(x,y)
            a[0]=divide(2.0*x,xs)-1.0;
            a[1]=divide(2.0*y,ys)-1.0;
            a[2]=0.0;
            matrix_mul_vector(a,iview,a);
            water.add(a[0]);
            water.add(a[1]);
            }

    // [ copy CPU image back to GL texture ]
    delete[] pyx;                           // release ScanLines no need for them anymore
    scr.txrs.txr.rgb2bgr();                 // I got RGB/BGR mismatch somewhere
    scr.txrs.txr_st(txr_map);               // scr.txrs.txr.txr holds pointer to 32bit pixel data
    scr.exe();

    // [ render texture to screen ]
    scr.cls();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    scr.txrs.bind(txr_map);
    glColor3f(1.0,1.0,1.0);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0,0.0); glVertex2f(-1.0,-1.0);
    glTexCoord2f(1.0,0.0); glVertex2f(+1.0,-1.0);
    glTexCoord2f(1.0,1.0); glVertex2f(+1.0,+1.0);
    glTexCoord2f(0.0,1.0); glVertex2f(-1.0,+1.0);
    glEnd();
    scr.txrs.unbind();
    // [info]
    glColor3f(1.0,1.0,1.0);
    scr.text_init_pix(1.0);
    scr.text(tcpu);
    scr.text(tgpu);
    scr.text_exit();

    scr.exe();
    scr.rfs();

    tend(); tcpu=" CPU time: "+tstr(1);
    tim.tend();
    }
//---------------------------------------------------------------------------
void TMain::mouse(double x,double y,TShiftState sh)
    {
    x=divide(2.0*x,scr.xs)-1.0;
    y=1.0-divide(2.0*y,scr.ys);
    if (sh.Contains(ssLeft))
        {
        viewx+=x-mx;
        viewy+=y-my;
        view_compute();
        _redraw=true;
        }
    mx=x;
    my=y;
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    scr.init(this);
    txr_map=fbo.add(scr);
//  map_load_csv("map.csv");
//  map_save_bin("map.bin");
    map_load_bin("map.bin");
    map_bbox();
    view_compute();
    draw();
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    scr.exit();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
    {
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
    {
    scr.resize();
    fbo.resize(scr);
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
    {
    if (Shift.Contains(ssShift))
        {
        if (WheelDelta>0) index++; else index--;
        if (index>=polygon.num) index=polygon.num-1;
        if (index<0) index=0;
        _redraw=true;
        }
    else{
        double p[3]={ mx,my,0.0 };
        view_compute();
        matrix_mul_vector(p,iview,p);
        if (WheelDelta>0) zoom*=dzoom; else zoom/=dzoom;
        view_compute();
        matrix_mul_vector(p,view,p);
        viewx-=p[0]-mx;
        viewy-=p[1]-my;
        view_compute();
        _redraw=true;
        }
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormMouseMove(TObject *Sender, TShiftState Shift, int X,int Y) { mouse(X,Y,Shift); }
void __fastcall TMain::FormMouseUp(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { mouse(X,Y,Shift); }
void __fastcall TMain::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y) { mouse(X,Y,Shift); }
//---------------------------------------------------------------------------
void __fastcall TMain::Timer1Timer(TObject *Sender)
    {
    tgpu=AnsiString().sprintf(" GPU time: [%8.3lf ms]",tim.time());
    if (_redraw) { draw(); _redraw=false; }
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDblClick(TObject *Sender)
    {
    Width+=10; // ignore this had some bug in resize FBO texture and this was for debugging it
    }
//---------------------------------------------------------------------------

我也使用我的动态列表模板,所以:


List<double> xxx;double xxx[];
xxx.add(5);添加5到列表末尾 相同
xxx[7]访问数组元素(安全)
xxx.dat[7]访问数组元素(不安全但快速直接访问)
xxx.num是数组的实际使用大小
xxx.reset()清除数组并为项目设置xxx.num=0
xxx.allocate(100)预分配空间100

如果您需要有关矩阵和向量数学例程的帮助,请参阅:

在底部的链接答案中,您甚至可以找到我使用的 C++ 实现......

您可以忽略应用程序上只有单个计时器的VCL40 ms内容,如果需要,可以间隔重新绘制,如果准备好则获取测量 GL 时间......

对你来说重要的事情只是draw()例行公事。

它是这样工作的:

  1. 绑定FBO以启用渲染到texture
  2. 清除它land color
  3. 渲染多边形轮廓edge color

    如果你有洞,用它们渲染它们,watter color填充后用它们再次渲染它们edge color

  4. watter color

    在第一帧中,您应该看到未缩放的视图,因此所有土地都被水包围。所以第一个瓦特点是纹理的边界矩形。

  5. 取消绑定FBO并将纹理像素数据复制到CPU端内存

  6. 将所有水填充到land color像素(停止edge color或任何其他)

    您可以使用任何填充,如洪水填充、分段线填充等,但要注意递归方法的堆栈溢出。我决定使用:

    因为它是迭代的。它没有那么快(因此较大的 CPU 时间,但大部分 CPU 时间是由于在 GPU/CPU 之间传输纹理时同步造成的),但可以通过将图像细分为“方形”区域并在需要时传播填充来显着加快速度.

  7. 从此图像创建水起点

    所以扫描整个图像(通过一些步骤不需要扫描所有点),如果找到水,将其添加watter为下一帧的水起点(在世界坐标中)。这工作得很好,直到您的视图不会在帧之间发生太大变化,因此限制缩放变化和平移步骤......

    当新框架包含没有任何起点且其他水无法访问的水时,就会出现 gfx 问题。这需要一些思考/测试,但我认为它应该可以通过从第一帧获得的一些静态预定义的水起点(每个多边形很少)来解决。

  8. 直接渲染CPU端图像或将其传回GL纹理并进行渲染。

这里预览:

预习

于 2017-01-23T08:46:49.383 回答
0

我有几个想法……你能把镶嵌分割成有组织的块吗,……有点像网格?然后,如果事情发生了变化,您只能智能地重新细分已更改的部分。

于 2017-01-23T08:07:13.470 回答