diff --git a/src/main/java/eu/jonahbauer/raytracing/Main.java b/src/main/java/eu/jonahbauer/raytracing/Main.java index 7e6c7c2..deabfbf 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Main.java +++ b/src/main/java/eu/jonahbauer/raytracing/Main.java @@ -2,7 +2,7 @@ package eu.jonahbauer.raytracing; import eu.jonahbauer.raytracing.render.Camera; import eu.jonahbauer.raytracing.render.ImageIO; -import eu.jonahbauer.raytracing.render.Scene; +import eu.jonahbauer.raytracing.shape.Scene; import eu.jonahbauer.raytracing.shape.Sphere; import java.io.IOException; @@ -16,7 +16,7 @@ public class Main { ); var camera = new Camera(256, 2, 16 / 9d); - var image = scene.render(camera); + var image = camera.render(scene); ImageIO.write(image, Path.of("scene-" + System.currentTimeMillis() + ".ppm")); } } \ No newline at end of file diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Camera.java b/src/main/java/eu/jonahbauer/raytracing/render/Camera.java index 7bd740c..7f95397 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Camera.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/Camera.java @@ -2,6 +2,7 @@ package eu.jonahbauer.raytracing.render; import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.shape.Scene; import org.jetbrains.annotations.NotNull; import java.util.Objects; @@ -43,6 +44,34 @@ public record Camera( this(width, height, viewportWidth, viewportHeight, Vec3.ZERO, Vec3.UNIT_Z); } + public @NotNull Image render(@NotNull Scene scene) { + var image = new Image(width(), height()); + + pixels().forEach(pixel -> { + var x = pixel.x(); + var y = pixel.y(); + var ray = pixel.ray(); + + var result = scene.hit(ray); + if (result.isPresent()) { + var normal = result.get().normal(); + image.set(x, y, getNormalColor(normal)); + } else { + image.set(x, y, scene.getSkyboxColor(ray)); + } + }); + + return image; + } + + private @NotNull Color getNormalColor(@NotNull Vec3 normal) { + return new Color( + 0.5 * (normal.x() + 1), + 0.5 * (normal.y() + 1), + 0.5 * (normal.z() + 1) + ); + } + public @NotNull Stream pixels() { // project direction onto xz-plane var d = direction.unit(); diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Scene.java b/src/main/java/eu/jonahbauer/raytracing/render/Scene.java deleted file mode 100644 index 8be64e1..0000000 --- a/src/main/java/eu/jonahbauer/raytracing/render/Scene.java +++ /dev/null @@ -1,72 +0,0 @@ -package eu.jonahbauer.raytracing.render; - -import eu.jonahbauer.raytracing.math.Range; -import eu.jonahbauer.raytracing.math.Ray; -import eu.jonahbauer.raytracing.math.Vec3; -import eu.jonahbauer.raytracing.shape.HitResult; -import eu.jonahbauer.raytracing.shape.Shape; -import org.jetbrains.annotations.NotNull; - -import java.util.List; -import java.util.Optional; - -public record Scene(@NotNull List<@NotNull Shape> shapes) { - - public Scene { - shapes = List.copyOf(shapes); - } - - public Scene(@NotNull Shape @NotNull ... shapes) { - this(List.of(shapes)); - } - - public @NotNull Image render(@NotNull Camera camera) { - var image = new Image(camera.width(), camera.height()); - - camera.pixels().forEach(pixel -> { - var x = pixel.x(); - var y = pixel.y(); - var ray = pixel.ray(); - - var result = hit(ray); - if (result.isPresent()) { - var normal = result.get().normal(); - image.set(x, y, getNormalColor(normal)); - } else { - image.set(x, y, getSkyboxColor(ray)); - } - }); - - return image; - } - - private @NotNull Optional hit(@NotNull Ray ray) { - var range = new Range(0, Double.POSITIVE_INFINITY); - var result = (HitResult) null; - for (var shape : shapes) { - var r = shape.hit(ray, range); - if (r.isPresent() && (result == null || r.get().t() < result.t())) { - result = r.get(); - range = new Range(0, result.t()); - } - } - return Optional.ofNullable(result); - } - - private @NotNull Color getNormalColor(@NotNull Vec3 normal) { - return new Color( - 0.5 * (normal.x() + 1), - 0.5 * (normal.y() + 1), - 0.5 * (normal.z() + 1) - ); - } - - private @NotNull Color getSkyboxColor(@NotNull Ray ray) { - // altitude from -pi/2 to pi/2 - var alt = Math.copySign( - Math.acos(ray.direction().withY(0).unit().times(ray.direction().unit())), - ray.direction().y() - ); - return Color.lerp(Color.WHITE, Color.SKY, alt / Math.PI + 0.5); - } -} diff --git a/src/main/java/eu/jonahbauer/raytracing/shape/Scene.java b/src/main/java/eu/jonahbauer/raytracing/shape/Scene.java new file mode 100644 index 0000000..add4160 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/shape/Scene.java @@ -0,0 +1,42 @@ +package eu.jonahbauer.raytracing.shape; + +import eu.jonahbauer.raytracing.math.Range; +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.render.Color; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Optional; + +public record Scene(@NotNull List<@NotNull Shape> shapes) { + + public Scene { + shapes = List.copyOf(shapes); + } + + public Scene(@NotNull Shape @NotNull ... shapes) { + this(List.of(shapes)); + } + + public @NotNull Optional hit(@NotNull Ray ray) { + var range = new Range(0, Double.POSITIVE_INFINITY); + var result = (HitResult) null; + for (var shape : shapes) { + var r = shape.hit(ray, range); + if (r.isPresent() && (result == null || r.get().t() < result.t())) { + result = r.get(); + range = new Range(0, result.t()); + } + } + return Optional.ofNullable(result); + } + + public @NotNull Color getSkyboxColor(@NotNull Ray ray) { + // altitude from -pi/2 to pi/2 + var alt = Math.copySign( + Math.acos(ray.direction().withY(0).unit().times(ray.direction().unit())), + ray.direction().y() + ); + return Color.lerp(Color.WHITE, Color.SKY, alt / Math.PI + 0.5); + } +}