问题

如何管理数据(GPU,CPU)以在3d中动态地放置数千个对象(OpenGL)的标签?

详细信息

对象:



计数:2k-10k

类型:点/四分之一

标签:


单板印刷

数据:文本和颜色

放置:


标签不重叠

有关另一个问题的更多信息

详细说明

要计算标签放置,我需要屏幕空间坐标。有很多用于此目的的算法,但是我什至不知道它们是否可以在着色器上实现(没有硬黑客),因为“顶点着色器”只能访问一个顶点。但是,由于许多对象都为所有顶点计算矩阵乘法(MVP *顶点位置),以便在CPU端获取屏幕空间坐标并将其发送给每一帧,因此imo相当慢且设计不良。

所以,我应该:



计算CPU上的屏幕空间坐标并将其发送到GPU,以便绘制对象,计算放置在CPU上的标签并发送数据发送到GPU
仅发送顶点的字坐标并在GPU上计算屏幕空间坐标,然后以某种方式在GPU上实现标签放置
读取GPU端计算的屏幕端坐标并在CPU上进行计算


我知道这取决于其他方面对CPU / GPU的负载,但是您怎么看?

#1 楼

由于您不熟悉计算机图形学,因此最好避免使用SIMD的复杂性,并坚持使用传统的“创建CPU线程+收集结果”方法,或者在任务足够轻量的情况下甚至在主线程中运行。

,但是如果该方法无法有效执行和/或您愿意使用OpenCL / GLSL计算着色器,请继续阅读...


对象计算,而不是每个顶点或片段计算,因此传统图形管道不是最佳选择。也就是说,您可以使用在每个对象世界位置处具有顶点位置的顶点缓冲区,然后在顶点着色器中将顶点转换为屏幕空间(请参见下文),但是考虑到您使用的是OpenGL 3.3,您可能仅限于编写通过渲染到纹理或转换反馈缓冲区将标签位置输出到纹理,这可能会很慢,具体取决于硬件。如果可以使用着色器图像加载存储扩展,则可能会有所帮助,但是将图形管道弯曲到GPGPU可能比它值得的工作还要多。

您尚未详细说明重叠要求(即,是否必须是完美的或近似的,重叠的概率,是否可以保证足够的屏幕空间以实现完美的非重叠,是否需要所有标签始终都在可见屏幕上等),但通常是成对出现的问题当同时调整多个粒子时,非收敛迭代最好在CPU的单个线程上完成。它可能是专用的CPU线程。

要理解非收敛迭代的含义,请想象一下一个由左下角和右上角接触的标签网格。如果同时在一个邻居的相反方向上对它们全部进行调整,则它们可能最终会陷入仅与另一个邻居重叠的情况。由于拐角重叠是相反的,因此此循环可能会无限期重复。是否实际发生取决于算法的细节以及对象的位置/尺寸,但是除非您可以做出一些对象间距保证或我不知道的其他假设,否则这是可能的。

访问GLSL计算机着色器或(甚至更好的)OpenCL,并且您不需要完美的重叠,也不需要预测重叠相对较少,因此以下简单的SIMD方法可能会胜过单线程CPU方法。


SIMD算法:在每帧的开始(或任何有意义的标签更新频率),将对象的世界位置,标签尺寸和相机投影+视图转换传递给每个对象+标签对计算着色器/内核来计算屏幕空间位置:

vec4 objScreenSpacePosition = camera.projectionViewTransform * object.worldPosition;

现在将相应的标签放在附近。

/>然后在着色器/内核中放置一个同步栅栏,以确保所有屏幕空间位置已在进行操作之前进行了计算。最后,对所有其他标签执行成对循环,并调整标签位置/深度以纠正重叠。如果您需要绝对保证零重叠,则可能需要多次重复循环,直到所有标签都没有重叠为止,或者您可以在一定数量的迭代后切断重叠调整,以确保它不会花费太长时间或无限期地运行(请参见以上关于非收敛性。

请注意,调整循环仅调整其计算着色器已分配的标签。为避免位置读取/写入争用情况,每次决定是否继续循环时,最好使用一个临时位置和围栏。这是一个示例:

vec4 objectScreenPos = camera.projViewTrans * object[kernelIndex].worldPos;

//...transform objectScreenPos to desired coordinate 
// system if needed; either [0,1] or [-1,1] or whatever 
// depending on your projViewTrans and needs of your 
// overlap correction algorithm...

label[kernelIndex].pos = objectScreenPos.xyz + someOffset;

fence();

bool overlap = true;
uint maxIters = 5;
vec3 tempPos = label[kernelIndex].pos;
for (uint numIters = 0; overlap && numIters < maxIters; numIters++)
{
    for (each labelIndex)
    {   
        // Of course a label overlaps itself!
        if (labelIndex == kernelIndex)
            continue;
        // ...check overlap and adjust position of tempPos...  
    }
    label[kernelIndex].pos = tempPos;
    fence();
}   


当然,尽可能避免栅栏和避免任何形式的跨内核对话是理想的,但是对于这个问题,您不能完全避免它。尽管如此,我还是会尝试在循环中关闭篱笆,看看性能和输出会发生什么。 =)

