排序三角形时快速排序太慢

quicksort too slow when sorting triangles

我想在 java 中从头开始构建 3d 渲染引擎。代码工作正常,我只是在按正确的顺序对三角形进行排序时遇到了一些问题,这样它们就不会相互渲染。我已经实现了面部剔除和快速排序,以根据 3 个顶点的平均 z 值对三角形进行排序。

无论如何,现在它可以工作,但我发现一旦三角形的数量接近 100k,排序算法似乎会花费大量时间,因此程序无法使用。

这是我对三角形进行排序的代码。我遵循了这个方案:https://www.baeldung.com/java-quicksort。我对它做了一些更改,因为在我的例子中我加载了一个 .obj 文件,其中面只是对顶点的引用(在另一个列表中)。所以这很令人困惑但它有效

public static ArrayList<Vector> sortList(ArrayList<Vector> array,
            Vector[] vectors, int begin, int end) {
        if (begin < end) {
            int partitionIndex = partition(array, vectors, begin, end);

            sortList(array, vectors, begin, partitionIndex - 1);
            sortList(array, vectors, partitionIndex + 1, end);
        }
        return array;
    }

    public static int partition(ArrayList<Vector> arr, Vector[] vectors,
            int begin, int end) {
        float pivot = vectors[(int) arr.get(end).x - 1].z;
        pivot += vectors[(int) arr.get(end).y - 1].z;
        pivot += vectors[(int) arr.get(end).z - 1].z;
        pivot /= 3;

        int i = (begin - 1);

        for (int j = begin; j < end; j++) {
            
            float check = vectors[(int) arr.get(j).x - 1].z;
            check += vectors[(int) arr.get(j).y - 1].z;
            check += vectors[(int) arr.get(j).z - 1].z;
            check /= 3;
            
            if (check <= pivot) {
                i++;

                Vector swapTemp = arr.get(i);
                arr.set(i, arr.get(j));
                arr.set(j, swapTemp);
            }
        }

        Vector swapTemp = arr.get(i+1);
        arr.set(i+1, arr.get(end));
        arr.set(end, swapTemp);

        return i + 1;
    }

这里我计算位置和顶点等

