在C ++ / OpenGL应用程序中,我有一堆半透明的对象排列在3d空间中。由于半透明,必须按从最远到最近的顺序绘制对象。 (出于“Transparency Sorting。”中描述的原因。)
幸运的是,相机是固定的。所以我计划维护一个指向3d对象的指针集合,按摄像机Z排序。每个帧,我将迭代集合,绘制每个对象。
快速插入和删除很重要,因为现有的对象经常变化。
我正在考虑使用std::list
作为容器。要插入,我将使用std::lower_bound
来确定新对象的去向。然后我将插入lower_bound
返回的迭代器。
这听起来像一个理智的方法吗?鉴于我提供的详细信息,您是否预见到我忽略的任何重大性能问题?
答案 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::vector
,lower_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))