3

我正在为我的工作编写一个简单但有用的 OpenGL 程序,其中包括显示矢量场的外观。所以程序只是从文件中获取数据并绘制箭头。我需要画几千支箭。我正在将 Qt 用于 Windows 和 OpenGL API。

箭头单元是一个圆柱体和一个圆锥体,在函数 Arrow() 中组合在一起。

for(long i = 0; i < modifiedArrows.size(); i++) {
    glColor4d(modifiedArrows[i].color.redF(),modifiedArrows[i].color.greenF(),
        modifiedArrows[i].color.blueF(),modifiedArrows[i].opacity);
    openGLobj->Arrow(modifiedArrows[i].fromX,modifiedArrows[i].fromY,
        modifiedArrows[i].fromZ,modifiedArrows[i].toX,
        modifiedArrows[i].toY,modifiedArrows[i].toZ, 
        simulationSettings->vectorsThickness);
}

现在的问题是运行一个无限循环来继续绘制它会使 CPU 完全忙碌,这不是很好。我尽可能地尝试从 paintGL() 函数中删除所有计算,只剩下简单的计算。我用 glFlush() 和 glFinish() 结束 paintGL() 函数,但我的主 CPU 总是满的。

如果我删除这个循环,CPU 就不会太忙了。但无论如何我都要画上千支箭。

除了并行化之外,还有其他解决方案吗?

4

2 回答 2

5

你没有指出你是如何实现你的 openGLobj->Arrow 方法的,但是如果你在这上面使用 100% CPU 时间,你可能正在用immediate mode绘制箭头。这确实是 CPU 密集型的,因为您必须为 glBegin() 和 glEnd() 中的每条指令将数据从 CPU 传输到 GPU。如果您使用 GLUT 来绘制数据,那么它也非常低效。

此处的方法是使用 GPU 内存和处理能力来显示您的数据。Phyatt 已经为您指出了一些方向,但我会尝试更具体:使用顶点缓冲区对象 (VBO)

这个想法是预先分配所需的内存以在 GPU 上显示您的数据,并在需要时更新这块内存。这可能会对代码的效率产生巨大影响,因为您将使用高效的视频卡驱动程序来处理 CPU->GPU 传输。

为了说明这个概念,我将在答案的末尾向您展示一些伪代码,但这绝不是完全正确的。我没有测试它,也没有时间为你实现绘图,但它是一个可以让你理清思路的概念。

class Form
{
    public:
    Form()
    {
        // generate a new VBO and get the associated ID
        glGenBuffers(1, &vboId);

        // bind VBO in order to use
        glBindBuffer(GL_ARRAY_BUFFER, vboId);

        //Populate the buffer vertices.
        generateVertices();

        // upload data to VBO
        glBufferData(GL_ARRAY_BUFFER_ARB, vertices.size(), vertices.data(), GL_STATIC_DRAW_ARB);
    }

    ~Form()
    {
        // it is safe to delete after copying data to VBO
        delete [] vertices;

        // delete VBO when program terminated
        glDeleteBuffersARB(1, &vboId);
    }

    //Implementing as virtual, because if you reimplement it on the child class, it will call the child method :)
    //Generally you will not need to reimplement this class
    virtual void draw()
    {
        glBindBuffer(GL_ARRAY_BUFFER, vboId);
        glEnableClientState(GL_VERTEX_ARRAY);           
        glVertexPointer(3, GL_FLOAT, 0, 0);

        //I am drawing the form as triangles, maybe you want to do it in your own way. Do it as you need! :)
        //Look! I am not using glBegin() and glEnd(), I am letting the video card driver handle the CPU->GPU 
        //transfer in a single instruction!
        glDrawElements(GL_TRIANGLES, vertices.size(), GL_UNSIGNED_BYTE, 0);

        glDisableClientState(GL_VERTEX_ARRAY);

        // bind with 0, so, switch back to normal pointer operation
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
    }

private:
    //Populate the vertices vector with the form vertices.
    //Remember, any geometric form in OpenGL is rendered as primitives (points, quads, triangles, etc).
    //The common way of rendering this is to use multiple triangles.
    //You can draw it using glBegin() and glEnd() just to debug. After that, instead of rendering the triangles, just put
    //the generated vertices inside the vertices buffer.
    //Consider that it's at origin. You can use push's and pop's to apply transformations to the form.
    //Each form(cone or cilinder) will have its own way of drawing.
    virtual void generateVertices() = 0;

    GLuint vboId; 
    std::vector<GLfloat> vertices;
}

class Cone : public Form
{
public:
    Cone() : Form() {}
    ~Cone() : ~Form() {}

private:
    void generateVertices()
    {
        //Populate the vertices with cone's formula. Good exercise :)
        //Reference: http://mathworld.wolfram.com/Cone.html
    }

    GLuint vboId; 
    std::vector<GLfloat> vertices;
}

class Cilinder : public Form
{
public:
    Cone() : Form() {}
    ~Cone() : ~Form() {}

private:
    void generateVertices()
    {
        //Populate the vertices with cilinders's formula. Good exercise :)
        //Reference: http://math.about.com/od/formulas/ss/surfaceareavol_3.htm
    }

    GLuint vboId; 
    std::vector<GLfloat> vertices;
}


class Visualizer : public QOpenGLWidget
{
public:
    //Reimplement the draw function to draw each arrow for each data using the classes below.
    void updateGL()
    {
        for(uint i = 0; i<data.size(); i++)
        {
            //I really don't have a clue on how you position your arrows around your world model.
            //Keep in mind that those functions glPush, glPop and glMatrix are deprecated. I recommend you reading
            //http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Chapter-3:-3D-transformation-and-projection.html if you want to implement this in the most efficient way.
            glPush();
                glMatrix(data[i].transform());
                cilinder.draw();
                cone.draw();
            glPop();
        }
    }

private:
    Cone cone;
    Cilinder cilinder;
    std::vector<Data> data;
} 

最后一点,我不能保证这是最有效的做事方式。可能,如果您有大量数据,您将需要一些数据结构,例如八叉树场景图来优化您的代码。

I recommend you taking a look at OpenSceneGraph or Visualization ToolKit to see if that methods are not already implemented for you, what would save you a lot of time.

于 2013-02-21T18:43:53.543 回答
1

试试这个链接以获得一些想法:

基本上,我所看到的人们为提高 FPS 和掉落质量所做的工作包括以下内容:

  • 使用显示列表。(缓存复杂或重复的矩阵堆栈)。

  • 使用顶点数组。

  • 使用较少面的更简单的几何体。

  • 使用更简单的照明。

  • 使用更简单的纹理。

OpenGL 的主要优点是可以与很多显卡一起使用,这些显卡可以非常快速地执行大量 4x4 矩阵转换、乘法等,并且它们提供更多 RAM 内存来存储渲染或部分渲染的对象。

假设所有的向量都发生了如此大的变化并且经常发生你无法缓存任何渲染的情况......

我解决这个问题的方法是将绘图简化为仅线和点,并以所需的帧速率进行绘制。(圆柱体的线条和方向末端的彩色点。)

在画得足够快之后,尝试使绘图更复杂,比如用矩形棱柱代替直线,用金字塔代替彩色点。

圆形物体通常需要更多的表面和计算。

我不是这方面的专家,但我会在谷歌上搜索其他处理优化的 OpenGL 教程。

希望有帮助。

编辑:由于评论,删除了对 NeHe 教程的引用。

于 2013-02-21T17:17:56.927 回答