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

Loading…
Cancel
Save