有更多聪明的重叠校正算法,其中大多数是近似的,但是从原理上讲,该算法可以完美地校正重叠,并且易于实现和理解。


根据实际对象的数量,GPGPU甚至线程CPU方法实际上可能比在CPU上执行单线程计算要慢。这就是为什么OpenCL非常适合这些问题:它使您可以选择在CPU上运行单线程或多线程,或者转移到GPU。如果对象数量随时间发生根本变化,甚至可以即时在CPU和GPU之间切换,并且可以提前预测到变化。

我可能会用OpenCL编写此代码,因为如果其他所有方法均失败了,我可以在CPU上的单个线程中运行它,并尽可能接近保证完美的无重叠。然后,我可以尝试使用多个线程/ GPU计算以及针对我内心的内容的配置文件,配置文件,配置文件。


更新-OpenGL 3.3图形管道方法:

尽管OpenCL或OpenGL计算管道是理想的选择,但您应该能够修改上述SIMD alg以使用OpenGL 3.3核心图形管道,而需要付出额外的努力,并且可能会损失性能。示例:在绘制点时,将对象位置和ID作为顶点数据传递到计算对象屏幕位置的顶点着色器。将glPosition设置为对象ID,然后使用flat修饰符将结果屏幕位置传递到片段着色器,以防止插值。将结果渲染到长度等于标签数量的一维帧缓冲区中,以使``像素''(即标签)和顶点(即对象)之间存在1:1的对应关系。现在,您将拥有一个充满标签位置的1D纹理。然后,您可以将其读回CPU以执行重叠消除(可能保证没有重叠),然后将更新的标签位置以统一的缓冲区阵列,纹理或任何适合您情况的最佳方式上载到OpenGL。或者,您可以启动另一个具有标签ID作为顶点属性的顶点着色器,并执行不保证重叠的消除,该重叠不依赖于其他标签的更新位置,然后按照之前的类似模式将更新后的位置渲染到第二个一维帧缓冲区,并最终在标签渲染期间将相关的帧缓冲区纹理用作标签ID索引的查找表,以确定四个广告牌角顶点中每个顶点的最终位置。可以使用统一属性或顶点属性来传递标签ID。

希望您开始看到GL 3.3核心图形管道在多大程度上限制了GPGPU的选择以及它使事情变得更加复杂。在将单个计算着色器或内核分解为多个着色器(可能还有多次绘制)之前,您必须滥用“顶点”和“像素”的概念来达到目的。要使用这种方法获得良好的性能,通常需要对所使用的图形管道和硬件有相当的了解。取决于硬件,驱动程序和对象的数量,上述图形管线算法可能无法击败简单地计算屏幕空间坐标并消除CPU上的重叠,然后将结果上传到GPU的问题。因此,我建议您选择OpenCL或OpenGL计算,或者坚持传统的CPU线程的原因。

评论


$ \ begingroup $
我不确定我是否理解正确,您的想法是通过顶点着色器计算屏幕空间位置,将其保存为某些纹理,然后读取该纹理并在CPU或GPGPU上进行计算(如果可能)?在GPGPU上计算所有内容并将其发送到OpenGL会更好?当前,我正在尝试找出要使用的体系结构和框架,而不是要使用的算法,但是使用SIMD方法的部分很有趣,谢谢您。
$ \ endgroup $
–BPiek
16年7月26日在8:39

$ \ begingroup $
我给出的示例算法可以完全在单个OpenCL内核或OpenGL计算着色器中执行。 OpenCL可以通过OpenGL / OpenCL互操作直接与OpenGL共享数据,而OpenGL可以在其着色器之间共享数据。 (数据=缓冲区或纹理。)无需占用CPU,尽管如果屏幕上的对象太少而无法克服网桥开销,则可能会更快地在CPU上使用OpenCL。
$ \ endgroup $
–holocronweaver
16年7月27日在19:22

$ \ begingroup $
用图形管道示例更新了我的答案。
$ \ endgroup $
–holocronweaver
16年7月27日在20:24