From 18c179f8e362a27097f69299f95755cd163871e7 Mon Sep 17 00:00:00 2001 From: jbb01 <32650546+jbb01@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:50:09 +0200 Subject: [PATCH] improve performance of boxes --- .../eu/jonahbauer/raytracing/Examples.java | 14 +- .../eu/jonahbauer/raytracing/math/AABB.java | 30 +--- .../raytracing/scene/hittable3d/Box.java | 136 ++++++++++++++++++ .../raytracing/scene/util/Hittables.java | 35 ----- 4 files changed, 150 insertions(+), 65 deletions(-) create mode 100644 src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java delete mode 100644 src/main/java/eu/jonahbauer/raytracing/scene/util/Hittables.java diff --git a/src/main/java/eu/jonahbauer/raytracing/Examples.java b/src/main/java/eu/jonahbauer/raytracing/Examples.java index 8e55352..2e39a32 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Examples.java +++ b/src/main/java/eu/jonahbauer/raytracing/Examples.java @@ -11,9 +11,9 @@ import eu.jonahbauer.raytracing.scene.Hittable; import eu.jonahbauer.raytracing.scene.Scene; import eu.jonahbauer.raytracing.scene.SkyBox; import eu.jonahbauer.raytracing.scene.hittable2d.Parallelogram; +import eu.jonahbauer.raytracing.scene.hittable3d.Box; import eu.jonahbauer.raytracing.scene.hittable3d.ConstantMedium; import eu.jonahbauer.raytracing.scene.hittable3d.Sphere; -import eu.jonahbauer.raytracing.scene.util.Hittables; import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree; import org.jetbrains.annotations.NotNull; @@ -168,8 +168,8 @@ public class Examples { new Parallelogram(new Vec3(0, 0, 0), new Vec3(555, 0, 0), new Vec3(0, 0, 555), white), new Parallelogram(new Vec3(555, 555, 555), new Vec3(-555, 0, 0), new Vec3(0, 0, -555), white), new Parallelogram(new Vec3(0, 0, 555), new Vec3(555, 0, 0), new Vec3(0, 555, 0), white), - Hittables.box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white).rotateY(Math.toRadians(15)).translate(new Vec3(265, 0, 295)), - Hittables.box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white).rotateY(Math.toRadians(-18)).translate(new Vec3(130, 0, 65)) + new Box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white).rotateY(Math.toRadians(15)).translate(new Vec3(265, 0, 295)), + new Box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white).rotateY(Math.toRadians(-18)).translate(new Vec3(130, 0, 65)) ), SimpleCamera.builder() .withImage(height, height) @@ -196,11 +196,11 @@ public class Examples { new Parallelogram(new Vec3(555, 555, 555), new Vec3(-555, 0, 0), new Vec3(0, 0, -555), white), new Parallelogram(new Vec3(0, 0, 555), new Vec3(555, 0, 0), new Vec3(0, 555, 0), white), new ConstantMedium( - Hittables.box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white).rotateY(Math.toRadians(15)).translate(new Vec3(265, 0, 295)), + new Box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white).rotateY(Math.toRadians(15)).translate(new Vec3(265, 0, 295)), 0.01, new IsotropicMaterial(Color.BLACK) ), new ConstantMedium( - Hittables.box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white).rotateY(Math.toRadians(-18)).translate(new Vec3(130, 0, 65)), + new Box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white).rotateY(Math.toRadians(-18)).translate(new Vec3(130, 0, 65)), 0.01, new IsotropicMaterial(Color.WHITE) ) ), @@ -243,7 +243,7 @@ public class Examples { for (int i = 0; i < data.size(); i++) { var partei = data.get(i); - objects.add(Hittables.box( + objects.add(new Box( new Vec3((i + 1) * spacing + i * size, 0, spacing), new Vec3((i + 1) * spacing + (i + 1) * size, partei.stimmen() * 15, spacing + size), new DielectricMaterial(1.5, partei.color()) @@ -316,7 +316,7 @@ public class Examples { var x1 = x0 + w; var y1 = random.nextInt(1, 101); var z1 = z0 + w; - boxes.add(Hittables.box(new Vec3(x0, y0, z0), new Vec3(x1, y1, z1), ground)); + boxes.add(new Box(new Vec3(x0, y0, z0), new Vec3(x1, y1, z1), ground)); } } objects.add(new HittableBinaryTree(boxes)); diff --git a/src/main/java/eu/jonahbauer/raytracing/math/AABB.java b/src/main/java/eu/jonahbauer/raytracing/math/AABB.java index b01dd28..38a4f1f 100644 --- a/src/main/java/eu/jonahbauer/raytracing/math/AABB.java +++ b/src/main/java/eu/jonahbauer/raytracing/math/AABB.java @@ -2,7 +2,6 @@ package eu.jonahbauer.raytracing.math; import eu.jonahbauer.raytracing.scene.Hittable; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.Comparator; import java.util.List; @@ -57,10 +56,6 @@ public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) { } public boolean hit(@NotNull Ray ray) { - return intersect(ray) != null; - } - - public @Nullable Range intersect(@NotNull Ray ray) { var origin = ray.origin(); var direction = ray.direction(); var invDirection = direction.inv(); @@ -73,35 +68,24 @@ public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) { // determine range of t for which the ray is inside this voxel double tlmax = Double.NEGATIVE_INFINITY; // lower limit maximum double tumin = Double.POSITIVE_INFINITY; // upper limit minimum + for (int i = 0; i < 3; i++) { // classify t values as lower or upper limit based on ray direction if (direction.get(i) >= 0) { // min is lower limit and max is upper limit - if (tmin[i] > tlmax) { - tlmax = tmin[i]; - } - if (tmax[i] < tumin) { - tumin = tmax[i]; - } + if (tmin[i] > tlmax) tlmax = tmin[i]; + if (tmax[i] < tumin) tumin = tmax[i]; } else { // max is lower limit and min is upper limit - if (tmax[i] > tlmax) { - tlmax = tmax[i]; - } - if (tmin[i] < tumin) { - tumin = tmin[i]; - } + if (tmax[i] > tlmax) tlmax = tmax[i]; + if (tmin[i] < tumin) tumin = tmin[i]; } } - return tlmax < tumin ? new Range(tlmax, tumin) : null; - } - - public static double @NotNull[] intersect(@NotNull Vec3 corner, @NotNull Ray ray) { - return intersect(corner, ray.origin(), ray.direction().inv()); + return tlmax < tumin; } - private static double @NotNull[] intersect(@NotNull Vec3 corner, @NotNull Vec3 origin, @NotNull Vec3 invDirection) { + public static double @NotNull[] intersect(@NotNull Vec3 corner, @NotNull Vec3 origin, @NotNull Vec3 invDirection) { return new double[] { (corner.x() - origin.x()) * invDirection.x(), (corner.y() - origin.y()) * invDirection.y(), diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java new file mode 100644 index 0000000..947d08c --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/scene/hittable3d/Box.java @@ -0,0 +1,136 @@ +package eu.jonahbauer.raytracing.scene.hittable3d; + +import eu.jonahbauer.raytracing.math.AABB; +import eu.jonahbauer.raytracing.math.Range; +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.material.Material; +import eu.jonahbauer.raytracing.scene.HitResult; +import eu.jonahbauer.raytracing.scene.Hittable; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Optional; + +public record Box(@NotNull AABB box, @NotNull Material material) implements Hittable { + + public Box { + Objects.requireNonNull(box, "box"); + Objects.requireNonNull(material, "material"); + } + + public Box(@NotNull Vec3 a, @NotNull Vec3 b, @NotNull Material material) { + this(new AABB(a, b), material); + } + + @Override + public @NotNull Optional hit(@NotNull Ray ray, @NotNull Range range) { + // based on AABB#hit with additional detection of the side hit + var origin = ray.origin(); + var direction = ray.direction(); + var invDirection = direction.inv(); + + var tmin = AABB.intersect(box.min(), origin, invDirection); + var tmax = AABB.intersect(box.max(), origin, invDirection); + + double tlmax = Double.NEGATIVE_INFINITY; + double tumin = Double.POSITIVE_INFINITY; + + Side entry = null; + Side exit = null; + + for (int i = 0; i < 3; i++) { + if (direction.get(i) >= 0) { + if (tmin[i] > tlmax) { + tlmax = tmin[i]; + entry = Side.NEGATIVE[i]; + } + if (tmax[i] < tumin) { + tumin = tmax[i]; + exit = Side.POSITIVE[i]; + } + } else { + if (tmax[i] > tlmax) { + tlmax = tmax[i]; + entry = Side.POSITIVE[i]; + } + if (tmin[i] < tumin) { + tumin = tmin[i]; + exit = Side.NEGATIVE[i]; + } + } + } + + if (tlmax >= tumin) return Optional.empty(); + assert entry != null && exit != null; + return hit0(tlmax, tumin, entry, exit, ray, range); + } + + private @NotNull Optional hit0(double tmin, double tmax, @NotNull Side entry, @NotNull Side exit, @NotNull Ray ray, @NotNull Range range) { + boolean frontFace; + double t; + if (range.surrounds(tmin)) { + frontFace = true; + t = tmin; + } else if (range.surrounds(tmax)) { + frontFace = false; + t = tmax; + } else { + return Optional.empty(); + } + + var side = frontFace ? entry : exit; + var normal = frontFace ? side.normal : side.normal.neg(); + var position = ray.at(t); + var u = side.getTextureU(box, position); + var v = side.getTextureV(box, position); + return Optional.of(new HitResult(t, position, normal, material, u, v, frontFace)); + } + + @Override + public @NotNull AABB getBoundingBox() { + return box; + } + + private enum Side { + NEG_X(Vec3.UNIT_X.neg()), + NEG_Y(Vec3.UNIT_Y.neg()), + NEG_Z(Vec3.UNIT_Z.neg()), + POS_X(Vec3.UNIT_X), + POS_Y(Vec3.UNIT_Y), + POS_Z(Vec3.UNIT_Z), + ; + + private static final Side[] NEGATIVE = new Side[] {Side.NEG_X, Side.NEG_Y, Side.NEG_Z}; + private static final Side[] POSITIVE = new Side[] {Side.POS_X, Side.POS_Y, Side.POS_Z}; + + private final @NotNull Vec3 normal; + + Side(@NotNull Vec3 normal) { + this.normal = Objects.requireNonNull(normal, "normal"); + } + + /** + * {@return the texture u coordinate for a position on this side of the box} + */ + public double getTextureU(@NotNull AABB box, @NotNull Vec3 pos) { + return switch (this) { + case NEG_X -> (pos.z() - box.min().z()) / (box.max().z() - box.min().z()); + case POS_X -> (box.max().z() - pos.z()) / (box.max().z() - box.min().z()); + case NEG_Y, POS_Y, POS_Z -> (pos.x() - box.min().x()) / (box.max().x() - box.min().x()); + case NEG_Z -> (box.max().x() - pos.x()) / (box.max().x() - box.min().x()); + }; + } + + /** + * {@return the texture v coordinate for a position on this side of the box} + */ + public double getTextureV(@NotNull AABB box, @NotNull Vec3 pos) { + return switch (this) { + case NEG_X, POS_X, NEG_Z, POS_Z -> (pos.y() - box.min().y()) / (box.max().y() - box.min().y()); + case NEG_Y -> (pos.z() - box.min().z()) / (box.max().z() - box.min().z()); + case POS_Y -> (box.max().z() - pos.z()) / (box.max().z() - box.min().z()); + }; + } + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/util/Hittables.java b/src/main/java/eu/jonahbauer/raytracing/scene/util/Hittables.java deleted file mode 100644 index 06c50c6..0000000 --- a/src/main/java/eu/jonahbauer/raytracing/scene/util/Hittables.java +++ /dev/null @@ -1,35 +0,0 @@ -package eu.jonahbauer.raytracing.scene.util; - -import eu.jonahbauer.raytracing.math.Vec3; -import eu.jonahbauer.raytracing.render.material.Material; -import eu.jonahbauer.raytracing.scene.Hittable; -import eu.jonahbauer.raytracing.scene.hittable2d.Parallelogram; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; - -public final class Hittables { - private Hittables() { - throw new UnsupportedOperationException(); - } - - public static @NotNull Hittable box(@NotNull Vec3 a, @NotNull Vec3 b, @NotNull Material material) { - var sides = new ArrayList(); - - var min = Vec3.min(a, b); - var max = Vec3.max(a, b); - - var dx = new Vec3(max.x() - min.x(), 0, 0); - var dy = new Vec3(0, max.y() - min.y(), 0); - var dz = new Vec3(0, 0, max.z() - min.z()); - - sides.add(new Parallelogram(new Vec3(min.x(), min.y(), max.z()), dx, dy, material)); // front - sides.add(new Parallelogram(new Vec3(max.x(), min.y(), max.z()), dz.neg(), dy, material)); // right - sides.add(new Parallelogram(new Vec3(max.x(), min.y(), min.z()), dx.neg(), dy, material)); // back - sides.add(new Parallelogram(new Vec3(min.x(), min.y(), min.z()), dz, dy, material)); // left - sides.add(new Parallelogram(new Vec3(min.x(), max.y(), max.z()), dx, dz.neg(), material)); // top - sides.add(new Parallelogram(new Vec3(min.x(), min.y(), min.z()), dx, dz, material)); // bottom - - return new HittableList(sides); - } -}