add stratification to pixel sampling

main
jbb01 6 months ago
parent a31488bc78
commit 5f1e816edd

@ -18,7 +18,13 @@ public interface Camera {
/**
* Casts a ray through the given pixel.
* @param x the pixel x coordinate
* @param y the pixel y coordinate
* @param i the subpixel x coordinate
* @param j the subpixel y coordinate
* @param n the subpixel count (per side)
* @param random a random number generator
* @return a new ray
*/
@NotNull Ray cast(int x, int y, @NotNull RandomGenerator random);
@NotNull Ray cast(int x, int y, int i, int j, int n, @NotNull RandomGenerator random);
}

@ -79,13 +79,20 @@ public final class SimpleCamera implements Camera {
/**
* {@inheritDoc}
* @param x {@inheritDoc}
* @param y {@inheritDoc}
* @param i {@inheritDoc}
* @param j {@inheritDoc}
* @param n {@inheritDoc}
* @param random {@inheritDoc}
*/
public @NotNull Ray cast(int x, int y, @NotNull RandomGenerator random) {
@Override
public @NotNull Ray cast(int x, int y, int i, int j, int n, @NotNull RandomGenerator random) {
Objects.checkIndex(x, width);
Objects.checkIndex(y, height);
var origin = getRayOrigin(random);
var target = getRayTarget(x, y, random);
var target = getRayTarget(x, y, i, j, n, random);
return new Ray(origin, target.minus(origin));
}
@ -98,8 +105,8 @@ public final class SimpleCamera implements Camera {
if (blurRadius <= 0) return origin;
while (true) {
var du = 2 * random.nextDouble() - 1;
var dv = 2 * random.nextDouble() - 1;
var du = Math.fma(2, random.nextDouble(), -1);
var dv = Math.fma(2, random.nextDouble(), -1);
if (du * du + dv * dv >= 1) continue;
var ru = blurRadius * du;
@ -116,9 +123,10 @@ public final class SimpleCamera implements Camera {
/**
* {@return the target vector for a ray through the given pixel} The position is randomized within the pixel.
*/
private @NotNull Vec3 getRayTarget(int x, int y, @NotNull RandomGenerator random) {
double dx = x + random.nextDouble() - 0.5;
double dy = y + random.nextDouble() - 0.5;
private @NotNull Vec3 getRayTarget(int x, int y, int i, int j, int n, @NotNull RandomGenerator random) {
var factor = 1d / n;
var dx = x + Math.fma(factor, i + random.nextDouble(), -0.5);
var dy = y + Math.fma(factor, j + random.nextDouble(), -0.5);
return pixel00.plus(pixelU.times(dx)).plus(pixelV.times(dy));
}

@ -14,7 +14,7 @@ import java.util.random.RandomGenerator;
import java.util.stream.IntStream;
public final class SimpleRenderer implements Renderer {
private final int samplesPerPixel;
private final int sqrtSamplesPerPixel;
private final int maxDepth;
private final double gamma;
@ -30,7 +30,7 @@ public final class SimpleRenderer implements Renderer {
}
private SimpleRenderer(@NotNull Builder builder) {
this.samplesPerPixel = builder.samplesPerPixel;
this.sqrtSamplesPerPixel = (int) Math.sqrt(builder.samplesPerPixel);
this.maxDepth = builder.maxDepth;
this.gamma = builder.gamma;
@ -38,6 +38,9 @@ public final class SimpleRenderer implements Renderer {
this.iterative = builder.iterative;
}
/**
* {@inheritDoc}
*/
@Override
public void render(@NotNull Camera camera, @NotNull Scene scene, @NotNull Canvas canvas) {
if (canvas.getWidth() != camera.getWidth() || canvas.getHeight() != camera.getHeight()) {
@ -45,19 +48,35 @@ public final class SimpleRenderer implements Renderer {
}
if (iterative) {
renderIterative(camera, scene, canvas);
} else {
renderNonIterative(camera, scene, canvas);
}
}
/**
* Renders the {@code scene} as seen by the {@code camera} to the {@code canvas}, taking one sample per pixel at
* a time and updating the canvas after each sample.
*/
private void renderIterative(@NotNull Camera camera, @NotNull Scene scene, @NotNull Canvas canvas) {
var random = new Random();
// render one sample after the other
for (int i = 1 ; i <= samplesPerPixel; i++) {
var sample = i;
int i = 0;
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
var sample = ++i;
var sif = si;
var sjf = sj;
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
for (int x = 0; x < camera.getWidth(); x++) {
var ray = camera.cast(x, y, random);
var ray = camera.cast(x, y, sif, sjf, sqrtSamplesPerPixel, random);
var c = getColor(scene, ray, random);
canvas.set(x, y, Color.average(canvas.get(x, y), c, sample));
}
});
}
}
// apply gamma correction
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
@ -65,23 +84,31 @@ public final class SimpleRenderer implements Renderer {
canvas.set(x, y, Color.gamma(canvas.get(x, y), gamma));
}
});
} else {
}
/**
* Renders the {@code scene} as seen by the {@code camera} to the {@code canvas}, taking some amount of samples
* per pixel and updating the canvas after each pixel.
*/
private void renderNonIterative(@NotNull Camera camera, @NotNull Scene scene, @NotNull Canvas canvas) {
var splittable = new SplittableRandom();
// render one pixel after the other
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
var random = splittable.split();
for (int x = 0; x < camera.getWidth(); x++) {
var color = Color.BLACK;
for (int i = 1; i <= samplesPerPixel; i++) {
var ray = camera.cast(x, y, random);
int i = 0;
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random);
var c = getColor(scene, ray, random);
color = Color.average(color, c, i);
color = Color.average(color, c, ++i);
}
}
canvas.set(x, y, Color.gamma(color, gamma));
}
});
}
}
/**
* {@return the color of the given ray in the given scene}

Loading…
Cancel
Save