add stratification to pixel sampling
This commit is contained in:
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,42 +48,66 @@ public final class SimpleRenderer implements Renderer {
|
||||
}
|
||||
|
||||
if (iterative) {
|
||||
var random = new Random();
|
||||
renderIterative(camera, scene, canvas);
|
||||
} else {
|
||||
renderNonIterative(camera, scene, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
// render one sample after the other
|
||||
for (int i = 1 ; i <= samplesPerPixel; i++) {
|
||||
var sample = i;
|
||||
/**
|
||||
* 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
|
||||
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 -> {
|
||||
for (int x = 0; x < camera.getWidth(); x++) {
|
||||
canvas.set(x, y, Color.gamma(canvas.get(x, y), gamma));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
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);
|
||||
var c = getColor(scene, ray, random);
|
||||
color = Color.average(color, c, i);
|
||||
}
|
||||
canvas.set(x, y, Color.gamma(color, gamma));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// apply gamma correction
|
||||
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
|
||||
for (int x = 0; x < camera.getWidth(); x++) {
|
||||
canvas.set(x, y, Color.gamma(canvas.get(x, y), gamma));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
canvas.set(x, y, Color.gamma(color, gamma));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user