wip
This commit is contained in:
parent
b40cc15a9f
commit
18b9a52404
@ -7,18 +7,77 @@ import eu.jonahbauer.raytracing.material.MetallicMaterial;
|
|||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.render.*;
|
import eu.jonahbauer.raytracing.render.*;
|
||||||
import eu.jonahbauer.raytracing.render.Color;
|
import eu.jonahbauer.raytracing.render.Color;
|
||||||
|
import eu.jonahbauer.raytracing.render.canvas.BufferedImageCanvas;
|
||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
import eu.jonahbauer.raytracing.scene.Scene;
|
import eu.jonahbauer.raytracing.scene.Scene;
|
||||||
import eu.jonahbauer.raytracing.scene.Sphere;
|
import eu.jonahbauer.raytracing.scene.Sphere;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.event.WindowAdapter;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class Main {
|
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<Hittable>();
|
var objects = new ArrayList<Hittable>();
|
||||||
objects.add(new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(new Color(0.5, 0.5, 0.5))));
|
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 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))));
|
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);
|
return 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"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,13 +3,17 @@ package eu.jonahbauer.raytracing.render;
|
|||||||
import eu.jonahbauer.raytracing.math.Range;
|
import eu.jonahbauer.raytracing.math.Range;
|
||||||
import eu.jonahbauer.raytracing.math.Ray;
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
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 eu.jonahbauer.raytracing.scene.Scene;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
import java.util.stream.LongStream;
|
||||||
|
|
||||||
public final class Camera {
|
public final class Camera {
|
||||||
// image size
|
// image size
|
||||||
@ -79,30 +83,16 @@ public final class Camera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void render(@NotNull Scene scene, @NotNull Canvas canvas) {
|
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();
|
if (canvas.width() != width || canvas.height() != height) throw new IllegalArgumentException();
|
||||||
|
|
||||||
var lines = new AtomicInteger();
|
var progress = new AtomicInteger();
|
||||||
IntStream.range(0, height).parallel().forEach(y -> {
|
mode.render(this, scene, canvas, () -> {
|
||||||
System.out.println(lines.incrementAndGet());
|
var val = progress.incrementAndGet();
|
||||||
for (int x = 0; x < width; x++) {
|
if (val % 1000 == 0) System.out.println(val);
|
||||||
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)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +156,69 @@ public final class Camera {
|
|||||||
return height;
|
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 {
|
public static class Builder {
|
||||||
private int imageWidth = 1920;
|
private int imageWidth = 1920;
|
||||||
private int imageHeight = 1080;
|
private int imageHeight = 1080;
|
||||||
|
@ -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 {
|
public Color {
|
||||||
if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) {
|
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");
|
throw new IllegalArgumentException("r, g and b must be in the range 0 to 1");
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.jonahbauer.raytracing.render;
|
package eu.jonahbauer.raytracing.render;
|
||||||
|
|
||||||
|
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
@ -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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
public class BufferedImageCanvas implements Canvas {
|
public class BufferedImageCanvas extends Image {
|
||||||
private final @NotNull BufferedImage image;
|
private final @NotNull BufferedImage image;
|
||||||
|
|
||||||
public BufferedImageCanvas(int width, int height) {
|
public BufferedImageCanvas(int width, int height) {
|
||||||
|
super(width, height);
|
||||||
this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
this.image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,24 +17,9 @@ public class BufferedImageCanvas implements Canvas {
|
|||||||
return image;
|
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
|
@Override
|
||||||
public void set(int x, int y, @NotNull Color color) {
|
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();
|
var rgb = color.red() << 16 | color.green() << 8 | color.blue();
|
||||||
image.setRGB(x, y, rgb);
|
image.setRGB(x, y, rgb);
|
||||||
}
|
}
|
@ -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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
@ -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 org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public final class Image implements Canvas {
|
public class Image implements Canvas {
|
||||||
private final int width;
|
private final int width;
|
||||||
private final int height;
|
private final int height;
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
package eu.jonahbauer.raytracing.render;
|
package eu.jonahbauer.raytracing.render;
|
||||||
|
|
||||||
|
import eu.jonahbauer.raytracing.render.canvas.Image;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
@ -23,12 +24,12 @@ class ImageTest {
|
|||||||
var g = (double) y / (image.height() - 1);
|
var g = (double) y / (image.height() - 1);
|
||||||
var b = 0;
|
var b = 0;
|
||||||
|
|
||||||
image.set(x, y, r, g, b);
|
image.set(x, y, new Color(r, g, b));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
System.out.println(dir);
|
System.out.println(dir);
|
||||||
ImageIO.write(image, dir.resolve("img.ppm"));
|
ImageFormat.PPM.write(image, dir.resolve("img.ppm"));
|
||||||
|
|
||||||
String expected;
|
String expected;
|
||||||
String actual;
|
String actual;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user