From af3dc8dac7a2fb09437ed2075d6f9645e8efb5f5 Mon Sep 17 00:00:00 2001 From: jbb01 <32650546+jbb01@users.noreply.github.com> Date: Sat, 3 Aug 2024 02:29:16 +0200 Subject: [PATCH] refactor Camera --- .../jonahbauer/raytracing/render/Camera.java | 160 ++++++++++++------ .../eu/jonahbauer/raytracing/scene/Scene.java | 10 -- 2 files changed, 109 insertions(+), 61 deletions(-) diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Camera.java b/src/main/java/eu/jonahbauer/raytracing/render/Camera.java index 1a60890..8098af8 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Camera.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/Camera.java @@ -7,22 +7,24 @@ import eu.jonahbauer.raytracing.scene.Scene; import org.jetbrains.annotations.NotNull; import java.util.Objects; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -public record Camera( - int width, int height, - double viewportWidth, double viewportHeight, - @NotNull Vec3 origin, @NotNull Vec3 direction -) { - public Camera { - if (width <= 0) throw new IllegalArgumentException("width must be positive"); - if (height <= 0) throw new IllegalArgumentException("height must be positive"); - if (viewportWidth <= 0 || !Double.isFinite(viewportWidth)) throw new IllegalArgumentException("viewportWidth must be positive"); - if (viewportHeight <= 0 || !Double.isFinite(viewportHeight)) throw new IllegalArgumentException("viewportHeight must be positive"); - Objects.requireNonNull(origin, "origin"); - Objects.requireNonNull(direction, "direction"); - } + +public final class Camera { + // image size + private final int width; + private final int height; + + // viewport size + private final double viewportWidth; + private final double viewportHeight; + + // camera position and orientation + private final @NotNull Vec3 origin; + private final @NotNull Vec3 direction; + + // internal properties + private final @NotNull Vec3 pixelU; + private final @NotNull Vec3 pixelV; + private final @NotNull Vec3 pixel00; /** * Creates a new camera with the given dimensions at the origin facing towards positive z with a focal length of 1. @@ -45,27 +47,76 @@ public record Camera( this(width, height, viewportWidth, viewportHeight, Vec3.ZERO, Vec3.UNIT_Z); } + public Camera( + int width, int height, + double viewportWidth, double viewportHeight, + @NotNull Vec3 origin, @NotNull Vec3 direction + ) { + if (width <= 0) throw new IllegalArgumentException("width must be positive"); + if (height <= 0) throw new IllegalArgumentException("height must be positive"); + if (viewportWidth <= 0 || !Double.isFinite(viewportWidth)) throw new IllegalArgumentException("viewportWidth must be positive"); + if (viewportHeight <= 0 || !Double.isFinite(viewportHeight)) throw new IllegalArgumentException("viewportHeight must be positive"); + + this.width = width; + this.height = height; + this.viewportWidth = viewportWidth; + this.viewportHeight = viewportHeight; + this.origin = Objects.requireNonNull(origin, "origin"); + this.direction = Objects.requireNonNull(direction, "direction"); + + // project direction onto xz-plane + var d = direction.unit(); + var dXZ = direction.withY(0).unit(); + + var viewportU = new Vec3(dXZ.z(), 0, - dXZ.x()); // perpendicular to dXZ in xz-plane + var viewportV = d.cross(viewportU); // perpendicular to viewportU and direction + + viewportU = viewportU.times(viewportWidth); // vector along the width of the viewport + viewportV = viewportV.times(- viewportHeight); // vector along the height of the viewport + + this.pixelU = viewportU.div(width); + this.pixelV = viewportV.div(height); + + this.pixel00 = origin.plus(direction) + .minus(viewportU.div(2)).minus(viewportV.div(2)) + .plus(pixelU.div(2)).plus(pixelV.div(2)); + } + 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, Range.NON_NEGATIVE); - if (result.isPresent()) { - var normal = result.get().normal(); - image.set(x, y, getNormalColor(normal)); - } else { - image.set(x, y, scene.getSkyboxColor(ray)); + var image = new Image(width, height); + + for (int y = 0; y < height; y++) { + var ray = getRay(x, y); + var color = getColor(scene, ray); + image.set(x, y, color); + for (int x = 0; x < width; x++) { } - }); + } return image; } - private @NotNull Color getNormalColor(@NotNull Vec3 normal) { + private @NotNull Ray getRay(int x, int y) { + return new Ray(origin, getPixel(x, y).minus(origin)); + } + + private @NotNull Vec3 getPixel(int x, int y) { + Objects.checkIndex(x, width); + Objects.checkIndex(y, height); + return pixel00.plus(pixelU.times(x)).plus(pixelV.times(y)); + } + + private @NotNull Color getColor(@NotNull Scene scene, @NotNull Ray ray) { + var result = scene.hit(ray, Range.NON_NEGATIVE); + if (result.isPresent()) { + var normal = result.get().normal(); + return getNormalColor(normal); + } else { + return getSkyboxColor(ray); + } + } + + private static @NotNull Color getNormalColor(@NotNull Vec3 normal) { return new Color( 0.5 * (normal.x() + 1), 0.5 * (normal.y() + 1), @@ -73,31 +124,38 @@ public record Camera( ); } - public @NotNull Stream pixels() { - // project direction onto xz-plane - var d = direction.unit(); - var dXZ = direction.withY(0).unit(); + private static @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); + } - var viewportU = new Vec3(dXZ.z(), 0, - dXZ.x()); // perpendicular to dXZ in xz-plane - var viewportV = d.cross(viewportU); // perpendicular to viewportU and direction + // getters - viewportU = viewportU.times(viewportWidth); // vector along the width of the viewport - viewportV = viewportV.times(- viewportHeight); // vector along the height of the viewport + public int width() { + return width; + } - var pixelU = viewportU.div(width); - var pixelV = viewportV.div(height); + public int height() { + return height; + } - var pixel00 = origin.plus(direction) - .minus(viewportU.div(2)).minus(viewportV.div(2)) - .plus(pixelU.div(2)).plus(pixelV.div(2)); + public double viewportWidth() { + return viewportWidth; + } - return IntStream.range(0, width * height).mapToObj(i -> { - var y = i / width; - var x = i % width; - var pixel = pixel00.plus(pixelU.times(x)).plus(pixelV.times(y)); - return new Pixel(x, y, new Ray(origin, pixel.minus(origin))); - }); + public double viewportHeight() { + return viewportHeight; } - public record Pixel(int x, int y, @NotNull Ray ray) {} + public @NotNull Vec3 origin() { + return origin; + } + + public @NotNull Vec3 direction() { + return direction; + } } diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java b/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java index ffb475c..7430895 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/Scene.java @@ -2,7 +2,6 @@ package eu.jonahbauer.raytracing.scene; 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; @@ -29,13 +28,4 @@ public record Scene(@NotNull List<@NotNull Hittable> objects) implements Hittabl } 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); - } }