Compare commits

...

9 Commits

67 changed files with 760 additions and 524 deletions

View File

@@ -1,11 +1,11 @@
package eu.jonahbauer.raytracing; package eu.jonahbauer.raytracing;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces; import eu.jonahbauer.raytracing.render.color.ColorSpaces;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum; import eu.jonahbauer.raytracing.render.spectrum.RGBAlbedoSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum; import eu.jonahbauer.raytracing.render.spectrum.RGBIlluminantSpectrum;
import eu.jonahbauer.raytracing.render.texture.CheckerTexture; 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.camera.SimpleCamera;
import eu.jonahbauer.raytracing.render.material.*; import eu.jonahbauer.raytracing.render.material.*;
import eu.jonahbauer.raytracing.render.texture.ImageTexture; import eu.jonahbauer.raytracing.render.texture.ImageTexture;
@@ -55,11 +55,11 @@ public class Examples {
var cs = ColorSpaces.sRGB; var cs = ColorSpaces.sRGB;
return new Example( return new Example(
new Scene(getSkyBox(), List.of( 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, -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(new ColorRGB(0.1, 0.2, 0.5), cs)), 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.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.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() SimpleCamera.builder()
.withImage(height * 16 / 9, height) .withImage(height * 16 / 9, height)
@@ -92,12 +92,12 @@ public class Examples {
if (rnd < 0.8) { if (rnd < 0.8) {
// diffuse // diffuse
var albedo = ColorRGB.random(rng).times(ColorRGB.random(rng)); 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) { } else if (rnd < 0.95) {
// metal // metal
var albedo = ColorRGB.random(rng, 0.5, 1.0); var albedo = ColorRGB.random(rng, 0.5, 1.0);
var fuzz = rng.nextDouble() * 0.5; var fuzz = rng.nextDouble() * 0.5;
material = new MetallicMaterial(albedo, cs, fuzz); material = new MetallicMaterial(cs.albedo(albedo), fuzz);
} else { } else {
// glass // glass
material = new DielectricMaterial(1.5); 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(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 LambertianMaterial(cs.albedo(0.4, 0.2, 0.1))));
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 MetallicMaterial(cs.albedo(0.7, 0.6, 0.5))));
var camera = SimpleCamera.builder() var camera = SimpleCamera.builder()
.withImage(height * 16 / 9, height) .withImage(height * 16 / 9, height)
@@ -128,11 +128,11 @@ public class Examples {
var cs = ColorSpaces.sRGB; var cs = ColorSpaces.sRGB;
return new Example( return new Example(
new Scene(getSkyBox(), List.of( 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(-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(new ColorRGB(0.2, 1.0, 0.2), cs)), 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(new ColorRGB(0.2, 0.2, 1.0), cs)), 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(new ColorRGB(1.0, 0.5, 0.0), cs)), 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(new ColorRGB(0.2, 0.8, 0.8), cs)) 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() SimpleCamera.builder()
.withImage(height, height) .withImage(height, height)
@@ -148,10 +148,10 @@ public class Examples {
var cs = ColorSpaces.sRGB; var cs = ColorSpaces.sRGB;
return new Example( return new Example(
new Scene(List.of( 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, -1000, 0), 1000, new LambertianMaterial(cs.albedo(0.2, 0.2, 0.2))),
new Sphere(new Vec3(0, 2, 0), 2, new LambertianMaterial(new ColorRGB(0.2, 0.2, 0.2), cs)), 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(new ColorRGB(4.0, 4.0, 4.0), cs)), 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(new ColorRGB(4.0, 4.0, 4.0), cs)) new Sphere(new Vec3(0, 7, 0), 2, new DiffuseLight(cs.illuminant(4.0)))
)), )),
SimpleCamera.builder() SimpleCamera.builder()
.withImage(height * 16 / 9, height) .withImage(height * 16 / 9, height)
@@ -166,10 +166,10 @@ public class Examples {
if (height <= 0) height = 600; if (height <= 0) height = 600;
var cs = ColorSpaces.sRGB; var cs = ColorSpaces.sRGB;
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs); var red = new LambertianMaterial(cs.albedo(.65, .05, .05));
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs); var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs); var green = new LambertianMaterial(cs.albedo(.12, .45, .15));
var light = new DiffuseLight(new ColorRGB(15.0, 15.0, 15.0), cs); var light = new DiffuseLight(cs.illuminant(15.0));
return new Example( return new Example(
new Scene(List.of( new Scene(List.of(
@@ -194,10 +194,10 @@ public class Examples {
public static @NotNull Example getCornellBoxSmoke(int height) { public static @NotNull Example getCornellBoxSmoke(int height) {
if (height <= 0) height = 600; if (height <= 0) height = 600;
var cs = ColorSpaces.sRGB; var cs = ColorSpaces.sRGB;
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs); var red = new LambertianMaterial(cs.albedo(.65, .05, .05));
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs); var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs); var green = new LambertianMaterial(cs.albedo(.12, .45, .15));
var light = new DiffuseLight(new ColorRGB(15.0, 15.0, 15.0), cs); var light = new DiffuseLight(cs.illuminant(15.0));
return new Example( return new Example(
new Scene(List.of( new Scene(List.of(
@@ -207,13 +207,13 @@ public class Examples {
new Box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white) new Box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white)
.rotateY(Math.toRadians(15)) .rotateY(Math.toRadians(15))
.translate(new Vec3(265, 0, 295)), .translate(new Vec3(265, 0, 295)),
0.01, new IsotropicMaterial(ColorRGB.BLACK, cs) 0.01, new IsotropicMaterial(cs.albedo(ColorRGB.BLACK))
), ),
new ConstantMedium( new ConstantMedium(
new Box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white) new Box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white)
.rotateY(Math.toRadians(-18)) .rotateY(Math.toRadians(-18))
.translate(new Vec3(130, 0, 65)), .translate(new Vec3(130, 0, 65)),
0.01, new IsotropicMaterial(ColorRGB.WHITE, cs) 0.01, new IsotropicMaterial(cs.albedo(ColorRGB.WHITE))
) )
)), )),
SimpleCamera.builder() SimpleCamera.builder()
@@ -229,10 +229,10 @@ public class Examples {
if (height <= 0) height = 600; if (height <= 0) height = 600;
var cs = ColorSpaces.sRGB; var cs = ColorSpaces.sRGB;
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs); var red = new LambertianMaterial(cs.albedo(.65, .05, .05));
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs); var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs); var green = new LambertianMaterial(cs.albedo(.12, .45, .15));
var light = new DiffuseLight(new ColorRGB(7.0, 7.0, 7.0), cs); var light = new DiffuseLight(cs.illuminant(7.0));
var glass = new DielectricMaterial(1.5); 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); 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; var cs = ColorSpaces.sRGB;
record Partei(String name, ColorRGB color, double stimmen) { } record Partei(String name, ColorRGB color, double stimmen) { }
var data = List.of( var data = List.of(
new Partei("CDU", new ColorRGB(0x00, 0x4B, 0x76), 18.9), new Partei("CDU", new ColorRGB(0x004B76), 18.9),
new Partei("SPD", new ColorRGB(0xC0, 0x00, 0x3C), 25.7), new Partei("SPD", new ColorRGB(0xC0003C), 25.7),
new Partei("AfD", new ColorRGB(0x80, 0xCD, 0xEC), 10.3), new Partei("AfD", new ColorRGB(0x80CDEC), 10.3),
new Partei("FDP", new ColorRGB(0xF7, 0xBB, 0x3D), 11.5), new Partei("FDP", new ColorRGB(0xF7BB3D), 11.5),
new Partei("DIE LINKE", new ColorRGB(0x5F, 0x31, 0x6E), 4.9), new Partei("DIE LINKE", new ColorRGB(0x5F316E), 4.9),
new Partei("GRÜNE", new ColorRGB(0x00, 0x85, 0x4A), 14.8), new Partei("GRÜNE", new ColorRGB(0x00854A), 14.8),
new Partei("CSU", new ColorRGB(0x00, 0x77, 0xB6), 5.2) 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 count = data.size();
var size = 75d; var size = 75d;
@@ -287,7 +287,7 @@ public class Examples {
objects.add(new Box( objects.add(new Box(
new Vec3((i + 1) * spacing + i * size, 0, spacing), new Vec3((i + 1) * spacing + i * size, 0, spacing),
new Vec3((i + 1) * spacing + (i + 1) * size, partei.stimmen() * 15, spacing + size), 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 // boxes
var boxes = new ArrayList<Hittable>(); 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 i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) { for (int j = 0; j < 20; j++) {
var w = 100.0; var w = 100.0;
@@ -364,23 +364,23 @@ public class Examples {
// light // light
objects.add(new Parallelogram( objects.add(new Parallelogram(
new Vec3(123, 554, 147), new Vec3(300, 0, 0), new Vec3(0, 0, 265), 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 // 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(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 // glass sphere filled with gas
var boundary = new Sphere(new Vec3(360, 150, 145), 70, new DielectricMaterial(1.5)); var boundary = new Sphere(new Vec3(360, 150, 145), 70, new DielectricMaterial(1.5));
objects.add(boundary); 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 // put the world in a glass sphere
objects.add(new ConstantMedium( objects.add(new ConstantMedium(
new Sphere(new Vec3(0, 0, 0), 5000, new DielectricMaterial(1.5)), 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 // 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)))); objects.add(new Sphere(new Vec3(220, 280, 300), 80, new LambertianMaterial(new PerlinTexture(0.2))));
// box from spheres // 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>(); var spheres = new ArrayList<Hittable>();
for (int j = 0; j < 1000; j++) { for (int j = 0; j < 1000; j++) {
spheres.add(new Sphere(new Vec3(random.nextDouble(165), random.nextDouble(165), random.nextDouble(165)), 10, white)); 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; package eu.jonahbauer.raytracing;
import eu.jonahbauer.raytracing.render.ImageFormat;
import eu.jonahbauer.raytracing.render.canvas.Canvas; import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.canvas.LiveCanvas; import eu.jonahbauer.raytracing.render.canvas.LiveCanvas;
import eu.jonahbauer.raytracing.render.canvas.XYZCanvas; 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.renderer.SimpleRenderer;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.color.ColorSpaces;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
@@ -26,6 +24,7 @@ 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)
@@ -44,10 +43,10 @@ public class Main {
renderer.render(camera, scene, canvas); renderer.render(camera, scene, canvas);
System.out.printf("rendering finished after %dms", (System.nanoTime() - time) / 1_000_000); 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) { public static @NotNull Config parse(@NotNull String @NotNull[] args) {
IntFunction<Example> example = null; IntFunction<Example> example = null;
Path path = null; Path path = null;
@@ -55,6 +54,7 @@ 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,6 +83,15 @@ 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 {
@@ -114,7 +123,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, depth); return new Config(example.apply(height), path, preview, iterative, parallel, samples, spectralSamples, depth);
} }
private static @NotNull RuntimeException fail(@NotNull String message) { 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 * @return {@code true} iff the ray intersects this bounding box, {@code false} otherwise
*/ */
public boolean hit(@NotNull Ray ray, @NotNull Range range) { public boolean hit(@NotNull Ray ray, @NotNull Range range) {
var origin = ray.origin(); var invDirection = ray.getInvDirection();
var direction = ray.direction(); var negInvOrigin = ray.getNegInvOrigin();
var invDirection = direction.inv();
// calculate t values for intersection points of ray with planes through min var tminX = Math.fma(min.x(), invDirection.x(), negInvOrigin.x());
var tmin = intersect(min(), origin, invDirection); var tminY = Math.fma(min.y(), invDirection.y(), negInvOrigin.y());
// calculate t values for intersection points of ray with planes through max var tminZ = Math.fma(min.z(), invDirection.z(), negInvOrigin.z());
var tmax = intersect(max(), origin, invDirection);
// determine range of t for which the ray is inside this voxel var tmaxX = Math.fma(max.x(), invDirection.x(), negInvOrigin.x());
double tlmax = Double.NEGATIVE_INFINITY; // lower limit maximum var tmaxY = Math.fma(max.y(), invDirection.y(), negInvOrigin.y());
double tumin = Double.POSITIVE_INFINITY; // upper limit minimum var tmaxZ = Math.fma(max.z(), invDirection.z(), negInvOrigin.z());
for (int i = 0; i < 3; i++) { var tlmax = max(
// classify t values as lower or upper limit based on ray direction Math.min(tminX, tmaxX),
if (direction.get(i) >= 0) { Math.min(tminY, tmaxY),
// min is lower limit and max is upper limit Math.min(tminZ, tmaxZ)
if (tmin[i] > tlmax) tlmax = tmin[i]; );
if (tmax[i] < tumin) tumin = tmax[i]; var tumin = min(
} else { Math.max(tminX, tmaxX),
// max is lower limit and min is upper limit Math.max(tminY, tmaxY),
if (tmax[i] > tlmax) tlmax = tmax[i]; Math.max(tminZ, tmaxZ)
if (tmin[i] < tumin) tumin = tmin[i]; );
}
}
return tlmax < tumin && tumin >= range.min() && tlmax <= range.max(); 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. * Computes the {@code t} values of the intersections of a ray with the axis-aligned planes through a point.
* @param corner the point * @param corner the point

View File

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

View File

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

View File

@@ -1,22 +1,97 @@
package eu.jonahbauer.raytracing.math; 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 org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda) { public final class Ray {
public Ray { private final @NotNull Vec3 origin;
Objects.requireNonNull(origin, "origin"); private final @NotNull Vec3 direction;
Objects.requireNonNull(direction, "direction"); private final @NotNull SampledWavelengths lambda;
Objects.requireNonNull(lambda, "lambda");
} private final @NotNull Vec3 inv;
private final @NotNull Vec3 negInvOrigin;
public Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) { public Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) {
this(origin, direction, SampledWavelengths.EMPTY); 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) { 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, 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) { 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; 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.Ray;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@@ -94,7 +93,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), SampledWavelengths.uniform(random.nextDouble())); return new Ray(origin, target.minus(origin));
} }
/** /**

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
package eu.jonahbauer.raytracing.render.canvas; package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum; import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths; import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace; import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ; import eu.jonahbauer.raytracing.render.color.ColorXYZ;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage; 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 * 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.IVec3;
import eu.jonahbauer.raytracing.math.Vec3;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Random; import java.util.Random;
import static eu.jonahbauer.raytracing.Main.DEBUG;
public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB> { 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 BLACK = new ColorRGB(0.0, 0.0, 0.0);
public static final @NotNull ColorRGB WHITE = new ColorRGB(1.0, 1.0, 1.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) { public ColorRGB(int rgb) {
this((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF); this(((rgb >> 16) & 0xFF) / 255d, ((rgb >> 8) & 0xFF) / 255d, (rgb & 0xFF) / 255d);
}
public ColorRGB(int red, int green, int blue) {
this(red / 255f, green / 255f, blue / 255f);
} }
public ColorRGB { public ColorRGB {
if (DEBUG && (!Double.isFinite(r) || !Double.isFinite(g) || !Double.isFinite(b))) { assert Double.isFinite(r) : "r must be finite";
throw new IllegalArgumentException("r, g and b 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 @Override
public @NotNull ColorRGB plus(@NotNull ColorRGB other) { public @NotNull ColorRGB plus(@NotNull ColorRGB other) {
return new ColorRGB(r + other.r, g + other.g, b + other.b); 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); 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 * 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.Matrix3;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.DenselySampledSpectrum; import eu.jonahbauer.raytracing.render.spectrum.*;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
@@ -26,16 +25,18 @@ public final class ColorSpace {
private final @NotNull Matrix3 XYZfromRGB; private final @NotNull Matrix3 XYZfromRGB;
private final @NotNull Matrix3 RGBfromXYZ; private final @NotNull Matrix3 RGBfromXYZ;
private final @NotNull SpectrumTable RGBtoSpectrumTable; private final @NotNull SpectrumTable RGBtoSpectrumTable;
private final @NotNull TransferFunction transferFunction;
public ColorSpace( public ColorSpace(
@NotNull Chromaticity r, @NotNull Chromaticity g, @NotNull Chromaticity b, @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.r = Objects.requireNonNull(r, "r");
this.g = Objects.requireNonNull(g, "g"); this.g = Objects.requireNonNull(g, "g");
this.b = Objects.requireNonNull(b, "b"); this.b = Objects.requireNonNull(b, "b");
this.illuminant = new DenselySampledSpectrum(illuminant); 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 = illuminant.toXYZ();
this.w = W.xy(); this.w = W.xy();
@@ -54,6 +55,10 @@ public final class ColorSpace {
this.RGBfromXYZ = XYZfromRGB.invert(); this.RGBfromXYZ = XYZfromRGB.invert();
} }
/*
* Conversions
*/
public @NotNull ColorRGB toRGB(@NotNull ColorXYZ xyz) { public @NotNull ColorRGB toRGB(@NotNull ColorXYZ xyz) {
var out = RGBfromXYZ.times(xyz.toVec3()); var out = RGBfromXYZ.times(xyz.toVec3());
return new ColorRGB(out.x(), out.y(), out.z()); return new ColorRGB(out.x(), out.y(), out.z());
@@ -61,7 +66,7 @@ public final class ColorSpace {
public @NotNull ColorXYZ toXYZ(@NotNull ColorRGB rgb) { public @NotNull ColorXYZ toXYZ(@NotNull ColorRGB rgb) {
var out = XYZfromRGB.times(rgb.toVec3()); var out = XYZfromRGB.times(rgb.toVec3());
return ColorXYZ.fromVec3(out); return new ColorXYZ(out);
} }
public @NotNull Vec3 toCIELab(@NotNull ColorRGB rgb) { 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( return RGBtoSpectrumTable.get(new ColorRGB(
Math.max(0, rgb.r()), Math.max(0, rgb.r()),
Math.max(0, rgb.g()), 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() { public @NotNull Chromaticity r() {
return 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 org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
@@ -13,21 +13,21 @@ public final class ColorSpaces {
new Chromaticity(0.6400, 0.3300), new Chromaticity(0.6400, 0.3300),
new Chromaticity(0.3000, 0.6000), new Chromaticity(0.3000, 0.6000),
new Chromaticity(0.1500, 0.0600), new Chromaticity(0.1500, 0.0600),
Spectra.D65, read("sRGB_spectrum.bin") Spectra.D65, read("sRGB_spectrum.bin"), TransferFunctions.sRGB
); );
// P3-D65 (display) // P3-D65 (display)
public static final @NotNull ColorSpace DCI_P3 = new ColorSpace( public static final @NotNull ColorSpace DCI_P3 = new ColorSpace(
new Chromaticity(0.680, 0.320), new Chromaticity(0.680, 0.320),
new Chromaticity(0.265, 0.690), new Chromaticity(0.265, 0.690),
new Chromaticity(0.150, 0.060), 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 // ITU-R Rec BT.2020
public static final @NotNull ColorSpace Rec2020 = new ColorSpace( public static final @NotNull ColorSpace Rec2020 = new ColorSpace(
new Chromaticity(0.708, 0.292), new Chromaticity(0.708, 0.292),
new Chromaticity(0.170, 0.797), new Chromaticity(0.170, 0.797),
new Chromaticity(0.131, 0.046), 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) { 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.IVec3;
import eu.jonahbauer.raytracing.math.Vec3; 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 * 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); 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 * 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 * 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; 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.Matrix3;
import eu.jonahbauer.raytracing.math.Vec3; 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 org.jetbrains.annotations.NotNull;
import java.io.IOException; 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.Ray;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.spectrum.Spectra;
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.texture.Texture; import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -14,38 +11,75 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.random.RandomGenerator; import java.util.random.RandomGenerator;
public record DielectricMaterial(double refractionIndex, @NotNull Texture texture) implements Material { public record DielectricMaterial(@NotNull RefractiveIndex ri, @NotNull Texture texture) implements Material {
public DielectricMaterial(double refractionIndex) {
this(refractionIndex, Spectra.WHITE);
}
public DielectricMaterial { public DielectricMaterial {
Objects.requireNonNull(ri, "ri");
Objects.requireNonNull(texture, "texture"); Objects.requireNonNull(texture, "texture");
} }
public DielectricMaterial(double refractionIndex, @NotNull ColorRGB color, @NotNull ColorSpace cs) { public DielectricMaterial(@NotNull RefractiveIndex ri) {
this(refractionIndex, new RGBAlbedoSpectrum(cs, color)); 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 @Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) { 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 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 reflect = reflectance > random.nextDouble();
var newDirection = (reflect ? Optional.<Vec3>empty() : Vec3.refract(ray.direction(), hit.normal(), ri)) var newDirection = (reflect ? Optional.<Vec3>empty() : Vec3.refract(ray.direction(), hit.normal(), ri))
.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, 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 // use schlick's approximation for reflectance
var r0 = (1 - refractionIndex) / (1 + refractionIndex); var r0 = (1 - ri) / (1 + ri);
r0 = r0 * r0; r0 = r0 * r0;
return r0 + (1 - r0) * (1 - cos) * (1 - cos) * (1 - cos) * (1 - cos) * (1 - cos); 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; package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
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.texture.Texture; import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -18,10 +15,6 @@ public record DiffuseLight(@NotNull Texture texture) implements Material {
Objects.requireNonNull(texture, "texture"); Objects.requireNonNull(texture, "texture");
} }
public DiffuseLight(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
this(new RGBIlluminantSpectrum(cs, color));
}
@Override @Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) { public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
return Optional.empty(); 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.Ray;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra; import eu.jonahbauer.raytracing.render.spectrum.Spectra;
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.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -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, new Ray(ray.at(hit.t()), ray.direction(), ray.lambda()))); return Optional.of(new SpecularScatterResult(Spectra.WHITE, ray.with(hit, ray.direction())));
} }
@Override @Override

View File

@@ -2,10 +2,7 @@ package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.renderer.pdf.SphereProbabilityDensityFunction; import eu.jonahbauer.raytracing.render.renderer.pdf.SphereProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
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.texture.Texture; import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -19,10 +16,6 @@ public record IsotropicMaterial(@NotNull Spectrum albedo) implements Material {
Objects.requireNonNull(albedo, "albedo"); Objects.requireNonNull(albedo, "albedo");
} }
public IsotropicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
this(new RGBAlbedoSpectrum(cs, color));
}
@Override @Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) { public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
return Optional.of(new PdfScatterResult(albedo(), new SphereProbabilityDensityFunction())); 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.math.Ray;
import eu.jonahbauer.raytracing.render.renderer.pdf.CosineProbabilityDensityFunction; 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.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -18,10 +15,6 @@ public record LambertianMaterial(@NotNull Texture texture) implements Material {
Objects.requireNonNull(texture, "texture"); Objects.requireNonNull(texture, "texture");
} }
public LambertianMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
this(new RGBAlbedoSpectrum(cs, color));
}
@Override @Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) { public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
var attenuation = texture.get(hit); 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.math.Ray;
import eu.jonahbauer.raytracing.render.renderer.pdf.ProbabilityDensityFunction; import eu.jonahbauer.raytracing.render.renderer.pdf.ProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra; import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum; import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.texture.Texture; import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull; 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.Ray;
import eu.jonahbauer.raytracing.math.Vec3; 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.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -19,14 +16,6 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
this(texture, 0); 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 { public MetallicMaterial {
Objects.requireNonNull(texture, "texture"); Objects.requireNonNull(texture, "texture");
if (fuzz < 0 || !Double.isFinite(fuzz)) throw new IllegalArgumentException("fuzz must be non-negative"); 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()); 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, 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.material.Material;
import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction; import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction; 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.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;
@@ -20,7 +21,10 @@ 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;
@@ -36,7 +40,10 @@ 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;
@@ -96,7 +103,8 @@ 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 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) { 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 + ")...");
} }
@@ -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) { private @NotNull SampledSpectrum getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
var color = SampledSpectrum.BLACK; var color = black;
var attenuation = SampledSpectrum.WHITE; var attenuation = white;
while (depth-- > 0) { while (depth-- > 0) {
var optional = scene.hit(ray); var optional = scene.hit(ray, random);
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);
@@ -136,7 +144,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 && !SampledSpectrum.BLACK.equals(emitted)) { if (DEBUG && !black.equals(emitted)) {
System.out.println(" Emitted: " + emitted); System.out.println(" Emitted: " + emitted);
} }
@@ -213,7 +221,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 double gamma = 2.0; private int spectralSamples = 4;
private boolean parallel = true; private boolean parallel = true;
private boolean iterative = false; private boolean iterative = false;
@@ -229,9 +237,9 @@ public final class SimpleRenderer implements Renderer {
return this; return this;
} }
public @NotNull Builder withGamma(double gamma) { public @NotNull Builder withSpectralSamples(int samples) {
if (gamma <= 0 || !Double.isFinite(gamma)) throw new IllegalArgumentException("gamma must be positive"); if (samples <= 0) throw new IllegalArgumentException("samples must be positive");
this.gamma = gamma; this.spectralSamples = samples;
return this; 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 { 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. * 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; 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; 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.color.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial; import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.color.ColorRGB;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public final class RGBAlbedoSpectrum implements Spectrum { 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) { if (rgb.r() < 0 || rgb.r() > 1 || rgb.g() < 0 || rgb.g() > 1 || rgb.b() < 0 || rgb.b() > 1) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
this.polynomial = cs.toSpectrum(rgb); this.polynomial = cs.toPolynomial(rgb);
} }
@Override @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.color.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial; import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.color.ColorRGB;
import org.jetbrains.annotations.NotNull; 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())); var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
this.scale = 2 * max; 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(); 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.color.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial; import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.color.ColorRGB;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public final class RGBUnboundedSpectrum implements Spectrum { 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())); var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
this.scale = 2 * max; 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 @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.math.IVec;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace; import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ; import eu.jonahbauer.raytracing.render.color.ColorXYZ;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum; import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Arrays; 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) {
@@ -31,6 +20,12 @@ 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;
} }

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.color.ColorXYZ;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra; import org.jetbrains.annotations.Contract;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Arrays; import java.util.Arrays;
@@ -11,32 +10,31 @@ 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) { public static @NotNull SampledWavelengths uniform(double rng, int count) {
return uniform(rng, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX); return uniform(rng, count, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX);
} }
public static @NotNull SampledWavelengths uniform(double rng, double min, double max) { public static @NotNull SampledWavelengths uniform(double rng, int count, double min, double max) {
var lambdas = new double[SAMPLES]; var lambdas = new double[count];
// 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) / SAMPLES; var delta = (max - min) / count;
for (int i = 1; i < SAMPLES; i++) { for (int i = 1; i < count; 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[SAMPLES]; var pdf = new double[count];
Arrays.fill(pdf, 1 / (max - min)); Arrays.fill(pdf, 1 / (max - min));
return new SampledWavelengths(lambdas, pdf); return new SampledWavelengths(lambdas, pdf);
} }
@@ -46,10 +44,12 @@ public final class SampledWavelengths {
this.pdf = pdf; this.pdf = pdf;
} }
@Contract(pure = true)
public double get(int index) { public double get(int index) {
return lambdas[index]; return lambdas[index];
} }
@Contract(pure = true)
public int size() { public int size() {
return lambdas.length; 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 * 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. * refraction that makes it incorrect to track multiple wavelengths together.
*/ */
public @NotNull SampledWavelengths collapse() { @Contract(mutates = "this")
if (pdf.length < 2 || pdf[1] == 0) return this; public double collapse() {
var newPdf = Arrays.copyOf(pdf, pdf.length); if (pdf.length >= 2 || pdf[1] != 0) {
Arrays.fill(newPdf, 1, newPdf.length, 0d); Arrays.fill(pdf, 1, pdf.length, 0d);
newPdf[0] /= newPdf.length; pdf[0] /= pdf.length;
return new SampledWavelengths(lambdas, newPdf); }
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; 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 org.jetbrains.annotations.NotNull;
import java.io.BufferedReader; 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.Ray;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace; import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ; import eu.jonahbauer.raytracing.render.color.ColorXYZ;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum; import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.texture.Texture; import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult; import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.SkyBox; import eu.jonahbauer.raytracing.scene.SkyBox;
@@ -74,4 +72,8 @@ public interface Spectrum extends Texture, SkyBox {
default @NotNull SampledSpectrum getColor(@NotNull Ray ray) { default @NotNull SampledSpectrum getColor(@NotNull Ray ray) {
return this.sample(ray.lambda()); 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; import org.jetbrains.annotations.NotNull;

View File

@@ -1,7 +1,7 @@
package eu.jonahbauer.raytracing.render.texture; package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3; 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 org.jetbrains.annotations.NotNull;
public record CheckerTexture(double scale, @NotNull Texture even, @NotNull Texture odd) implements Texture { 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; package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3; 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.spectral.colors.ColorSpace; import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum; import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@@ -21,22 +19,21 @@ public final class ImageTexture implements Texture {
private final int height; private final int height;
private final @NotNull Spectrum[][] spectra; 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.width = image.getWidth();
this.height = image.getHeight(); this.height = image.getHeight();
this.spectra = new Spectrum[height][width]; this.spectra = new Spectrum[height][width];
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
var rgb = new ColorRGB(image.getRGB(x, y)); var rgb = cs.decode(new ColorRGB(image.getRGB(x, y)));
if (gamma) rgb = ColorRGB.inverseGamma(rgb); spectra[y][x] = cs.toSpectrum(rgb, type);
spectra[y][x] = type.newSpectrum(cs, rgb);
} }
} }
} }
public ImageTexture(@NotNull String path, @NotNull ColorSpace cs) { 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) { private static @NotNull BufferedImage read(@NotNull String path) {
@@ -55,22 +52,4 @@ public final class ImageTexture implements Texture {
int y = (int) (v * (height - 1)); int y = (int) (v * (height - 1));
return spectra[y][x]; 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; package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra; import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum; import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;

View File

@@ -1,7 +1,7 @@
package eu.jonahbauer.raytracing.render.texture; package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3; 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 eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull; 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 eu.jonahbauer.raytracing.render.texture.Texture;
import org.jetbrains.annotations.NotNull; 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 t the {@code t} value at which the hit occurs
* @param position the position of the hit * @param position the position of the hit
* @param normal the surface normal at the hit position * @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, double t, @NotNull Vec3 position, @NotNull Vec3 normal, @NotNull Hittable target,
@NotNull Material material, double u, double v, boolean isFrontFace @NotNull Material material, double u, double v, boolean isFrontFace
) implements Comparable<HitResult> { ) implements Comparable<HitResult> {
public HitResult {
Objects.requireNonNull(position, "position");
normal = normal.unit();
}
public @NotNull HitResult withPositionAndNormal(@NotNull Vec3 position, @NotNull Vec3 normal) { public @NotNull HitResult withPositionAndNormal(@NotNull Vec3 position, @NotNull Vec3 normal) {
return new HitResult(t, position, normal, target, material, u, v, isFrontFace); 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 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) * @see #hit(Ray, Range, RandomGenerator)
*/ */
default @NotNull Optional<HitResult> hit(@NotNull Ray ray) { default @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull RandomGenerator random) {
return hit(ray, FORWARD); 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 * @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 Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random);
/** /**
* {@return the axis-aligned bounding box of this hittable} * {@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.AABB;
import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum; import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra; import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree; import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
import eu.jonahbauer.raytracing.scene.util.HittableCollection; import eu.jonahbauer.raytracing.scene.util.HittableCollection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -12,6 +12,7 @@ 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;
@@ -42,8 +43,8 @@ public final class Scene extends HittableCollection {
} }
@Override @Override
public void hit(@NotNull Ray ray, @NotNull State state) { public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
objects.hit(ray, state); objects.hit(ray, state, random);
} }
@Override @Override

View File

@@ -1,8 +1,8 @@
package eu.jonahbauer.raytracing.scene; package eu.jonahbauer.raytracing.scene;
import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum; import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum; import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@FunctionalInterface @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.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;
@@ -36,7 +38,7 @@ public abstract class Hittable2D implements Hittable {
} }
@Override @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); 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

View File

@@ -48,7 +48,7 @@ public final class Box implements Hittable, Target {
} }
@Override @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 // 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[side.ordinal()]; material = materials[entry.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[side.ordinal()]; material = materials[exit.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)).isEmpty()) return 0; if (hit(new Ray(origin, direction), null).isEmpty()) return 0;
var solidAngle = 0d; var solidAngle = 0d;
for (var s : Side.values()) { for (var s : Side.values()) {

View File

@@ -10,15 +10,16 @@ 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) { public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
var hit1 = boundary.hit(ray, Range.UNIVERSE); var hit1 = boundary.hit(ray, Range.UNIVERSE, random);
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)); var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY), random);
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());
@@ -28,7 +29,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(Math.random()) / density; var hitDistance = - Math.log(random.nextDouble()) / density;
if (hitDistance > distance) return Optional.empty(); if (hitDistance > distance) return Optional.empty();
var t = tmin + hitDistance / length; 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.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;
@@ -40,7 +41,7 @@ public final class Sphere implements Hittable, Target {
} }
@Override @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); var t = hit0(ray, range);
if (Double.isNaN(t)) return Optional.empty(); 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.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;
@@ -24,7 +22,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) { public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
return object.hit(transform(ray), range).map(this::transform); 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.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;
@@ -46,17 +47,17 @@ public final class HittableBinaryTree extends HittableCollection {
} }
@Override @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 (!bbox.hit(ray, state.getRange())) return;
if (left instanceof HittableCollection coll) { if (left instanceof HittableCollection coll) {
coll.hit(ray, state); coll.hit(ray, state, random);
} else if (left != null) { } else if (left != null) {
hit(state, ray, left); hit(state, ray, left, random);
} }
if (right instanceof HittableCollection coll) { if (right instanceof HittableCollection coll) {
coll.hit(ray, state); coll.hit(ray, state, random);
} else if (right != null) { } 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.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) { public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
var state = new State(range); var state = new State(range);
hit(ray, state); hit(ray, state, random);
return state.getResult(); 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) { protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object, @NotNull RandomGenerator random) {
var r = object.hit(ray, state.range); var r = object.hit(ray, state.range, random);
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();

View File

@@ -7,6 +7,7 @@ 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;
@@ -22,8 +23,8 @@ public final class HittableList extends HittableCollection {
} }
@Override @Override
public void hit(@NotNull Ray ray, @NotNull State state) { public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
objects.forEach(object -> hit(state, ray, object)); objects.forEach(object -> hit(state, ray, object, random));
} }
@Override @Override

View File

@@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.render.canvas; 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 eu.jonahbauer.raytracing.render.ImageFormat;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir; 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.Range;
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.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.material.LambertianMaterial; import eu.jonahbauer.raytracing.render.material.LambertianMaterial;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;