我最近发现LibGDX中的默认可渲染排序功能不能完全满足我的需求。 (请参见; Draw order changes strangely as camera moves?
本质上是一些应该在后面渲染的对象在前面渲染。

幸运的是,所讨论的可渲染对象始终具有保证的关系。这些对象彼此附着,因此当一个对象移动时,另一个对象移动。一个对象可以看成是“固定”在另一个对象上的,因此总是放在前面。

这给了我一个想法,如果我为每个对象都指定了“ z-index”(int)和“ groupname”(String),则可以手动接管绘制顺序,对于具有相同组名的东西,请确保它们的位置在列表中彼此相邻,按z-index指定的顺序。 (从低到高)

 //For example an array of renderables like
 0."testgroup2",11
 1."testgroup",20
 2."testgroup2",10
 3.(no zindex attribute)
 4."testgroup",50
 //Should sort to become
 0."testgroup",20
 1."testgroup",50
 2.(no zindex attribute)
 3."testgroup2",10
 4."testgroup2",11

 // assuming the object2 in testgroup2 are closer to the camera, the one without a index second closest, and the rest furthest<br>
 //(It is assumed that things within the same group wont be drastically different distances)


我在libgdx中实现了一个排序系统,如下所述;

/**
 * The goal of this sorter is to sort the renderables the same way LibGDX would do normally (in DefaultRenderableSorter)<br>
 * except if they have a ZIndex Attribute.<br>
 * A Zindex attribute provides a groupname string and a number.<br>
 * Renderables with the attribute are placed next to others of the same group, with the order within the group determined by the number<br>
 *
 * For example an array of renderables like;<br><br>
 * 0."testgroup",20<br>
 * 1."testgroup2",10<br>
 * 2.(no zindex attribute)<br>
 * 3."testgroup",50<br>
 * <br>Should become;<br><br>
 * 0."testgroup",20<br>
 * 1."testgroup",50<br>
 * 2.(no zindex attribute)<br>
 * 3."testgroup2",10<br>
 * <br>
 * assuming the object in testgroup2 is closer to the camera, the one without a index second closest, and the rest furthest<br>
 * (It is assumed that things within the same group wont be drastically different distances)<br>
 *
 * @param camera - the camera in use to determine normal sort order when we cant place in a existing group
 * @param resultList - an array of renderables to change the order of
 */
private void customSorter(Camera camera, Array<Renderable> resultList) {

    //make a copy of the list to sort. (This is probably a bad start)
    Array <Renderable> renderables = new Array <Renderable> (resultList);

    //we work by clearing and rebuilding the Renderables array (probably not a good method)
    resultList.clear();

    //loop over the copy we made
    for (Renderable o1 : renderables) {

        //depending of if the Renderable as a ZIndexAttribute or not, we sort it differently
        //if it has one we do the following....
        if (o1.material.has(ZIndexAttribute.ID)){

            //get the index and index group name of it.
            int      o1Index   =  ((ZIndexAttribute)o1.material.get(ZIndexAttribute.ID)).zIndex;
            String o1GroupName =  ((ZIndexAttribute)o1.material.get(ZIndexAttribute.ID)).group;

            //setup some variables
            boolean placementFound = false; //Determines if a placement was found for this renderable (this happens if it comes across another with the same groupname)
            int defaultPosition = -1; //if it doesn't find another renderable with the same groupname, this will be its position in the list. Consider this the "natural" position based on distance from camera

            //start looping over all objects so far in the results (urg, told you this was probably not a good method)
            for (int i = 0; i < resultList.size; i++) {

                //first get the renderable and its ZIndexAttribute (null if none found)
                Renderable o2 = resultList.get(i);
                ZIndexAttribute o2szindex = ((ZIndexAttribute)o2.material.get(ZIndexAttribute.ID));

                if (o2szindex!=null){
                    //if the renderable we are comparing too has a zindex, then we get its information
                    int    o2index    = o2szindex.zIndex;
                    String o2groupname = o2szindex.group;

                    //if its in the same group as o1, then we start the processing of placing them nexto eachother
                    if (o2groupname.equals(o1GroupName)){

                        //we either place it in front or behind based on zindex
                        if (o1Index<o2index){
                            //if lower z-index then behind it
                            resultList.insert(i, o1);
                            placementFound = true;
                            break;
                        }

                        if (o1Index>o2index){
                            //if higher z-index then it should go in front UNLESS there is another of this group already there too
                            //in which case we just continue (which will cause this to fire again on the next renderable in the inner loop)
                            if (resultList.size>(i+1)){

                                Renderable o3 = resultList.get(i+1);
                                ZIndexAttribute o3szindex = ((ZIndexAttribute)o3.material.get(ZIndexAttribute.ID));

                                if (o3szindex!=null){
                                    String o3groupname = o3szindex.group;
                                    if (o3groupname!=null && o3groupname.equals(o1GroupName)){
                                        //the next element is also a renderable with the same groupname, so we loop and test that one instead
                                        continue;
                                    }
                                }

                            }
                        //  Gdx.app.log("zindex", "__..placeing at:"+(i+1));
                            //else we place after the current one
                            resultList.insert(i+1, o1);
                            placementFound = true;
                            break;
                        }

                    }

                }


                //if no matching groupname found we need to work out a default placement.
                int placement = normalcompare(o1, o2); //normal compare is the compare function in DefaultRenderableSorter.

                if (placement>0){
                    //after then we skip
                    //(we are waiting till we are either under something or at the end

                } else {
                    //if placement is before, then we remember this position as the default (but keep looking as there still might be matching groupname, which should take priority)
                    defaultPosition = i;
                    //break; //break out the loop
                }


            }

            //if we have checked all the renderables positioned in the results list, and none were found with matching groupname
            //then we use the defaultposition to insert it
            if (!placementFound){
                //Gdx.app.log("zindex", "__no placement found using default which is:"+defaultPosition);
                if (defaultPosition>-1){
                    resultList.insert(defaultPosition, o1);
                } else {
                    resultList.add(o1);
                }

            }

            continue;

        }

        //...(breath out)...
        //ok NOW we do placement for things that have no got a ZIndexSpecified
        boolean placementFound = false;

        //again, loop over all the elements in results
        for (int i = 0; i < resultList.size; i++) {

            Renderable o2 = resultList.get(i);

            //if not we compare by default to place before/after
            int placement = normalcompare(o1, o2);

            if (placement>0){
                //after then we skip
                //(we are waiting till we are either under something or at the end)
                continue;
            } else {
                //before
                resultList.insert(i, o1);
                placementFound = true;
                break; //break out the loop
            }


        }
        //if no placement found we go at the end by default
        if (!placementFound){
            resultList.add(o1);

        };


    } //go back to check the next element in the incomeing list of renderables (that is, the copy we made at the start)

    //done


}


//Copy of the default sorters compare function
//;
private Camera camera;
private final Vector3 tmpV1 = new Vector3();
private final Vector3 tmpV2 = new Vector3();

public int normalcompare (final Renderable o1, final Renderable o2) {
    final boolean b1 = o1.material.has(BlendingAttribute.Type) && ((BlendingAttribute)o1.material.get(BlendingAttribute.Type)).blended;
    final boolean b2 = o2.material.has(BlendingAttribute.Type) && ((BlendingAttribute)o2.material.get(BlendingAttribute.Type)).blended;
    if (b1 != b2) return b1 ? 1 : -1;
    // FIXME implement better sorting algorithm
    // final boolean same = o1.shader == o2.shader && o1.mesh == o2.mesh && (o1.lights == null) == (o2.lights == null) &&
    // o1.material.equals(o2.material);
    o1.worldTransform.getTranslation(tmpV1);
    o2.worldTransform.getTranslation(tmpV2);
    final float dst = (int)(1000f * camera.position.dst2(tmpV1)) - (int)(1000f * camera.position.dst2(tmpV2));
    final int result = dst < 0 ? -1 : (dst > 0 ? 1 : 0);
    return b1 ? -result : result;
}


据我所知,customSorter函数产生所需的顺序-现在,可渲染对象看起来像是按正确的顺序绘制的。

但是,这似乎也很简单,我确信我的排序算法效率低下。

我想要如何做的建议;

a)改进我自己的算法,尤其是在进行跨平台LibGDX开发时要谨记的任何怪癖(例如,数组类型,关于android / web的内存管理等)

