add command line option for number of spectral samples
This commit is contained in:
parent
533461204a
commit
9b72909d27
@ -24,6 +24,7 @@ public class Main {
|
||||
|
||||
var renderer = SimpleRenderer.builder()
|
||||
.withSamplesPerPixel(config.samples)
|
||||
.withSpectralSamples(config.spectralSamples)
|
||||
.withMaxDepth(config.depth)
|
||||
.withIterative(config.iterative)
|
||||
.withParallel(config.parallel)
|
||||
@ -45,7 +46,7 @@ public class Main {
|
||||
PNGImageWriter.sRGB.write(canvas, config.path);
|
||||
}
|
||||
|
||||
private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int depth) {
|
||||
private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int spectralSamples, int depth) {
|
||||
public static @NotNull Config parse(@NotNull String @NotNull[] args) {
|
||||
IntFunction<Example> example = null;
|
||||
Path path = null;
|
||||
@ -53,6 +54,7 @@ public class Main {
|
||||
boolean iterative = false;
|
||||
boolean parallel = false;
|
||||
int samples = 1000;
|
||||
int spectralSamples = 4;
|
||||
int depth = 50;
|
||||
int height = -1;
|
||||
|
||||
@ -81,6 +83,15 @@ public class Main {
|
||||
throw fail("value " + args[i] + " is not a valid integer");
|
||||
}
|
||||
}
|
||||
case "--spectral-samples" -> {
|
||||
if (i + 1 == args.length) throw fail("missing value for parameter --spectral-samples");
|
||||
try {
|
||||
spectralSamples = Integer.parseInt(args[++i]);
|
||||
if (spectralSamples <= 0) throw fail("spectral samples must be positive");
|
||||
} catch (NumberFormatException ex) {
|
||||
throw fail("value " + args[i] + " is not a valid integer");
|
||||
}
|
||||
}
|
||||
case "--depth" -> {
|
||||
if (i + 1 == args.length) throw fail("missing value for parameter --depth");
|
||||
try {
|
||||
@ -112,7 +123,7 @@ public class Main {
|
||||
|
||||
if (example == null) example = Examples::getCornellBoxSmoke;
|
||||
if (path == null) path = Path.of("scene-" + System.currentTimeMillis() + ".png");
|
||||
return new Config(example.apply(height), path, preview, iterative, parallel, samples, depth);
|
||||
return new Config(example.apply(height), path, preview, iterative, parallel, samples, spectralSamples, depth);
|
||||
}
|
||||
|
||||
private static @NotNull RuntimeException fail(@NotNull String message) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package eu.jonahbauer.raytracing.math;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
@ -19,4 +20,16 @@ public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull Sample
|
||||
public @NotNull Vec3 at(double t) {
|
||||
return Vec3.fma(t, direction, origin);
|
||||
}
|
||||
|
||||
public @NotNull Ray with(@NotNull HitResult hit, @NotNull Vec3 direction) {
|
||||
return new Ray(hit.position(), direction, lambda);
|
||||
}
|
||||
|
||||
public @NotNull Ray with(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||
return new Ray(origin, direction, lambda);
|
||||
}
|
||||
|
||||
public @NotNull Ray with(@NotNull SampledWavelengths lambda) {
|
||||
return new Ray(origin, direction, lambda);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package eu.jonahbauer.raytracing.render.camera;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -94,7 +93,7 @@ public final class SimpleCamera implements Camera {
|
||||
|
||||
var origin = getRayOrigin(random);
|
||||
var target = getRayTarget(x, y, i, j, n, random);
|
||||
return new Ray(origin, target.minus(origin), SampledWavelengths.uniform(random.nextDouble()));
|
||||
return new Ray(origin, target.minus(origin));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -45,7 +45,7 @@ public record DielectricMaterial(@NotNull RefractiveIndex ri, @NotNull Texture t
|
||||
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
|
||||
|
||||
var attenuation = texture.get(hit);
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
|
||||
return Optional.of(new SpecularScatterResult(attenuation, ray.with(hit, newDirection)));
|
||||
}
|
||||
|
||||
private double reflectance(double cos, double ri) {
|
||||
|
@ -41,7 +41,7 @@ public final class DirectionalMaterial implements Material {
|
||||
if (back != null) return back.scatter(ray, hit, random);
|
||||
}
|
||||
// let the ray pass through without obstruction
|
||||
return Optional.of(new SpecularScatterResult(Spectra.WHITE, new Ray(ray.at(hit.t()), ray.direction(), ray.lambda())));
|
||||
return Optional.of(new SpecularScatterResult(Spectra.WHITE, ray.with(hit, ray.direction())));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -28,6 +28,6 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
|
||||
newDirection = Vec3.fma(fuzz, Vec3.random(random), newDirection.unit());
|
||||
}
|
||||
var attenuation = texture.get(hit);
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
|
||||
return Optional.of(new SpecularScatterResult(attenuation, ray.with(hit, newDirection)));
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFun
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.camera.Camera;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.scene.Scene;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -20,7 +21,10 @@ import static eu.jonahbauer.raytracing.Main.DEBUG;
|
||||
public final class SimpleRenderer implements Renderer {
|
||||
private final int sqrtSamplesPerPixel;
|
||||
private final int maxDepth;
|
||||
private final double gamma;
|
||||
|
||||
private final int spectralSamples;
|
||||
private final SampledSpectrum black;
|
||||
private final SampledSpectrum white;
|
||||
|
||||
private final boolean parallel;
|
||||
private final boolean iterative;
|
||||
@ -36,7 +40,10 @@ public final class SimpleRenderer implements Renderer {
|
||||
private SimpleRenderer(@NotNull Builder builder) {
|
||||
this.sqrtSamplesPerPixel = (int) Math.sqrt(builder.samplesPerPixel);
|
||||
this.maxDepth = builder.maxDepth;
|
||||
this.gamma = builder.gamma;
|
||||
|
||||
this.spectralSamples = builder.spectralSamples;
|
||||
this.black = new SampledSpectrum(spectralSamples, 0);
|
||||
this.white = new SampledSpectrum(spectralSamples, 1);
|
||||
|
||||
this.parallel = builder.parallel;
|
||||
this.iterative = builder.iterative;
|
||||
@ -96,7 +103,8 @@ public final class SimpleRenderer implements Renderer {
|
||||
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 lambda = SampledWavelengths.uniform(random.nextDouble(), spectralSamples);
|
||||
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random).with(lambda);
|
||||
if (DEBUG) {
|
||||
System.out.println("Casting ray " + ray + " through pixel (" + x + "," + y + ") at subpixel (" + si + "," + sj + ")...");
|
||||
}
|
||||
@ -116,8 +124,8 @@ public final class SimpleRenderer implements Renderer {
|
||||
}
|
||||
|
||||
private @NotNull SampledSpectrum getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
|
||||
var color = SampledSpectrum.BLACK;
|
||||
var attenuation = SampledSpectrum.WHITE;
|
||||
var color = black;
|
||||
var attenuation = white;
|
||||
|
||||
while (depth-- > 0) {
|
||||
var optional = scene.hit(ray);
|
||||
@ -136,7 +144,7 @@ public final class SimpleRenderer implements Renderer {
|
||||
}
|
||||
var material = hit.material();
|
||||
var emitted = material.emitted(hit).sample(ray.lambda());
|
||||
if (DEBUG && !SampledSpectrum.BLACK.equals(emitted)) {
|
||||
if (DEBUG && !black.equals(emitted)) {
|
||||
System.out.println(" Emitted: " + emitted);
|
||||
}
|
||||
|
||||
@ -213,7 +221,7 @@ public final class SimpleRenderer implements Renderer {
|
||||
public static class Builder {
|
||||
private int samplesPerPixel = 100;
|
||||
private int maxDepth = 10;
|
||||
private double gamma = 2.0;
|
||||
private int spectralSamples = 4;
|
||||
private boolean parallel = true;
|
||||
private boolean iterative = false;
|
||||
|
||||
@ -229,9 +237,9 @@ public final class SimpleRenderer implements Renderer {
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NotNull Builder withGamma(double gamma) {
|
||||
if (gamma <= 0 || !Double.isFinite(gamma)) throw new IllegalArgumentException("gamma must be positive");
|
||||
this.gamma = gamma;
|
||||
public @NotNull Builder withSpectralSamples(int samples) {
|
||||
if (samples <= 0) throw new IllegalArgumentException("samples must be positive");
|
||||
this.spectralSamples = samples;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -10,16 +10,6 @@ import java.util.Arrays;
|
||||
|
||||
// TODO use Vector API to parallelize operations
|
||||
public final class SampledSpectrum implements IVec<SampledSpectrum> {
|
||||
public static final SampledSpectrum BLACK;
|
||||
public static final SampledSpectrum WHITE;
|
||||
|
||||
static {
|
||||
BLACK = new SampledSpectrum(new double[SampledWavelengths.SAMPLES]);
|
||||
var one = new double[SampledWavelengths.SAMPLES];
|
||||
Arrays.fill(one, 1);
|
||||
WHITE = new SampledSpectrum(one);
|
||||
}
|
||||
|
||||
private final double @NotNull[] values;
|
||||
|
||||
public SampledSpectrum(@NotNull SampledWavelengths lambdas, @NotNull Spectrum spectrum) {
|
||||
@ -30,6 +20,12 @@ public final class SampledSpectrum implements IVec<SampledSpectrum> {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public SampledSpectrum(int count, double value) {
|
||||
var values = new double[count];
|
||||
Arrays.fill(values, value);
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
private SampledSpectrum(double @NotNull[] values) {
|
||||
this.values = values;
|
||||
}
|
||||
|
@ -10,32 +10,31 @@ import java.util.Arrays;
|
||||
* A set of sampled wavelength that can be tracked together.
|
||||
*/
|
||||
public final class SampledWavelengths {
|
||||
public static final int SAMPLES = 4;
|
||||
public static final SampledWavelengths EMPTY = new SampledWavelengths(new double[0], new double[0]);
|
||||
|
||||
private final double @NotNull[] lambdas;
|
||||
private final double @NotNull[] pdf;
|
||||
|
||||
public static @NotNull SampledWavelengths uniform(double rng) {
|
||||
return uniform(rng, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX);
|
||||
public static @NotNull SampledWavelengths uniform(double rng, int count) {
|
||||
return uniform(rng, count, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX);
|
||||
}
|
||||
|
||||
public static @NotNull SampledWavelengths uniform(double rng, double min, double max) {
|
||||
var lambdas = new double[SAMPLES];
|
||||
public static @NotNull SampledWavelengths uniform(double rng, int count, double min, double max) {
|
||||
var lambdas = new double[count];
|
||||
|
||||
// choose first sample at random
|
||||
lambdas[0] = (1 - rng) * min + rng * max;
|
||||
|
||||
// choose next samples in equal intervals, wrapping if necessary
|
||||
var delta = (max - min) / SAMPLES;
|
||||
for (int i = 1; i < SAMPLES; i++) {
|
||||
var delta = (max - min) / count;
|
||||
for (int i = 1; i < count; i++) {
|
||||
lambdas[i] = lambdas[i - 1] + delta;
|
||||
if (lambdas[i] > max) {
|
||||
lambdas[i] = min + (lambdas[i] - max);
|
||||
}
|
||||
}
|
||||
|
||||
var pdf = new double[SAMPLES];
|
||||
var pdf = new double[count];
|
||||
Arrays.fill(pdf, 1 / (max - min));
|
||||
return new SampledWavelengths(lambdas, pdf);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user