Compare commits
No commits in common. "871c837c341fa47d73897d99e04a1f21ebe8bf99" and "533461204ad321ababcb7e4b8efc47e6c043003c" have entirely different histories.
871c837c34
...
533461204a
@ -24,7 +24,6 @@ public class Main {
|
|||||||
|
|
||||||
var renderer = SimpleRenderer.builder()
|
var renderer = SimpleRenderer.builder()
|
||||||
.withSamplesPerPixel(config.samples)
|
.withSamplesPerPixel(config.samples)
|
||||||
.withSpectralSamples(config.spectralSamples)
|
|
||||||
.withMaxDepth(config.depth)
|
.withMaxDepth(config.depth)
|
||||||
.withIterative(config.iterative)
|
.withIterative(config.iterative)
|
||||||
.withParallel(config.parallel)
|
.withParallel(config.parallel)
|
||||||
@ -46,7 +45,7 @@ public class Main {
|
|||||||
PNGImageWriter.sRGB.write(canvas, config.path);
|
PNGImageWriter.sRGB.write(canvas, config.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int spectralSamples, int depth) {
|
private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int depth) {
|
||||||
public static @NotNull Config parse(@NotNull String @NotNull[] args) {
|
public static @NotNull Config parse(@NotNull String @NotNull[] args) {
|
||||||
IntFunction<Example> example = null;
|
IntFunction<Example> example = null;
|
||||||
Path path = null;
|
Path path = null;
|
||||||
@ -54,7 +53,6 @@ public class Main {
|
|||||||
boolean iterative = false;
|
boolean iterative = false;
|
||||||
boolean parallel = false;
|
boolean parallel = false;
|
||||||
int samples = 1000;
|
int samples = 1000;
|
||||||
int spectralSamples = 4;
|
|
||||||
int depth = 50;
|
int depth = 50;
|
||||||
int height = -1;
|
int height = -1;
|
||||||
|
|
||||||
@ -83,15 +81,6 @@ public class Main {
|
|||||||
throw fail("value " + args[i] + " is not a valid integer");
|
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" -> {
|
case "--depth" -> {
|
||||||
if (i + 1 == args.length) throw fail("missing value for parameter --depth");
|
if (i + 1 == args.length) throw fail("missing value for parameter --depth");
|
||||||
try {
|
try {
|
||||||
@ -123,7 +112,7 @@ public class Main {
|
|||||||
|
|
||||||
if (example == null) example = Examples::getCornellBoxSmoke;
|
if (example == null) example = Examples::getCornellBoxSmoke;
|
||||||
if (path == null) path = Path.of("scene-" + System.currentTimeMillis() + ".png");
|
if (path == null) path = Path.of("scene-" + System.currentTimeMillis() + ".png");
|
||||||
return new Config(example.apply(height), path, preview, iterative, parallel, samples, spectralSamples, depth);
|
return new Config(example.apply(height), path, preview, iterative, parallel, samples, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NotNull RuntimeException fail(@NotNull String message) {
|
private static @NotNull RuntimeException fail(@NotNull String message) {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.jonahbauer.raytracing.math;
|
package eu.jonahbauer.raytracing.math;
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -20,16 +19,4 @@ public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull Sample
|
|||||||
public @NotNull Vec3 at(double t) {
|
public @NotNull Vec3 at(double t) {
|
||||||
return Vec3.fma(t, direction, origin);
|
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,6 +2,7 @@ package eu.jonahbauer.raytracing.render.camera;
|
|||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.Ray;
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
|
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
@ -93,7 +94,7 @@ public final class SimpleCamera implements Camera {
|
|||||||
|
|
||||||
var origin = getRayOrigin(random);
|
var origin = getRayOrigin(random);
|
||||||
var target = getRayTarget(x, y, i, j, n, random);
|
var target = getRayTarget(x, y, i, j, n, random);
|
||||||
return new Ray(origin, target.minus(origin));
|
return new Ray(origin, target.minus(origin), SampledWavelengths.uniform(random.nextDouble()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,7 +45,7 @@ public record DielectricMaterial(@NotNull RefractiveIndex ri, @NotNull Texture t
|
|||||||
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
|
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
|
||||||
|
|
||||||
var attenuation = texture.get(hit);
|
var attenuation = texture.get(hit);
|
||||||
return Optional.of(new SpecularScatterResult(attenuation, ray.with(hit, newDirection)));
|
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private double reflectance(double cos, double ri) {
|
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);
|
if (back != null) return back.scatter(ray, hit, random);
|
||||||
}
|
}
|
||||||
// let the ray pass through without obstruction
|
// let the ray pass through without obstruction
|
||||||
return Optional.of(new SpecularScatterResult(Spectra.WHITE, ray.with(hit, ray.direction())));
|
return Optional.of(new SpecularScatterResult(Spectra.WHITE, new Ray(ray.at(hit.t()), ray.direction(), ray.lambda())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -28,6 +28,6 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
|
|||||||
newDirection = Vec3.fma(fuzz, Vec3.random(random), newDirection.unit());
|
newDirection = Vec3.fma(fuzz, Vec3.random(random), newDirection.unit());
|
||||||
}
|
}
|
||||||
var attenuation = texture.get(hit);
|
var attenuation = texture.get(hit);
|
||||||
return Optional.of(new SpecularScatterResult(attenuation, ray.with(hit, newDirection)));
|
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFun
|
|||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
||||||
import eu.jonahbauer.raytracing.render.camera.Camera;
|
import eu.jonahbauer.raytracing.render.camera.Camera;
|
||||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
|
||||||
import eu.jonahbauer.raytracing.scene.Scene;
|
import eu.jonahbauer.raytracing.scene.Scene;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -21,10 +20,7 @@ import static eu.jonahbauer.raytracing.Main.DEBUG;
|
|||||||
public final class SimpleRenderer implements Renderer {
|
public final class SimpleRenderer implements Renderer {
|
||||||
private final int sqrtSamplesPerPixel;
|
private final int sqrtSamplesPerPixel;
|
||||||
private final int maxDepth;
|
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 parallel;
|
||||||
private final boolean iterative;
|
private final boolean iterative;
|
||||||
@ -40,10 +36,7 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
private SimpleRenderer(@NotNull Builder builder) {
|
private SimpleRenderer(@NotNull Builder builder) {
|
||||||
this.sqrtSamplesPerPixel = (int) Math.sqrt(builder.samplesPerPixel);
|
this.sqrtSamplesPerPixel = (int) Math.sqrt(builder.samplesPerPixel);
|
||||||
this.maxDepth = builder.maxDepth;
|
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.parallel = builder.parallel;
|
||||||
this.iterative = builder.iterative;
|
this.iterative = builder.iterative;
|
||||||
@ -103,8 +96,7 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
|
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
|
||||||
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
|
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
|
||||||
var lambda = SampledWavelengths.uniform(random.nextDouble(), spectralSamples);
|
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random);
|
||||||
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random).with(lambda);
|
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
System.out.println("Casting ray " + ray + " through pixel (" + x + "," + y + ") at subpixel (" + si + "," + sj + ")...");
|
System.out.println("Casting ray " + ray + " through pixel (" + x + "," + y + ") at subpixel (" + si + "," + sj + ")...");
|
||||||
}
|
}
|
||||||
@ -124,11 +116,11 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private @NotNull SampledSpectrum getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
|
private @NotNull SampledSpectrum getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
|
||||||
var color = black;
|
var color = SampledSpectrum.BLACK;
|
||||||
var attenuation = white;
|
var attenuation = SampledSpectrum.WHITE;
|
||||||
|
|
||||||
while (depth-- > 0) {
|
while (depth-- > 0) {
|
||||||
var optional = scene.hit(ray, random);
|
var optional = scene.hit(ray);
|
||||||
if (optional.isEmpty()) {
|
if (optional.isEmpty()) {
|
||||||
var background = scene.getBackgroundColor(ray);
|
var background = scene.getBackgroundColor(ray);
|
||||||
color = SampledSpectrum.fma(attenuation, background, color);
|
color = SampledSpectrum.fma(attenuation, background, color);
|
||||||
@ -144,7 +136,7 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
var material = hit.material();
|
var material = hit.material();
|
||||||
var emitted = material.emitted(hit).sample(ray.lambda());
|
var emitted = material.emitted(hit).sample(ray.lambda());
|
||||||
if (DEBUG && !black.equals(emitted)) {
|
if (DEBUG && !SampledSpectrum.BLACK.equals(emitted)) {
|
||||||
System.out.println(" Emitted: " + emitted);
|
System.out.println(" Emitted: " + emitted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +213,7 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
public static class Builder {
|
public static class Builder {
|
||||||
private int samplesPerPixel = 100;
|
private int samplesPerPixel = 100;
|
||||||
private int maxDepth = 10;
|
private int maxDepth = 10;
|
||||||
private int spectralSamples = 4;
|
private double gamma = 2.0;
|
||||||
private boolean parallel = true;
|
private boolean parallel = true;
|
||||||
private boolean iterative = false;
|
private boolean iterative = false;
|
||||||
|
|
||||||
@ -237,9 +229,9 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull Builder withSpectralSamples(int samples) {
|
public @NotNull Builder withGamma(double gamma) {
|
||||||
if (samples <= 0) throw new IllegalArgumentException("samples must be positive");
|
if (gamma <= 0 || !Double.isFinite(gamma)) throw new IllegalArgumentException("gamma must be positive");
|
||||||
this.spectralSamples = samples;
|
this.gamma = gamma;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,16 @@ import java.util.Arrays;
|
|||||||
|
|
||||||
// TODO use Vector API to parallelize operations
|
// TODO use Vector API to parallelize operations
|
||||||
public final class SampledSpectrum implements IVec<SampledSpectrum> {
|
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;
|
private final double @NotNull[] values;
|
||||||
|
|
||||||
public SampledSpectrum(@NotNull SampledWavelengths lambdas, @NotNull Spectrum spectrum) {
|
public SampledSpectrum(@NotNull SampledWavelengths lambdas, @NotNull Spectrum spectrum) {
|
||||||
@ -20,12 +30,6 @@ public final class SampledSpectrum implements IVec<SampledSpectrum> {
|
|||||||
this.values = values;
|
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) {
|
private SampledSpectrum(double @NotNull[] values) {
|
||||||
this.values = values;
|
this.values = values;
|
||||||
}
|
}
|
||||||
|
@ -10,31 +10,32 @@ import java.util.Arrays;
|
|||||||
* A set of sampled wavelength that can be tracked together.
|
* A set of sampled wavelength that can be tracked together.
|
||||||
*/
|
*/
|
||||||
public final class SampledWavelengths {
|
public final class SampledWavelengths {
|
||||||
|
public static final int SAMPLES = 4;
|
||||||
public static final SampledWavelengths EMPTY = new SampledWavelengths(new double[0], new double[0]);
|
public static final SampledWavelengths EMPTY = new SampledWavelengths(new double[0], new double[0]);
|
||||||
|
|
||||||
private final double @NotNull[] lambdas;
|
private final double @NotNull[] lambdas;
|
||||||
private final double @NotNull[] pdf;
|
private final double @NotNull[] pdf;
|
||||||
|
|
||||||
public static @NotNull SampledWavelengths uniform(double rng, int count) {
|
public static @NotNull SampledWavelengths uniform(double rng) {
|
||||||
return uniform(rng, count, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX);
|
return uniform(rng, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull SampledWavelengths uniform(double rng, int count, double min, double max) {
|
public static @NotNull SampledWavelengths uniform(double rng, double min, double max) {
|
||||||
var lambdas = new double[count];
|
var lambdas = new double[SAMPLES];
|
||||||
|
|
||||||
// choose first sample at random
|
// choose first sample at random
|
||||||
lambdas[0] = (1 - rng) * min + rng * max;
|
lambdas[0] = (1 - rng) * min + rng * max;
|
||||||
|
|
||||||
// choose next samples in equal intervals, wrapping if necessary
|
// choose next samples in equal intervals, wrapping if necessary
|
||||||
var delta = (max - min) / count;
|
var delta = (max - min) / SAMPLES;
|
||||||
for (int i = 1; i < count; i++) {
|
for (int i = 1; i < SAMPLES; i++) {
|
||||||
lambdas[i] = lambdas[i - 1] + delta;
|
lambdas[i] = lambdas[i - 1] + delta;
|
||||||
if (lambdas[i] > max) {
|
if (lambdas[i] > max) {
|
||||||
lambdas[i] = min + (lambdas[i] - max);
|
lambdas[i] = min + (lambdas[i] - max);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pdf = new double[count];
|
var pdf = new double[SAMPLES];
|
||||||
Arrays.fill(pdf, 1 / (max - min));
|
Arrays.fill(pdf, 1 / (max - min));
|
||||||
return new SampledWavelengths(lambdas, pdf);
|
return new SampledWavelengths(lambdas, pdf);
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,15 @@ import eu.jonahbauer.raytracing.scene.transform.Translate;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.random.RandomGenerator;
|
|
||||||
|
|
||||||
public interface Hittable {
|
public interface Hittable {
|
||||||
@NotNull Range FORWARD = new Range(0.001, Double.POSITIVE_INFINITY);
|
@NotNull Range FORWARD = new Range(0.001, Double.POSITIVE_INFINITY);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see #hit(Ray, Range, RandomGenerator)
|
* @see #hit(Ray, Range)
|
||||||
*/
|
*/
|
||||||
default @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull RandomGenerator random) {
|
default @NotNull Optional<HitResult> hit(@NotNull Ray ray) {
|
||||||
return hit(ray, FORWARD, random);
|
return hit(ray, FORWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,7 +31,7 @@ public interface Hittable {
|
|||||||
* @return the result of the hit test, containing (among others) the value {@code t} such that {@code ray.at(t)} is
|
* @return the result of the hit test, containing (among others) the value {@code t} such that {@code ray.at(t)} is
|
||||||
* a point on {@code this} hittable
|
* a point on {@code this} hittable
|
||||||
*/
|
*/
|
||||||
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random);
|
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@return the axis-aligned bounding box of this hittable}
|
* {@return the axis-aligned bounding box of this hittable}
|
||||||
|
@ -12,7 +12,6 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.random.RandomGenerator;
|
|
||||||
|
|
||||||
public final class Scene extends HittableCollection {
|
public final class Scene extends HittableCollection {
|
||||||
private final @NotNull HittableCollection objects;
|
private final @NotNull HittableCollection objects;
|
||||||
@ -43,8 +42,8 @@ public final class Scene extends HittableCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
|
public void hit(@NotNull Ray ray, @NotNull State state) {
|
||||||
objects.hit(ray, state, random);
|
objects.hit(ray, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -7,11 +7,9 @@ import eu.jonahbauer.raytracing.render.material.Material;
|
|||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.random.RandomGenerator;
|
|
||||||
|
|
||||||
public abstract class Hittable2D implements Hittable {
|
public abstract class Hittable2D implements Hittable {
|
||||||
protected final @NotNull Vec3 origin;
|
protected final @NotNull Vec3 origin;
|
||||||
@ -38,7 +36,7 @@ public abstract class Hittable2D implements Hittable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
|
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||||
var denominator = ray.direction().dot(normal);
|
var denominator = ray.direction().dot(normal);
|
||||||
if (Math.abs(denominator) < 1e-8) return Optional.empty(); // parallel
|
if (Math.abs(denominator) < 1e-8) return Optional.empty(); // parallel
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ public final class Box implements Hittable, Target {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
|
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||||
// based on AABB#hit with additional detection of the side hit
|
// based on AABB#hit with additional detection of the side hit
|
||||||
var origin = ray.origin();
|
var origin = ray.origin();
|
||||||
var direction = ray.direction();
|
var direction = ray.direction();
|
||||||
@ -103,13 +103,13 @@ public final class Box implements Hittable, Target {
|
|||||||
t = tmin;
|
t = tmin;
|
||||||
side = entry;
|
side = entry;
|
||||||
frontFace = true;
|
frontFace = true;
|
||||||
material = materials[entry.ordinal()];
|
material = materials[side.ordinal()];
|
||||||
normal = side.normal;
|
normal = side.normal;
|
||||||
} else if (range.surrounds(tmax) && materials[exit.ordinal()] != null) {
|
} else if (range.surrounds(tmax) && materials[exit.ordinal()] != null) {
|
||||||
t = tmax;
|
t = tmax;
|
||||||
side = exit;
|
side = exit;
|
||||||
frontFace = false;
|
frontFace = false;
|
||||||
material = materials[exit.ordinal()];
|
material = materials[side.ordinal()];
|
||||||
normal = side.normal.neg();
|
normal = side.normal.neg();
|
||||||
} else {
|
} else {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@ -130,7 +130,7 @@ public final class Box implements Hittable, Target {
|
|||||||
@Override
|
@Override
|
||||||
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||||
if (contains(origin)) return 1 / (4 * Math.PI);
|
if (contains(origin)) return 1 / (4 * Math.PI);
|
||||||
if (hit(new Ray(origin, direction), null).isEmpty()) return 0;
|
if (hit(new Ray(origin, direction)).isEmpty()) return 0;
|
||||||
|
|
||||||
var solidAngle = 0d;
|
var solidAngle = 0d;
|
||||||
for (var s : Side.values()) {
|
for (var s : Side.values()) {
|
||||||
|
@ -10,16 +10,15 @@ import eu.jonahbauer.raytracing.scene.Hittable;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.random.RandomGenerator;
|
|
||||||
|
|
||||||
public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNull IsotropicMaterial material) implements Hittable {
|
public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNull IsotropicMaterial material) implements Hittable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
|
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||||
var hit1 = boundary.hit(ray, Range.UNIVERSE, random);
|
var hit1 = boundary.hit(ray, Range.UNIVERSE);
|
||||||
if (hit1.isEmpty()) return Optional.empty();
|
if (hit1.isEmpty()) return Optional.empty();
|
||||||
|
|
||||||
var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY), random);
|
var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY));
|
||||||
if (hit2.isEmpty()) return Optional.empty();
|
if (hit2.isEmpty()) return Optional.empty();
|
||||||
|
|
||||||
var tmin = Math.max(range.min(), hit1.get().t());
|
var tmin = Math.max(range.min(), hit1.get().t());
|
||||||
@ -29,7 +28,7 @@ public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNul
|
|||||||
|
|
||||||
var length = ray.direction().length();
|
var length = ray.direction().length();
|
||||||
var distance = length * (tmax - tmin);
|
var distance = length * (tmax - tmin);
|
||||||
var hitDistance = - Math.log(random.nextDouble()) / density;
|
var hitDistance = - Math.log(Math.random()) / density;
|
||||||
if (hitDistance > distance) return Optional.empty();
|
if (hitDistance > distance) return Optional.empty();
|
||||||
|
|
||||||
var t = tmin + hitDistance / length;
|
var t = tmin + hitDistance / length;
|
||||||
|
@ -9,7 +9,6 @@ import eu.jonahbauer.raytracing.scene.HitResult;
|
|||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
import eu.jonahbauer.raytracing.scene.Target;
|
import eu.jonahbauer.raytracing.scene.Target;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -41,7 +40,7 @@ public final class Sphere implements Hittable, Target {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
|
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||||
var t = hit0(ray, range);
|
var t = hit0(ray, range);
|
||||||
if (Double.isNaN(t)) return Optional.empty();
|
if (Double.isNaN(t)) return Optional.empty();
|
||||||
|
|
||||||
|
@ -2,8 +2,10 @@ package eu.jonahbauer.raytracing.scene.transform;
|
|||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.Range;
|
import eu.jonahbauer.raytracing.math.Range;
|
||||||
import eu.jonahbauer.raytracing.math.Ray;
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
|
import eu.jonahbauer.raytracing.scene.Target;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -22,7 +24,7 @@ public abstract class Transform implements Hittable {
|
|||||||
protected abstract @NotNull HitResult transform(@NotNull HitResult result);
|
protected abstract @NotNull HitResult transform(@NotNull HitResult result);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
|
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||||
return object.hit(transform(ray), range, random).map(this::transform);
|
return object.hit(transform(ray), range).map(this::transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.random.RandomGenerator;
|
|
||||||
|
|
||||||
public final class HittableBinaryTree extends HittableCollection {
|
public final class HittableBinaryTree extends HittableCollection {
|
||||||
private final @Nullable Hittable left;
|
private final @Nullable Hittable left;
|
||||||
@ -47,17 +46,17 @@ public final class HittableBinaryTree extends HittableCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
|
public void hit(@NotNull Ray ray, @NotNull State state) {
|
||||||
if (!bbox.hit(ray, state.getRange())) return;
|
if (!bbox.hit(ray, state.getRange())) return;
|
||||||
if (left instanceof HittableCollection coll) {
|
if (left instanceof HittableCollection coll) {
|
||||||
coll.hit(ray, state, random);
|
coll.hit(ray, state);
|
||||||
} else if (left != null) {
|
} else if (left != null) {
|
||||||
hit(state, ray, left, random);
|
hit(state, ray, left);
|
||||||
}
|
}
|
||||||
if (right instanceof HittableCollection coll) {
|
if (right instanceof HittableCollection coll) {
|
||||||
coll.hit(ray, state, random);
|
coll.hit(ray, state);
|
||||||
} else if (right != null) {
|
} else if (right != null) {
|
||||||
hit(state, ray, right, random);
|
hit(state, ray, right);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,21 +8,20 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.random.RandomGenerator;
|
|
||||||
|
|
||||||
public abstract class HittableCollection implements Hittable {
|
public abstract class HittableCollection implements Hittable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
|
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||||
var state = new State(range);
|
var state = new State(range);
|
||||||
hit(ray, state, random);
|
hit(ray, state);
|
||||||
return state.getResult();
|
return state.getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random);
|
public abstract void hit(@NotNull Ray ray, @NotNull State state);
|
||||||
|
|
||||||
protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object, @NotNull RandomGenerator random) {
|
protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object) {
|
||||||
var r = object.hit(ray, state.range, random);
|
var r = object.hit(ray, state.range);
|
||||||
if (r.isPresent()) {
|
if (r.isPresent()) {
|
||||||
if (state.range.surrounds(r.get().t())){
|
if (state.range.surrounds(r.get().t())){
|
||||||
state.result = r.get();
|
state.result = r.get();
|
||||||
|
@ -7,7 +7,6 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.random.RandomGenerator;
|
|
||||||
|
|
||||||
public final class HittableList extends HittableCollection {
|
public final class HittableList extends HittableCollection {
|
||||||
private final @NotNull List<Hittable> objects;
|
private final @NotNull List<Hittable> objects;
|
||||||
@ -23,8 +22,8 @@ public final class HittableList extends HittableCollection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
|
public void hit(@NotNull Ray ray, @NotNull State state) {
|
||||||
objects.forEach(object -> hit(state, ray, object, random));
|
objects.forEach(object -> hit(state, ray, object));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
x
Reference in New Issue
Block a user