b)具有与常规绘制顺序排序类似的“ z索引覆盖”的其他更有效的解决方案。

笔记;
。分组是必要的。这是因为尽管事物在组中相对彼此牢固地固定在一起,但组本身也可以在彼此的前面/后面移动。 (但不能在两者之间)。这使得绘制顺序的“全局”替代,而不是每个组的局部替代,变得棘手。

。如果有帮助,我可以以任何方式添加/更改zindexattribute对象。

。我在想以某种方式“预存储”数组中的每组对象可能会有所帮助,但不是100%确定如何做。

最佳答案

首先,如果不需要,请勿复制列表。具有可渲染对象的列表可能确实非常庞大,因为它也可能包含资源。复制将非常非常慢。如果您需要本地的东西并且需要性能,请尝试使其最终确定,因为它可以提高性能。

因此,一种简单的方法将是Java的默认排序。您需要为您的类实现一个Comperator,例如带有z索引的Class可能看起来像这样:

public class MyRenderable {
    private float z_index;
    public MyRenderable(float i)
    {
        z_index = i;
    }

    public float getZ_index() {
        return z_index;
    }

    public void setZ_index(float z_index) {
        this.z_index = z_index;
    }
}


如果您希望更快的排序,因为列表在运行时不会发生太大变化,则可以实现插入排序,因为如果列表是预先排序的,则插入排序会更快。如果未进行预排序,则花费的时间会更长,但通常,它应该仅是您的情况下无序的第一个排序调用。

