使用OpenGL绘图,无需占用CPU,也无需并行化
Drawing with OpenGL without killing the CPU and without parallelizing
我正在为我的工作编写一个简单但有用的OpenGL程序,该程序包括显示矢量场的外观。因此,该程序只需从文件中获取数据并绘制箭头。我需要画几千支箭。我正在使用Qt for 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就不会再太忙了。但无论如何,我必须画成千上万的箭。
除了并行化之外,还有其他解决方案吗?
您没有指出如何实现openGLobj->Arrow方法,但如果您在这方面使用了100%的CPU时间,那么您可能正在使用即时模式绘制箭头。这确实是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;
}
最后,我不能向你保证这是最有效的做事方式。也许,如果你有大量的数据,你需要一些数据结构,比如八叉树或场景图来优化你的代码。
我建议你看看OpenSceneGraph或可视化工具包,看看这些方法是否还没有为你实现,这会为你节省很多时间。
尝试此链接以获得一些想法:
- OpenGL编码(特别是面向对象)的一些最佳实践是什么
基本上,我看到人们为了提高FPS和降低质量所做的事情包括:
-
使用DisplayLists。(缓存复杂或重复的矩阵堆栈)。
-
使用顶点阵列。
-
使用面较少的更简单的几何体。
-
使用更简单的照明。
-
使用更简单的纹理。
OpenGL的主要优点是可以与许多显卡配合使用,这些显卡可以非常快速地进行许多4x4矩阵变换、乘法运算等,并且可以提供更多的RAM内存来存储渲染或部分渲染的对象。
假设所有向量都在频繁变化,以至于无法缓存任何渲染。。。
我解决这个问题的方法是将绘图简化为直线和点,并以所需的帧速率绘制。(圆柱体的一条线和末端的一个彩色点表示方向。)
在画得足够快之后,试着让画得更复杂,比如用矩形棱镜代替直线,用金字塔代替彩色点。
圆形对象通常需要更多的曲面和计算。
我不是这方面的专家,但我会在谷歌上搜索其他涉及优化的OpenGL教程。
希望能有所帮助。
编辑:由于有评论,删除了对NeHe教程的引用。
- 如何使用OpenMP并行化此矩阵时间矢量运算
- 如何使用 MPI 的远程内存访问 (RMA) 功能并行化数据聚合?
- 在C++中使用并行化的预期速度是多少(不是 OpenMp,而是 <thread>)
- 如何使用 OpenMP 并行化最近邻搜索
- Malloc 在使用线程并行化 SSH 调用时存在问题
- 如何使用 OpenMP 正确并行化 for 循环?
- 如何将矩阵的行随机复制到内存中的另一个矩阵的过程并行化?
- 如何使用 Pthreads 并行化图像翻转?
- MPI:反复并行化缓冲区
- 是否可以使用OpenMP并行化一个列表,该列表可以在每次迭代中添加新元素
- 如何在Visual Studio中并行化armadillo
- 嵌套循环 OpenMP 并行化、私有索引还是公共索引?
- 如何并行化增加循环的大小
- 在 C++ 中使用 OpenMP 并行化两个 for 循环不会提供更好的性能
- OpenMP C++:并行化 for 循环的负载不平衡
- OpenMP 条件并行化 - 并行部分中 if 子句的语法
- C++ 犰狳和OpenMp:外积求和的并行化 - 定义犰狳矩阵的约简
- 将 for 循环与嵌套的 while 循环并行化时出现 OpenMP 分段错误
- 迭代卡拉苏巴算法在C++中使用OpenACC并行化和矢量化
- 使用OpenGL绘图,无需占用CPU,也无需并行化