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.
This commit is contained in:
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,18 +43,39 @@ public final class SimpleRenderer implements Renderer {
|
||||
throw new IllegalArgumentException("sizes of camera and canvas are different");
|
||||
}
|
||||
|
||||
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);
|
||||
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));
|
||||
});
|
||||
}
|
||||
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
|
||||
* 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…
x
Reference in New Issue
Block a user