Compare commits
9 Commits
00fbf4e4f1
...
feature/sp
Author | SHA1 | Date | |
---|---|---|---|
32b27e2225 | |||
871c837c34 | |||
9b72909d27 | |||
533461204a | |||
b8aae8c2e5 | |||
9eb8afcb59 | |||
75c56c0032 | |||
903ab1409b | |||
791ee606c4 |
@@ -1,11 +1,11 @@
|
||||
package eu.jonahbauer.raytracing;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.RGBIlluminantSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.CheckerTexture;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.camera.SimpleCamera;
|
||||
import eu.jonahbauer.raytracing.render.material.*;
|
||||
import eu.jonahbauer.raytracing.render.texture.ImageTexture;
|
||||
@@ -55,11 +55,11 @@ public class Examples {
|
||||
var cs = ColorSpaces.sRGB;
|
||||
return new Example(
|
||||
new Scene(getSkyBox(), List.of(
|
||||
new Sphere(new Vec3(0, -100.5, -1.0), 100.0, new LambertianMaterial(new ColorRGB(0.8, 0.8, 0.0), cs)),
|
||||
new Sphere(new Vec3(0, 0, -1.2), 0.5, new LambertianMaterial(new ColorRGB(0.1, 0.2, 0.5), cs)),
|
||||
new Sphere(new Vec3(0, -100.5, -1.0), 100.0, new LambertianMaterial(cs.albedo(0.8, 0.8, 0.0))),
|
||||
new Sphere(new Vec3(0, 0, -1.2), 0.5, new LambertianMaterial(cs.albedo(0.1, 0.2, 0.5))),
|
||||
new Sphere(new Vec3(-1.0, 0, -1.2), 0.5, new DielectricMaterial(1.5)),
|
||||
new Sphere(new Vec3(-1.0, 0, -1.2), 0.4, new DielectricMaterial(1 / 1.5)),
|
||||
new Sphere(new Vec3(1.0, 0, -1.2), 0.5, new MetallicMaterial(new ColorRGB(0.8, 0.6, 0.2), cs, 0.0))
|
||||
new Sphere(new Vec3(1.0, 0, -1.2), 0.5, new MetallicMaterial(cs.albedo(0.8, 0.6, 0.2)))
|
||||
)),
|
||||
SimpleCamera.builder()
|
||||
.withImage(height * 16 / 9, height)
|
||||
@@ -92,12 +92,12 @@ public class Examples {
|
||||
if (rnd < 0.8) {
|
||||
// diffuse
|
||||
var albedo = ColorRGB.random(rng).times(ColorRGB.random(rng));
|
||||
material = new LambertianMaterial(albedo, cs);
|
||||
material = new LambertianMaterial(cs.albedo(albedo));
|
||||
} else if (rnd < 0.95) {
|
||||
// metal
|
||||
var albedo = ColorRGB.random(rng, 0.5, 1.0);
|
||||
var fuzz = rng.nextDouble() * 0.5;
|
||||
material = new MetallicMaterial(albedo, cs, fuzz);
|
||||
material = new MetallicMaterial(cs.albedo(albedo), fuzz);
|
||||
} else {
|
||||
// glass
|
||||
material = new DielectricMaterial(1.5);
|
||||
@@ -108,8 +108,8 @@ public class Examples {
|
||||
}
|
||||
|
||||
objects.add(new Sphere(new Vec3(0, 1, 0), 1.0, new DielectricMaterial(1.5)));
|
||||
objects.add(new Sphere(new Vec3(-4, 1, 0), 1.0, new LambertianMaterial(new ColorRGB(0.4, 0.2, 0.1), cs)));
|
||||
objects.add(new Sphere(new Vec3(4, 1, 0), 1.0, new MetallicMaterial(new ColorRGB(0.7, 0.6, 0.5), cs)));
|
||||
objects.add(new Sphere(new Vec3(-4, 1, 0), 1.0, new LambertianMaterial(cs.albedo(0.4, 0.2, 0.1))));
|
||||
objects.add(new Sphere(new Vec3(4, 1, 0), 1.0, new MetallicMaterial(cs.albedo(0.7, 0.6, 0.5))));
|
||||
|
||||
var camera = SimpleCamera.builder()
|
||||
.withImage(height * 16 / 9, height)
|
||||
@@ -128,11 +128,11 @@ public class Examples {
|
||||
var cs = ColorSpaces.sRGB;
|
||||
return new Example(
|
||||
new Scene(getSkyBox(), List.of(
|
||||
new Parallelogram(new Vec3(-3, -2, 5), new Vec3(0, 0, -4), new Vec3(0, 4, 0), new LambertianMaterial(new ColorRGB(1.0, 0.2, 0.2), cs)),
|
||||
new Parallelogram(new Vec3(-2, -2, 0), new Vec3(4, 0, 0), new Vec3(0, 4, 0), new LambertianMaterial(new ColorRGB(0.2, 1.0, 0.2), cs)),
|
||||
new Parallelogram(new Vec3(3, -2, 1), new Vec3(0, 0, 4), new Vec3(0, 4, 0), new LambertianMaterial(new ColorRGB(0.2, 0.2, 1.0), cs)),
|
||||
new Parallelogram(new Vec3(-2, 3, 1), new Vec3(4, 0, 0), new Vec3(0, 0, 4), new LambertianMaterial(new ColorRGB(1.0, 0.5, 0.0), cs)),
|
||||
new Parallelogram(new Vec3(-2, -3, 5), new Vec3(4, 0, 0), new Vec3(0, 0, -4), new LambertianMaterial(new ColorRGB(0.2, 0.8, 0.8), cs))
|
||||
new Parallelogram(new Vec3(-3, -2, 5), new Vec3(0, 0, -4), new Vec3(0, 4, 0), new LambertianMaterial(cs.albedo(1.0, 0.2, 0.2))),
|
||||
new Parallelogram(new Vec3(-2, -2, 0), new Vec3(4, 0, 0), new Vec3(0, 4, 0), new LambertianMaterial(cs.albedo(0.2, 1.0, 0.2))),
|
||||
new Parallelogram(new Vec3(3, -2, 1), new Vec3(0, 0, 4), new Vec3(0, 4, 0), new LambertianMaterial(cs.albedo(0.2, 0.2, 1.0))),
|
||||
new Parallelogram(new Vec3(-2, 3, 1), new Vec3(4, 0, 0), new Vec3(0, 0, 4), new LambertianMaterial(cs.albedo(1.0, 0.5, 0.0))),
|
||||
new Parallelogram(new Vec3(-2, -3, 5), new Vec3(4, 0, 0), new Vec3(0, 0, -4), new LambertianMaterial(cs.albedo(0.2, 0.8, 0.8)))
|
||||
)),
|
||||
SimpleCamera.builder()
|
||||
.withImage(height, height)
|
||||
@@ -148,10 +148,10 @@ public class Examples {
|
||||
var cs = ColorSpaces.sRGB;
|
||||
return new Example(
|
||||
new Scene(List.of(
|
||||
new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(new ColorRGB(0.2, 0.2, 0.2), cs)),
|
||||
new Sphere(new Vec3(0, 2, 0), 2, new LambertianMaterial(new ColorRGB(0.2, 0.2, 0.2), cs)),
|
||||
new Parallelogram(new Vec3(3, 1, -2), new Vec3(2, 0, 0), new Vec3(0, 2, 0), new DiffuseLight(new ColorRGB(4.0, 4.0, 4.0), cs)),
|
||||
new Sphere(new Vec3(0, 7, 0), 2, new DiffuseLight(new ColorRGB(4.0, 4.0, 4.0), cs))
|
||||
new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(cs.albedo(0.2, 0.2, 0.2))),
|
||||
new Sphere(new Vec3(0, 2, 0), 2, new LambertianMaterial(cs.albedo(0.2, 0.2, 0.2))),
|
||||
new Parallelogram(new Vec3(3, 1, -2), new Vec3(2, 0, 0), new Vec3(0, 2, 0), new DiffuseLight(cs.illuminant(4.0))),
|
||||
new Sphere(new Vec3(0, 7, 0), 2, new DiffuseLight(cs.illuminant(4.0)))
|
||||
)),
|
||||
SimpleCamera.builder()
|
||||
.withImage(height * 16 / 9, height)
|
||||
@@ -166,10 +166,10 @@ public class Examples {
|
||||
if (height <= 0) height = 600;
|
||||
|
||||
var cs = ColorSpaces.sRGB;
|
||||
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs);
|
||||
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
|
||||
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs);
|
||||
var light = new DiffuseLight(new ColorRGB(15.0, 15.0, 15.0), cs);
|
||||
var red = new LambertianMaterial(cs.albedo(.65, .05, .05));
|
||||
var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
|
||||
var green = new LambertianMaterial(cs.albedo(.12, .45, .15));
|
||||
var light = new DiffuseLight(cs.illuminant(15.0));
|
||||
|
||||
return new Example(
|
||||
new Scene(List.of(
|
||||
@@ -194,10 +194,10 @@ public class Examples {
|
||||
public static @NotNull Example getCornellBoxSmoke(int height) {
|
||||
if (height <= 0) height = 600;
|
||||
var cs = ColorSpaces.sRGB;
|
||||
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs);
|
||||
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
|
||||
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs);
|
||||
var light = new DiffuseLight(new ColorRGB(15.0, 15.0, 15.0), cs);
|
||||
var red = new LambertianMaterial(cs.albedo(.65, .05, .05));
|
||||
var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
|
||||
var green = new LambertianMaterial(cs.albedo(.12, .45, .15));
|
||||
var light = new DiffuseLight(cs.illuminant(15.0));
|
||||
|
||||
return new Example(
|
||||
new Scene(List.of(
|
||||
@@ -207,13 +207,13 @@ public class Examples {
|
||||
new Box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white)
|
||||
.rotateY(Math.toRadians(15))
|
||||
.translate(new Vec3(265, 0, 295)),
|
||||
0.01, new IsotropicMaterial(ColorRGB.BLACK, cs)
|
||||
0.01, new IsotropicMaterial(cs.albedo(ColorRGB.BLACK))
|
||||
),
|
||||
new ConstantMedium(
|
||||
new Box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white)
|
||||
.rotateY(Math.toRadians(-18))
|
||||
.translate(new Vec3(130, 0, 65)),
|
||||
0.01, new IsotropicMaterial(ColorRGB.WHITE, cs)
|
||||
0.01, new IsotropicMaterial(cs.albedo(ColorRGB.WHITE))
|
||||
)
|
||||
)),
|
||||
SimpleCamera.builder()
|
||||
@@ -229,10 +229,10 @@ public class Examples {
|
||||
if (height <= 0) height = 600;
|
||||
|
||||
var cs = ColorSpaces.sRGB;
|
||||
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs);
|
||||
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
|
||||
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs);
|
||||
var light = new DiffuseLight(new ColorRGB(7.0, 7.0, 7.0), cs);
|
||||
var red = new LambertianMaterial(cs.albedo(.65, .05, .05));
|
||||
var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
|
||||
var green = new LambertianMaterial(cs.albedo(.12, .45, .15));
|
||||
var light = new DiffuseLight(cs.illuminant(7.0));
|
||||
var glass = new DielectricMaterial(1.5);
|
||||
|
||||
var room = new Box(new Vec3(0, 0, 0), new Vec3(555, 555, 555), white, white, red, green, white, null);
|
||||
@@ -259,15 +259,15 @@ public class Examples {
|
||||
var cs = ColorSpaces.sRGB;
|
||||
record Partei(String name, ColorRGB color, double stimmen) { }
|
||||
var data = List.of(
|
||||
new Partei("CDU", new ColorRGB(0x00, 0x4B, 0x76), 18.9),
|
||||
new Partei("SPD", new ColorRGB(0xC0, 0x00, 0x3C), 25.7),
|
||||
new Partei("AfD", new ColorRGB(0x80, 0xCD, 0xEC), 10.3),
|
||||
new Partei("FDP", new ColorRGB(0xF7, 0xBB, 0x3D), 11.5),
|
||||
new Partei("DIE LINKE", new ColorRGB(0x5F, 0x31, 0x6E), 4.9),
|
||||
new Partei("GRÜNE", new ColorRGB(0x00, 0x85, 0x4A), 14.8),
|
||||
new Partei("CSU", new ColorRGB(0x00, 0x77, 0xB6), 5.2)
|
||||
new Partei("CDU", new ColorRGB(0x004B76), 18.9),
|
||||
new Partei("SPD", new ColorRGB(0xC0003C), 25.7),
|
||||
new Partei("AfD", new ColorRGB(0x80CDEC), 10.3),
|
||||
new Partei("FDP", new ColorRGB(0xF7BB3D), 11.5),
|
||||
new Partei("DIE LINKE", new ColorRGB(0x5F316E), 4.9),
|
||||
new Partei("GRÜNE", new ColorRGB(0x00854A), 14.8),
|
||||
new Partei("CSU", new ColorRGB(0x0077B6), 5.2)
|
||||
);
|
||||
var white = new LambertianMaterial(new ColorRGB(.99, .99, .99), cs);
|
||||
var white = new LambertianMaterial(cs.albedo(.99, .99, .99));
|
||||
|
||||
var count = data.size();
|
||||
var size = 75d;
|
||||
@@ -287,7 +287,7 @@ public class Examples {
|
||||
objects.add(new Box(
|
||||
new Vec3((i + 1) * spacing + i * size, 0, spacing),
|
||||
new Vec3((i + 1) * spacing + (i + 1) * size, partei.stimmen() * 15, spacing + size),
|
||||
new DielectricMaterial(1.5, partei.color(), cs)
|
||||
new DielectricMaterial(1.5, cs.albedo(partei.color()))
|
||||
));
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ public class Examples {
|
||||
|
||||
// boxes
|
||||
var boxes = new ArrayList<Hittable>();
|
||||
var ground = new LambertianMaterial(new ColorRGB(0.48, 0.83, 0.53), cs);
|
||||
var ground = new LambertianMaterial(cs.albedo(0.48, 0.83, 0.53));
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 20; j++) {
|
||||
var w = 100.0;
|
||||
@@ -364,23 +364,23 @@ public class Examples {
|
||||
// light
|
||||
objects.add(new Parallelogram(
|
||||
new Vec3(123, 554, 147), new Vec3(300, 0, 0), new Vec3(0, 0, 265),
|
||||
new DiffuseLight(new ColorRGB(7., 7., 7.), cs)
|
||||
new DiffuseLight(cs.illuminant(7.0))
|
||||
));
|
||||
|
||||
// spheres with different materials
|
||||
objects.add(new Sphere(new Vec3(400, 400, 200), 50, new LambertianMaterial(new ColorRGB(0.7, 0.3, 0.1), cs)));
|
||||
objects.add(new Sphere(new Vec3(400, 400, 200), 50, new LambertianMaterial(cs.albedo(0.7, 0.3, 0.1))));
|
||||
objects.add(new Sphere(new Vec3(260, 150, 45), 50, new DielectricMaterial(1.5)));
|
||||
objects.add(new Sphere(new Vec3(0, 150, 145), 50, new MetallicMaterial(new ColorRGB(0.8, 0.8, 0.9), cs, 1.0)));
|
||||
objects.add(new Sphere(new Vec3(0, 150, 145), 50, new MetallicMaterial(cs.albedo(0.8, 0.8, 0.9), 1.0)));
|
||||
|
||||
// glass sphere filled with gas
|
||||
var boundary = new Sphere(new Vec3(360, 150, 145), 70, new DielectricMaterial(1.5));
|
||||
objects.add(boundary);
|
||||
objects.add(new ConstantMedium(boundary, 0.2, new IsotropicMaterial(new ColorRGB(0.2, 0.4, 0.9), cs)));
|
||||
objects.add(new ConstantMedium(boundary, 0.2, new IsotropicMaterial(cs.albedo(0.2, 0.4, 0.9))));
|
||||
|
||||
// put the world in a glass sphere
|
||||
objects.add(new ConstantMedium(
|
||||
new Sphere(new Vec3(0, 0, 0), 5000, new DielectricMaterial(1.5)),
|
||||
0.0001, new IsotropicMaterial(new ColorRGB(1., 1., 1.), cs)
|
||||
0.0001, new IsotropicMaterial(cs.albedo(1.0, 1.0, 1.0))
|
||||
));
|
||||
|
||||
// textures spheres
|
||||
@@ -388,7 +388,7 @@ public class Examples {
|
||||
objects.add(new Sphere(new Vec3(220, 280, 300), 80, new LambertianMaterial(new PerlinTexture(0.2))));
|
||||
|
||||
// box from spheres
|
||||
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
|
||||
var white = new LambertianMaterial(cs.albedo(.73, .73, .73));
|
||||
var spheres = new ArrayList<Hittable>();
|
||||
for (int j = 0; j < 1000; j++) {
|
||||
spheres.add(new Sphere(new Vec3(random.nextDouble(165), random.nextDouble(165), random.nextDouble(165)), 10, white));
|
||||
|
@@ -1,13 +1,11 @@
|
||||
package eu.jonahbauer.raytracing;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.ImageFormat;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||
import eu.jonahbauer.raytracing.render.canvas.LiveCanvas;
|
||||
import eu.jonahbauer.raytracing.render.canvas.XYZCanvas;
|
||||
import eu.jonahbauer.raytracing.render.image.PNGImageWriter;
|
||||
import eu.jonahbauer.raytracing.render.renderer.SimpleRenderer;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpaces;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -26,6 +24,7 @@ public class Main {
|
||||
|
||||
var renderer = SimpleRenderer.builder()
|
||||
.withSamplesPerPixel(config.samples)
|
||||
.withSpectralSamples(config.spectralSamples)
|
||||
.withMaxDepth(config.depth)
|
||||
.withIterative(config.iterative)
|
||||
.withParallel(config.parallel)
|
||||
@@ -44,10 +43,10 @@ public class Main {
|
||||
renderer.render(camera, scene, canvas);
|
||||
System.out.printf("rendering finished after %dms", (System.nanoTime() - time) / 1_000_000);
|
||||
|
||||
ImageFormat.PNG.write(canvas, config.path);
|
||||
PNGImageWriter.sRGB.write(canvas, config.path);
|
||||
}
|
||||
|
||||
private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int depth) {
|
||||
private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int spectralSamples, int depth) {
|
||||
public static @NotNull Config parse(@NotNull String @NotNull[] args) {
|
||||
IntFunction<Example> example = null;
|
||||
Path path = null;
|
||||
@@ -55,6 +54,7 @@ public class Main {
|
||||
boolean iterative = false;
|
||||
boolean parallel = false;
|
||||
int samples = 1000;
|
||||
int spectralSamples = 4;
|
||||
int depth = 50;
|
||||
int height = -1;
|
||||
|
||||
@@ -83,6 +83,15 @@ public class Main {
|
||||
throw fail("value " + args[i] + " is not a valid integer");
|
||||
}
|
||||
}
|
||||
case "--spectral-samples" -> {
|
||||
if (i + 1 == args.length) throw fail("missing value for parameter --spectral-samples");
|
||||
try {
|
||||
spectralSamples = Integer.parseInt(args[++i]);
|
||||
if (spectralSamples <= 0) throw fail("spectral samples must be positive");
|
||||
} catch (NumberFormatException ex) {
|
||||
throw fail("value " + args[i] + " is not a valid integer");
|
||||
}
|
||||
}
|
||||
case "--depth" -> {
|
||||
if (i + 1 == args.length) throw fail("missing value for parameter --depth");
|
||||
try {
|
||||
@@ -114,7 +123,7 @@ public class Main {
|
||||
|
||||
if (example == null) example = Examples::getCornellBoxSmoke;
|
||||
if (path == null) path = Path.of("scene-" + System.currentTimeMillis() + ".png");
|
||||
return new Config(example.apply(height), path, preview, iterative, parallel, samples, depth);
|
||||
return new Config(example.apply(height), path, preview, iterative, parallel, samples, spectralSamples, depth);
|
||||
}
|
||||
|
||||
private static @NotNull RuntimeException fail(@NotNull String message) {
|
||||
|
@@ -79,35 +79,39 @@ public record AABB(@NotNull Vec3 min, @NotNull Vec3 max) {
|
||||
* @return {@code true} iff the ray intersects this bounding box, {@code false} otherwise
|
||||
*/
|
||||
public boolean hit(@NotNull Ray ray, @NotNull Range range) {
|
||||
var origin = ray.origin();
|
||||
var direction = ray.direction();
|
||||
var invDirection = direction.inv();
|
||||
var invDirection = ray.getInvDirection();
|
||||
var negInvOrigin = ray.getNegInvOrigin();
|
||||
|
||||
// calculate t values for intersection points of ray with planes through min
|
||||
var tmin = intersect(min(), origin, invDirection);
|
||||
// calculate t values for intersection points of ray with planes through max
|
||||
var tmax = intersect(max(), origin, invDirection);
|
||||
var tminX = Math.fma(min.x(), invDirection.x(), negInvOrigin.x());
|
||||
var tminY = Math.fma(min.y(), invDirection.y(), negInvOrigin.y());
|
||||
var tminZ = Math.fma(min.z(), invDirection.z(), negInvOrigin.z());
|
||||
|
||||
// determine range of t for which the ray is inside this voxel
|
||||
double tlmax = Double.NEGATIVE_INFINITY; // lower limit maximum
|
||||
double tumin = Double.POSITIVE_INFINITY; // upper limit minimum
|
||||
var tmaxX = Math.fma(max.x(), invDirection.x(), negInvOrigin.x());
|
||||
var tmaxY = Math.fma(max.y(), invDirection.y(), negInvOrigin.y());
|
||||
var tmaxZ = Math.fma(max.z(), invDirection.z(), negInvOrigin.z());
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// classify t values as lower or upper limit based on ray direction
|
||||
if (direction.get(i) >= 0) {
|
||||
// min is lower limit and max is upper limit
|
||||
if (tmin[i] > tlmax) tlmax = tmin[i];
|
||||
if (tmax[i] < tumin) tumin = tmax[i];
|
||||
} else {
|
||||
// max is lower limit and min is upper limit
|
||||
if (tmax[i] > tlmax) tlmax = tmax[i];
|
||||
if (tmin[i] < tumin) tumin = tmin[i];
|
||||
}
|
||||
}
|
||||
var tlmax = max(
|
||||
Math.min(tminX, tmaxX),
|
||||
Math.min(tminY, tmaxY),
|
||||
Math.min(tminZ, tmaxZ)
|
||||
);
|
||||
var tumin = min(
|
||||
Math.max(tminX, tmaxX),
|
||||
Math.max(tminY, tmaxY),
|
||||
Math.max(tminZ, tmaxZ)
|
||||
);
|
||||
|
||||
return tlmax < tumin && tumin >= range.min() && tlmax <= range.max();
|
||||
}
|
||||
|
||||
private static double max(double a, double b, double c) {
|
||||
return Math.max(a, Math.max(b, c));
|
||||
}
|
||||
|
||||
private static double min(double a, double b, double c) {
|
||||
return Math.min(a, Math.min(b, c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the {@code t} values of the intersections of a ray with the axis-aligned planes through a point.
|
||||
* @param corner the point
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package eu.jonahbauer.raytracing.math;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -7,15 +8,26 @@ import org.jetbrains.annotations.NotNull;
|
||||
* @param <T> the type
|
||||
*/
|
||||
public interface IVec<T extends IVec<T>> {
|
||||
@Contract(pure = true)
|
||||
double get(int i);
|
||||
|
||||
@Contract(pure = true)
|
||||
@NotNull T plus(@NotNull T other);
|
||||
|
||||
@Contract(pure = true)
|
||||
@NotNull T minus(@NotNull T other);
|
||||
|
||||
@Contract(pure = true)
|
||||
@NotNull T times(@NotNull T other);
|
||||
|
||||
@Contract(pure = true)
|
||||
@NotNull T times(double d);
|
||||
|
||||
@Contract(pure = true)
|
||||
default @NotNull T div(double d) {
|
||||
return times(1 / d);
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
double @NotNull[] toArray();
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package eu.jonahbauer.raytracing.math;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -7,17 +8,23 @@ import org.jetbrains.annotations.NotNull;
|
||||
* @param <T> the type
|
||||
*/
|
||||
public interface IVec3<T extends Record & IVec3<T>> extends IVec<T> {
|
||||
@Contract(pure = true)
|
||||
default double component1() {
|
||||
return toVec3().x();
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
default double component2() {
|
||||
return toVec3().y();
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
default double component3() {
|
||||
return toVec3().z();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract(pure = true)
|
||||
default double get(int i) {
|
||||
return switch (i) {
|
||||
case 0 -> component1();
|
||||
@@ -27,9 +34,13 @@ public interface IVec3<T extends Record & IVec3<T>> extends IVec<T> {
|
||||
};
|
||||
}
|
||||
|
||||
@NotNull Vec3 toVec3();
|
||||
@Contract(pure = true)
|
||||
default @NotNull Vec3 toVec3() {
|
||||
return new Vec3(component1(), component2(), component3());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Contract(pure = true)
|
||||
default double @NotNull [] toArray() {
|
||||
return new double[] {component1(), component2(), component3()};
|
||||
}
|
||||
|
@@ -1,22 +1,97 @@
|
||||
package eu.jonahbauer.raytracing.math;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda) {
|
||||
public Ray {
|
||||
Objects.requireNonNull(origin, "origin");
|
||||
Objects.requireNonNull(direction, "direction");
|
||||
Objects.requireNonNull(lambda, "lambda");
|
||||
}
|
||||
public final class Ray {
|
||||
private final @NotNull Vec3 origin;
|
||||
private final @NotNull Vec3 direction;
|
||||
private final @NotNull SampledWavelengths lambda;
|
||||
|
||||
private final @NotNull Vec3 inv;
|
||||
private final @NotNull Vec3 negInvOrigin;
|
||||
|
||||
public Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||
this(origin, direction, SampledWavelengths.EMPTY);
|
||||
}
|
||||
|
||||
public Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda) {
|
||||
this.origin = Objects.requireNonNull(origin, "origin");
|
||||
this.direction = Objects.requireNonNull(direction, "direction");
|
||||
this.lambda = Objects.requireNonNull(lambda, "lambda");
|
||||
|
||||
this.inv = direction.inv();
|
||||
this.negInvOrigin = inv.neg().times(origin);
|
||||
}
|
||||
|
||||
private Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda, @NotNull Vec3 inv, @NotNull Vec3 negInvOrigin) {
|
||||
this.origin = origin;
|
||||
this.direction = direction;
|
||||
this.lambda = lambda;
|
||||
this.inv = inv;
|
||||
this.negInvOrigin = negInvOrigin;
|
||||
}
|
||||
|
||||
public @NotNull Vec3 at(double t) {
|
||||
return Vec3.fma(t, direction, origin);
|
||||
}
|
||||
|
||||
public @NotNull Ray with(@NotNull HitResult hit, @NotNull Vec3 direction) {
|
||||
return new Ray(hit.position(), direction, lambda);
|
||||
}
|
||||
|
||||
public @NotNull Ray with(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||
return new Ray(origin, direction, lambda);
|
||||
}
|
||||
|
||||
public @NotNull Ray with(@NotNull SampledWavelengths lambda) {
|
||||
return new Ray(origin, direction, lambda, inv, negInvOrigin);
|
||||
}
|
||||
|
||||
public @NotNull Vec3 origin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
public @NotNull Vec3 direction() {
|
||||
return direction;
|
||||
}
|
||||
|
||||
public @NotNull SampledWavelengths lambda() {
|
||||
return lambda;
|
||||
}
|
||||
|
||||
public @NotNull Vec3 getInvDirection() {
|
||||
return inv;
|
||||
}
|
||||
|
||||
public @NotNull Vec3 getNegInvOrigin() {
|
||||
return negInvOrigin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) return true;
|
||||
if (obj == null || obj.getClass() != this.getClass()) return false;
|
||||
var that = (Ray) obj;
|
||||
return Objects.equals(this.origin, that.origin) &&
|
||||
Objects.equals(this.direction, that.direction) &&
|
||||
Objects.equals(this.lambda, that.lambda);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(origin, direction, lambda);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return "Ray[" +
|
||||
"origin=" + origin + ", " +
|
||||
"direction=" + direction + ", " +
|
||||
"lambda=" + lambda + ']';
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -160,6 +160,14 @@ public record Vec3(double x, double y, double z) implements IVec3<Vec3> {
|
||||
);
|
||||
}
|
||||
|
||||
public static @NotNull Vec3 fma(@NotNull Vec3 a, @NotNull Vec3 b, @NotNull Vec3 c) {
|
||||
return new Vec3(
|
||||
Math.fma(a.x(), b.x(), c.x()),
|
||||
Math.fma(a.y(), b.y(), c.y()),
|
||||
Math.fma(a.z(), b.z(), c.z())
|
||||
);
|
||||
}
|
||||
|
||||
public static double tripleProduct(@NotNull Vec3 a, @NotNull Vec3 b, @NotNull Vec3 c) {
|
||||
return a.x * b.y * c.z + a.y * b.z * c.x + a.z * b.x * c.y - c.x * b.y * a.z - c.y * b.z * a.x - c.z * b.x * a.y;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
@@ -2,7 +2,6 @@ package eu.jonahbauer.raytracing.render.camera;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -94,7 +93,7 @@ public final class SimpleCamera implements Camera {
|
||||
|
||||
var origin = getRayOrigin(random);
|
||||
var target = getRayTarget(x, y, i, j, n, random);
|
||||
return new Ray(origin, target.minus(origin), SampledWavelengths.uniform(random.nextDouble()));
|
||||
return new Ray(origin, target.minus(origin));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface Canvas {
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -36,7 +36,7 @@ public final class LiveCanvas implements Canvas {
|
||||
@Override
|
||||
public void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda) {
|
||||
delegate.add(x, y, n, spectrum, lambda);
|
||||
var color = ColorRGB.gamma(delegate.getRGB(x, y, cs));
|
||||
var color = cs.encode(delegate.getRGB(x, y, cs));
|
||||
var rgb = color.red() << 16 | color.green() << 8 | color.blue();
|
||||
image.setRGB(x, y, rgb);
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
@@ -1,10 +1,10 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
@@ -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
|
@@ -1,13 +1,10 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.colors;
|
||||
package eu.jonahbauer.raytracing.render.color;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.IVec3;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static eu.jonahbauer.raytracing.Main.DEBUG;
|
||||
|
||||
public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB> {
|
||||
public static final @NotNull ColorRGB BLACK = new ColorRGB(0.0, 0.0, 0.0);
|
||||
public static final @NotNull ColorRGB WHITE = new ColorRGB(1.0, 1.0, 1.0);
|
||||
@@ -26,17 +23,13 @@ public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB>
|
||||
}
|
||||
|
||||
public ColorRGB(int rgb) {
|
||||
this((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
|
||||
}
|
||||
|
||||
public ColorRGB(int red, int green, int blue) {
|
||||
this(red / 255f, green / 255f, blue / 255f);
|
||||
this(((rgb >> 16) & 0xFF) / 255d, ((rgb >> 8) & 0xFF) / 255d, (rgb & 0xFF) / 255d);
|
||||
}
|
||||
|
||||
public ColorRGB {
|
||||
if (DEBUG && (!Double.isFinite(r) || !Double.isFinite(g) || !Double.isFinite(b))) {
|
||||
throw new IllegalArgumentException("r, g and b must be finite");
|
||||
}
|
||||
assert Double.isFinite(r) : "r must be finite";
|
||||
assert Double.isFinite(g) : "g must be finite";
|
||||
assert Double.isFinite(b) : "b must be finite";
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -65,24 +58,6 @@ public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB>
|
||||
);
|
||||
}
|
||||
|
||||
public static @NotNull ColorRGB gamma(@NotNull ColorRGB color) {
|
||||
return new ColorRGB(gamma(color.r), gamma(color.g), gamma(color.b));
|
||||
}
|
||||
|
||||
public static @NotNull ColorRGB inverseGamma(@NotNull ColorRGB color) {
|
||||
return new ColorRGB(inverseGamma(color.r), inverseGamma(color.g), inverseGamma(color.b));
|
||||
}
|
||||
|
||||
private static double gamma(double value) {
|
||||
if (value <= 0.0031308) return 12.92 * value;
|
||||
return 1.055 * Math.pow(value, 1. / 2.4) - 0.055;
|
||||
}
|
||||
|
||||
private static double inverseGamma(double value) {
|
||||
if (value <= 0.04045) return value / 12.92;
|
||||
return Math.pow((value + 0.055) / 1.055, 2.4d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ColorRGB plus(@NotNull ColorRGB other) {
|
||||
return new ColorRGB(r + other.r, g + other.g, b + other.b);
|
||||
@@ -103,19 +78,6 @@ public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB>
|
||||
return new ColorRGB(r * other.r, g * other.g, b * other.b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Vec3
|
||||
*/
|
||||
|
||||
@Override
|
||||
public @NotNull Vec3 toVec3() {
|
||||
return new Vec3(r, g, b);
|
||||
}
|
||||
|
||||
public static @NotNull ColorRGB fromVec3(@NotNull Vec3 vec) {
|
||||
return new ColorRGB(vec.x(), vec.y(), vec.z());
|
||||
}
|
||||
|
||||
/*
|
||||
* Accessors
|
||||
*/
|
@@ -1,9 +1,8 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.colors;
|
||||
package eu.jonahbauer.raytracing.render.color;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Matrix3;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.DenselySampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -26,16 +25,18 @@ public final class ColorSpace {
|
||||
private final @NotNull Matrix3 XYZfromRGB;
|
||||
private final @NotNull Matrix3 RGBfromXYZ;
|
||||
private final @NotNull SpectrumTable RGBtoSpectrumTable;
|
||||
private final @NotNull TransferFunction transferFunction;
|
||||
|
||||
public ColorSpace(
|
||||
@NotNull Chromaticity r, @NotNull Chromaticity g, @NotNull Chromaticity b,
|
||||
@NotNull Spectrum illuminant, @NotNull SpectrumTable table
|
||||
@NotNull Spectrum illuminant, @NotNull SpectrumTable table, @NotNull TransferFunction transferFunction
|
||||
) {
|
||||
this.r = Objects.requireNonNull(r, "r");
|
||||
this.g = Objects.requireNonNull(g, "g");
|
||||
this.b = Objects.requireNonNull(b, "b");
|
||||
this.illuminant = new DenselySampledSpectrum(illuminant);
|
||||
this.RGBtoSpectrumTable = table;
|
||||
this.RGBtoSpectrumTable = table; // no null-check
|
||||
this.transferFunction = transferFunction; // no null-check
|
||||
|
||||
this.W = illuminant.toXYZ();
|
||||
this.w = W.xy();
|
||||
@@ -54,6 +55,10 @@ public final class ColorSpace {
|
||||
this.RGBfromXYZ = XYZfromRGB.invert();
|
||||
}
|
||||
|
||||
/*
|
||||
* Conversions
|
||||
*/
|
||||
|
||||
public @NotNull ColorRGB toRGB(@NotNull ColorXYZ xyz) {
|
||||
var out = RGBfromXYZ.times(xyz.toVec3());
|
||||
return new ColorRGB(out.x(), out.y(), out.z());
|
||||
@@ -61,7 +66,7 @@ public final class ColorSpace {
|
||||
|
||||
public @NotNull ColorXYZ toXYZ(@NotNull ColorRGB rgb) {
|
||||
var out = XYZfromRGB.times(rgb.toVec3());
|
||||
return ColorXYZ.fromVec3(out);
|
||||
return new ColorXYZ(out);
|
||||
}
|
||||
|
||||
public @NotNull Vec3 toCIELab(@NotNull ColorRGB rgb) {
|
||||
@@ -85,7 +90,19 @@ public final class ColorSpace {
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull SigmoidPolynomial toSpectrum(@NotNull ColorRGB rgb) {
|
||||
public @NotNull ColorRGB encode(@NotNull ColorRGB rgb) {
|
||||
return transferFunction.encode(rgb);
|
||||
}
|
||||
|
||||
public @NotNull ColorRGB decode(@NotNull ColorRGB rgb) {
|
||||
return transferFunction.decode(rgb);
|
||||
}
|
||||
|
||||
/*
|
||||
* Spectrum
|
||||
*/
|
||||
|
||||
public @NotNull SigmoidPolynomial toPolynomial(@NotNull ColorRGB rgb) {
|
||||
return RGBtoSpectrumTable.get(new ColorRGB(
|
||||
Math.max(0, rgb.r()),
|
||||
Math.max(0, rgb.g()),
|
||||
@@ -93,6 +110,38 @@ public final class ColorSpace {
|
||||
));
|
||||
}
|
||||
|
||||
public @NotNull Spectrum toSpectrum(@NotNull ColorRGB rgb, @NotNull Spectrum.Type type) {
|
||||
return switch (type) {
|
||||
case ALBEDO -> new RGBAlbedoSpectrum(this, rgb);
|
||||
case ILLUMINANT -> new RGBIlluminantSpectrum(this, rgb);
|
||||
case UNBOUNDED -> new RGBUnboundedSpectrum(this, rgb);
|
||||
};
|
||||
}
|
||||
|
||||
public @NotNull Spectrum albedo(double r, double g, double b) {
|
||||
return albedo(new ColorRGB(r, g, b));
|
||||
}
|
||||
|
||||
public @NotNull Spectrum albedo(@NotNull ColorRGB rgb) {
|
||||
return toSpectrum(rgb, Spectrum.Type.ALBEDO);
|
||||
}
|
||||
|
||||
public @NotNull Spectrum illuminant(double intensity) {
|
||||
return illuminant.scale(intensity);
|
||||
}
|
||||
|
||||
public @NotNull Spectrum illuminant(double r, double g, double b) {
|
||||
return illuminant(new ColorRGB(r, g, b));
|
||||
}
|
||||
|
||||
public @NotNull Spectrum illuminant(@NotNull ColorRGB rgb) {
|
||||
return toSpectrum(rgb, Spectrum.Type.ILLUMINANT);
|
||||
}
|
||||
|
||||
/*
|
||||
* Accessors
|
||||
*/
|
||||
|
||||
public @NotNull Chromaticity r() {
|
||||
return r;
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.colors;
|
||||
package eu.jonahbauer.raytracing.render.color;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -13,21 +13,21 @@ public final class ColorSpaces {
|
||||
new Chromaticity(0.6400, 0.3300),
|
||||
new Chromaticity(0.3000, 0.6000),
|
||||
new Chromaticity(0.1500, 0.0600),
|
||||
Spectra.D65, read("sRGB_spectrum.bin")
|
||||
Spectra.D65, read("sRGB_spectrum.bin"), TransferFunctions.sRGB
|
||||
);
|
||||
// P3-D65 (display)
|
||||
public static final @NotNull ColorSpace DCI_P3 = new ColorSpace(
|
||||
new Chromaticity(0.680, 0.320),
|
||||
new Chromaticity(0.265, 0.690),
|
||||
new Chromaticity(0.150, 0.060),
|
||||
Spectra.D65, read("DCI_P3_spectrum.bin")
|
||||
Spectra.D65, read("DCI_P3_spectrum.bin"), TransferFunctions.sRGB
|
||||
);
|
||||
// ITU-R Rec BT.2020
|
||||
public static final @NotNull ColorSpace Rec2020 = new ColorSpace(
|
||||
new Chromaticity(0.708, 0.292),
|
||||
new Chromaticity(0.170, 0.797),
|
||||
new Chromaticity(0.131, 0.046),
|
||||
Spectra.D65, read("Rec2020_spectrum.bin")
|
||||
Spectra.D65, read("Rec2020_spectrum.bin"), null
|
||||
);
|
||||
|
||||
private static @NotNull SpectrumTable read(@NotNull String name) {
|
@@ -1,4 +1,4 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.colors;
|
||||
package eu.jonahbauer.raytracing.render.color;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.IVec3;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
@@ -25,6 +25,16 @@ public record ColorXYZ(double x, double y, double z) implements IVec3<ColorXYZ>
|
||||
);
|
||||
}
|
||||
|
||||
public ColorXYZ(@NotNull Vec3 vec) {
|
||||
this(vec.x(), vec.y(), vec.z());
|
||||
}
|
||||
|
||||
public ColorXYZ {
|
||||
assert Double.isFinite(x) : "x must be finite";
|
||||
assert Double.isFinite(y) : "y must be finite";
|
||||
assert Double.isFinite(z) : "z must be finite";
|
||||
}
|
||||
|
||||
/*
|
||||
* Math
|
||||
*/
|
||||
@@ -71,19 +81,6 @@ public record ColorXYZ(double x, double y, double z) implements IVec3<ColorXYZ>
|
||||
return new ColorXYZ(x * d, y * d, z * d);
|
||||
}
|
||||
|
||||
/*
|
||||
* Vec3
|
||||
*/
|
||||
|
||||
@Override
|
||||
public @NotNull Vec3 toVec3() {
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
|
||||
public static @NotNull ColorXYZ fromVec3(@NotNull Vec3 vec) {
|
||||
return new ColorXYZ(vec.x(), vec.y(), vec.z());
|
||||
}
|
||||
|
||||
/*
|
||||
* Accessors
|
||||
*/
|
@@ -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
|
@@ -1,4 +1,4 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.colors;
|
||||
package eu.jonahbauer.raytracing.render.color;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.colors;
|
||||
package eu.jonahbauer.raytracing.render.color;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Matrix3;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
@@ -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);
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
@@ -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;
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,10 +2,7 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -14,38 +11,75 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public record DielectricMaterial(double refractionIndex, @NotNull Texture texture) implements Material {
|
||||
public DielectricMaterial(double refractionIndex) {
|
||||
this(refractionIndex, Spectra.WHITE);
|
||||
}
|
||||
|
||||
public record DielectricMaterial(@NotNull RefractiveIndex ri, @NotNull Texture texture) implements Material {
|
||||
public DielectricMaterial {
|
||||
Objects.requireNonNull(ri, "ri");
|
||||
Objects.requireNonNull(texture, "texture");
|
||||
}
|
||||
|
||||
public DielectricMaterial(double refractionIndex, @NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(refractionIndex, new RGBAlbedoSpectrum(cs, color));
|
||||
public DielectricMaterial(@NotNull RefractiveIndex ri) {
|
||||
this(ri, Spectra.WHITE);
|
||||
}
|
||||
|
||||
public DielectricMaterial(double ri) {
|
||||
this(new ConstantRefractiveIndex(ri), Spectra.WHITE);
|
||||
}
|
||||
|
||||
public DielectricMaterial(double ri, @NotNull Texture texture) {
|
||||
this(new ConstantRefractiveIndex(ri), texture);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
var ri = hit.isFrontFace() ? (1 / refractionIndex) : refractionIndex;
|
||||
var ri = switch (this.ri) {
|
||||
case ConstantRefractiveIndex(var x) -> x;
|
||||
case RefractiveIndex x -> x.get(ray.lambda().collapse());
|
||||
};
|
||||
if (hit.isFrontFace()) ri = 1 / ri;
|
||||
|
||||
var cosTheta = Math.min(- ray.direction().unit().dot(hit.normal()), 1.0);
|
||||
var reflectance = reflectance(cosTheta);
|
||||
var reflectance = reflectance(cosTheta, ri);
|
||||
var reflect = reflectance > random.nextDouble();
|
||||
|
||||
var newDirection = (reflect ? Optional.<Vec3>empty() : Vec3.refract(ray.direction(), hit.normal(), ri))
|
||||
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
|
||||
|
||||
var attenuation = texture.get(hit);
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
|
||||
return Optional.of(new SpecularScatterResult(attenuation, ray.with(hit, newDirection)));
|
||||
}
|
||||
|
||||
private double reflectance(double cos) {
|
||||
private double reflectance(double cos, double ri) {
|
||||
// use schlick's approximation for reflectance
|
||||
var r0 = (1 - refractionIndex) / (1 + refractionIndex);
|
||||
var r0 = (1 - ri) / (1 + ri);
|
||||
r0 = r0 * r0;
|
||||
return r0 + (1 - r0) * (1 - cos) * (1 - cos) * (1 - cos) * (1 - cos) * (1 - cos);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RefractiveIndex {
|
||||
double get(double lambda);
|
||||
}
|
||||
|
||||
public record ConstantRefractiveIndex(double ri) implements RefractiveIndex {
|
||||
|
||||
@Override
|
||||
public double get(double lambda) {
|
||||
return ri;
|
||||
}
|
||||
}
|
||||
|
||||
public record SellmeierRefractiveIndex(
|
||||
double B1, double B2, double B3,
|
||||
double C1, double C2, double C3
|
||||
) implements RefractiveIndex {
|
||||
|
||||
@Override
|
||||
public double get(double lambda) {
|
||||
var l2 = lambda * lambda * 1E-6; // square and convert to µm
|
||||
var x = 1 + B1 * l2 / (l2 - C1)
|
||||
+ B2 * l2 / (l2 - C2)
|
||||
+ B3 * l2 / (l2 - C3);
|
||||
return Math.sqrt(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -18,10 +15,6 @@ public record DiffuseLight(@NotNull Texture texture) implements Material {
|
||||
Objects.requireNonNull(texture, "texture");
|
||||
}
|
||||
|
||||
public DiffuseLight(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(new RGBIlluminantSpectrum(cs, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
return Optional.empty();
|
||||
|
@@ -2,8 +2,8 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -41,7 +41,7 @@ public final class DirectionalMaterial implements Material {
|
||||
if (back != null) return back.scatter(ray, hit, random);
|
||||
}
|
||||
// let the ray pass through without obstruction
|
||||
return Optional.of(new SpecularScatterResult(Spectra.WHITE, new Ray(ray.at(hit.t()), ray.direction(), ray.lambda())));
|
||||
return Optional.of(new SpecularScatterResult(Spectra.WHITE, ray.with(hit, ray.direction())));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -2,10 +2,7 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.SphereProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -19,10 +16,6 @@ public record IsotropicMaterial(@NotNull Spectrum albedo) implements Material {
|
||||
Objects.requireNonNull(albedo, "albedo");
|
||||
}
|
||||
|
||||
public IsotropicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(new RGBAlbedoSpectrum(cs, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
return Optional.of(new PdfScatterResult(albedo(), new SphereProbabilityDensityFunction()));
|
||||
|
@@ -2,9 +2,6 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.CosineProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -18,10 +15,6 @@ public record LambertianMaterial(@NotNull Texture texture) implements Material {
|
||||
Objects.requireNonNull(texture, "texture");
|
||||
}
|
||||
|
||||
public LambertianMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(new RGBAlbedoSpectrum(cs, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
var attenuation = texture.get(hit);
|
||||
|
@@ -2,9 +2,9 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.ProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -2,9 +2,6 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -19,14 +16,6 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
|
||||
this(texture, 0);
|
||||
}
|
||||
|
||||
public MetallicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(color, cs, 0);
|
||||
}
|
||||
|
||||
public MetallicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs, double fuzz) {
|
||||
this(new RGBAlbedoSpectrum(cs, color), fuzz);
|
||||
}
|
||||
|
||||
public MetallicMaterial {
|
||||
Objects.requireNonNull(texture, "texture");
|
||||
if (fuzz < 0 || !Double.isFinite(fuzz)) throw new IllegalArgumentException("fuzz must be non-negative");
|
||||
@@ -39,6 +28,6 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
|
||||
newDirection = Vec3.fma(fuzz, Vec3.random(random), newDirection.unit());
|
||||
}
|
||||
var attenuation = texture.get(hit);
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
|
||||
return Optional.of(new SpecularScatterResult(attenuation, ray.with(hit, newDirection)));
|
||||
}
|
||||
}
|
||||
|
@@ -4,9 +4,10 @@ import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.material.Material;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.camera.Camera;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.scene.Scene;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -20,7 +21,10 @@ import static eu.jonahbauer.raytracing.Main.DEBUG;
|
||||
public final class SimpleRenderer implements Renderer {
|
||||
private final int sqrtSamplesPerPixel;
|
||||
private final int maxDepth;
|
||||
private final double gamma;
|
||||
|
||||
private final int spectralSamples;
|
||||
private final SampledSpectrum black;
|
||||
private final SampledSpectrum white;
|
||||
|
||||
private final boolean parallel;
|
||||
private final boolean iterative;
|
||||
@@ -36,7 +40,10 @@ public final class SimpleRenderer implements Renderer {
|
||||
private SimpleRenderer(@NotNull Builder builder) {
|
||||
this.sqrtSamplesPerPixel = (int) Math.sqrt(builder.samplesPerPixel);
|
||||
this.maxDepth = builder.maxDepth;
|
||||
this.gamma = builder.gamma;
|
||||
|
||||
this.spectralSamples = builder.spectralSamples;
|
||||
this.black = new SampledSpectrum(spectralSamples, 0);
|
||||
this.white = new SampledSpectrum(spectralSamples, 1);
|
||||
|
||||
this.parallel = builder.parallel;
|
||||
this.iterative = builder.iterative;
|
||||
@@ -96,7 +103,8 @@ public final class SimpleRenderer implements Renderer {
|
||||
int i = 0;
|
||||
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
|
||||
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
|
||||
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random);
|
||||
var lambda = SampledWavelengths.uniform(random.nextDouble(), spectralSamples);
|
||||
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random).with(lambda);
|
||||
if (DEBUG) {
|
||||
System.out.println("Casting ray " + ray + " through pixel (" + x + "," + y + ") at subpixel (" + si + "," + sj + ")...");
|
||||
}
|
||||
@@ -116,11 +124,11 @@ public final class SimpleRenderer implements Renderer {
|
||||
}
|
||||
|
||||
private @NotNull SampledSpectrum getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
|
||||
var color = SampledSpectrum.BLACK;
|
||||
var attenuation = SampledSpectrum.WHITE;
|
||||
var color = black;
|
||||
var attenuation = white;
|
||||
|
||||
while (depth-- > 0) {
|
||||
var optional = scene.hit(ray);
|
||||
var optional = scene.hit(ray, random);
|
||||
if (optional.isEmpty()) {
|
||||
var background = scene.getBackgroundColor(ray);
|
||||
color = SampledSpectrum.fma(attenuation, background, color);
|
||||
@@ -136,7 +144,7 @@ public final class SimpleRenderer implements Renderer {
|
||||
}
|
||||
var material = hit.material();
|
||||
var emitted = material.emitted(hit).sample(ray.lambda());
|
||||
if (DEBUG && !SampledSpectrum.BLACK.equals(emitted)) {
|
||||
if (DEBUG && !black.equals(emitted)) {
|
||||
System.out.println(" Emitted: " + emitted);
|
||||
}
|
||||
|
||||
@@ -213,7 +221,7 @@ public final class SimpleRenderer implements Renderer {
|
||||
public static class Builder {
|
||||
private int samplesPerPixel = 100;
|
||||
private int maxDepth = 10;
|
||||
private double gamma = 2.0;
|
||||
private int spectralSamples = 4;
|
||||
private boolean parallel = true;
|
||||
private boolean iterative = false;
|
||||
|
||||
@@ -229,9 +237,9 @@ public final class SimpleRenderer implements Renderer {
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NotNull Builder withGamma(double gamma) {
|
||||
if (gamma <= 0 || !Double.isFinite(gamma)) throw new IllegalArgumentException("gamma must be positive");
|
||||
this.gamma = gamma;
|
||||
public @NotNull Builder withSpectralSamples(int samples) {
|
||||
if (samples <= 0) throw new IllegalArgumentException("samples must be positive");
|
||||
this.spectralSamples = samples;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
public final class BlackbodySpectrum implements Spectrum {
|
||||
/**
|
@@ -1,4 +1,4 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
/**
|
||||
* A constant spectrum.
|
@@ -1,4 +1,4 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class RGBAlbedoSpectrum implements Spectrum {
|
||||
@@ -12,7 +12,7 @@ public final class RGBAlbedoSpectrum implements Spectrum {
|
||||
if (rgb.r() < 0 || rgb.r() > 1 || rgb.g() < 0 || rgb.g() > 1 || rgb.b() < 0 || rgb.b() > 1) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
this.polynomial = cs.toSpectrum(rgb);
|
||||
this.polynomial = cs.toPolynomial(rgb);
|
||||
}
|
||||
|
||||
@Override
|
@@ -1,8 +1,8 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -20,7 +20,7 @@ public final class RGBIlluminantSpectrum implements Spectrum {
|
||||
}
|
||||
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
|
||||
this.scale = 2 * max;
|
||||
this.polynomial = cs.toSpectrum(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
|
||||
this.polynomial = cs.toPolynomial(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
|
||||
this.illuminant = cs.illuminant();
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.color.SigmoidPolynomial;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class RGBUnboundedSpectrum implements Spectrum {
|
||||
@@ -15,7 +15,7 @@ public final class RGBUnboundedSpectrum implements Spectrum {
|
||||
}
|
||||
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
|
||||
this.scale = 2 * max;
|
||||
this.polynomial = cs.toSpectrum(scale == 0 ? rgb.div(scale) : ColorRGB.BLACK);
|
||||
this.polynomial = cs.toPolynomial(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
|
||||
}
|
||||
|
||||
@Override
|
@@ -1,26 +1,15 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.IVec;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO use Vector API to parallelize operations
|
||||
public final class SampledSpectrum implements IVec<SampledSpectrum> {
|
||||
public static final SampledSpectrum BLACK;
|
||||
public static final SampledSpectrum WHITE;
|
||||
|
||||
static {
|
||||
BLACK = new SampledSpectrum(new double[SampledWavelengths.SAMPLES]);
|
||||
var one = new double[SampledWavelengths.SAMPLES];
|
||||
Arrays.fill(one, 1);
|
||||
WHITE = new SampledSpectrum(one);
|
||||
}
|
||||
|
||||
private final double @NotNull[] values;
|
||||
|
||||
public SampledSpectrum(@NotNull SampledWavelengths lambdas, @NotNull Spectrum spectrum) {
|
||||
@@ -31,6 +20,12 @@ public final class SampledSpectrum implements IVec<SampledSpectrum> {
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public SampledSpectrum(int count, double value) {
|
||||
var values = new double[count];
|
||||
Arrays.fill(values, value);
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
private SampledSpectrum(double @NotNull[] values) {
|
||||
this.values = values;
|
||||
}
|
@@ -1,8 +1,7 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -11,32 +10,31 @@ import java.util.Arrays;
|
||||
* A set of sampled wavelength that can be tracked together.
|
||||
*/
|
||||
public final class SampledWavelengths {
|
||||
public static final int SAMPLES = 4;
|
||||
public static final SampledWavelengths EMPTY = new SampledWavelengths(new double[0], new double[0]);
|
||||
|
||||
private final double @NotNull[] lambdas;
|
||||
private final double @NotNull[] pdf;
|
||||
|
||||
public static @NotNull SampledWavelengths uniform(double rng) {
|
||||
return uniform(rng, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX);
|
||||
public static @NotNull SampledWavelengths uniform(double rng, int count) {
|
||||
return uniform(rng, count, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX);
|
||||
}
|
||||
|
||||
public static @NotNull SampledWavelengths uniform(double rng, double min, double max) {
|
||||
var lambdas = new double[SAMPLES];
|
||||
public static @NotNull SampledWavelengths uniform(double rng, int count, double min, double max) {
|
||||
var lambdas = new double[count];
|
||||
|
||||
// choose first sample at random
|
||||
lambdas[0] = (1 - rng) * min + rng * max;
|
||||
|
||||
// choose next samples in equal intervals, wrapping if necessary
|
||||
var delta = (max - min) / SAMPLES;
|
||||
for (int i = 1; i < SAMPLES; i++) {
|
||||
var delta = (max - min) / count;
|
||||
for (int i = 1; i < count; i++) {
|
||||
lambdas[i] = lambdas[i - 1] + delta;
|
||||
if (lambdas[i] > max) {
|
||||
lambdas[i] = min + (lambdas[i] - max);
|
||||
}
|
||||
}
|
||||
|
||||
var pdf = new double[SAMPLES];
|
||||
var pdf = new double[count];
|
||||
Arrays.fill(pdf, 1 / (max - min));
|
||||
return new SampledWavelengths(lambdas, pdf);
|
||||
}
|
||||
@@ -46,10 +44,12 @@ public final class SampledWavelengths {
|
||||
this.pdf = pdf;
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public double get(int index) {
|
||||
return lambdas[index];
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
public int size() {
|
||||
return lambdas.length;
|
||||
}
|
||||
@@ -58,12 +58,13 @@ public final class SampledWavelengths {
|
||||
* Terminates the secondary wavelengths. This method should be called after a wavelength-dependent operation like
|
||||
* refraction that makes it incorrect to track multiple wavelengths together.
|
||||
*/
|
||||
public @NotNull SampledWavelengths collapse() {
|
||||
if (pdf.length < 2 || pdf[1] == 0) return this;
|
||||
var newPdf = Arrays.copyOf(pdf, pdf.length);
|
||||
Arrays.fill(newPdf, 1, newPdf.length, 0d);
|
||||
newPdf[0] /= newPdf.length;
|
||||
return new SampledWavelengths(lambdas, newPdf);
|
||||
@Contract(mutates = "this")
|
||||
public double collapse() {
|
||||
if (pdf.length >= 2 || pdf[1] != 0) {
|
||||
Arrays.fill(pdf, 1, pdf.length, 0d);
|
||||
pdf[0] /= pdf.length;
|
||||
}
|
||||
return lambdas[0];
|
||||
}
|
||||
|
||||
/*
|
@@ -1,4 +1,4 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.BufferedReader;
|
@@ -1,12 +1,10 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorXYZ;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import eu.jonahbauer.raytracing.scene.SkyBox;
|
||||
@@ -74,4 +72,8 @@ public interface Spectrum extends Texture, SkyBox {
|
||||
default @NotNull SampledSpectrum getColor(@NotNull Ray ray) {
|
||||
return this.sample(ray.lambda());
|
||||
}
|
||||
|
||||
enum Type {
|
||||
ALBEDO, ILLUMINANT, UNBOUNDED
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
package eu.jonahbauer.raytracing.render.spectrum;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package eu.jonahbauer.raytracing.render.texture;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record CheckerTexture(double scale, @NotNull Texture even, @NotNull Texture odd) implements Texture {
|
||||
|
@@ -1,11 +1,9 @@
|
||||
package eu.jonahbauer.raytracing.render.texture;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -21,22 +19,21 @@ public final class ImageTexture implements Texture {
|
||||
private final int height;
|
||||
private final @NotNull Spectrum[][] spectra;
|
||||
|
||||
public ImageTexture(@NotNull BufferedImage image, @NotNull ColorSpace cs, @NotNull Type type, boolean gamma) {
|
||||
public ImageTexture(@NotNull BufferedImage image, @NotNull ColorSpace cs, @NotNull Spectrum.Type type) {
|
||||
this.width = image.getWidth();
|
||||
this.height = image.getHeight();
|
||||
this.spectra = new Spectrum[height][width];
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
var rgb = new ColorRGB(image.getRGB(x, y));
|
||||
if (gamma) rgb = ColorRGB.inverseGamma(rgb);
|
||||
spectra[y][x] = type.newSpectrum(cs, rgb);
|
||||
var rgb = cs.decode(new ColorRGB(image.getRGB(x, y)));
|
||||
spectra[y][x] = cs.toSpectrum(rgb, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ImageTexture(@NotNull String path, @NotNull ColorSpace cs) {
|
||||
this(read(path), cs, Type.ALBEDO, true);
|
||||
this(read(path), cs, Spectrum.Type.ALBEDO);
|
||||
}
|
||||
|
||||
private static @NotNull BufferedImage read(@NotNull String path) {
|
||||
@@ -55,22 +52,4 @@ public final class ImageTexture implements Texture {
|
||||
int y = (int) (v * (height - 1));
|
||||
return spectra[y][x];
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
ALBEDO {
|
||||
@Override
|
||||
protected @NotNull Spectrum newSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
||||
return new RGBAlbedoSpectrum(cs, rgb);
|
||||
}
|
||||
},
|
||||
ILLUMINANT {
|
||||
@Override
|
||||
protected @NotNull Spectrum newSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
||||
return new RGBIlluminantSpectrum(cs, rgb);
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
protected abstract @NotNull Spectrum newSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb);
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package eu.jonahbauer.raytracing.render.texture;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package eu.jonahbauer.raytracing.render.texture;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
@@ -7,10 +7,10 @@ import eu.jonahbauer.raytracing.render.material.Material;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
/**
|
||||
* The result of a {@linkplain Hittable#hit(Ray, Range) hit}.
|
||||
* The result of a {@linkplain Hittable#hit(Ray, Range, RandomGenerator) hit}.
|
||||
* @param t the {@code t} value at which the hit occurs
|
||||
* @param position the position of the hit
|
||||
* @param normal the surface normal at the hit position
|
||||
@@ -24,10 +24,6 @@ public record HitResult(
|
||||
double t, @NotNull Vec3 position, @NotNull Vec3 normal, @NotNull Hittable target,
|
||||
@NotNull Material material, double u, double v, boolean isFrontFace
|
||||
) implements Comparable<HitResult> {
|
||||
public HitResult {
|
||||
Objects.requireNonNull(position, "position");
|
||||
normal = normal.unit();
|
||||
}
|
||||
|
||||
public @NotNull HitResult withPositionAndNormal(@NotNull Vec3 position, @NotNull Vec3 normal) {
|
||||
return new HitResult(t, position, normal, target, material, u, v, isFrontFace);
|
||||
|
@@ -9,15 +9,16 @@ import eu.jonahbauer.raytracing.scene.transform.Translate;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public interface Hittable {
|
||||
@NotNull Range FORWARD = new Range(0.001, Double.POSITIVE_INFINITY);
|
||||
|
||||
/**
|
||||
* @see #hit(Ray, Range)
|
||||
* @see #hit(Ray, Range, RandomGenerator)
|
||||
*/
|
||||
default @NotNull Optional<HitResult> hit(@NotNull Ray ray) {
|
||||
return hit(ray, FORWARD);
|
||||
default @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull RandomGenerator random) {
|
||||
return hit(ray, FORWARD, random);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,7 +32,7 @@ public interface Hittable {
|
||||
* @return the result of the hit test, containing (among others) the value {@code t} such that {@code ray.at(t)} is
|
||||
* a point on {@code this} hittable
|
||||
*/
|
||||
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range);
|
||||
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random);
|
||||
|
||||
/**
|
||||
* {@return the axis-aligned bounding box of this hittable}
|
||||
|
@@ -2,8 +2,8 @@ package eu.jonahbauer.raytracing.scene;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.AABB;
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
|
||||
import eu.jonahbauer.raytracing.scene.util.HittableCollection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public final class Scene extends HittableCollection {
|
||||
private final @NotNull HittableCollection objects;
|
||||
@@ -42,8 +43,8 @@ public final class Scene extends HittableCollection {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hit(@NotNull Ray ray, @NotNull State state) {
|
||||
objects.hit(ray, state);
|
||||
public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
|
||||
objects.hit(ray, state, random);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package eu.jonahbauer.raytracing.scene;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectrum.Spectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@FunctionalInterface
|
||||
|
@@ -7,9 +7,11 @@ import eu.jonahbauer.raytracing.render.material.Material;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public abstract class Hittable2D implements Hittable {
|
||||
protected final @NotNull Vec3 origin;
|
||||
@@ -36,7 +38,7 @@ public abstract class Hittable2D implements Hittable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
|
||||
var denominator = ray.direction().dot(normal);
|
||||
if (Math.abs(denominator) < 1e-8) return Optional.empty(); // parallel
|
||||
|
||||
|
@@ -48,7 +48,7 @@ public final class Box implements Hittable, Target {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
|
||||
// based on AABB#hit with additional detection of the side hit
|
||||
var origin = ray.origin();
|
||||
var direction = ray.direction();
|
||||
@@ -103,13 +103,13 @@ public final class Box implements Hittable, Target {
|
||||
t = tmin;
|
||||
side = entry;
|
||||
frontFace = true;
|
||||
material = materials[side.ordinal()];
|
||||
material = materials[entry.ordinal()];
|
||||
normal = side.normal;
|
||||
} else if (range.surrounds(tmax) && materials[exit.ordinal()] != null) {
|
||||
t = tmax;
|
||||
side = exit;
|
||||
frontFace = false;
|
||||
material = materials[side.ordinal()];
|
||||
material = materials[exit.ordinal()];
|
||||
normal = side.normal.neg();
|
||||
} else {
|
||||
return Optional.empty();
|
||||
@@ -130,7 +130,7 @@ public final class Box implements Hittable, Target {
|
||||
@Override
|
||||
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||
if (contains(origin)) return 1 / (4 * Math.PI);
|
||||
if (hit(new Ray(origin, direction)).isEmpty()) return 0;
|
||||
if (hit(new Ray(origin, direction), null).isEmpty()) return 0;
|
||||
|
||||
var solidAngle = 0d;
|
||||
for (var s : Side.values()) {
|
||||
|
@@ -10,15 +10,16 @@ import eu.jonahbauer.raytracing.scene.Hittable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNull IsotropicMaterial material) implements Hittable {
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||
var hit1 = boundary.hit(ray, Range.UNIVERSE);
|
||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
|
||||
var hit1 = boundary.hit(ray, Range.UNIVERSE, random);
|
||||
if (hit1.isEmpty()) return Optional.empty();
|
||||
|
||||
var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY));
|
||||
var hit2 = boundary.hit(ray, new Range(hit1.get().t() + 0.0001, Double.POSITIVE_INFINITY), random);
|
||||
if (hit2.isEmpty()) return Optional.empty();
|
||||
|
||||
var tmin = Math.max(range.min(), hit1.get().t());
|
||||
@@ -28,7 +29,7 @@ public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNul
|
||||
|
||||
var length = ray.direction().length();
|
||||
var distance = length * (tmax - tmin);
|
||||
var hitDistance = - Math.log(Math.random()) / density;
|
||||
var hitDistance = - Math.log(random.nextDouble()) / density;
|
||||
if (hitDistance > distance) return Optional.empty();
|
||||
|
||||
var t = tmin + hitDistance / length;
|
||||
|
@@ -9,6 +9,7 @@ import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||
import eu.jonahbauer.raytracing.scene.Target;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -40,7 +41,7 @@ public final class Sphere implements Hittable, Target {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @Nullable RandomGenerator random) {
|
||||
var t = hit0(ray, range);
|
||||
if (Double.isNaN(t)) return Optional.empty();
|
||||
|
||||
|
@@ -2,10 +2,8 @@ package eu.jonahbauer.raytracing.scene.transform;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Range;
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||
import eu.jonahbauer.raytracing.scene.Target;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
@@ -24,7 +22,7 @@ public abstract class Transform implements Hittable {
|
||||
protected abstract @NotNull HitResult transform(@NotNull HitResult result);
|
||||
|
||||
@Override
|
||||
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||
return object.hit(transform(ray), range).map(this::transform);
|
||||
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
|
||||
return object.hit(transform(ray), range, random).map(this::transform);
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public final class HittableBinaryTree extends HittableCollection {
|
||||
private final @Nullable Hittable left;
|
||||
@@ -46,17 +47,17 @@ public final class HittableBinaryTree extends HittableCollection {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hit(@NotNull Ray ray, @NotNull State state) {
|
||||
public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
|
||||
if (!bbox.hit(ray, state.getRange())) return;
|
||||
if (left instanceof HittableCollection coll) {
|
||||
coll.hit(ray, state);
|
||||
coll.hit(ray, state, random);
|
||||
} else if (left != null) {
|
||||
hit(state, ray, left);
|
||||
hit(state, ray, left, random);
|
||||
}
|
||||
if (right instanceof HittableCollection coll) {
|
||||
coll.hit(ray, state);
|
||||
coll.hit(ray, state, random);
|
||||
} else if (right != null) {
|
||||
hit(state, ray, right);
|
||||
hit(state, ray, right, random);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -8,20 +8,21 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public abstract class HittableCollection implements Hittable {
|
||||
|
||||
@Override
|
||||
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
|
||||
public final @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range, @NotNull RandomGenerator random) {
|
||||
var state = new State(range);
|
||||
hit(ray, state);
|
||||
hit(ray, state, random);
|
||||
return state.getResult();
|
||||
}
|
||||
|
||||
public abstract void hit(@NotNull Ray ray, @NotNull State state);
|
||||
public abstract void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random);
|
||||
|
||||
protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object) {
|
||||
var r = object.hit(ray, state.range);
|
||||
protected static boolean hit(@NotNull State state, @NotNull Ray ray, @NotNull Hittable object, @NotNull RandomGenerator random) {
|
||||
var r = object.hit(ray, state.range, random);
|
||||
if (r.isPresent()) {
|
||||
if (state.range.surrounds(r.get().t())){
|
||||
state.result = r.get();
|
||||
|
@@ -7,6 +7,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public final class HittableList extends HittableCollection {
|
||||
private final @NotNull List<Hittable> objects;
|
||||
@@ -22,8 +23,8 @@ public final class HittableList extends HittableCollection {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hit(@NotNull Ray ray, @NotNull State state) {
|
||||
objects.forEach(object -> hit(state, ray, object));
|
||||
public void hit(@NotNull Ray ray, @NotNull State state, @NotNull RandomGenerator random) {
|
||||
objects.forEach(object -> hit(state, ray, object, random));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -1,6 +1,6 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.ImageFormat;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
@@ -3,7 +3,7 @@ package eu.jonahbauer.raytracing.scene.hittable3d;
|
||||
import eu.jonahbauer.raytracing.math.Range;
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.color.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.material.LambertianMaterial;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
Reference in New Issue
Block a user