【问题标题】:OpenGL: Draw Multiple Linestrips EfficientlyOpenGL:有效地绘制多个线条
【发布时间】:2017-11-09 03:32:23
【问题描述】:

我是 openGL 的新手,我正在开发一个应用程序,我需要在其中绘制多个线条,每个线条带有多个顶点。总共有 300,000 个顶点。

为了尝试有效地执行此操作,我使用 VBO 来存储顶点数据。

我使用单个 VBO 来存储所有线条的所有顶点。然后我使用GLDrawElements 来绘制整个 VBO,向它传递一个索引数组来指定要绘制的索引,并利用primitiveRestart 来指定 Linestrips 开始/结束的位置(参见下面的代码)。

这并没有像我希望的那样快速执行。我只能以大约 10Hz 的频率渲染。我在想也许是因为我传递了大量索引,每次渲染都必须将它们复制到 GPU。

我无法确定尝试提高性能的正确方向。着色器是正确的方法吗?有没有办法在渲染期间不将索引数组写入 GPU(DrawElements 需要)。

任何帮助确定正确的探索方向将不胜感激。

我正在使用 openTK 在 C# 中编写此代码。

    //Enabled Primitive Restart
    GL.Enable(EnableCap.PrimitiveRestart);
    GL.PrimitiveRestartIndex(PrimitiveRestartIndex);

    //Generate Linestrip data to buffer (array of vertices)
    int[] FrameData = ScanBuffer.getBufferData();

    //Create the buffer on the GPU
    ScanBuffer.vbo_id = new int[1];
    GL.GenBuffers(1, ScanBuffer.vbo_id);

    //Buffer the data
    GL.BindBuffer(BufferTarget.ArrayBuffer, ScanBuffer.vbo_id[0]);
    GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(sizeof(int) * FrameData.Length), FrameData, BufferUsageHint.StaticDraw);
    GL.VertexPointer(2, VertexPointerType.Int, 0, 0);
    GL.EnableClientState(ArrayCap.VertexArray);

    //Generate the vertex index array
    uint[] IndexData = ScanBuffer.getIndexData();

    //Draw the VBO
    GL.BindBuffer(BufferTarget.ArrayBuffer, ScanBuffer.vbo_id[0]);
    GL.VertexPointer(2, VertexPointerType.Int, 0, 0);
    GL.DrawElements(BeginMode.LineStrip, IndexData.Length, DrawElementsType.UnsignedInt, IndexData);

更新

我能够找到一个可行的解决方案,使用数组元素缓冲区来存储我的索引。我得到了大约 10 倍的性能提升。缓冲区和渲染命令如下所示。

缓冲区

    vertexData = generateVertexData();
    indexData = generateIndexData();

    //Bind to the VAO
    GL.BindVertexArray(vaoID[0]);

    //Bind + Write Index Data to the Buffer
    GL.BindBuffer(BufferTarget.ElementArrayBuffer, veoID[0]);
    GL.BufferData(BufferTarget.ElementArrayBuffer, new IntPtr(sizeof(uint) * IndexData.Count()), IndexData.ToArray(), BufferUsageHint.StaticDraw);

    //Bind + Write Scan Vertex Data to the Buffer
    GL.BindBuffer(BufferTarget.ArrayBuffer, vboID[0]);
    GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(sizeof(int) * VertexData.Count()), VertexData.ToArray(), BufferUsageHint.StaticDraw);
    GL.VertexPointer(2, VertexPointerType.Int, 0, 0);
    GL.EnableClientState(ArrayCap.VertexArray);

    //Remove Binding
    GL.BindVertexArray(0);
    GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
    GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);

渲染循环

    //Enabled Primitive Restart
    GL.Enable(EnableCap.PrimitiveRestart);
    GL.PrimitiveRestartIndex(PrimitiveRestartIndex);

    GL.LineWidth(1);
    GL.Color3(Color.Red);
    GL.BindBuffer(BufferTarget.ArrayBuffer, vboID[0]);
    GL.VertexPointer(2, VertexPointerType.Int, 0, 0);

    //-Draw using a VEO-
    GL.BindBuffer(BufferTarget.ElementArrayBuffer, veoID[0]);
    GL.DrawElements(BeginMode.LineStrip, scanVertexBufferLength, DrawElementsType.UnsignedInt, 0);
    GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
    GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
    GL.BindVertexArray(0);

    GL.Disable(EnableCap.PrimitiveRestart);

【问题讨论】:

  • 您的数据是静态的还是动态的。如果是静态数据,您应该只上传一次数据,而不是每帧。
  • 它是静态的。我认为这是我缺少的一件事。我只上传一次顶点数据,但我上传的索引数组每次都上传。有没有办法只上传一次该数组?
  • 好吧……我在代码编译之间写了我的答案,最后我在你找到自己的答案后提交了它似乎

标签: c# opengl opentk


【解决方案1】:

着色器最终可能会帮助您提高性能,但目前它们绝对不是瓶颈。您的渲染工作量非常轻,如果处理得当,应该会非常快。

首先,您对 OpenGL 的一些用法已经过时了。确保您遵循最新的文档和教程以避免这种情况。我知道在进入一个新领域时很难判断什么是过时的。如果可以的话,我建议从最近出版的 OpenGL 书籍开始。

现在开始你的代码。

GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(sizeof(int) * FrameData.Length), FrameData, BufferUsageHint.StaticDraw);

This tells OpenGL 为您的顶点数据分配一个新缓冲区,每次渲染几何图形时都会这样做。这显然对性能非常不利。你想要做的是只分配一次缓冲区,或者至少只在它需要增长时分配。此调用还会将您的 300000 个顶点位置上传到 GPU 每帧,这也很昂贵。如果顶点位置没有改变,则上传一次,然后将缓冲区重新用于下一帧。如果位置确实发生了变化,请使用glBufferSubData() 上传新数据,而无需重新分配顶点缓冲区对象的内存。

GL.DrawElements(BeginMode.LineStrip, IndexData.Length, DrawElementsType.UnsignedInt, IndexData);

这是性能不佳的第二大罪魁祸首。在您必须使用的OpenGL“兼容模式”中,glDrawElements() 在没有缓冲区(id 0)绑定到GL_ELEMENT_ARRAY_BUFFER 时从最后一个参数(IndexData)读取顶点索引(显然是您的情况)。本质上,这与上一段中描述的相同:分配一个新的缓冲区并在每帧上传数据。您应该创建另一个保存索引的缓冲区,将其绑定到GL_ELEMENT_ARRAY_BUFFER 并绘制。由于上述原因,这将避免内存分配和无用的数据传输。

最后,OpenGL 是一个状态机。避免像GL.VertexPointer(2, VertexPointerType.Int, 0, 0); 这样的冗余调用。为了获得最佳性能,通常需要减少 OpenGL 函数调用的次数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多