Vector[] vectors = new Vector[obj.vertices.size()];
        Vector[] vectors3D = new Vector[obj.vertices.size()];

        // transformation and rotation

        for (int i = 0; i < obj.vertices.size(); i++) {
            Vector vertex = obj.vertices.get(i);
            vectors[i] = Calc.matmul(Calc.rotationY(rotY), vertex);
            vectors[i] = Calc.matmul(Calc.rotationX(rotX), vectors[i]);
            vectors[i] = Calc.matmul(Calc.rotationY(rotZ), vectors[i]);

            vectors3D[i] = vectors[i];

            // projection
            float depth = 1 / (z + camera.z - vectors[i].z);
            float[][] projection = { { depth, 0, 0 }, // Orthographic:
                                                        // depth instead
                                                        // of 1/camera.z
                    { 0, depth, 0 } // Perspective: 1/camera.z isntead of
                                    // depth
            };
            vectors[i] = Calc.matmul(projection, vectors[i]);
            vectors[i].mult(size);
            vectors[i].add(new Vector(x, y))
                    .add(new Vector(camera.x, camera.y));
        }

        // drawing

        if (type.equals(Object.CORN)) {
            for (int i = 0; i < vectors.length; i++) {
                triangles.add(new ArrayList<Vector>());
                triangles.get(triangles.size() - 1).add(
                        new Vector(vectors[i].x, vectors[i].y));
                // point(vectors[i].x, vectors[i].y);
            }
        } else if (type.equals(Object.MESH)) {

            for (int i = 0; i < obj.faces.size(); i++) {
                Vector corner = obj.faces.get(i);
                float x1 = vectors[(int) corner.x - 1].x;
                float y1 = vectors[(int) corner.x - 1].y;
                float x2 = vectors[(int) corner.y - 1].x;
                float y2 = vectors[(int) corner.y - 1].y;
                float x3 = vectors[(int) corner.z - 1].x;
                float y3 = vectors[(int) corner.z - 1].y;
                triangles.add(new ArrayList<Vector>());
                triangles.get(triangles.size() - 1).add(new Vector(x1, y1));
                triangles.get(triangles.size() - 1).add(new Vector(x2, y2));
                triangles.get(triangles.size() - 1).add(new Vector(x3, y3));
            }
        } else if (type.equals(Object.FILL)) {

            ArrayList<Vector> faces = obj.faces;

            // sort back to front
            faces = Calc.sortList(faces, vectors3D, 0, faces.size() - 1);

            for (int i = 0; i < faces.size(); i++) {

                Vector corner = faces.get(i);
                float x1 = vectors3D[(int) corner.x - 1].x;
                float y1 = vectors3D[(int) corner.x - 1].y;
                float z1 = vectors3D[(int) corner.x - 1].z;
                float x2 = vectors3D[(int) corner.y - 1].x;
                float y2 = vectors3D[(int) corner.y - 1].y;
                float z2 = vectors3D[(int) corner.y - 1].z;
                float x3 = vectors3D[(int) corner.z - 1].x;
                float y3 = vectors3D[(int) corner.z - 1].y;
                float z3 = vectors3D[(int) corner.z - 1].z;

                float nx = (y2 - y1) * (z3 - z1) - (z2 - z1) * (y3 - y1);
                float ny = (z2 - z1) * (x3 - x1) - (x2 - x1) * (z3 - z1);
                float nz = (x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1);

                Vector lightVector = new Vector(light.x, light.y, light.z)
                        .normalize();
                Vector normalVector = new Vector(nx, ny, nz).normalize();

                // face culling
                if (Calc.dotProduct(new Vector(0, 0, -1), normalVector) < 0) {

                    int col = (int) Calc.map(
                            Calc.dotProduct(lightVector, normalVector), -1, 1,
                            0, 255);
                    colors.add(new Color(col, col, col));

                    x1 = vectors[(int) corner.x - 1].x;
                    y1 = vectors[(int) corner.x - 1].y;
                    x2 = vectors[(int) corner.y - 1].x;
                    y2 = vectors[(int) corner.y - 1].y;
                    x3 = vectors[(int) corner.z - 1].x;
                    y3 = vectors[(int) corner.z - 1].y;

                    triangles.add(new ArrayList<Vector>());
                    triangles.get(triangles.size() - 1).add(new Vector(x1, y1));
                    triangles.get(triangles.size() - 1).add(new Vector(x2, y2));
                    triangles.get(triangles.size() - 1).add(new Vector(x3, y3));
                }
            }
        }
        this.repaint();

.obj 文件遵循以下规则:

v x y z(v表示顶点) [...] f [顶点索引]/[纹理索引]/[法线索引] [顶点索引]/[纹理索引]/[法线索引] [顶点索引]/[纹理索引]/[法线索引](对于三角形)

所以当我有 v 0 0 1 v 0 1 0 v 1 0 0 f 1/1/1 2/2/2 3/3/3

我用 (f arraylist of arraylist of vectors) f.get(0).get(0).x f.get(0).get(1).y f.get(0).get(2)

我希望这是可以理解的

请注意,对三角形进行排序必然会对性能造成巨大影响,如果不需要(例如由于透明度)引擎通常依赖于 z 缓冲区并接受透支。

一般来说,100k 个渲染的三角形对于纯软件渲染引擎来说已经很多了。因此,与其尝试加快排序,不如尝试减少渲染三角形的数量:

  • 计算相机视锥体内的一组潜在可见的三角形 (pvs)
  • 尝试使用更复杂的 pvs 机制来剔除墙后的对象等
  • 尽可能减小视锥体的大小(不要渲染太近或太远的物体)
  • 尝试使用细节层次渲染 (LOD),即对较远的模型使用较低的分辨率,甚至可能只对最远的模型使用广告牌
  • 在对三角形进行排序
  • 之前使用背面剔除移除不可见的三角形

另请注意,您可能不必对每一帧的所有三角形进行排序。如果相机或其他物体不会移动得太快或突然出现或消失,您可以尝试将已经排序的三角形列表重新用于几帧 - 或者您可以尝试仅对一部分元素进行排序。

此外,您可以尝试不按平均 z 排序,而是使用 min/max z 表示三角形,即顶点。计算平均 z 也会影响性能