private void sortList(ArrayList<MyRenderable> array) {
    // double starttime = System.nanoTime();
    for (int i = 1; i < array.size(); i++) {
        final MyRenderable temp = array.get(i);
        int j = i - 1;

        while (j >= 0 && array.get(j).getZ_index() < temp.getZ_index()) {
            array.set(j + 1, array.get(j));
            j--;
        }
        array.set(j + 1, temp);
    }
    // System.out.println("Time taken: " + (System.nanoTime() - starttime));
}


要使用此方法,只需在数组中调用它

sortList(renderbales);


在您的情况下,您需要照顾那些没有Z索引的对象。也许您可以给他们一个0,因为他们会在正确的位置进行排序(我猜)。否则,您可以像现在一样在z情况下使用给定的方法,在没有z情况下使用常规的方法。



谈话后在评论中。我认为将所有内容都放入一个列表中不是一个好主意。很难排序,而且速度很慢。更好的方法是列出组。由于您要创建组,因此可以对组进行编程。不要使用字符串名称,不要使用ID或类型(排序起来更容易,也没有关系)。所以一个简单的小组是这样的:

public class Group{
//think about privates and getters or methods to add things which also checks some conditions and so on
    public int groupType;
    public ArrayList<MyRenderable> renderables;
}


现在,您所有的组都进入列表。 (然后包含您所有的渲染包)

ArrayList<Group> allRenderables = new ArrayList<>();


最后但并非最不重要的一点是,对组进行排序并对可渲染对象进行排序。由于我认为您的组ID /名称在运行时不会更改,因此对它们进行一次排序,甚至使用SortedSet而不是ArrayList。但基本上整个排序看起来像这样:

    for(Group g: allRenderables)
        sortRenderables(g.renderables); //now every group is sorted
    //now sort by group names
    sortGroup(allRenderables);


如上所示,具有以下插入类别

public static void sortRenderables(ArrayList<MyRenderable> array) {
    for (int i = 1; i < array.size(); i++) {
        final MyRenderable temp = array.get(i);
        int j = i - 1;

        while (j >= 0 && array.get(j).getZ_index() < temp.getZ_index()) {
            array.set(j + 1, array.get(j));
            j--;
        }
        array.set(j + 1, temp);
    }
}

public static void sortGroup(ArrayList<Group> array) {
    for (int i = 1; i < array.size(); i++) {
        final Group temp = array.get(i);
        int j = i - 1;

        while (j >= 0 && array.get(j).groupType < temp.groupType) {
            array.set(j + 1, array.get(j));
            j--;
        }
        array.set(j + 1, temp);
    }
}

10-06 13:34
查看更多