Compare commits

...

9 Commits

67 changed files with 760 additions and 524 deletions

View File

@@ -1,11 +1,11 @@
package eu.jonahbauer.raytracing;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
import eu.jonahbauer.raytracing.render.spectrum.RGBAlbedoSpectrum;
import eu.jonahbauer.raytracing.render.spectrum.RGBIlluminantSpectrum;
import eu.jonahbauer.raytracing.render.texture.CheckerTexture;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.camera.SimpleCamera;
import eu.jonahbauer.raytracing.render.material.*;
import eu.jonahbauer.raytracing.render.texture.ImageTexture;
@@ -55,11 +55,11 @@ public class Examples {
var cs = ColorSpaces.sRGB;
return new Example(
new Scene(getSkyBox(), List.of(
new Sphere(new Vec3(0, -100.5, -1.0), 100.0, new LambertianMaterial(new ColorRGB(0.8, 0.8, 0.0), cs)),
new Sphere(new Vec3(0, 0, -1.2), 0.5, new LambertianMaterial(new ColorRGB(0.1, 0.2, 0.5), cs)),
new Sphere(new Vec3(0, -100.5, -1.0), 100.0, new LambertianMaterial(cs.albedo(0.8, 0.8, 0.0))),
new Sphere(new Vec3(0, 0, -1.2), 0.5, new LambertianMaterial(cs.albedo(0.1, 0.2, 0.5))),
new Sphere(new Vec3(-1.0, 0, -1.2), 0.5, new DielectricMaterial(1.5)),
new Sphere(new Vec3(-1.0, 0, -1.2), 0.4, new DielectricMaterial(1 / 1.5)),
new Sphere(new Vec3(1.0, 0, -1.2), 0.5, new MetallicMaterial(new ColorRGB(0.8, 0.6, 0.2), cs, 0.0))
new Sphere(new Vec3(1.0, 0, -1.2), 0.5, new MetallicMaterial(cs.albedo(0.8, 0.6, 0.2)))
)),
SimpleCamera.builder()
.withImage(height * 16 / 9, height)
@@ -92,12 +92,12 @@ public class Examples {
if (rnd < 0.8) {
// diffuse
var albedo = ColorRGB.random(rng).times(ColorRGB.random(rng));
material = new LambertianMaterial(albedo, cs);
material = new LambertianMaterial(cs.albedo(albedo));
} else if (rnd < 0.95) {
// metal
var albedo = ColorRGB.random(rng, 0.5, 1.0);
var fuzz = rng.nextDouble() * 0.5;
material = new MetallicMaterial(albedo, cs, fuzz);
material = new MetallicMaterial(cs.albedo(albedo), fuzz);
} else {
// glass
material = new DielectricMaterial(1.5);
@@ -108,8 +108,8 @@ public class Examples {
}
objects.add(new Sphere(new Vec3(0, 1, 0), 1.0, new DielectricMaterial(1.5)));
objects.add(new Sphere(new Vec3(-4, 1, 0), 1.0, new LambertianMaterial(new ColorRGB(0.4, 0.2, 0.1), cs)));
objects.add(new Sphere(new Vec3(4, 1, 0), 1.0, new MetallicMaterial(new ColorRGB(0.7, 0.6, 0.5), cs)));
objects.add(new Sphere(new Vec3(-4, 1, 0), 1.0, new LambertianMaterial(cs.albedo(0.4, 0.2, 0.1))));
objects.add(new Sphere(new Vec3(4, 1, 0), 1.0, new MetallicMaterial(cs.albedo(0.7, 0.6, 0.5))));
var camera = SimpleCamera.builder()
.withImage(height * 16 / 9, height)
@@ -128,11 +128,11 @@ public class Examples {
var cs = ColorSpaces.sRGB;
return new Example(
new Scene(getSkyBox(), List.of(
new Parallelogram(new Vec3(-3, -2, 5), new Vec3(0, 0, -4), new Vec3(0, 4, 0), new LambertianMaterial(new ColorRGB(1.0, 0.2, 0.2), cs)),
new Parallelogram(new Vec3(-2, -2, 0), new Vec3(4, 0, 0), new Vec3(0, 4, 0), new LambertianMaterial(new ColorRGB(0.2, 1.0, 0.2), cs)),
new Parallelogram(new Vec3(3, -2, 1), new Vec3(0, 0, 4), new Vec3(0, 4, 0), new LambertianMaterial(new ColorRGB(0.2, 0.2, 1.0), cs)),
new Parallelogram(new Vec3(-2, 3, 1), new Vec3(4, 0, 0), new Vec3(0, 0, 4), new LambertianMaterial(new ColorRGB(1.0, 0.5, 0.0), cs)),
new Parallelogram(new Vec3(-2, -3, 5), new Vec3(4, 0, 0), new Vec3(0, 0, -4), new LambertianMaterial(new ColorRGB(0.2, 0.8, 0.8), cs))
new Parallelogram(new Vec3(-3, -2, 5), new Vec3(0, 0, -4), new Vec3(0, 4, 0), new LambertianMaterial(cs.albedo(1.0, 0.2, 0.2))),
new Parallelogram(new Vec3(-2, -2, 0), new Vec3(4, 0, 0), new Vec3(0, 4, 0), new LambertianMaterial(cs.albedo(0.2, 1.0, 0.2))),
new Parallelogram(new Vec3(3, -2, 1), new Vec3(0, 0, 4), new Vec3(0, 4, 0), new LambertianMaterial(cs.albedo(0.2, 0.2, 1.0))),
new Parallelogram(new Vec3(-2, 3, 1), new Vec3(4, 0, 0), new Vec3(0, 0, 4), new LambertianMaterial(cs.albedo(1.0, 0.5, 0.0))),
new Parallelogram(new Vec3(-2, -3, 5), new Vec3(4, 0, 0), new Vec3(0, 0, -4), new LambertianMaterial(cs.albedo(0.2, 0.8, 0.8)))
)),
SimpleCamera.builder()
.withImage(height, height)
@@ -148,10 +148,10 @@ public class Examples {
var cs = ColorSpaces.sRGB;
return new Example(
new Scene(List.of(
new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(new ColorRGB(0.2, 0.2, 0.2), cs)),
new Sphere(new Vec3(0, 2, 0), 2, new LambertianMaterial(new ColorRGB(0.2, 0.2, 0.2), cs)),
new Parallelogram(new Vec3(3, 1, -2), new Vec3(2, 0, 0), new Vec3(0, 2, 0), new DiffuseLight(new ColorRGB(4.0, 4.0, 4.0), cs)),
new Sphere(new Vec3(0, 7, 0), 2, new DiffuseLight(new ColorRGB(4.0, 4.0, 4.0), cs))
new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(cs.albedo(0.2, 0.2, 0.2))),
new Sphere(new Vec3(0, 2, 0), 2, new LambertianMaterial(cs.albedo(0.2, 0.2, 0.2))),
new Parallelogram(new Vec3(3, 1, -2), new Vec3(2, 0, 0), new Vec3(0, 2, 0), new DiffuseLight(cs.illuminant(4.0))),
new Sphere(new Vec3(0, 7, 0), 2, new DiffuseLight(cs.illuminant(4.0)))
)),
SimpleCamera.builder()
.withImage(height * 16 / 9, height)
@@ -166,10 +166,10 @@ public class Examples {
if (height <= 0) height = 600;
var cs = ColorSpaces.sRGB;
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs);
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs);
var light = new DiffuseLight(new ColorRGB(15.0, 15.0, 15.0), cs);
var red = new LambertianMaterial(cs.albedo(.65, .05, .05));
var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
var green = new LambertianMaterial(cs.albedo(.12, .45, .15));
var light = new DiffuseLight(cs.illuminant(15.0));
return new Example(
new Scene(List.of(
@@ -194,10 +194,10 @@ public class Examples {
public static @NotNull Example getCornellBoxSmoke(int height) {
if (height <= 0) height = 600;
var cs = ColorSpaces.sRGB;
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs);
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs);
var light = new DiffuseLight(new ColorRGB(15.0, 15.0, 15.0), cs);
var red = new LambertianMaterial(cs.albedo(.65, .05, .05));
var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
var green = new LambertianMaterial(cs.albedo(.12, .45, .15));
var light = new DiffuseLight(cs.illuminant(15.0));
return new Example(
new Scene(List.of(
@@ -207,13 +207,13 @@ public class Examples {
new Box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white)
.rotateY(Math.toRadians(15))
.translate(new Vec3(265, 0, 295)),
0.01, new IsotropicMaterial(ColorRGB.BLACK, cs)
0.01, new IsotropicMaterial(cs.albedo(ColorRGB.BLACK))
),
new ConstantMedium(
new Box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white)
.rotateY(Math.toRadians(-18))
.translate(new Vec3(130, 0, 65)),
0.01, new IsotropicMaterial(ColorRGB.WHITE, cs)
0.01, new IsotropicMaterial(cs.albedo(ColorRGB.WHITE))
)
)),
SimpleCamera.builder()
@@ -229,10 +229,10 @@ public class Examples {
if (height <= 0) height = 600;
var cs = ColorSpaces.sRGB;
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs);
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs);
var light = new DiffuseLight(new ColorRGB(7.0, 7.0, 7.0), cs);
var red = new LambertianMaterial(cs.albedo(.65, .05, .05));
var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
var green = new LambertianMaterial(cs.albedo(.12, .45, .15));
var light = new DiffuseLight(cs.illuminant(7.0));
var glass = new DielectricMaterial(1.5);
var room = new Box(new Vec3(0, 0, 0), new Vec3(555, 555, 555), white, white, red, green, white, null);
@@ -259,15 +259,15 @@ public class Examples {
var cs = ColorSpaces.sRGB;
record Partei(String name, ColorRGB color, double stimmen) { }
var data = List.of(
new Partei("CDU", new ColorRGB(0x00, 0x4B, 0x76), 18.9),
new Partei("SPD", new ColorRGB(0xC0, 0x00, 0x3C), 25.7),
new Partei("AfD", new ColorRGB(0x80, 0xCD, 0xEC), 10.3),
new Partei("FDP", new ColorRGB(0xF7, 0xBB, 0x3D), 11.5),
new Partei("DIE LINKE", new ColorRGB(0x5F, 0x31, 0x6E), 4.9),
new Partei("GRÜNE", new ColorRGB(0x00, 0x85, 0x4A), 14.8),
new Partei("CSU", new ColorRGB(0x00, 0x77, 0xB6), 5.2)
new Partei("CDU", new ColorRGB(0x004B76), 18.9),
new Partei("SPD", new ColorRGB(0xC0003C), 25.7),
new Partei("AfD", new ColorRGB(0x80CDEC), 10.3),
new Partei("FDP", new ColorRGB(0xF7BB3D), 11.5),
new Partei("DIE LINKE", new ColorRGB(0x5F316E), 4.9),
new Partei("GRÜNE", new ColorRGB(0x00854A), 14.8),
new Partei("CSU", new ColorRGB(0x0077B6), 5.2)
);
var white = new LambertianMaterial(new ColorRGB(.99, .99, .99), cs);
var white = new LambertianMaterial(cs.albedo(.99, .99, .99));
var count = data.size();
var size = 75d;
@@ -287,7 +287,7 @@ public class Examples {
objects.add(new Box(
new Vec3((i + 1) * spacing + i * size, 0, spacing),
new Vec3((i + 1) * spacing + (i + 1) * size, partei.stimmen() * 15, spacing + size),
new DielectricMaterial(1.5, partei.color(), cs)
new DielectricMaterial(1.5, cs.albedo(partei.color()))
));
}
@@ -346,7 +346,7 @@ public class Examples {
// boxes
var boxes = new ArrayList<Hittable>();
var ground = new LambertianMaterial(new ColorRGB(0.48, 0.83, 0.53), cs);
var ground = new LambertianMaterial(cs.albedo(0.48, 0.83, 0.53));
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
var w = 100.0;
@@ -364,23 +364,23 @@ public class Examples {
// light
objects.add(new Parallelogram(
new Vec3(123, 554, 147), new Vec3(300, 0, 0), new Vec3(0, 0, 265),
new DiffuseLight(new ColorRGB(7., 7., 7.), cs)
new DiffuseLight(cs.illuminant(7.0))
));
// spheres with different materials
objects.add(new Sphere(new Vec3(400, 400, 200), 50, new LambertianMaterial(new ColorRGB(0.7, 0.3, 0.1), cs)));
objects.add(new Sphere(new Vec3(400, 400, 200), 50, new LambertianMaterial(cs.albedo(0.7, 0.3, 0.1))));
objects.add(new Sphere(new Vec3(260, 150, 45), 50, new DielectricMaterial(1.5)));
objects.add(new Sphere(new Vec3(0, 150, 145), 50, new MetallicMaterial(new ColorRGB(0.8, 0.8, 0.9), cs, 1.0)));
objects.add(new Sphere(new Vec3(0, 150, 145), 50, new MetallicMaterial(cs.albedo(0.8, 0.8, 0.9), 1.0)));
// glass sphere filled with gas
var boundary = new Sphere(new Vec3(360, 150, 145), 70, new DielectricMaterial(1.5));
objects.add(boundary);
objects.add(new ConstantMedium(boundary, 0.2, new IsotropicMaterial(new ColorRGB(0.2, 0.4, 0.9), cs)));
objects.add(new ConstantMedium(boundary, 0.2, new IsotropicMaterial(cs.albedo(0.2, 0.4, 0.9))));
// put the world in a glass sphere
objects.add(new ConstantMedium(
new Sphere(new Vec3(0, 0, 0), 5000, new DielectricMaterial(1.5)),
0.0001, new IsotropicMaterial(new ColorRGB(1., 1., 1.), cs)
0.0001, new IsotropicMaterial(cs.albedo(1.0, 1.0, 1.0))
));
// textures spheres
@@ -388,7 +388,7 @@ public class Examples {
objects.add(new Sphere(new Vec3(220, 280, 300), 80, new LambertianMaterial(new PerlinTexture(0.2))));
// box from spheres
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
var spheres = new ArrayList<Hittable>();
for (int j = 0; j < 1000; j++) {
spheres.add(new Sphere(new Vec3(random.nextDouble(165), random.nextDouble(165), random.nextDouble(165)), 10, white));

View File

@@ -1,13 +1,11 @@
package eu.jonahbauer.raytracing;
import eu.jonahbauer.raytracing.render.ImageFormat;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.canvas.LiveCanvas;
import eu.jonahbauer.raytracing.render.canvas.XYZCanvas;
import eu.jonahbauer.raytracing.render.image.PNGImageWriter;
import eu.jonahbauer.raytracing.render.renderer.SimpleRenderer;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@@ -26,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)
@@ -44,10 +43,10 @@ public class Main {
renderer.render(camera, scene, canvas);
System.out.printf("rendering finished after %dms", (System.nanoTime() - time) / 1_000_000);
ImageFormat.PNG.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 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;
@@ -55,6 +54,7 @@ public class Main {
boolean iterative = false;
boolean parallel = false;
int samples = 1000;
int spectralSamples = 4;
int depth = 50;
int height = -1;
@@ -83,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 {
@@ -114,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) {

View File

@@ -79,35 +79,39 @@ public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) {
* @return {@code true} iff the ray intersects this bounding box, {@code false} otherwise
*/
public boolean hit(@NotNull Ray ray, @NotNull Range range) {
var origin = ray.origin();
var direction = ray.direction();
var invDirection = direction.inv();
var invDirection = ray.getInvDirection();
var negInvOrigin = ray.getNegInvOrigin();
// calculate t values for intersection points of ray with planes through min
var tmin = intersect(min(), origin, invDirection);
// calculate t values for intersection points of ray with planes through max
var tmax = intersect(max(), origin, invDirection);
var tminX = Math.fma(min.x(), invDirection.x(), negInvOrigin.x());
var tminY = Math.fma(min.y(), invDirection.y(), negInvOrigin.y());
var tminZ = Math.fma(min.z(), invDirection.z(), negInvOrigin.z());
// determine range of t for which the ray is inside this voxel
double tlmax = Double.NEGATIVE_INFINITY; // lower limit maximum
double tumin = Double.POSITIVE_INFINITY; // upper limit minimum
var tmaxX = Math.fma(max.x(), invDirection.x(), negInvOrigin.x());
var tmaxY = Math.fma(max.y(), invDirection.y(), negInvOrigin.y());
var tmaxZ = Math.fma(max.z(), invDirection.z(), negInvOrigin.z());
for (int i = 0; i < 3; i++) {
// classify t values as lower or upper limit based on ray direction
if (direction.get(i) >= 0) {
// min is lower limit and max is upper limit
if (tmin[i] > tlmax) tlmax = tmin[i];
if (tmax[i] < tumin) tumin = tmax[i];
} else {
// max is lower limit and min is upper limit
if (tmax[i] > tlmax) tlmax = tmax[i];
if (tmin[i] < tumin) tumin = tmin[i];
}
}
var tlmax = max(
Math.min(tminX, tmaxX),
Math.min(tminY, tmaxY),
Math.min(tminZ, tmaxZ)
);
var tumin = min(
Math.max(tminX, tmaxX),
Math.max(tminY, tmaxY),
Math.max(tminZ, tmaxZ)
);
return tlmax < tumin && tumin >= range.min() && tlmax <= range.max();
}
private static double max(double a, double b, double c) {
return Math.max(a, Math.max(b, c));
}
private static double min(double a, double b, double c) {
return Math.min(a, Math.min(b, c));
}
/**
* Computes the {@code t} values of the intersections of a ray with the axis-aligned planes through a point.
* @param corner the point

View File

@@ -1,5 +1,6 @@
package eu.jonahbauer.raytracing.math;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
/**
@@ -7,15 +8,26 @@ import org.jetbrains.annotations.NotNull;
* @param <T> the type
*/
public interface IVec<T extends IVec<T>> {
@Contract(pure = true)
double get(int i);
@Contract(pure = true)
@NotNull T plus(@NotNull T other);
@Contract(pure = true)
@NotNull T minus(@NotNull T other);
@Contract(pure = true)
@NotNull T times(@NotNull T other);
@Contract(pure = true)
@NotNull T times(double d);
@Contract(pure = true)
default @NotNull T div(double d) {
return times(1 / d);
}
@Contract(pure = true)
double @NotNull[] toArray();
}

View File

@@ -1,5 +1,6 @@
package eu.jonahbauer.raytracing.math;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
/**
@@ -7,17 +8,23 @@ import org.jetbrains.annotations.NotNull;
* @param <T> the type
*/
public interface IVec3<T extends Record & IVec3<T>> extends IVec<T> {
@Contract(pure = true)
default double component1() {
return toVec3().x();
}
@Contract(pure = true)
default double component2() {
return toVec3().y();
}
@Contract(pure = true)
default double component3() {
return toVec3().z();
}
@Override
@Contract(pure = true)
default double get(int i) {
return switch (i) {
case 0 -> component1();
@@ -27,9 +34,13 @@ public interface IVec3<T extends Record & IVec3<T>> extends IVec<T> {
};
}
@NotNull Vec3 toVec3();
@Contract(pure = true)
default @NotNull Vec3 toVec3() {
return new Vec3(component1(), component2(), component3());
}
@Override
@Contract(pure = true)
default double @NotNull [] toArray() {
return new double[] {component1(), component2(), component3()};
}

View File

@@ -1,22 +1,97 @@
package eu.jonahbauer.raytracing.math;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda) {
public Ray {
Objects.requireNonNull(origin, "origin");
Objects.requireNonNull(direction, "direction");
Objects.requireNonNull(lambda, "lambda");
}
public final class Ray {
private final @NotNull Vec3 origin;
private final @NotNull Vec3 direction;
private final @NotNull SampledWavelengths lambda;
private final @NotNull Vec3 inv;
private final @NotNull Vec3 negInvOrigin;
public Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) {
this(origin, direction, SampledWavelengths.EMPTY);
}
public Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda) {
this.origin = Objects.requireNonNull(origin, "origin");
this.direction = Objects.requireNonNull(direction, "direction");
this.lambda = Objects.requireNonNull(lambda, "lambda");
this.inv = direction.inv();
this.negInvOrigin = inv.neg().times(origin);
}
private Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda, @NotNull Vec3 inv, @NotNull Vec3 negInvOrigin) {
this.origin = origin;
this.direction = direction;
this.lambda = lambda;
this.inv = inv;
this.negInvOrigin = negInvOrigin;
}
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, inv, negInvOrigin);
}
public @NotNull Vec3 origin() {
return origin;
}
public @NotNull Vec3 direction() {
return direction;
}
public @NotNull SampledWavelengths lambda() {
return lambda;
}
public @NotNull Vec3 getInvDirection() {
return inv;
}
public @NotNull Vec3 getNegInvOrigin() {
return negInvOrigin;
}
@Override
public boolean equals(Object obj) {
if (obj == this) return true;
if (obj == null || obj.getClass() != this.getClass()) return false;
var that = (Ray) obj;
return Objects.equals(this.origin, that.origin) &&
Objects.equals(this.direction, that.direction) &&
Objects.equals(this.lambda, that.lambda);
}
@Override
public int hashCode() {
return Objects.hash(origin, direction, lambda);
}
@Override
public @NotNull String toString() {
return "Ray[" +
"origin=" + origin + ", " +
"direction=" + direction + ", " +
"lambda=" + lambda + ']';
}
}

View File

@@ -160,6 +160,14 @@ public record Vec3(double x, double y, double z) implements IVec3<Vec3> {
);
}
public static @NotNull Vec3 fma(@NotNull Vec3 a, @NotNull Vec3 b, @NotNull Vec3 c) {
return new Vec3(
Math.fma(a.x(), b.x(), c.x()),
Math.fma(a.y(), b.y(), c.y()),
Math.fma(a.z(), b.z(), c.z())
);
}
public static double tripleProduct(@NotNull Vec3 a, @NotNull Vec3 b, @NotNull Vec3 c) {
return a.x * b.y * c.z + a.y * b.z * c.x + a.z * b.x * c.y - c.x * b.y * a.z - c.y * b.z * a.x - c.z * b.x * a.y;
}

View File

@@ -1,135 +0,0 @@
package eu.jonahbauer.raytracing.render;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.DeflaterOutputStream;
public enum ImageFormat {
PPM {
@Override
public void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException {
try (var writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.US_ASCII))) {
writer.write("P3\n");
writer.write(String.valueOf(image.getWidth()));
writer.write(" ");
writer.write(String.valueOf(image.getHeight()));
writer.write("\n255\n");
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
var color = image.getRGB(x, y, ColorSpaces.sRGB);
writer.write(String.valueOf(color.red()));
writer.write(" ");
writer.write(String.valueOf(color.green()));
writer.write(" ");
writer.write(String.valueOf(color.blue()));
writer.write("\n");
}
}
}
}
},
PNG {
private static final byte[] MAGIC = new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
private static final int IHDR_LENGTH = 13;
private static final int IHDR_TYPE = 0x49484452;
private static final int IDAT_TYPE = 0x49444154;
private static final int IEND_TYPE = 0x49454E44;
private static final int IEND_CRC = 0xAE426082;
@Override
public void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException {
try (var data = new NoCloseDataOutputStream(out); var _ = data.closeable()) {
data.write(MAGIC);
writeIHDR(image, data);
writeIDAT(image, data);
writeIEND(image, data);
}
}
private void writeIHDR(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
data.writeInt(IHDR_LENGTH);
try (
var crc = new CheckedOutputStream(data, new CRC32());
var ihdr = new DataOutputStream(crc)
) {
ihdr.writeInt(IHDR_TYPE);
ihdr.writeInt(image.getWidth()); // image width
ihdr.writeInt(image.getHeight()); // image height
ihdr.writeByte(8); // bit depth
ihdr.writeByte(2); // color type
ihdr.writeByte(0); // compression method
ihdr.writeByte(0); // filter method
ihdr.writeByte(0); // interlace method
ihdr.flush();
data.writeInt((int) crc.getChecksum().getValue());
}
}
private void writeIDAT(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
try (
var baos = new ByteArrayOutputStream();
var crc = new CheckedOutputStream(baos, new CRC32());
var idat = new DataOutputStream(crc)
) {
idat.writeInt(IDAT_TYPE);
try (var deflate = new DataOutputStream(new DeflaterOutputStream(idat))) {
for (int y = 0; y < image.getHeight(); y++) {
deflate.writeByte(0); // filter type
for (int x = 0; x < image.getWidth(); x++) {
var pixel = image.getRGB(x, y, ColorSpaces.sRGB);
deflate.writeByte(pixel.red());
deflate.writeByte(pixel.green());
deflate.writeByte(pixel.blue());
}
}
}
var bytes = baos.toByteArray();
data.writeInt(bytes.length - 4); // don't include type in length
data.write(bytes);
data.writeInt((int) crc.getChecksum().getValue());
}
}
private void writeIEND(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
data.writeInt(0);
data.writeInt(IEND_TYPE);
data.writeInt(IEND_CRC);
}
private static class NoCloseDataOutputStream extends DataOutputStream {
public NoCloseDataOutputStream(OutputStream out) {
super(out);
}
@Override
public void close() {
// do nothing
}
public Closeable closeable() {
return super::close;
}
}
},
;
public void write(@NotNull Canvas image, @NotNull Path path) throws IOException {
try (var out = Files.newOutputStream(path)) {
write(image, out);
}
}
public abstract void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException;
}

View File

@@ -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.spectral.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));
}
/**

View File

@@ -1,9 +1,9 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import org.jetbrains.annotations.NotNull;
public interface Canvas {

View File

@@ -1,9 +1,9 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
@@ -36,7 +36,7 @@ public final class LiveCanvas implements Canvas {
@Override
public void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda) {
delegate.add(x, y, n, spectrum, lambda);
var color = ColorRGB.gamma(delegate.getRGB(x, y, cs));
var color = cs.encode(delegate.getRGB(x, y, cs));
var rgb = color.red() << 16 | color.green() << 8 | color.blue();
image.setRGB(x, y, rgb);
}

View File

@@ -1,9 +1,9 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;

View File

@@ -1,10 +1,10 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;

View File

@@ -1,4 +1,4 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
package eu.jonahbauer.raytracing.render.color;
/**
* A pair of chromaticity coordinates in the xyY color space

View File

@@ -1,13 +1,10 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
package eu.jonahbauer.raytracing.render.color;
import eu.jonahbauer.raytracing.math.IVec3;
import eu.jonahbauer.raytracing.math.Vec3;
import org.jetbrains.annotations.NotNull;
import java.util.Random;
import static eu.jonahbauer.raytracing.Main.DEBUG;
public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB> {
public static final @NotNull ColorRGB BLACK = new ColorRGB(0.0, 0.0, 0.0);
public static final @NotNull ColorRGB WHITE = new ColorRGB(1.0, 1.0, 1.0);
@@ -26,17 +23,13 @@ public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB>
}
public ColorRGB(int rgb) {
this((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
}
public ColorRGB(int red, int green, int blue) {
this(red / 255f, green / 255f, blue / 255f);
this(((rgb >> 16) & 0xFF) / 255d, ((rgb >> 8) & 0xFF) / 255d, (rgb & 0xFF) / 255d);
}
public ColorRGB {
if (DEBUG && (!Double.isFinite(r) || !Double.isFinite(g) || !Double.isFinite(b))) {
throw new IllegalArgumentException("r, g and b must be finite");
}
assert Double.isFinite(r) : "r must be finite";
assert Double.isFinite(g) : "g must be finite";
assert Double.isFinite(b) : "b must be finite";
}
/*
@@ -65,24 +58,6 @@ public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB>
);
}
public static @NotNull ColorRGB gamma(@NotNull ColorRGB color) {
return new ColorRGB(gamma(color.r), gamma(color.g), gamma(color.b));
}
public static @NotNull ColorRGB inverseGamma(@NotNull ColorRGB color) {
return new ColorRGB(inverseGamma(color.r), inverseGamma(color.g), inverseGamma(color.b));
}
private static double gamma(double value) {
if (value <= 0.0031308) return 12.92 * value;
return 1.055 * Math.pow(value, 1. / 2.4) - 0.055;
}
private static double inverseGamma(double value) {
if (value <= 0.04045) return value / 12.92;
return Math.pow((value + 0.055) / 1.055, 2.4d);
}
@Override
public @NotNull ColorRGB plus(@NotNull ColorRGB other) {
return new ColorRGB(r + other.r, g + other.g, b + other.b);
@@ -103,19 +78,6 @@ public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB>
return new ColorRGB(r * other.r, g * other.g, b * other.b);
}
/*
* Vec3
*/
@Override
public @NotNull Vec3 toVec3() {
return new Vec3(r, g, b);
}
public static @NotNull ColorRGB fromVec3(@NotNull Vec3 vec) {
return new ColorRGB(vec.x(), vec.y(), vec.z());
}
/*
* Accessors
*/

View File

@@ -1,9 +1,8 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
package eu.jonahbauer.raytracing.render.color;
import eu.jonahbauer.raytracing.math.Matrix3;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.DenselySampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.*;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
@@ -26,16 +25,18 @@ public final class ColorSpace {
private final @NotNull Matrix3 XYZfromRGB;
private final @NotNull Matrix3 RGBfromXYZ;
private final @NotNull SpectrumTable RGBtoSpectrumTable;
private final @NotNull TransferFunction transferFunction;
public ColorSpace(
@NotNull Chromaticity r, @NotNull Chromaticity g, @NotNull Chromaticity b,
@NotNull Spectrum illuminant, @NotNull SpectrumTable table
@NotNull Spectrum illuminant, @NotNull SpectrumTable table, @NotNull TransferFunction transferFunction
) {
this.r = Objects.requireNonNull(r, "r");
this.g = Objects.requireNonNull(g, "g");
this.b = Objects.requireNonNull(b, "b");
this.illuminant = new DenselySampledSpectrum(illuminant);
this.RGBtoSpectrumTable = table;
this.RGBtoSpectrumTable = table; // no null-check
this.transferFunction = transferFunction; // no null-check
this.W = illuminant.toXYZ();
this.w = W.xy();
@@ -54,6 +55,10 @@ public final class ColorSpace {
this.RGBfromXYZ = XYZfromRGB.invert();
}
/*
* Conversions
*/
public @NotNull ColorRGB toRGB(@NotNull ColorXYZ xyz) {
var out = RGBfromXYZ.times(xyz.toVec3());
return new ColorRGB(out.x(), out.y(), out.z());
@@ -61,7 +66,7 @@ public final class ColorSpace {
public @NotNull ColorXYZ toXYZ(@NotNull ColorRGB rgb) {
var out = XYZfromRGB.times(rgb.toVec3());
return ColorXYZ.fromVec3(out);
return new ColorXYZ(out);
}
public @NotNull Vec3 toCIELab(@NotNull ColorRGB rgb) {
@@ -85,7 +90,19 @@ public final class ColorSpace {
}
}
public @NotNull SigmoidPolynomial toSpectrum(@NotNull ColorRGB rgb) {
public @NotNull ColorRGB encode(@NotNull ColorRGB rgb) {
return transferFunction.encode(rgb);
}
public @NotNull ColorRGB decode(@NotNull ColorRGB rgb) {
return transferFunction.decode(rgb);
}
/*
* Spectrum
*/
public @NotNull SigmoidPolynomial toPolynomial(@NotNull ColorRGB rgb) {
return RGBtoSpectrumTable.get(new ColorRGB(
Math.max(0, rgb.r()),
Math.max(0, rgb.g()),
@@ -93,6 +110,38 @@ public final class ColorSpace {
));
}
public @NotNull Spectrum toSpectrum(@NotNull ColorRGB rgb, @NotNull Spectrum.Type type) {
return switch (type) {
case ALBEDO -> new RGBAlbedoSpectrum(this, rgb);
case ILLUMINANT -> new RGBIlluminantSpectrum(this, rgb);
case UNBOUNDED -> new RGBUnboundedSpectrum(this, rgb);
};
}
public @NotNull Spectrum albedo(double r, double g, double b) {
return albedo(new ColorRGB(r, g, b));
}
public @NotNull Spectrum albedo(@NotNull ColorRGB rgb) {
return toSpectrum(rgb, Spectrum.Type.ALBEDO);
}
public @NotNull Spectrum illuminant(double intensity) {
return illuminant.scale(intensity);
}
public @NotNull Spectrum illuminant(double r, double g, double b) {
return illuminant(new ColorRGB(r, g, b));
}
public @NotNull Spectrum illuminant(@NotNull ColorRGB rgb) {
return toSpectrum(rgb, Spectrum.Type.ILLUMINANT);
}
/*
* Accessors
*/
public @NotNull Chromaticity r() {
return r;
}

View File

@@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
package eu.jonahbauer.raytracing.render.color;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@@ -13,21 +13,21 @@ public final class ColorSpaces {
new Chromaticity(0.6400, 0.3300),
new Chromaticity(0.3000, 0.6000),
new Chromaticity(0.1500, 0.0600),
Spectra.D65, read("sRGB_spectrum.bin")
Spectra.D65, read("sRGB_spectrum.bin"), TransferFunctions.sRGB
);
// P3-D65 (display)
public static final @NotNull ColorSpace DCI_P3 = new ColorSpace(
new Chromaticity(0.680, 0.320),
new Chromaticity(0.265, 0.690),
new Chromaticity(0.150, 0.060),
Spectra.D65, read("DCI_P3_spectrum.bin")
Spectra.D65, read("DCI_P3_spectrum.bin"), TransferFunctions.sRGB
);
// ITU-R Rec BT.2020
public static final @NotNull ColorSpace Rec2020 = new ColorSpace(
new Chromaticity(0.708, 0.292),
new Chromaticity(0.170, 0.797),
new Chromaticity(0.131, 0.046),
Spectra.D65, read("Rec2020_spectrum.bin")
Spectra.D65, read("Rec2020_spectrum.bin"), null
);
private static @NotNull SpectrumTable read(@NotNull String name) {

View File

@@ -1,4 +1,4 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
package eu.jonahbauer.raytracing.render.color;
import eu.jonahbauer.raytracing.math.IVec3;
import eu.jonahbauer.raytracing.math.Vec3;
@@ -25,6 +25,16 @@ public record ColorXYZ(double x, double y, double z) implements IVec3<ColorXYZ>
);
}
public ColorXYZ(@NotNull Vec3 vec) {
this(vec.x(), vec.y(), vec.z());
}
public ColorXYZ {
assert Double.isFinite(x) : "x must be finite";
assert Double.isFinite(y) : "y must be finite";
assert Double.isFinite(z) : "z must be finite";
}
/*
* Math
*/
@@ -71,19 +81,6 @@ public record ColorXYZ(double x, double y, double z) implements IVec3<ColorXYZ>
return new ColorXYZ(x * d, y * d, z * d);
}
/*
* Vec3
*/
@Override
public @NotNull Vec3 toVec3() {
return new Vec3(x, y, z);
}
public static @NotNull ColorXYZ fromVec3(@NotNull Vec3 vec) {
return new ColorXYZ(vec.x(), vec.y(), vec.z());
}
/*
* Accessors
*/

View File

@@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
package eu.jonahbauer.raytracing.render.color;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
/**
* A function of the form {@code s(p(x))} where {@code p} is a polynomial of second degree and {@code s} is the sigmoid

View File

@@ -1,4 +1,4 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
package eu.jonahbauer.raytracing.render.color;
import org.jetbrains.annotations.NotNull;

View File

@@ -1,8 +1,8 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
package eu.jonahbauer.raytracing.render.color;
import eu.jonahbauer.raytracing.math.Matrix3;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;

View File

@@ -0,0 +1,8 @@
package eu.jonahbauer.raytracing.render.color;
import org.jetbrains.annotations.NotNull;
public interface TransferFunction {
@NotNull ColorRGB decode(@NotNull ColorRGB rgb);
@NotNull ColorRGB encode(@NotNull ColorRGB rgb);
}

View File

@@ -0,0 +1,50 @@
package eu.jonahbauer.raytracing.render.color;
import org.jetbrains.annotations.NotNull;
public final class TransferFunctions {
public static final @NotNull TransferFunction sRGB = new ComponentTransferFunction() {
@Override
protected double encode(double value) {
if (value <= 0.0031308) return 12.92 * value;
return 1.055 * Math.pow(value, 1. / 2.4) - 0.055;
}
@Override
protected double decode(double value) {
if (value <= 0.04045) return value / 12.92;
return Math.pow((value + 0.055) / 1.055, 2.4d);
}
};
public static final @NotNull TransferFunction LINEAR = new TransferFunction() {
@Override
public @NotNull ColorRGB encode(@NotNull ColorRGB rgb) {
return rgb;
}
@Override
public @NotNull ColorRGB decode(@NotNull ColorRGB rgb) {
return rgb;
}
};
private TransferFunctions() {
throw new UnsupportedOperationException();
}
private abstract static class ComponentTransferFunction implements TransferFunction {
@Override
public final @NotNull ColorRGB decode(@NotNull ColorRGB rgb) {
return new ColorRGB(decode(rgb.r()), decode(rgb.g()), decode(rgb.b()));
}
@Override
public final @NotNull ColorRGB encode(@NotNull ColorRGB rgb) {
return new ColorRGB(encode(rgb.r()), encode(rgb.g()), encode(rgb.b()));
}
protected abstract double encode(double value);
protected abstract double decode(double value);
}
}

View File

@@ -0,0 +1,19 @@
package eu.jonahbauer.raytracing.render.image;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
public interface ImageWriter {
default void write(@NotNull Canvas image, @NotNull Path path) throws IOException {
try (var out = Files.newOutputStream(path)) {
write(image, out);
}
}
void write(@NotNull Canvas canvas, @NotNull OutputStream out) throws IOException;
}

View File

@@ -0,0 +1,107 @@
package eu.jonahbauer.raytracing.render.image;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.util.Objects;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.DeflaterOutputStream;
public class PNGImageWriter implements ImageWriter {
public static final @NotNull PNGImageWriter sRGB = new PNGImageWriter(ColorSpaces.sRGB);
private static final byte[] MAGIC = new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
private static final int IHDR_LENGTH = 13;
private static final int IHDR_TYPE = 0x49484452;
private static final int IDAT_TYPE = 0x49444154;
private static final int IEND_TYPE = 0x49454E44;
private static final int IEND_CRC = 0xAE426082;
private final @NotNull ColorSpace cs;
public PNGImageWriter(@NotNull ColorSpace cs) {
this.cs = Objects.requireNonNull(cs, "cs");
}
@Override
public void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException {
try (var data = new NoCloseDataOutputStream(out); var _ = data.closeable()) {
data.write(MAGIC);
writeIHDR(image, data);
writeIDAT(image, data);
writeIEND(image, data);
}
}
private void writeIHDR(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
data.writeInt(IHDR_LENGTH);
try (
var crc = new CheckedOutputStream(data, new CRC32());
var ihdr = new DataOutputStream(crc)
) {
ihdr.writeInt(IHDR_TYPE);
ihdr.writeInt(image.getWidth()); // image width
ihdr.writeInt(image.getHeight()); // image height
ihdr.writeByte(8); // bit depth
ihdr.writeByte(2); // color type
ihdr.writeByte(0); // compression method
ihdr.writeByte(0); // filter method
ihdr.writeByte(0); // interlace method
ihdr.flush();
data.writeInt((int) crc.getChecksum().getValue());
}
}
private void writeIDAT(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
try (
var baos = new ByteArrayOutputStream();
var crc = new CheckedOutputStream(baos, new CRC32());
var idat = new DataOutputStream(crc)
) {
idat.writeInt(IDAT_TYPE);
try (var deflate = new DataOutputStream(new DeflaterOutputStream(idat))) {
for (int y = 0; y < image.getHeight(); y++) {
deflate.writeByte(0); // filter type
for (int x = 0; x < image.getWidth(); x++) {
var pixel = cs.encode(image.getRGB(x, y, cs));
deflate.writeByte(pixel.red());
deflate.writeByte(pixel.green());
deflate.writeByte(pixel.blue());
}
}
}
var bytes = baos.toByteArray();
data.writeInt(bytes.length - 4); // don't include type in length
data.write(bytes);
data.writeInt((int) crc.getChecksum().getValue());
}
}
private void writeIEND(@NotNull Canvas image, @NotNull DataOutputStream data) throws IOException {
data.writeInt(0);
data.writeInt(IEND_TYPE);
data.writeInt(IEND_CRC);
}
private static class NoCloseDataOutputStream extends DataOutputStream {
public NoCloseDataOutputStream(OutputStream out) {
super(out);
}
@Override
public void close() {
// do nothing
}
public Closeable closeable() {
return super::close;
}
}
}

View File

@@ -0,0 +1,46 @@
package eu.jonahbauer.raytracing.render.image;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
public class PPMImageWriter implements ImageWriter {
public static final PPMImageWriter sRGB = new PPMImageWriter(ColorSpaces.sRGB);
private final @NotNull ColorSpace cs;
public PPMImageWriter(@NotNull ColorSpace cs) {
this.cs = Objects.requireNonNull(cs, "cs");
}
@Override
public void write(@NotNull Canvas image, @NotNull OutputStream out) throws IOException {
try (var writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.US_ASCII))) {
writer.write("P3\n");
writer.write(String.valueOf(image.getWidth()));
writer.write(" ");
writer.write(String.valueOf(image.getHeight()));
writer.write("\n255\n");
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
var color = cs.encode(image.getRGB(x, y, cs));
writer.write(String.valueOf(color.red()));
writer.write(" ");
writer.write(String.valueOf(color.green()));
writer.write(" ");
writer.write(String.valueOf(color.blue()));
writer.write("\n");
}
}
}
}
}

View File

@@ -2,10 +2,7 @@ package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@@ -14,38 +11,75 @@ import java.util.Objects;
import java.util.Optional;
import java.util.random.RandomGenerator;
public record DielectricMaterial(double refractionIndex, @NotNull Texture texture) implements Material {
public DielectricMaterial(double refractionIndex) {
this(refractionIndex, Spectra.WHITE);
}
public record DielectricMaterial(@NotNull RefractiveIndex ri, @NotNull Texture texture) implements Material {
public DielectricMaterial {
Objects.requireNonNull(ri, "ri");
Objects.requireNonNull(texture, "texture");
}
public DielectricMaterial(double refractionIndex, @NotNull ColorRGB color, @NotNull ColorSpace cs) {
this(refractionIndex, new RGBAlbedoSpectrum(cs, color));
public DielectricMaterial(@NotNull RefractiveIndex ri) {
this(ri, Spectra.WHITE);
}
public DielectricMaterial(double ri) {
this(new ConstantRefractiveIndex(ri), Spectra.WHITE);
}
public DielectricMaterial(double ri, @NotNull Texture texture) {
this(new ConstantRefractiveIndex(ri), texture);
}
@Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
var ri = hit.isFrontFace() ? (1 / refractionIndex) : refractionIndex;
var ri = switch (this.ri) {
case ConstantRefractiveIndex(var x) -> x;
case RefractiveIndex x -> x.get(ray.lambda().collapse());
};
if (hit.isFrontFace()) ri = 1 / ri;
var cosTheta = Math.min(- ray.direction().unit().dot(hit.normal()), 1.0);
var reflectance = reflectance(cosTheta);
var reflectance = reflectance(cosTheta, ri);
var reflect = reflectance > random.nextDouble();
var newDirection = (reflect ? Optional.<Vec3>empty() : Vec3.refract(ray.direction(), hit.normal(), ri))
.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) {
private double reflectance(double cos, double ri) {
// use schlick's approximation for reflectance
var r0 = (1 - refractionIndex) / (1 + refractionIndex);
var r0 = (1 - ri) / (1 + ri);
r0 = r0 * r0;
return r0 + (1 - r0) * (1 - cos) * (1 - cos) * (1 - cos) * (1 - cos) * (1 - cos);
}
@FunctionalInterface
public interface RefractiveIndex {
double get(double lambda);
}
public record ConstantRefractiveIndex(double ri) implements RefractiveIndex {
@Override
public double get(double lambda) {
return ri;
}
}
public record SellmeierRefractiveIndex(
double B1, double B2, double B3,
double C1, double C2, double C3
) implements RefractiveIndex {
@Override
public double get(double lambda) {
var l2 = lambda * lambda * 1E-6; // square and convert to µm
var x = 1 + B1 * l2 / (l2 - C1)
+ B2 * l2 / (l2 - C2)
+ B3 * l2 / (l2 - C3);
return Math.sqrt(x);
}
}
}

View File

@@ -1,10 +1,7 @@
package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@@ -18,10 +15,6 @@ public record DiffuseLight(@NotNull Texture texture) implements Material {
Objects.requireNonNull(texture, "texture");
}
public DiffuseLight(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
this(new RGBIlluminantSpectrum(cs, color));
}
@Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
return Optional.empty();

View File

@@ -2,8 +2,8 @@ package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@@ -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

View File

@@ -2,10 +2,7 @@ package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.renderer.pdf.SphereProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@@ -19,10 +16,6 @@ public record IsotropicMaterial(@NotNull Spectrum albedo) implements Material {
Objects.requireNonNull(albedo, "albedo");
}
public IsotropicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
this(new RGBAlbedoSpectrum(cs, color));
}
@Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
return Optional.of(new PdfScatterResult(albedo(), new SphereProbabilityDensityFunction()));

View File

@@ -2,9 +2,6 @@ package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.renderer.pdf.CosineProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@@ -18,10 +15,6 @@ public record LambertianMaterial(@NotNull Texture texture) implements Material {
Objects.requireNonNull(texture, "texture");
}
public LambertianMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
this(new RGBAlbedoSpectrum(cs, color));
}
@Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
var attenuation = texture.get(hit);

View File

@@ -2,9 +2,9 @@ package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.renderer.pdf.ProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;

View File

@@ -0,0 +1,25 @@
package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
import eu.jonahbauer.raytracing.render.material.DielectricMaterial.SellmeierRefractiveIndex;
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.texture.CheckerTexture;
import org.jetbrains.annotations.NotNull;
public final class Materials {
public static final @NotNull Material GLASS = new DielectricMaterial(new SellmeierRefractiveIndex(
1.0361212, 0.231792344, 1.01046945,
6.00069867E-3, 2.00179144E-2, 1.03560653E2
));
public static final @NotNull Material MIRROR = new MetallicMaterial(ColorSpaces.sRGB.albedo(0.7, 0.7, 0.7));
public static final @NotNull Material DEBUG = new DirectionalMaterial(
new LambertianMaterial(new CheckerTexture(50.0, ColorSpaces.sRGB.albedo(1.0, 0.0, 1.0), Spectra.BLACK)),
new LambertianMaterial(new CheckerTexture(50.0, ColorSpaces.sRGB.albedo(1.0, 1.0, 1.0), Spectra.BLACK))
);
private Materials() {
throw new UnsupportedOperationException();
}
}

View File

@@ -2,9 +2,6 @@ package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@@ -19,14 +16,6 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
this(texture, 0);
}
public MetallicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
this(color, cs, 0);
}
public MetallicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs, double fuzz) {
this(new RGBAlbedoSpectrum(cs, color), fuzz);
}
public MetallicMaterial {
Objects.requireNonNull(texture, "texture");
if (fuzz < 0 || !Double.isFinite(fuzz)) throw new IllegalArgumentException("fuzz must be non-negative");
@@ -39,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)));
}
}

View File

@@ -4,9 +4,10 @@ import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.material.Material;
import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
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,11 +124,11 @@ 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);
var optional = scene.hit(ray, random);
if (optional.isEmpty()) {
var background = scene.getBackgroundColor(ray);
color = SampledSpectrum.fma(attenuation, background, color);
@@ -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;
}

View File

@@ -1,4 +1,4 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
public final class BlackbodySpectrum implements Spectrum {
/**

View File

@@ -1,4 +1,4 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
/**
* A constant spectrum.

View File

@@ -1,4 +1,4 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
import org.jetbrains.annotations.NotNull;

View File

@@ -1,4 +1,4 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
import java.util.Arrays;

View File

@@ -1,8 +1,8 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import org.jetbrains.annotations.NotNull;
public final class RGBAlbedoSpectrum implements Spectrum {
@@ -12,7 +12,7 @@ public final class RGBAlbedoSpectrum implements Spectrum {
if (rgb.r() < 0 || rgb.r() > 1 || rgb.g() < 0 || rgb.g() > 1 || rgb.b() < 0 || rgb.b() > 1) {
throw new IllegalArgumentException();
}
this.polynomial = cs.toSpectrum(rgb);
this.polynomial = cs.toPolynomial(rgb);
}
@Override

View File

@@ -1,8 +1,8 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import org.jetbrains.annotations.NotNull;
/**
@@ -20,7 +20,7 @@ public final class RGBIlluminantSpectrum implements Spectrum {
}
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
this.scale = 2 * max;
this.polynomial = cs.toSpectrum(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
this.polynomial = cs.toPolynomial(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
this.illuminant = cs.illuminant();
}

View File

@@ -1,8 +1,8 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import org.jetbrains.annotations.NotNull;
public final class RGBUnboundedSpectrum implements Spectrum {
@@ -15,7 +15,7 @@ public final class RGBUnboundedSpectrum implements Spectrum {
}
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
this.scale = 2 * max;
this.polynomial = cs.toSpectrum(scale == 0 ? rgb.div(scale) : ColorRGB.BLACK);
this.polynomial = cs.toPolynomial(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
}
@Override

View File

@@ -1,26 +1,15 @@
package eu.jonahbauer.raytracing.render.spectral;
package eu.jonahbauer.raytracing.render.spectrum;
import eu.jonahbauer.raytracing.math.IVec;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import org.jetbrains.annotations.NotNull;
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) {
@@ -31,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;
}

View File

@@ -1,8 +1,7 @@
package eu.jonahbauer.raytracing.render.spectral;
package eu.jonahbauer.raytracing.render.spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
@@ -11,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);
}
@@ -46,10 +44,12 @@ public final class SampledWavelengths {
this.pdf = pdf;
}
@Contract(pure = true)
public double get(int index) {
return lambdas[index];
}
@Contract(pure = true)
public int size() {
return lambdas.length;
}
@@ -58,12 +58,13 @@ public final class SampledWavelengths {
* Terminates the secondary wavelengths. This method should be called after a wavelength-dependent operation like
* refraction that makes it incorrect to track multiple wavelengths together.
*/
public @NotNull SampledWavelengths collapse() {
if (pdf.length < 2 || pdf[1] == 0) return this;
var newPdf = Arrays.copyOf(pdf, pdf.length);
Arrays.fill(newPdf, 1, newPdf.length, 0d);
newPdf[0] /= newPdf.length;
return new SampledWavelengths(lambdas, newPdf);
@Contract(mutates = "this")
public double collapse() {
if (pdf.length >= 2 || pdf[1] != 0) {
Arrays.fill(pdf, 1, pdf.length, 0d);
pdf[0] /= pdf.length;
}
return lambdas[0];
}
/*

View File

@@ -1,4 +1,4 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
import org.jetbrains.annotations.NotNull;

View File

@@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;

View File

@@ -1,12 +1,10 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.SkyBox;
@@ -74,4 +72,8 @@ public interface Spectrum extends Texture, SkyBox {
default @NotNull SampledSpectrum getColor(@NotNull Ray ray) {
return this.sample(ray.lambda());
}
enum Type {
ALBEDO, ILLUMINANT, UNBOUNDED
}
}

View File

@@ -1,4 +1,4 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
package eu.jonahbauer.raytracing.render.spectrum;
import org.jetbrains.annotations.NotNull;

View File

@@ -1,7 +1,7 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull;
public record CheckerTexture(double scale, @NotNull Texture even, @NotNull Texture odd) implements Texture {

View File

@@ -1,11 +1,9 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull;
import javax.imageio.ImageIO;
@@ -21,22 +19,21 @@ public final class ImageTexture implements Texture {
private final int height;
private final @NotNull Spectrum[][] spectra;
public ImageTexture(@NotNull BufferedImage image, @NotNull ColorSpace cs, @NotNull Type type, boolean gamma) {
public ImageTexture(@NotNull BufferedImage image, @NotNull ColorSpace cs, @NotNull Spectrum.Type type) {
this.width = image.getWidth();
this.height = image.getHeight();
this.spectra = new Spectrum[height][width];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
var rgb = new ColorRGB(image.getRGB(x, y));
if (gamma) rgb = ColorRGB.inverseGamma(rgb);
spectra[y][x] = type.newSpectrum(cs, rgb);
var rgb = cs.decode(new ColorRGB(image.getRGB(x, y)));
spectra[y][x] = cs.toSpectrum(rgb, type);
}
}
}
public ImageTexture(@NotNull String path, @NotNull ColorSpace cs) {
this(read(path), cs, Type.ALBEDO, true);
this(read(path), cs, Spectrum.Type.ALBEDO);
}
private static @NotNull BufferedImage read(@NotNull String path) {
@@ -55,22 +52,4 @@ public final class ImageTexture implements Texture {
int y = (int) (v * (height - 1));
return spectra[y][x];
}
public enum Type {
ALBEDO {
@Override
protected @NotNull Spectrum newSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
return new RGBAlbedoSpectrum(cs, rgb);
}
},
ILLUMINANT {
@Override
protected @NotNull Spectrum newSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
return new RGBIlluminantSpectrum(cs, rgb);
}
},
;
protected abstract @NotNull Spectrum newSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb);
}
}

View File

@@ -1,8 +1,8 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;

View File

@@ -1,7 +1,7 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;

View File

@@ -7,10 +7,10 @@ import eu.jonahbauer.raytracing.render.material.Material;
import eu.jonahbauer.raytracing.render.texture.Texture;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.random.RandomGenerator;
/**
* The result of a {@linkplain Hittable#hit(Ray, Range) hit}.
* The result of a {@linkplain Hittable#hit(Ray, Range, RandomGenerator) hit}.
* @param t the {@code t} value at which the hit occurs
* @param position the position of the hit
* @param normal the surface normal at the hit position
@@ -24,10 +24,6 @@ public record HitResult(
double t, @NotNull Vec3 position, @NotNull Vec3 normal, @NotNull Hittable target,
@NotNull Material material, double u, double v, boolean isFrontFace
) implements Comparable<HitResult> {
public HitResult {
Objects.requireNonNull(position, "position");
normal = normal.unit();
}
public @NotNull HitResult withPositionAndNormal(@NotNull Vec3 position, @NotNull Vec3 normal) {
return new HitResult(t, position, normal, target, material, u, v, isFrontFace);

View File

@@ -9,15 +9,16 @@ import eu.jonahbauer.raytracing.scene.transform.Translate;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.random.RandomGenerator;
public interface Hittable {
@NotNull Range FORWARD = new Range(0.001, Double.POSITIVE_INFINITY);
/**
* @see #hit(Ray, Range)
* @see #hit(Ray, Range, RandomGenerator)
*/
default @NotNull Optional<HitResult> hit(@NotNull Ray ray) {
return hit(ray, FORWARD);
default @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull RandomGenerator random) {
return hit(ray, FORWARD, random);
}
/**
@@ -31,7 +32,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
* a point on {@code this} hittable
*/
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range);
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random);
/**
* {@return the axis-aligned bounding box of this hittable}

View File

@@ -2,8 +2,8 @@ package eu.jonahbauer.raytracing.scene;
import eu.jonahbauer.raytracing.math.AABB;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
import eu.jonahbauer.raytracing.scene.util.HittableCollection;
import org.jetbrains.annotations.NotNull;
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.random.RandomGenerator;
public final class Scene extends HittableCollection {
private final @NotNull HittableCollection objects;
@@ -42,8 +43,8 @@ public final class Scene extends HittableCollection {
}
@Override
public void hit(@NotNull Ray ray, @NotNull State state) {
objects.hit(ray, state);
public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
objects.hit(ray, state, random);
}
@Override

View File

@@ -1,8 +1,8 @@
package eu.jonahbauer.raytracing.scene;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface

View File

@@ -7,9 +7,11 @@ import eu.jonahbauer.raytracing.render.material.Material;
import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.Optional;
import java.util.random.RandomGenerator;
public abstract class Hittable2D implements Hittable {
protected final @NotNull Vec3 origin;
@@ -36,7 +38,7 @@ public abstract class Hittable2D implements Hittable {
}
@Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
var denominator = ray.direction().dot(normal);
if (Math.abs(denominator) < 1e-8) return Optional.empty(); // parallel

View File

@@ -48,7 +48,7 @@ public final class Box implements Hittable, Target {
}
@Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
// based on AABB#hit with additional detection of the side hit
var origin = ray.origin();
var direction = ray.direction();
@@ -103,13 +103,13 @@ public final class Box implements Hittable, Target {
t = tmin;
side = entry;
frontFace = true;
material = materials[side.ordinal()];
material = materials[entry.ordinal()];
normal = side.normal;
} else if (range.surrounds(tmax) && materials[exit.ordinal()] != null) {
t = tmax;
side = exit;
frontFace = false;
material = materials[side.ordinal()];
material = materials[exit.ordinal()];
normal = side.normal.neg();
} else {
return Optional.empty();
@@ -130,7 +130,7 @@ public final class Box implements Hittable, Target {
@Override
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
if (contains(origin)) return 1 / (4 * Math.PI);
if (hit(new Ray(origin, direction)).isEmpty()) return 0;
if (hit(new Ray(origin, direction), null).isEmpty()) return 0;
var solidAngle = 0d;
for (var s : Side.values()) {

View File

@@ -10,15 +10,16 @@ import eu.jonahbauer.raytracing.scene.Hittable;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.random.RandomGenerator;
public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNull IsotropicMaterial material) implements Hittable {
@Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
var hit1 = boundary.hit(ray, Range.UNIVERSE);
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
var hit1 = boundary.hit(ray, Range.UNIVERSE, random);
if (hit1.isEmpty()) return Optional.empty();
var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY));
var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY), random);
if (hit2.isEmpty()) return Optional.empty();
var tmin = Math.max(range.min(), hit1.get().t());
@@ -28,7 +29,7 @@ public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNul
var length = ray.direction().length();
var distance = length * (tmax - tmin);
var hitDistance = - Math.log(Math.random()) / density;
var hitDistance = - Math.log(random.nextDouble()) / density;
if (hitDistance > distance) return Optional.empty();
var t = tmin + hitDistance / length;

View File

@@ -9,6 +9,7 @@ import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.Target;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Objects;
import java.util.Optional;
@@ -40,7 +41,7 @@ public final class Sphere implements Hittable, Target {
}
@Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
var t = hit0(ray, range);
if (Double.isNaN(t)) return Optional.empty();

View File

@@ -2,10 +2,8 @@ package eu.jonahbauer.raytracing.scene.transform;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.Hittable;
import eu.jonahbauer.raytracing.scene.Target;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
@@ -24,7 +22,7 @@ public abstract class Transform implements Hittable {
protected abstract @NotNull HitResult transform(@NotNull HitResult result);
@Override
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
return object.hit(transform(ray), range).map(this::transform);
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
return object.hit(transform(ray), range, random).map(this::transform);
}
}

View File

@@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Comparator;
import java.util.List;
import java.util.random.RandomGenerator;
public final class HittableBinaryTree extends HittableCollection {
private final @Nullable Hittable left;
@@ -46,17 +47,17 @@ public final class HittableBinaryTree extends HittableCollection {
}
@Override
public void hit(@NotNull Ray ray, @NotNull State state) {
public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
if (!bbox.hit(ray, state.getRange())) return;
if (left instanceof HittableCollection coll) {
coll.hit(ray, state);
coll.hit(ray, state, random);
} else if (left != null) {
hit(state, ray, left);
hit(state, ray, left, random);
}
if (right instanceof HittableCollection coll) {
coll.hit(ray, state);
coll.hit(ray, state, random);
} else if (right != null) {
hit(state, ray, right);
hit(state, ray, right, random);
}
}

View File

@@ -8,20 +8,21 @@ import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.Optional;
import java.util.random.RandomGenerator;
public abstract class HittableCollection implements Hittable {
@Override
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
var state = new State(range);
hit(ray, state);
hit(ray, state, random);
return state.getResult();
}
public abstract void hit(@NotNull Ray ray, @NotNull State state);
public abstract void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random);
protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object) {
var r = object.hit(ray, state.range);
protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object, @NotNull RandomGenerator random) {
var r = object.hit(ray, state.range, random);
if (r.isPresent()) {
if (state.range.surrounds(r.get().t())){
state.result = r.get();

View File

@@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.random.RandomGenerator;
public final class HittableList extends HittableCollection {
private final @NotNull List<Hittable> objects;
@@ -22,8 +23,8 @@ public final class HittableList extends HittableCollection {
}
@Override
public void hit(@NotNull Ray ray, @NotNull State state) {
objects.forEach(object -> hit(state, ray, object));
public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
objects.forEach(object -> hit(state, ray, object, random));
}
@Override

View File

@@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.ImageFormat;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

View File

@@ -3,7 +3,7 @@ package eu.jonahbauer.raytracing.scene.hittable3d;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.material.LambertianMaterial;
import org.junit.jupiter.api.Test;