OpenGL:有效地绘制多条线条

时间:2017-11-07 22:47:25

标签: c# opengl opentk

我是openGL的新手,我正在开发一个应用程序,我需要绘制几个带有几个顶点的线条。总之,我们谈论的是300,000个顶点。

为了尝试有效地做到这一点,我使用VBO来存储顶点数据。

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

这不会像我希望的那样快速执行。我只能在10Hz左右渲染。我想也许是因为我传递了一大堆索引,每个索引都必须复制到每个渲染的GPU上。

我无法确定尝试提高效果的正确方向。着色器是否是正确的方法?有没有办法在渲染过程中不将索引数组写入GPU(DrawElements需要)。

任何有助于确定探索正确方向的帮助都将受到赞赏。

我是用C#使用openTK编写的。

    //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);

RenderLoop

    //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);

1 个答案:

答案 0 :(得分:0)

着色器最终可能有助于您的表现,但它们绝对不是此时的瓶颈。您的渲染工作量非常轻,如果正确完成,应该非常快。

首先,您对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)绑定到IndexData时(显然是您的情况),从最后一个参数(GL_ELEMENT_ARRAY_BUFFER)读取顶点索引。实质上,这与前一段中描述的内容相同:分配新缓冲区并每帧上传数据。您应该创建另一个包含索引的缓冲区,将其绑定到GL_ELEMENT_ARRAY_BUFFER并绘制。由于上述原因,这将避免内存分配和无用的数据传输。

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