Z排序的3d对象列表

时间:2014-10-29 15:44:30

标签: c++ opengl

在C ++ / OpenGL应用程序中,我有一堆半透明的对象排列在3d空间中。由于半透明,必须按从最远到最近的顺序绘制对象。 (出于“Transparency Sorting。”中描述的原因。)

幸运的是,相机是固定的。所以我计划维护一个指向3d对象的指针集合,按摄像机Z排序。每个帧,我将迭代集合,绘制每个对象。

快速插入和删除很重要,因为现有的对象经常变化。

我正在考虑使用std::list作为容器。要插入,我将使用std::lower_bound来确定新对象的去向。然后我将插入lower_bound返回的迭代器。

这听起来像一个理智的方法吗?鉴于我提供的详细信息,您是否预见到我忽略的任何重大性能问题?

3 个答案:

答案 0 :(得分:1)

我不认为std::list对这个用例来说是个不错的选择。虽然插入效率非常低,但您需要遍历列表以找到插入的正确位置,这会使O(n)复杂。

如果你想保持简单,std::set已经好多了,甚至比std::list更容易申请。它实现为平衡树,因此插入是O(log n)复杂度,只需在容器上调用insert()方法即可完成。迭代器以排序顺序为您提供元素。它在迭代期间确实具有非本地内存访问模式的缺点,这使得它不具有缓存友好性。

另一种方法是直觉上应该非常有效。它的基本思想类似于@ratchet_freak已经提出的,但它不会在每次迭代时复制整个向量:

  • 包含数据主要部分的容器是std::vector,始终保持排序。
  • 新元素被添加到"溢出"容器,可以是std::set,也可以是保持排序的另一个std::vector。这只允许达到一定的大小。
  • 迭代时,使用类似逻辑进行合并排序,同时遍历主容器和溢出容器。
  • 当溢出容器达到大小限制时,将其与主容器合并,从而生成一个新的主容器。

代码的粗略草图:

const size_t OVERFLOW_SIZE = 32;

// Ping pong between two vectors when merging.
std::vector<Entry> mainVecs[2];
unsigned activeIdx = 0;

std::vector<Entry> overflowVec;
overflowVec.reserve(OVERFLOW_SIZE);

void insert(const Entry& entry) {
    std::vector<Entry>::iterator pos =
        std::upper_bound(overflowVec.begin(), overflowVec.end(), entry);
    overflowVec.insert(pos, 1, entry);

    if (overflowVec.size() == OVERFLOW_SIZE) {
        std::merge(mainVecs[activeIdx].begin(), mainVecs[activeIdx].end(),
                   overflowVec.begin(), overflowVec.end(),
                   mainVecs[1 - activeIdx].begin());

        mainVecs[activeIdx].clear();
        overflowVec.clear();
        activeIdx = 1 - activeIdx;
    }
}

void draw() {
    std::vector<Entry>::const_iterator mainIt = mainVecs[activeIdx].begin();
    std::vector<Entry>::const_iterator mainEndIt = mainVecs[activeIdx].begin();

    std::vector<Entry>::const_iterator overflowIt = overflowVec.begin();
    std::vector<Entry>::const_iterator overflowEndIt = overflowVec.end();

    for (;;) {
        if (overflowIt == overflowEndIt) {
            if (mainIt == mainEndIt) {
                break;
            }
            draw(*mainIt);
            ++mainIt;
        } else if (mainIt == mainEndIt) {
            if (overflowIt == overflowEndIt) {
                break;
            }
            draw(*overflowIt);
            ++overflowIt;
        } else if (*mainIt < *overflowIt) {
            draw(*mainIt);
            ++mainIt;
        } else {
            draw(*overflowIt);
            ++overflowIt;
        }
    }
}

答案 1 :(得分:0)

std::list是一个非随机访问容器,

  

lower_bound的复杂性。

     

平均值,第一个和最后一个距离的对数:执行近似log2(N)+1个元素比较(其中N是此距离)。   在非随机访问迭代器上,迭代器进展产生了平均N的额外线性复杂度

所以这似乎不是一个好主意。

使用std::vectorlower_bound具有正确的复杂性。

对于插入/删除元素,您可能也有更好的性能(但复杂性较低)。

答案 2 :(得分:0)

根据列表的大小,您可以保留较小的&#34;突变集&#34;对于添加/更改了最后一帧的对象和一个大的现有有序集。

然后在绘制时进行合并的每个帧:

vector<GameObject*> newList;
newList.reserve(mutationSet.size()+ExistingSet.size();
sort(mutationSet.begin(), mutationSet.end(), byZCoord);//small list -> faster sort
auto mutationIt = mutationSet.begin();
for(auto it = ExistingSet.begin(); it != ExistingSet.end(); ++it){
    if(*it->isRemoved()){
        //release to pool and 
        continue;
    }

    while(mutationIt != mutationSet.end() && *mutationIt->getZ() < *it->getZ()){
        *mutationIt->render();
        newList.pushBack(*mutationIt);

    }
    *it->render();
    newList.pushBack(*iIt);
}
while(mutationIt != mutationSet.end()){
    *mutationIt->render();
    newList.pushBack(*mutationIt);

}

mutationSet.clear();
ExistingSet.clear();
swap(ExistingSet, newList);

无论如何,您将进行迭代,并且对小列表进行排序比添加新列表并对所有内容进行排序O(n + k + k log k)O( (n+k)log(n+k))

相比更快