add iterative rendering mode for faster results

In normal mode, the image is rendered one pixel at a time, taking multiple samples per pixel and averaging them before continuing with the next pixel.

In iterative mode, the image is rendered one sample at a time, taking one sample per pixel, then taking another sample and averaging it with the one before, and so on.
main
jbb01 6 months ago
parent 0c6db707e0
commit b47ded6c56

@ -36,6 +36,7 @@ public class Main {
var renderer = SimpleRenderer.builder() var renderer = SimpleRenderer.builder()
.withSamplesPerPixel(500) .withSamplesPerPixel(500)
.withMaxDepth(50) .withMaxDepth(50)
.withIterative(true)
.build(); .build();
var image = new LiveCanvas(new Image(camera.getWidth(), camera.getHeight())); var image = new LiveCanvas(new Image(camera.getWidth(), camera.getHeight()));

@ -17,6 +17,9 @@ public final class SimpleRenderer implements Renderer {
private final int maxDepth; private final int maxDepth;
private final double gamma; private final double gamma;
private final boolean parallel;
private final boolean iterative;
public static @NotNull Builder builder() { public static @NotNull Builder builder() {
return new Builder(); return new Builder();
} }
@ -29,6 +32,9 @@ public final class SimpleRenderer implements Renderer {
this.samplesPerPixel = builder.samplesPerPixel; this.samplesPerPixel = builder.samplesPerPixel;
this.maxDepth = builder.maxDepth; this.maxDepth = builder.maxDepth;
this.gamma = builder.gamma; this.gamma = builder.gamma;
this.parallel = builder.parallel;
this.iterative = builder.iterative;
} }
@Override @Override
@ -37,18 +43,39 @@ public final class SimpleRenderer implements Renderer {
throw new IllegalArgumentException("sizes of camera and canvas are different"); throw new IllegalArgumentException("sizes of camera and canvas are different");
} }
getPixelStream(camera.getWidth(), camera.getHeight()).parallel().forEach(pixel -> { if (iterative) {
var y = (int) (pixel >> 32); // render one sample after the other
var x = (int) pixel; for (int i = 1 ; i <= samplesPerPixel; i++) {
var sample = i;
var color = Color.BLACK; getPixelStream(camera.getWidth(), camera.getHeight(), parallel).forEach(pixel -> {
for (int i = 1; i <= samplesPerPixel; i++) { var y = (int) (pixel >> 32);
var ray = camera.cast(x, y); var x = (int) pixel;
var c = getColor(scene, ray); var ray = camera.cast(x, y);
color = Color.average(color, c, i); var c = getColor(scene, ray);
canvas.set(x, y, Color.average(canvas.get(x, y), c, sample));
});
} }
canvas.set(x, y, Color.gamma(color, gamma)); // apply gamma correction
}); getPixelStream(camera.getWidth(), camera.getHeight(), parallel).forEach(pixel -> {
var y = (int) (pixel >> 32);
var x = (int) pixel;
canvas.set(x, y, Color.gamma(canvas.get(x, y), gamma));
});
} else {
// render one pixel after the other
getPixelStream(camera.getWidth(), camera.getHeight(), parallel).forEach(pixel -> {
var y = (int) (pixel >> 32);
var x = (int) pixel;
var color = Color.BLACK;
for (int i = 1; i <= samplesPerPixel; i++) {
var ray = camera.cast(x, y);
var c = getColor(scene, ray);
color = Color.average(color, c, i);
}
canvas.set(x, y, Color.gamma(color, gamma));
});
}
} }
/** /**
@ -77,10 +104,11 @@ public final class SimpleRenderer implements Renderer {
* {@return a stream of the pixels in a canvas with the given size} The pixels {@code x} and {@code y} coordinate * {@return a stream of the pixels in a canvas with the given size} The pixels {@code x} and {@code y} coordinate
* are encoded in the longs lower and upper 32 bits respectively. * are encoded in the longs lower and upper 32 bits respectively.
*/ */
private static @NotNull LongStream getPixelStream(int width, int height) { private static @NotNull LongStream getPixelStream(int width, int height, boolean parallel) {
return IntStream.range(0, height) var stream = IntStream.range(0, height)
.mapToObj(y -> IntStream.range(0, width).mapToLong(x -> (long) y << 32 | x)) .mapToObj(y -> IntStream.range(0, width).mapToLong(x -> (long) y << 32 | x))
.flatMapToLong(Function.identity()); .flatMapToLong(Function.identity());
return parallel ? stream.parallel() : stream;
} }
/** /**
@ -100,6 +128,8 @@ public final class SimpleRenderer implements Renderer {
private int samplesPerPixel = 100; private int samplesPerPixel = 100;
private int maxDepth = 10; private int maxDepth = 10;
private double gamma = 2.0; private double gamma = 2.0;
private boolean parallel = true;
private boolean iterative = false;
public @NotNull Builder withSamplesPerPixel(int samples) { public @NotNull Builder withSamplesPerPixel(int samples) {
if (samples <= 0) throw new IllegalArgumentException("samples must be positive"); if (samples <= 0) throw new IllegalArgumentException("samples must be positive");
@ -119,6 +149,16 @@ public final class SimpleRenderer implements Renderer {
return this; return this;
} }
public @NotNull Builder withParallel(boolean parallel) {
this.parallel = parallel;
return this;
}
public @NotNull Builder withIterative(boolean iterative) {
this.iterative = iterative;
return this;
}
public @NotNull SimpleRenderer build() { public @NotNull SimpleRenderer build() {
return new SimpleRenderer(this); return new SimpleRenderer(this);
} }

Loading…
Cancel
Save