排序三角形时快速排序太慢
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 也会影响性能
我想在 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 也会影响性能