From 18b9a52404c49f8ace42ce01c2fbe83c7fcaaca9 Mon Sep 17 00:00:00 2001 From: jbb01 <32650546+jbb01@users.noreply.github.com> Date: Sun, 4 Aug 2024 14:38:34 +0200 Subject: [PATCH] wip --- .../java/eu/jonahbauer/raytracing/Main.java | 96 +++++++++++------- .../jonahbauer/raytracing/render/Camera.java | 97 ++++++++++++++----- .../jonahbauer/raytracing/render/Color.java | 8 ++ .../raytracing/render/ImageFormat.java | 1 + .../{ => canvas}/BufferedImageCanvas.java | 23 +---- .../render/{ => canvas}/Canvas.java | 3 +- .../raytracing/render/{ => canvas}/Image.java | 5 +- .../raytracing/render/ImageTest.java | 5 +- 8 files changed, 158 insertions(+), 80 deletions(-) rename src/main/java/eu/jonahbauer/raytracing/render/{ => canvas}/BufferedImageCanvas.java (55%) rename src/main/java/eu/jonahbauer/raytracing/render/{ => canvas}/Canvas.java (84%) rename src/main/java/eu/jonahbauer/raytracing/render/{ => canvas}/Image.java (88%) diff --git a/src/main/java/eu/jonahbauer/raytracing/Main.java b/src/main/java/eu/jonahbauer/raytracing/Main.java index 8de1518..5cdbc6d 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Main.java +++ b/src/main/java/eu/jonahbauer/raytracing/Main.java @@ -7,18 +7,77 @@ import eu.jonahbauer.raytracing.material.MetallicMaterial; import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.render.*; import eu.jonahbauer.raytracing.render.Color; +import eu.jonahbauer.raytracing.render.canvas.BufferedImageCanvas; import eu.jonahbauer.raytracing.scene.Hittable; import eu.jonahbauer.raytracing.scene.Scene; import eu.jonahbauer.raytracing.scene.Sphere; +import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; public class Main { - public static void main(String[] args) throws IOException, InterruptedException { + public static void main(String[] args) throws IOException { + var scene = getScene(); + var camera = Camera.builder() +// .withImage(1920, 1080) + .withImage(800, 450) + .withPosition(new Vec3(13, 2, 3)) + .withTarget(new Vec3(0, 0, 0)) + .withSamplesPerPixel(100) + .withMaxDepth(10) + .withFieldOfView(Math.toRadians(20)) + .withBlurAngle(Math.toRadians(0.6)) + .withFocusDistance(10.0) + .build(); + + var canvas = new BufferedImageCanvas(camera.width(), camera.height()); + preview(canvas); + + camera.render(scene, canvas, Camera.RenderMode.SAMPLES); + ImageFormat.PNG.write(canvas, Path.of("scene-" + System.currentTimeMillis() + ".png")); + } + + private static Thread preview(@NotNull BufferedImageCanvas canvas) { + var frame = new JFrame(); + frame.setSize(canvas.width(), canvas.height()); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.setContentPane(new JPanel() { + @Override + protected void paintComponent(Graphics g) { + g.drawImage(canvas.getImage(), 0, 0, null); + } + }); + frame.setResizable(false); + frame.setVisible(true); + + var update = Thread.ofVirtual().start(() -> { + while (!Thread.interrupted()) { + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + break; + } + frame.repaint(); + } + }); + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + update.interrupt(); + } + }); + + return update; + } + + private static @NotNull Scene getScene() { var objects = new ArrayList(); objects.add(new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(new Color(0.5, 0.5, 0.5)))); @@ -51,39 +110,6 @@ public class Main { objects.add(new Sphere(new Vec3(-4, 1, 0), 1.0, new LambertianMaterial(new Color(0.4, 0.2, 0.1)))); objects.add(new Sphere(new Vec3(4, 1, 0), 1.0, new MetallicMaterial(new Color(0.7, 0.6, 0.5)))); - var scene = new Scene(objects); - - var camera = Camera.builder() - .withImage(1280, 720) - .withPosition(new Vec3(13, 2, 3)) - .withTarget(new Vec3(0, 0, 0)) - .withSamplesPerPixel(100) - .withMaxDepth(10) - .withFieldOfView(Math.toRadians(20)) - .withBlurAngle(Math.toRadians(0.6)) - .withFocusDistance(10.0) - .build(); - - var canvas = new BufferedImageCanvas(camera.width(), camera.height()); - var thread = Thread.ofVirtual().start(() -> camera.render(scene, canvas)); - - var frame = new JFrame(); - frame.setSize(camera.width(), camera.height()); - frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - frame.setContentPane(new JPanel() { - @Override - protected void paintComponent(Graphics g) { - g.drawImage(canvas.getImage(), 0, 0, null); - } - }); - frame.setResizable(false); - frame.setVisible(true); - - while (thread.isAlive()) { - Thread.sleep(1000); - frame.repaint(); - } - - ImageFormat.PNG.write(canvas, Path.of("scene-" + System.currentTimeMillis() + ".png")); + return new Scene(objects); } } \ 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 e839948..6811829 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Camera.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/Camera.java @@ -3,13 +3,17 @@ 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.render.canvas.Canvas; +import eu.jonahbauer.raytracing.render.canvas.Image; import eu.jonahbauer.raytracing.scene.Scene; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.stream.IntStream; +import java.util.stream.LongStream; public final class Camera { // image size @@ -79,30 +83,16 @@ public final class Camera { } public void render(@NotNull Scene scene, @NotNull Canvas canvas) { + render(scene, canvas, RenderMode.SEQUENTIEL); + } + + public void render(@NotNull Scene scene, @NotNull Canvas canvas, @NotNull RenderMode mode) { if (canvas.width() != width || canvas.height() != height) throw new IllegalArgumentException(); - var lines = new AtomicInteger(); - IntStream.range(0, height).parallel().forEach(y -> { - System.out.println(lines.incrementAndGet()); - for (int x = 0; x < width; x++) { - var r = 0d; - var g = 0d; - var b = 0d; - - for (int i = 0; i < samplesPerPixel; i++) { - var ray = getRay(x, y); - var color = getColor(scene, ray); - r += color.r(); - g += color.g(); - b += color.b(); - } - - canvas.set(x, y, new Color( - Math.pow(r / samplesPerPixel, 1 / gamma), - Math.pow(g / samplesPerPixel, 1 / gamma), - Math.pow(b / samplesPerPixel, 1 / gamma) - )); - } + var progress = new AtomicInteger(); + mode.render(this, scene, canvas, () -> { + var val = progress.incrementAndGet(); + if (val % 1000 == 0) System.out.println(val); }); } @@ -166,6 +156,69 @@ public final class Camera { return height; } + public enum RenderMode { + SEQUENTIEL { + @Override + public void render(@NotNull Camera camera, @NotNull Scene scene, @NotNull Canvas canvas, @NotNull Runnable onProgressUpdate) { + for (int y = 0; y < camera.height; y++) { + for (int x = 0; x < camera.width; x++) { + Color color = Color.BLACK; + for (int i = 1; i <= camera.samplesPerPixel; i++) { + var ray = camera.getRay(x, y); + var c = camera.getColor(scene, ray); + color = Color.average(color, c, i); + } + canvas.set(x, y, color); + onProgressUpdate.run(); + } + } + } + }, + PARALLEL { + @Override + public void render(@NotNull Camera camera, @NotNull Scene scene, @NotNull Canvas canvas, @NotNull Runnable onProgressUpdate) { + coordinates(camera.width, camera.height).parallel().forEach(pos -> { + var x = (int) pos; + var y = (int) (pos >> 32); + Color color = Color.BLACK; + for (int i = 1; i <= camera.samplesPerPixel; i++) { + var ray = camera.getRay(x, y); + var c = camera.getColor(scene, ray); + color = Color.average(color, c, i); + } + canvas.set(x, y, color); + onProgressUpdate.run(); + }); + } + }, + SAMPLES { + @Override + public void render(@NotNull Camera camera, @NotNull Scene scene, @NotNull Canvas canvas, @NotNull Runnable onProgressUpdate) { + for (int i = 1; i <= camera.samplesPerPixel; i++) { + var sample = i; + coordinates(camera.width, camera.height).forEach(pos -> { + var x = (int) pos; + var y = (int) (pos >> 32); + var ray = camera.getRay(x, y); + var color = Color.average(canvas.get(x, y), camera.getColor(scene, ray), sample); + canvas.set(x, y, color); + }); + onProgressUpdate.run(); + } + } + } + ; + + private static LongStream coordinates(int width, int height) { + return IntStream.range(0, height) + .mapToObj(y -> IntStream.range(0, width).mapToLong(x -> (long) y << 32 | x)) + .flatMapToLong(Function.identity()); + } + + public abstract void render(@NotNull Camera camera, @NotNull Scene scene, @NotNull Canvas canvas, @NotNull Runnable onProgressUpdate); + + } + public static class Builder { private int imageWidth = 1920; private int imageHeight = 1080; diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Color.java b/src/main/java/eu/jonahbauer/raytracing/render/Color.java index b56e572..5107dbb 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Color.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/Color.java @@ -37,6 +37,14 @@ public record Color(double r, double g, double b) { ); } + public static @NotNull Color average(@NotNull Color current, @NotNull Color next, int index) { + return new Color( + current.r() + (next.r() - current.r()) / index, + current.g() + (next.g() - current.g()) / index, + current.b() + (next.b() - current.b()) / index + ); + } + public Color { if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) { throw new IllegalArgumentException("r, g and b must be in the range 0 to 1"); diff --git a/src/main/java/eu/jonahbauer/raytracing/render/ImageFormat.java b/src/main/java/eu/jonahbauer/raytracing/render/ImageFormat.java index c49b936..d9ddc51 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/ImageFormat.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/ImageFormat.java @@ -1,5 +1,6 @@ package eu.jonahbauer.raytracing.render; +import eu.jonahbauer.raytracing.render.canvas.Canvas; import org.jetbrains.annotations.NotNull; import java.io.*; diff --git a/src/main/java/eu/jonahbauer/raytracing/render/BufferedImageCanvas.java b/src/main/java/eu/jonahbauer/raytracing/render/canvas/BufferedImageCanvas.java similarity index 55% rename from src/main/java/eu/jonahbauer/raytracing/render/BufferedImageCanvas.java rename to src/main/java/eu/jonahbauer/raytracing/render/canvas/BufferedImageCanvas.java index ebbfe0d..bace484 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/BufferedImageCanvas.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/canvas/BufferedImageCanvas.java @@ -1,13 +1,15 @@ -package eu.jonahbauer.raytracing.render; +package eu.jonahbauer.raytracing.render.canvas; +import eu.jonahbauer.raytracing.render.Color; import org.jetbrains.annotations.NotNull; import java.awt.image.BufferedImage; -public class BufferedImageCanvas implements Canvas { +public class BufferedImageCanvas extends Image { private final @NotNull BufferedImage image; public BufferedImageCanvas(int width, int height) { + super(width, height); this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); } @@ -15,24 +17,9 @@ public class BufferedImageCanvas implements Canvas { return image; } - @Override - public int width() { - return image.getWidth(); - } - - @Override - public int height() { - return image.getHeight(); - } - - @Override - public @NotNull Color get(int x, int y) { - var rgb = image.getRGB(x, y); - return new Color((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF); - } - @Override public void set(int x, int y, @NotNull Color color) { + super.set(x, y, color); var rgb = color.red() << 16 | color.green() << 8 | color.blue(); image.setRGB(x, y, rgb); } diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Canvas.java b/src/main/java/eu/jonahbauer/raytracing/render/canvas/Canvas.java similarity index 84% rename from src/main/java/eu/jonahbauer/raytracing/render/Canvas.java rename to src/main/java/eu/jonahbauer/raytracing/render/canvas/Canvas.java index da39b51..c1c376b 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Canvas.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/canvas/Canvas.java @@ -1,5 +1,6 @@ -package eu.jonahbauer.raytracing.render; +package eu.jonahbauer.raytracing.render.canvas; +import eu.jonahbauer.raytracing.render.Color; import org.jetbrains.annotations.NotNull; import java.util.function.Function; diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Image.java b/src/main/java/eu/jonahbauer/raytracing/render/canvas/Image.java similarity index 88% rename from src/main/java/eu/jonahbauer/raytracing/render/Image.java rename to src/main/java/eu/jonahbauer/raytracing/render/canvas/Image.java index c01eb02..8256439 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Image.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/canvas/Image.java @@ -1,10 +1,11 @@ -package eu.jonahbauer.raytracing.render; +package eu.jonahbauer.raytracing.render.canvas; +import eu.jonahbauer.raytracing.render.Color; import org.jetbrains.annotations.NotNull; import java.util.Objects; -public final class Image implements Canvas { +public class Image implements Canvas { private final int width; private final int height; diff --git a/src/test/java/eu/jonahbauer/raytracing/render/ImageTest.java b/src/test/java/eu/jonahbauer/raytracing/render/ImageTest.java index 58976ac..879e6e4 100644 --- a/src/test/java/eu/jonahbauer/raytracing/render/ImageTest.java +++ b/src/test/java/eu/jonahbauer/raytracing/render/ImageTest.java @@ -1,5 +1,6 @@ package eu.jonahbauer.raytracing.render; +import eu.jonahbauer.raytracing.render.canvas.Image; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -23,12 +24,12 @@ class ImageTest { var g = (double) y / (image.height() - 1); var b = 0; - image.set(x, y, r, g, b); + image.set(x, y, new Color(r, g, b)); } } System.out.println(dir); - ImageIO.write(image, dir.resolve("img.ppm")); + ImageFormat.PPM.write(image, dir.resolve("img.ppm")); String expected; String actual;