Compare commits

..

3 Commits

@ -1,8 +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.texture.CheckerTexture;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.camera.SimpleCamera;
import eu.jonahbauer.raytracing.render.material.*;
import eu.jonahbauer.raytracing.render.texture.ImageTexture;
@ -49,13 +52,14 @@ public class Examples {
public static @NotNull Example getSimpleScene(int height) {
if (height <= 0) height = 675;
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 Color(0.8, 0.8, 0.0))),
new Sphere(new Vec3(0, 0, -1.2), 0.5, new LambertianMaterial(new Color(0.1, 0.2, 0.5))),
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(-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 Color(0.8, 0.6, 0.2), 0.0))
new Sphere(new Vec3(1.0, 0, -1.2), 0.5, new MetallicMaterial(new ColorRGB(0.8, 0.6, 0.2), cs, 0.0))
)),
SimpleCamera.builder()
.withImage(height * 16 / 9, height)
@ -66,11 +70,16 @@ public class Examples {
public static @NotNull Example getSpheres(int height) {
if (height <= 0) height = 675;
var cs = ColorSpaces.sRGB;
var rng = new Random(1);
var objects = new ArrayList<Hittable>();
objects.add(new Sphere(
new Vec3(0, -1000, 0), 1000,
new LambertianMaterial(new CheckerTexture(0.32, new Color(.2, .3, .1), new Color(.9, .9, .9)))
new LambertianMaterial(new CheckerTexture(0.32,
new RGBAlbedoSpectrum(cs, new ColorRGB(.2, .3, .1)),
new RGBAlbedoSpectrum(cs, new ColorRGB(.9, .9, .9))
))
));
for (int a = -11; a < 11; a++) {
@ -82,13 +91,13 @@ public class Examples {
var rnd = rng.nextDouble();
if (rnd < 0.8) {
// diffuse
var albedo = Color.multiply(Color.random(rng), Color.random(rng));
material = new LambertianMaterial(albedo);
var albedo = ColorRGB.random(rng).times(ColorRGB.random(rng));
material = new LambertianMaterial(albedo, cs);
} else if (rnd < 0.95) {
// metal
var albedo = Color.random(rng, 0.5, 1.0);
var albedo = ColorRGB.random(rng, 0.5, 1.0);
var fuzz = rng.nextDouble() * 0.5;
material = new MetallicMaterial(albedo, fuzz);
material = new MetallicMaterial(albedo, cs, fuzz);
} else {
// glass
material = new DielectricMaterial(1.5);
@ -99,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 Color(0.4, 0.2, 0.1))));
objects.add(new Sphere(new Vec3(4, 1, 0), 1.0, new MetallicMaterial(new Color(0.7, 0.6, 0.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)));
var camera = SimpleCamera.builder()
.withImage(height * 16 / 9, height)
@ -116,13 +125,14 @@ public class Examples {
public static @NotNull Example getSquares(int height) {
if (height <= 0) height = 600;
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 Color(1.0, 0.2, 0.2))),
new Parallelogram(new Vec3(-2, -2, 0), new Vec3(4, 0, 0), new Vec3(0, 4, 0), new LambertianMaterial(new Color(0.2, 1.0, 0.2))),
new Parallelogram(new Vec3(3, -2, 1), new Vec3(0, 0, 4), new Vec3(0, 4, 0), new LambertianMaterial(new Color(0.2, 0.2, 1.0))),
new Parallelogram(new Vec3(-2, 3, 1), new Vec3(4, 0, 0), new Vec3(0, 0, 4), new LambertianMaterial(new Color(1.0, 0.5, 0.0))),
new Parallelogram(new Vec3(-2, -3, 5), new Vec3(4, 0, 0), new Vec3(0, 0, -4), new LambertianMaterial(new Color(0.2, 0.8, 0.8)))
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))
)),
SimpleCamera.builder()
.withImage(height, height)
@ -135,12 +145,13 @@ public class Examples {
public static @NotNull Example getLight(int height) {
if (height <= 0) height = 225;
var cs = ColorSpaces.sRGB;
return new Example(
new Scene(List.of(
new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(new Color(0.2, 0.2, 0.2))),
new Sphere(new Vec3(0, 2, 0), 2, new LambertianMaterial(new Color(0.2, 0.2, 0.2))),
new Parallelogram(new Vec3(3, 1, -2), new Vec3(2, 0, 0), new Vec3(0, 2, 0), new DiffuseLight(new Color(4.0, 4.0, 4.0))),
new Sphere(new Vec3(0, 7, 0), 2, new DiffuseLight(new Color(4.0, 4.0, 4.0)))
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))
)),
SimpleCamera.builder()
.withImage(height * 16 / 9, height)
@ -154,10 +165,11 @@ public class Examples {
public static @NotNull Example getCornellBox(int height) {
if (height <= 0) height = 600;
var red = new LambertianMaterial(new Color(.65, .05, .05));
var white = new LambertianMaterial(new Color(.73, .73, .73));
var green = new LambertianMaterial(new Color(.12, .45, .15));
var light = new DiffuseLight(new Color(15.0, 15.0, 15.0));
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);
return new Example(
new Scene(List.of(
@ -181,10 +193,11 @@ public class Examples {
public static @NotNull Example getCornellBoxSmoke(int height) {
if (height <= 0) height = 600;
var red = new LambertianMaterial(new Color(.65, .05, .05));
var white = new LambertianMaterial(new Color(.73, .73, .73));
var green = new LambertianMaterial(new Color(.12, .45, .15));
var light = new DiffuseLight(new Color(7.0, 7.0, 7.0));
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);
return new Example(
new Scene(List.of(
@ -194,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(Color.BLACK)
0.01, new IsotropicMaterial(ColorRGB.BLACK, cs)
),
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(Color.WHITE)
0.01, new IsotropicMaterial(ColorRGB.WHITE, cs)
)
)),
SimpleCamera.builder()
@ -215,10 +228,11 @@ public class Examples {
public static @NotNull Example getCornellBoxSphere(int height) {
if (height <= 0) height = 600;
var red = new LambertianMaterial(new Color(.65, .05, .05));
var white = new LambertianMaterial(new Color(.73, .73, .73));
var green = new LambertianMaterial(new Color(.12, .45, .15));
var light = new DiffuseLight(new Color(7.0, 7.0, 7.0));
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 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);
@ -242,17 +256,18 @@ public class Examples {
public static @NotNull Example getDiagramm(int height) {
if (height <= 0) height = 450;
record Partei(String name, Color color, double stimmen) { }
var cs = ColorSpaces.sRGB;
record Partei(String name, ColorRGB color, double stimmen) { }
var data = List.of(
new Partei("CDU", new Color(0x00, 0x4B, 0x76), 18.9),
new Partei("SPD", new Color(0xC0, 0x00, 0x3C), 25.7),
new Partei("AfD", new Color(0x80, 0xCD, 0xEC), 10.3),
new Partei("FDP", new Color(0xF7, 0xBB, 0x3D), 11.5),
new Partei("DIE LINKE", new Color(0x5F, 0x31, 0x6E), 4.9),
new Partei("GRÜNE", new Color(0x00, 0x85, 0x4A), 14.8),
new Partei("CSU", new Color(0x00, 0x77, 0xB6), 5.2)
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)
);
var white = new LambertianMaterial(new Color(.99, .99, .99));
var white = new LambertianMaterial(new ColorRGB(.99, .99, .99), cs);
var count = data.size();
var size = 75d;
@ -272,12 +287,12 @@ 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())
new DielectricMaterial(1.5, partei.color(), cs)
));
}
return new Example(
new Scene(new Color(1.25, 1.25, 1.25), objects),
new Scene(cs.illuminant().scale(1.25), objects),
SimpleCamera.builder()
.withImage(height * 16 / 9, height)
.withPosition(new Vec3(700, 250, 800))
@ -292,7 +307,7 @@ public class Examples {
return new Example(
new Scene(getSkyBox(), List.of(
new Sphere(Vec3.ZERO, 2, new LambertianMaterial(new ImageTexture("/eu/jonahbauer/raytracing/textures/earthmap.jpg")))
new Sphere(Vec3.ZERO, 2, new LambertianMaterial(new ImageTexture("earthmap.jpg", ColorSpaces.sRGB)))
)),
SimpleCamera.builder()
.withImage(height * 16 / 9, height)
@ -325,12 +340,13 @@ public class Examples {
public static @NotNull Example getFinal(int height) {
if (height <= 0) height = 400;
var cs = ColorSpaces.sRGB;
var objects = new ArrayList<Hittable>();
var random = new Random(1);
// boxes
var boxes = new ArrayList<Hittable>();
var ground = new LambertianMaterial(new Color(0.48, 0.83, 0.53));
var ground = new LambertianMaterial(new ColorRGB(0.48, 0.83, 0.53), cs);
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
var w = 100.0;
@ -348,31 +364,31 @@ 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 Color(7., 7., 7.))
new DiffuseLight(new ColorRGB(7., 7., 7.), cs)
));
// spheres with different materials
objects.add(new Sphere(new Vec3(400, 400, 200), 50, new LambertianMaterial(new Color(0.7, 0.3, 0.1))));
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(260, 150, 45), 50, new DielectricMaterial(1.5)));
objects.add(new Sphere(new Vec3(0, 150, 145), 50, new MetallicMaterial(new Color(0.8, 0.8, 0.9), 1.0)));
objects.add(new Sphere(new Vec3(0, 150, 145), 50, new MetallicMaterial(new ColorRGB(0.8, 0.8, 0.9), cs, 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 Color(0.2, 0.4, 0.9))));
objects.add(new ConstantMedium(boundary, 0.2, new IsotropicMaterial(new ColorRGB(0.2, 0.4, 0.9), cs)));
// 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 Color(1., 1., 1.))
0.0001, new IsotropicMaterial(new ColorRGB(1., 1., 1.), cs)
));
// textures spheres
objects.add(new Sphere(new Vec3(400, 200, 400), 100, new LambertianMaterial(new ImageTexture("/eu/jonahbauer/raytracing/textures/earthmap.jpg"))));
objects.add(new Sphere(new Vec3(400, 200, 400), 100, new LambertianMaterial(new ImageTexture("earthmap.jpg", cs))));
objects.add(new Sphere(new Vec3(220, 280, 300), 80, new LambertianMaterial(new PerlinTexture(0.2))));
// box from spheres
var white = new LambertianMaterial(new Color(.73, .73, .73));
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
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));
@ -391,6 +407,9 @@ public class Examples {
}
private static @NotNull SkyBox getSkyBox() {
return SkyBox.gradient(new Color(0.5, 0.7, 1.0), Color.WHITE);
return SkyBox.gradient(
new RGBIlluminantSpectrum(ColorSpaces.sRGB, new ColorRGB(0.5, 0.7, 1.0)),
ColorSpaces.sRGB.illuminant()
);
}
}

@ -2,9 +2,12 @@ package eu.jonahbauer.raytracing;
import eu.jonahbauer.raytracing.render.ImageFormat;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.canvas.Image;
import eu.jonahbauer.raytracing.render.canvas.LiveCanvas;
import eu.jonahbauer.raytracing.render.canvas.XYZCanvas;
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 org.jetbrains.annotations.NotNull;
import java.io.IOException;
@ -30,11 +33,11 @@ public class Main {
Canvas canvas;
if (config.preview) {
var image = new LiveCanvas(new Image(camera.getWidth(), camera.getHeight()));
var image = new LiveCanvas(new XYZCanvas(camera.getWidth(), camera.getHeight()), ColorSpaces.sRGB);
image.preview();
canvas = image;
} else {
canvas = new Image(camera.getWidth(), camera.getHeight());
canvas = new XYZCanvas(camera.getWidth(), camera.getHeight());
}
long time = System.nanoTime();

@ -0,0 +1,21 @@
package eu.jonahbauer.raytracing.math;
import org.jetbrains.annotations.NotNull;
/**
* A vector-like object that implements the standard mathematical operations
* @param <T> the type
*/
public interface IVec<T extends IVec<T>> {
double get(int i);
@NotNull T plus(@NotNull T other);
@NotNull T minus(@NotNull T other);
@NotNull T times(@NotNull T other);
@NotNull T times(double d);
default @NotNull T div(double d) {
return times(1 / d);
}
double @NotNull[] toArray();
}

@ -0,0 +1,36 @@
package eu.jonahbauer.raytracing.math;
import org.jetbrains.annotations.NotNull;
/**
* A vector-like object with three components.
* @param <T> the type
*/
public interface IVec3<T extends Record & IVec3<T>> extends IVec<T> {
default double component1() {
return toVec3().x();
}
default double component2() {
return toVec3().y();
}
default double component3() {
return toVec3().z();
}
@Override
default double get(int i) {
return switch (i) {
case 0 -> component1();
case 1 -> component2();
case 2 -> component3();
default -> throw new IndexOutOfBoundsException(i);
};
}
@NotNull Vec3 toVec3();
@Override
default double @NotNull [] toArray() {
return new double[] {component1(), component2(), component3()};
}
}

@ -2,11 +2,46 @@ package eu.jonahbauer.raytracing.math;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public record Matrix3(
double a11, double a12, double a13,
double a21, double a22, double a23,
double a31, double a32, double a33
) {
public static @NotNull Matrix3 fromRows(@NotNull Vec3 @NotNull[] rows) {
if (rows.length != 3) throw new IllegalArgumentException();
return fromRows(rows[0], rows[1], rows[2]);
}
public static @NotNull Matrix3 fromRows(@NotNull Vec3 row0, @NotNull Vec3 row1, @NotNull Vec3 row2) {
return new Matrix3(
row0.x(), row0.y(), row0.z(),
row1.x(), row1.y(), row1.z(),
row2.x(), row2.y(), row2.z()
);
}
public static @NotNull Matrix3 fromColumns(@NotNull Vec3 @NotNull[] cols) {
if (cols.length != 3) throw new IllegalArgumentException();
return fromColumns(cols[0], cols[1], cols[2]);
}
public static @NotNull Matrix3 fromColumns(@NotNull Vec3 col0, @NotNull Vec3 col1, @NotNull Vec3 col2) {
return new Matrix3(
col0.x(), col1.x(), col2.x(),
col0.y(), col1.y(), col2.y(),
col0.z(), col1.z(), col2.z()
);
}
public static @NotNull Matrix3 fromArray(double @NotNull[] @NotNull[] array) {
return new Matrix3(
array[0][0], array[0][1], array[0][2],
array[1][0], array[1][1], array[1][2],
array[2][0], array[2][1], array[2][2]
);
}
public Matrix3() {
this(1, 1, 1);
@ -64,9 +99,157 @@ public record Matrix3(
if (det == 0) throw new IllegalStateException();
var t = 1 / det;
return new Matrix3(
t * (a22 * a33 - a23 * a32), t * (-a12 * a33 + a13 * a32), t * (a12 * a23 - a13 * a22),
t * (-a21 * a33 + a23 * a31), t * (a11 * a33 - a13 * a31), t * (-a11 * a23 + a13 * a21),
t * (a21 * a32 - a22 * a31), t * (a11 * a32 + a12 * a31), t * (a11 * a22 - a12 * a21)
t * (Math.fma( a22, a33, -a23 * a32)),
t * (Math.fma(-a12, a33, a13 * a32)),
t * (Math.fma( a12, a23, -a13 * a22)),
t * (Math.fma(-a21, a33, a23 * a31)),
t * (Math.fma( a11, a33, -a13 * a31)),
t * (Math.fma(-a11, a23, a13 * a21)),
t * (Math.fma( a21, a32, -a22 * a31)),
t * (Math.fma(-a11, a32, a12 * a31)),
t * (Math.fma( a11, a22, -a12 * a21))
);
}
public @NotNull Vec3 column(int i) {
return switch (i) {
case 0 -> new Vec3(a11, a21, a31);
case 1 -> new Vec3(a12, a22, a32);
case 2 -> new Vec3(a13, a23, a33);
default -> throw new IndexOutOfBoundsException(i);
};
}
public @NotNull Vec3 @NotNull[] columns() {
return new Vec3[] {
new Vec3(a11, a21, a31),
new Vec3(a12, a22, a32),
new Vec3(a13, a23, a33)
};
}
public @NotNull Vec3 row(int i) {
return switch (i) {
case 0 -> new Vec3(a11, a12, a13);
case 1 -> new Vec3(a21, a22, a23);
case 2 -> new Vec3(a31, a32, a33);
default -> throw new IndexOutOfBoundsException(i);
};
}
public @NotNull Vec3 @NotNull[] rows() {
return new Vec3[] {
new Vec3(a11, a12, a13),
new Vec3(a21, a22, a23),
new Vec3(a31, a32, a33)
};
}
public double @NotNull[] @NotNull[] toArray() {
return new double[][] {
{a11, a12, a13},
{a21, a22, a23},
{a31, a32, a33}
};
}
public double get(int i, int j) {
Objects.checkIndex(i, 3);
Objects.checkIndex(j, 3);
var idx = 3 * i + j;
return switch (idx) {
case 0 -> a11;
case 1 -> a12;
case 2 -> a13;
case 3 -> a21;
case 4 -> a22;
case 5 -> a23;
case 6 -> a31;
case 7 -> a32;
case 8 -> a33;
default -> throw new AssertionError();
};
}
/**
* Performs lower-upper decomposition with partial pivoting (LUP decomposition) on {@code this} matrix.
* @param tolerance a small tolerance number to detect failure when the matrix is near degenerate
* @see <a href="https://en.wikipedia.org/w/index.php?title=LU_decomposition&oldid=1213102558#C_code_example">LU decomposition Wikipedia, The Free Encyclopedia</a>
*/
public @NotNull LUPDecomposition decompose(double tolerance) {
// unit permutation matrix
var perm = new int[] {0, 1, 2, 3};
var A = toArray();
var N = 3;
for (int i = 0; i < N; i++) {
double maxA = 0.0;
int imax = i;
for (int k = i; k < N; k++) {
double absA = Math.abs(A[k][i]);
if (absA > maxA) {
maxA = absA;
imax = k;
}
}
if (maxA < tolerance) throw new IllegalArgumentException("matrix is degenerate");
if (imax != i) {
// pivoting P
int j = perm[i];
perm[i] = perm[imax];
perm[imax] = j;
// pivoting rows of A
var ptr = A[i];
A[i] = A[imax];
A[imax] = ptr;
// counting pivots starting from N (for determinant)
perm[3]++;
}
for (int j = i + 1; j < N; j++) {
A[j][i] /= A[i][i];
for (int k = i + 1; k < N; k++) {
A[j][k] -= A[j][i] * A[i][k];
}
}
}
return new LUPDecomposition(fromArray(A), perm);
}
public record LUPDecomposition(@NotNull Matrix3 matrix, int @NotNull[] permutation) {
/**
* Solves the equation {@code Ax = b} where {@code A} is the matrix that {@code this} decomposition was derived
* from.
* @param b the right hand side vector
* @return the solution vector
*/
public @NotNull Vec3 solve(@NotNull Vec3 b) {
var N = 3;
var x = new double[N];
for (int i = 0; i < N; i++) {
x[i] = b.get(permutation[i]);
for (int k = 0; k < i; k++) {
x[i] -= matrix.get(i, k) * x[k];
}
}
for (int i = N - 1; i >= 0; i--) {
for (int k = i + 1; k < N; k++) {
x[i] -= matrix.get(i, k) * x[k];
}
x[i] /= matrix.get(i, i);
}
return new Vec3(x[0], x[1], x[2]);
}
}
}

@ -1,13 +1,19 @@
package eu.jonahbauer.raytracing.math;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) {
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 Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) {
this(origin, direction, SampledWavelengths.EMPTY);
}
public @NotNull Vec3 at(double t) {

@ -7,10 +7,8 @@ import java.util.random.RandomGenerator;
import static eu.jonahbauer.raytracing.Main.DEBUG;
public record Vec3(double x, double y, double z) {
public record Vec3(double x, double y, double z) implements IVec3<Vec3> {
public static final Vec3 ZERO = new Vec3(0, 0, 0);
public static final Vec3 MAX = new Vec3(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE);
public static final Vec3 MIN = new Vec3(-Double.MAX_VALUE, -Double.MAX_VALUE, -Double.MAX_VALUE);
public static final Vec3 UNIT_X = new Vec3(1, 0, 0);
public static final Vec3 UNIT_Y = new Vec3(0, 1, 0);
public static final Vec3 UNIT_Z = new Vec3(0, 0, 1);
@ -62,7 +60,7 @@ public record Vec3(double x, double y, double z) {
* @return the reflected vector
*/
public static @NotNull Vec3 reflect(@NotNull Vec3 vec, @NotNull Vec3 normal) {
var factor = - 2 * normal.times(vec);
var factor = - 2 * normal.dot(vec);
return Vec3.fma(factor, normal, vec);
}
@ -75,7 +73,7 @@ public record Vec3(double x, double y, double z) {
*/
public static @NotNull Optional<Vec3> refract(@NotNull Vec3 vec, @NotNull Vec3 normal, double ri) {
vec = vec.unit();
var cosTheta = Math.min(- vec.times(normal), 1.0);
var cosTheta = Math.min(- vec.dot(normal), 1.0);
var sinTheta = Math.sqrt(1 - cosTheta * cosTheta);
if (ri * sinTheta > 1) return Optional.empty();
@ -166,6 +164,10 @@ public record Vec3(double x, double y, double z) {
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;
}
/*
* Math
*/
public @NotNull Vec3 plus(double x, double y, double z) {
return new Vec3(this.x + x, this.y + y, this.z + z);
}
@ -179,6 +181,7 @@ public record Vec3(double x, double y, double z) {
* @param other a vector
* @return the sum of this and the other vector
*/
@Override
public @NotNull Vec3 plus(@NotNull Vec3 other) {
return new Vec3(this.x + other.x, this.y + other.y, this.z + other.z);
}
@ -188,49 +191,58 @@ public record Vec3(double x, double y, double z) {
* @param other a vector
* @return the difference of this and the other vector
*/
@Override
public @NotNull Vec3 minus(@NotNull Vec3 other) {
return new Vec3(this.x - other.x, this.y - other.y, this.z - other.z);
}
/**
* Computes the scalar product of this and another vector
* @param other a vector
* @return the scalar product
*/
public double times(@NotNull Vec3 other) {
return this.x * other.x + this.y * other.y + this.z * other.z;
}
/**
* Multiplies this vector with a scalar
* @param t a scalar
* @return the product of this vector and the scalar
*/
@Override
public @NotNull Vec3 times(double t) {
return new Vec3(this.x * t, this.y * t, this.z * t);
}
/**
* Negates this vector.
* {@return the negated vector}
* Multiplies this vector with another vector component-wise.
* @param other a vector
* @return the component-wise product of this vector and the other vector
*/
public @NotNull Vec3 neg() {
return new Vec3(-x, -y, -z);
@Override
public @NotNull Vec3 times(@NotNull Vec3 other) {
return new Vec3(this.x * other.x, this.y * other.y, this.z * other.z);
}
/**
* Inverts each component of this vector.
* @return the inverted vector.
* Divides this vector by a scalar
* @param t a scalar
* @return this vector divided by the scalar
*/
public @NotNull Vec3 inv() {
return new Vec3(1 / x, 1 / y, 1 / z);
@Override
public @NotNull Vec3 div(double t) {
return times(1 / t);
}
/**
* Computes the cross-product of this and another vector
* Computes the scalar product of this and another vector
* @param other a vector
* @return the cross-product
* @return the scalar product
*/
public double dot(@NotNull Vec3 other) {
return this.x * other.x + this.y * other.y + this.z * other.z;
}
public @NotNull Vec3 neg() {
return new Vec3(-x, -y, -z);
}
public @NotNull Vec3 inv() {
return new Vec3(1 / x, 1 / y, 1 / z);
}
public @NotNull Vec3 cross(@NotNull Vec3 other) {
return new Vec3(
Math.fma(this.y, other.z, - other.y * this.z),
@ -239,15 +251,6 @@ public record Vec3(double x, double y, double z) {
);
}
/**
* Divides this vector by a scalar
* @param t a scalar
* @return this vector divided by the scalar
*/
public @NotNull Vec3 div(double t) {
return new Vec3(this.x / t, this.y / t, this.z / t);
}
/**
* {@return the squared length of this vector}
*/
@ -279,17 +282,28 @@ public record Vec3(double x, double y, double z) {
return div(Math.sqrt(squared));
}
/**
* {@return the n-th component of this vector}
* @param axis the component index
/*
* Accessors
*/
public double get(int axis) {
return switch (axis) {
case 0 -> x;
case 1 -> y;
case 2 -> z;
default -> throw new IndexOutOfBoundsException(axis);
};
@Override
public double component1() {
return x;
}
@Override
public double component2() {
return y;
}
@Override
public double component3() {
return z;
}
@Override
public @NotNull Vec3 toVec3() {
return this;
}
public @NotNull Vec3 withX(double x) {

@ -1,7 +1,7 @@
package eu.jonahbauer.raytracing.render;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.canvas.Image;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
import org.jetbrains.annotations.NotNull;
import java.io.*;
@ -11,7 +11,6 @@ import java.nio.file.Path;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
public enum ImageFormat {
PPM {
@ -24,15 +23,16 @@ public enum ImageFormat {
writer.write(String.valueOf(image.getHeight()));
writer.write("\n255\n");
var it = image.pixels().iterator();
while (it.hasNext()) {
var color = it.next();
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");
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");
}
}
}
}
@ -84,13 +84,14 @@ public enum ImageFormat {
idat.writeInt(IDAT_TYPE);
try (var deflate = new DataOutputStream(new DeflaterOutputStream(idat))) {
var pixels = image.pixels().iterator();
for (int i = 0; pixels.hasNext(); i = (i + 1) % image.getWidth()) {
if (i == 0) deflate.writeByte(0); // filter type
var pixel = pixels.next();
deflate.writeByte(pixel.red());
deflate.writeByte(pixel.green());
deflate.writeByte(pixel.blue());
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());
}
}
}

@ -2,6 +2,7 @@ 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;
@ -93,7 +94,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));
return new Ray(origin, target.minus(origin), SampledWavelengths.uniform(random.nextDouble()));
}
/**

@ -1,22 +1,37 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.texture.Color;
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 org.jetbrains.annotations.NotNull;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public interface Canvas {
/**
* {@return the width of this canvas}
*/
int getWidth();
/**
* {@return the height of this canvas}
*/
int getHeight();
void set(int x, int y, @NotNull Color color);
@NotNull Color get(int x, int y);
/**
* Adds a sample to this canvas
* @param x the pixel x coordinate
* @param y the pixel y coordinate
* @param n the index of the sample
* @param spectrum the sampled spectrum
* @param lambda the sampled wavelengths
*/
void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda);
default @NotNull Stream<Color> pixels() {
return IntStream.range(0, getHeight())
.mapToObj(y -> IntStream.range(0, getWidth()).mapToObj(x -> get(x, y)))
.flatMap(Function.identity());
}
/**
* {@return the color at a given pixel}
* @param x the pixel x coordinate
* @param y the pixel y coordinate
* @param cs the color space of the output
*/
@NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs);
}

@ -1,58 +0,0 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.texture.Color;
import org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;
import java.util.Objects;
public final class Image implements Canvas {
private final int width;
private final int height;
private final Color[][] data;
public Image(int width, int height) {
this.width = width;
this.height = height;
if (width <= 0) throw new IllegalArgumentException("width must be positive");
if (height <= 0) throw new IllegalArgumentException("height must be positive");
this.data = new Color[height][width];
}
public Image(@NotNull BufferedImage image) {
this(image.getWidth(), image.getHeight());
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
this.data[y][x] = new Color(image.getRGB(x, y));
}
}
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public @NotNull Color get(int x, int y) {
Objects.checkIndex(x, width);
Objects.checkIndex(y, height);
return Objects.requireNonNullElse(this.data[y][x], Color.BLACK);
}
@Override
public void set(int x, int y, @NotNull Color color) {
Objects.checkIndex(x, width);
Objects.checkIndex(y, height);
this.data[y][x] = Objects.requireNonNull(color);
}
}

@ -1,6 +1,9 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.texture.Color;
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 org.jetbrains.annotations.NotNull;
import javax.swing.*;
@ -12,10 +15,12 @@ import java.awt.image.BufferedImage;
public final class LiveCanvas implements Canvas {
private final @NotNull Canvas delegate;
private final @NotNull BufferedImage image;
private final @NotNull ColorSpace cs;
public LiveCanvas(@NotNull Canvas delegate) {
public LiveCanvas(@NotNull Canvas delegate, @NotNull ColorSpace cs) {
this.delegate = delegate;
this.image = new BufferedImage(delegate.getWidth(), delegate.getHeight(), BufferedImage.TYPE_INT_RGB);
this.cs = cs;
}
@Override
@ -29,15 +34,16 @@ public final class LiveCanvas implements Canvas {
}
@Override
public void set(int x, int y, @NotNull Color color) {
delegate.set(x, y, color);
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 rgb = color.red() << 16 | color.green() << 8 | color.blue();
image.setRGB(x, y, rgb);
}
@Override
public @NotNull Color get(int x, int y) {
return delegate.get(x, y);
public @NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs) {
return delegate.getRGB(x, y, cs);
}
public @NotNull Thread preview() {

@ -0,0 +1,76 @@
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 org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;
import java.util.Objects;
public final class RGBCanvas implements Canvas {
private final int width;
private final int height;
private final @NotNull ColorSpace cs;
private final @NotNull ColorRGB[][] data;
public RGBCanvas(int width, int height, @NotNull ColorSpace cs) {
this.width = width;
this.height = height;
this.cs = Objects.requireNonNull(cs);
if (width <= 0) throw new IllegalArgumentException("width must be positive");
if (height <= 0) throw new IllegalArgumentException("height must be positive");
this.data = new ColorRGB[height][width];
}
public RGBCanvas(@NotNull BufferedImage image, @NotNull ColorSpace cs) {
this(image.getWidth(), image.getHeight(), cs);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
this.data[y][x] = new ColorRGB(image.getRGB(x, y));
}
}
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda) {
assert x < width;
assert y < height;
var rgb = spectrum.toRGB(lambda, cs);
data[y][x] = ColorRGB.average(data[y][x], rgb, n);
}
@Override
public @NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs) {
if (cs == this.cs) return get(x, y);
return cs.toRGB(this.cs.toXYZ(get(x, y)));
}
public @NotNull ColorRGB get(int x, int y) {
assert x < width;
assert y < height;
return Objects.requireNonNullElse(data[y][x], ColorRGB.BLACK);
}
public void set(int x, int y, @NotNull ColorRGB color) {
assert x < width;
assert y < height;
data[y][x] = Objects.requireNonNull(color);
}
}

@ -0,0 +1,74 @@
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 org.jetbrains.annotations.NotNull;
import java.awt.image.BufferedImage;
import java.util.Objects;
public final class XYZCanvas implements Canvas {
private final int width;
private final int height;
private final @NotNull ColorXYZ[][] data;
public XYZCanvas(int width, int height) {
this.width = width;
this.height = height;
if (width <= 0) throw new IllegalArgumentException("width must be positive");
if (height <= 0) throw new IllegalArgumentException("height must be positive");
this.data = new ColorXYZ[height][width];
}
public XYZCanvas(@NotNull BufferedImage image, @NotNull ColorSpace cs) {
this(image.getWidth(), image.getHeight());
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
data[y][x] = cs.toXYZ(new ColorRGB(image.getRGB(x, y)));
}
}
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda) {
assert x < width;
assert y < height;
var xyz = spectrum.toXYZ(lambda);
data[y][x] = ColorXYZ.average(get(x, y), xyz, n);
}
public @NotNull ColorXYZ get(int x, int y) {
assert x < width;
assert y < height;
return Objects.requireNonNullElse(data[y][x], ColorXYZ.BLACK);
}
@Override
public @NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs) {
return cs.toRGB(get(x, y));
}
public void set(int x, int y, @NotNull ColorXYZ color) {
assert x < width;
assert y < height;
data[y][x] = Objects.requireNonNull(color);
}
}

@ -2,7 +2,10 @@ package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.texture.Color;
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.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@ -13,18 +16,22 @@ import java.util.random.RandomGenerator;
public record DielectricMaterial(double refractionIndex, @NotNull Texture texture) implements Material {
public DielectricMaterial(double refractionIndex) {
this(refractionIndex, Color.WHITE);
this(refractionIndex, Spectra.WHITE);
}
public DielectricMaterial {
Objects.requireNonNull(texture, "texture");
}
public DielectricMaterial(double refractionIndex, @NotNull ColorRGB color, @NotNull ColorSpace cs) {
this(refractionIndex, new RGBAlbedoSpectrum(cs, color));
}
@Override
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
var ri = hit.isFrontFace() ? (1 / refractionIndex) : refractionIndex;
var cosTheta = Math.min(- ray.direction().unit().times(hit.normal()), 1.0);
var cosTheta = Math.min(- ray.direction().unit().dot(hit.normal()), 1.0);
var reflectance = reflectance(cosTheta);
var reflect = reflectance > random.nextDouble();
@ -32,7 +39,7 @@ public record DielectricMaterial(double refractionIndex, @NotNull Texture textur
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
var attenuation = texture.get(hit);
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection)));
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
}
private double reflectance(double cos) {

@ -1,22 +1,34 @@
package eu.jonahbauer.raytracing.render.material;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.texture.Color;
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.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.Optional;
import java.util.random.RandomGenerator;
public record DiffuseLight(@NotNull Texture texture) implements Material {
public DiffuseLight {
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();
}
@Override
public @NotNull Color emitted(@NotNull HitResult hit) {
public @NotNull Spectrum emitted(@NotNull HitResult hit) {
return texture.get(hit);
}
}

@ -2,7 +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.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@ -40,11 +41,11 @@ 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(Color.WHITE, new Ray(ray.at(hit.t()), ray.direction())));
return Optional.of(new SpecularScatterResult(Spectra.WHITE, new Ray(ray.at(hit.t()), ray.direction(), ray.lambda())));
}
@Override
public @NotNull Color emitted(@NotNull HitResult hit) {
public @NotNull Spectrum emitted(@NotNull HitResult hit) {
if (hit.isFrontFace()) {
if (front != null) return front.emitted(hit);
} else {
@ -56,7 +57,7 @@ public final class DirectionalMaterial implements Material {
private record DirectionalTexture(@Nullable Texture front, @Nullable Texture back) implements Texture {
@Override
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
public @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
throw new UnsupportedOperationException();
}

@ -2,15 +2,27 @@ 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.texture.Color;
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.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.Optional;
import java.util.random.RandomGenerator;
public record IsotropicMaterial(@NotNull Color albedo) implements Material {
public record IsotropicMaterial(@NotNull Spectrum albedo) implements Material {
public IsotropicMaterial {
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,6 +2,9 @@ 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;
@ -15,6 +18,10 @@ 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,7 +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.texture.Color;
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.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@ -30,10 +32,10 @@ public interface Material {
/**
* {@return the color emitted for a given hit}
* @implSpec the default implementation returns {@linkplain Color#BLACK black}, i.e. no emission
* @implSpec the default implementation returns {@linkplain ColorRGB#BLACK black}, i.e. no emission
*/
default @NotNull Color emitted(@NotNull HitResult hit) {
return Color.BLACK;
default @NotNull Spectrum emitted(@NotNull HitResult hit) {
return Spectra.BLACK;
}
/**
@ -48,7 +50,7 @@ public interface Material {
* @param attenuation the attenuation of the scattered light ray
* @param ray the scattered light ray
*/
record SpecularScatterResult(@NotNull Color attenuation, @NotNull Ray ray) implements ScatterResult {
record SpecularScatterResult(@NotNull Spectrum attenuation, @NotNull Ray ray) implements ScatterResult {
public SpecularScatterResult {
Objects.requireNonNull(attenuation, "attenuation");
Objects.requireNonNull(ray, "ray");
@ -62,7 +64,7 @@ public interface Material {
* @param attenuation the attenuation of the scattered light ray
* @param pdf the probability density function
*/
record PdfScatterResult(@NotNull Color attenuation, @NotNull ProbabilityDensityFunction pdf) implements ScatterResult {
record PdfScatterResult(@NotNull Spectrum attenuation, @NotNull ProbabilityDensityFunction pdf) implements ScatterResult {
public PdfScatterResult {
Objects.requireNonNull(attenuation, "attenuation");
Objects.requireNonNull(pdf, "pdf");

@ -2,6 +2,9 @@ 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;
@ -16,6 +19,14 @@ 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");
@ -28,6 +39,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)));
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
}
}

@ -2,13 +2,13 @@ package eu.jonahbauer.raytracing.render.renderer;
import eu.jonahbauer.raytracing.render.camera.Camera;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.canvas.Image;
import eu.jonahbauer.raytracing.render.canvas.XYZCanvas;
import eu.jonahbauer.raytracing.scene.Scene;
import org.jetbrains.annotations.NotNull;
public interface Renderer {
default @NotNull Image render(@NotNull Camera camera, @NotNull Scene scene) {
var image = new Image(camera.getWidth(), camera.getHeight());
default @NotNull Canvas render(@NotNull Camera camera, @NotNull Scene scene) {
var image = new XYZCanvas(camera.getWidth(), camera.getHeight());
render(camera, scene, image);
return image;
}

@ -1,11 +1,10 @@
package eu.jonahbauer.raytracing.render.renderer;
import eu.jonahbauer.raytracing.math.Range;
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.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.camera.Camera;
import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.scene.Scene;
@ -77,18 +76,11 @@ public final class SimpleRenderer implements Renderer {
for (int x = 0; x < camera.getWidth(); x++) {
var ray = camera.cast(x, y, sif, sjf, sqrtSamplesPerPixel, random);
var c = getColor(scene, ray, random);
canvas.set(x, y, Color.average(canvas.get(x, y), c, sample));
canvas.add(x, y, sample, c, ray.lambda());
}
});
}
}
// apply gamma correction
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
for (int x = 0; x < camera.getWidth(); x++) {
canvas.set(x, y, Color.gamma(canvas.get(x, y), gamma));
}
});
}
/**
@ -101,7 +93,6 @@ public final class SimpleRenderer implements Renderer {
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
var random = splittable.split();
for (int x = 0; x < camera.getWidth(); x++) {
var color = Color.BLACK;
int i = 0;
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
@ -110,10 +101,9 @@ public final class SimpleRenderer implements Renderer {
System.out.println("Casting ray " + ray + " through pixel (" + x + "," + y + ") at subpixel (" + si + "," + sj + ")...");
}
var c = getColor(scene, ray, random);
color = Color.average(color, c, ++i);
canvas.add(x, y, ++i, c, ray.lambda());
}
}
canvas.set(x, y, Color.gamma(color, gamma));
}
});
}
@ -121,19 +111,19 @@ public final class SimpleRenderer implements Renderer {
/**
* {@return the color of the given ray in the given scene}
*/
private @NotNull Color getColor(@NotNull Scene scene, @NotNull Ray ray, @NotNull RandomGenerator random) {
private @NotNull SampledSpectrum getColor(@NotNull Scene scene, @NotNull Ray ray, @NotNull RandomGenerator random) {
return getColor0(scene, ray, maxDepth, random);
}
private @NotNull Color getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
var color = Color.BLACK;
var attenuation = Color.WHITE;
private @NotNull SampledSpectrum getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
var color = SampledSpectrum.BLACK;
var attenuation = SampledSpectrum.WHITE;
while (depth-- > 0) {
var optional = scene.hit(ray);
if (optional.isEmpty()) {
var background = scene.getBackgroundColor(ray);
color = Color.add(color, Color.multiply(attenuation, background));
color = SampledSpectrum.fma(attenuation, background, color);
if (DEBUG) {
System.out.println(" Hit background: " + background);
}
@ -145,13 +135,13 @@ public final class SimpleRenderer implements Renderer {
System.out.println(" Hit " + hit.target() + " at t=" + hit.t() + " (" + hit.position() + ")");
}
var material = hit.material();
var emitted = material.emitted(hit);
if (DEBUG && !Color.BLACK.equals(emitted)) {
var emitted = material.emitted(hit).sample(ray.lambda());
if (DEBUG && !SampledSpectrum.BLACK.equals(emitted)) {
System.out.println(" Emitted: " + emitted);
}
var result = material.scatter(ray, hit, random);
color = Color.add(color, Color.multiply(attenuation, emitted));
color = SampledSpectrum.fma(attenuation, emitted, color);
if (result.isEmpty()) {
if (DEBUG) {
@ -162,7 +152,7 @@ public final class SimpleRenderer implements Renderer {
switch (result.get()) {
case Material.SpecularScatterResult(var a, var scattered) -> {
attenuation = Color.multiply(attenuation, a);
attenuation = attenuation.times(a.sample(ray.lambda()));
ray = scattered;
if (DEBUG) {
@ -171,8 +161,8 @@ public final class SimpleRenderer implements Renderer {
}
case Material.PdfScatterResult(var a, var pdf) -> {
if (scene.getTargets() == null) {
attenuation = Color.multiply(attenuation, a);
ray = new Ray(hit.position(), pdf.generate(random));
attenuation = attenuation.times(a.sample(ray.lambda()));
ray = new Ray(hit.position(), pdf.generate(random), ray.lambda());
if (DEBUG) {
System.out.println(" Pdf scattering with albedo " + a);
@ -185,10 +175,10 @@ public final class SimpleRenderer implements Renderer {
var actualPdf = mixed.value(direction);
if (actualPdf == 0) break; // when actualPdf is 0, the ray should have never been generated by mixed.generate
var factor = idealPdf / actualPdf;
var factor = idealPdf / actualPdf;
attenuation = Color.multiply(attenuation, Color.multiply(a, factor));
ray = new Ray(hit.position(), direction);
attenuation = attenuation.times(a.sample(ray.lambda()).times(factor));
ray = new Ray(hit.position(), direction, ray.lambda());
if (DEBUG) {
System.out.println(" Pdf scattering with albedo " + a + " and factor " + factor);
@ -216,7 +206,7 @@ public final class SimpleRenderer implements Renderer {
* are encoded in the longs lower and upper 32 bits respectively.
*/
private static @NotNull IntStream getScanlineStream(int height, boolean parallel) {
var stream = IntStream.range(0, height);
var stream = IntStream.range(0, height).map(i -> height - i - 1);
return parallel ? stream.parallel() : stream;
}

@ -15,7 +15,7 @@ public record CosineProbabilityDensityFunction(@NotNull Vec3 normal) implements
@Override
public double value(@NotNull Vec3 direction) {
var cos = normal.times(direction);
var cos = normal.dot(direction);
return Math.max(0, cos / Math.PI);
}

@ -1,19 +1,28 @@
package eu.jonahbauer.raytracing.render.spectral;
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.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import org.jetbrains.annotations.NotNull;
public final class SampledSpectrum {
private final double @NotNull[] values;
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;
public static void main(String[] args) {
System.out.println();
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) {
var values = new double[lambdas.size()];
for (int i = 0; i < values.length; i++) {
@ -26,30 +35,74 @@ public final class SampledSpectrum {
this.values = values;
}
public static @NotNull SampledSpectrum multiply(@NotNull SampledSpectrum a, @NotNull SampledSpectrum b) {
/*
* Math
*/
public static @NotNull SampledSpectrum fma(@NotNull SampledSpectrum a, @NotNull SampledSpectrum b, @NotNull SampledSpectrum c) {
var out = new double[a.values.length];
for (int i = 0; i < a.values.length; i++) {
out[i] = a.values[i] * b.values[i];
out[i] = Math.fma(a.values[i], b.values[i], c.values[i]);
}
return new SampledSpectrum(out);
}
public static @NotNull SampledSpectrum multiply(@NotNull SampledSpectrum a, double b) {
public static @NotNull SampledSpectrum lerp(@NotNull SampledSpectrum a, @NotNull SampledSpectrum b, double t) {
if (t < 0) return a;
if (t > 1) return b;
var out = new double[a.values.length];
for (int i = 0; i < a.values.length; i++) {
out[i] = a.values[i] * b;
out[i] = Math.fma(t, b.values[i] - a.values[i], a.values[i]);
}
return new SampledSpectrum(out);
}
public static @NotNull SampledSpectrum add(@NotNull SampledSpectrum a, @NotNull SampledSpectrum b) {
var out = new double[a.values.length];
for (int i = 0; i < a.values.length; i++) {
out[i] = a.values[i] + b.values[i];
@Override
public @NotNull SampledSpectrum plus(@NotNull SampledSpectrum other) {
var out = new double[other.values.length];
for (int i = 0; i < other.values.length; i++) {
out[i] = values[i] + other.values[i];
}
return new SampledSpectrum(out);
}
@Override
public @NotNull SampledSpectrum minus(@NotNull SampledSpectrum other) {
var out = new double[other.values.length];
for (int i = 0; i < other.values.length; i++) {
out[i] = values[i] - other.values[i];
}
return new SampledSpectrum(out);
}
@Override
public @NotNull SampledSpectrum times(@NotNull SampledSpectrum other) {
var out = new double[other.values.length];
for (int i = 0; i < other.values.length; i++) {
out[i] = values[i] * other.values[i];
}
return new SampledSpectrum(out);
}
@Override
public @NotNull SampledSpectrum times(double d) {
var out = new double[values.length];
for (int i = 0; i < values.length; i++) {
out[i] = values[i] * d;
}
return new SampledSpectrum(out);
}
@Override
public double @NotNull [] toArray() {
return Arrays.copyOf(values, values.length);
}
/*
* Accessors
*/
@Override
public double get(int index) {
return values[index];
}
@ -58,19 +111,34 @@ public final class SampledSpectrum {
return values.length;
}
public double average() {
double avg = 0;
for (int i = 0; i < values.length; i++) {
avg = Math.fma(1d / (i + 1), values[i] - avg, avg);
}
return avg;
/*
* Object
*/
@Override
public boolean equals(Object obj) {
return obj instanceof SampledSpectrum o && Arrays.equals(values, o.values);
}
@Override
public int hashCode() {
return Arrays.hashCode(values);
}
@Override
public @NotNull String toString() {
return "SampledSpectrum[values=" + Arrays.toString(values) + "]";
}
/*
* Conversions
*/
public @NotNull ColorXYZ toXYZ(@NotNull SampledWavelengths lambdas) {
return lambdas.toXYZ(this);
}
public @NotNull Color toRGB(@NotNull SampledWavelengths lambdas, @NotNull ColorSpace cs) {
public @NotNull ColorRGB toRGB(@NotNull SampledWavelengths lambdas, @NotNull ColorSpace cs) {
return cs.toRGB(toXYZ(lambdas));
}
}

@ -11,6 +11,9 @@ 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;
@ -19,21 +22,21 @@ public final class SampledWavelengths {
}
public static @NotNull SampledWavelengths uniform(double rng, double min, double max) {
var lambdas = new double[Spectrum.SAMPLES];
var lambdas = new double[SAMPLES];
// 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) / Spectrum.SAMPLES;
for (int i = 1; i < Spectrum.SAMPLES; i++) {
var delta = (max - min) / SAMPLES;
for (int i = 1; i < SAMPLES; i++) {
lambdas[i] = lambdas[i - 1] + delta;
if (lambdas[i] > max) {
lambdas[i] = min + (lambdas[i] - max);
}
}
var pdf = new double[Spectrum.SAMPLES];
var pdf = new double[SAMPLES];
Arrays.fill(pdf, 1 / (max - min));
return new SampledWavelengths(lambdas, pdf);
}
@ -55,12 +58,37 @@ 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 void terminateSecondary() {
if (pdf.length < 2 || pdf[1] == 0) return;
Arrays.fill(pdf, 1, pdf.length, 0d);
pdf[0] /= pdf.length;
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);
}
/*
* Object
*/
@Override
public boolean equals(Object obj) {
return obj instanceof SampledWavelengths o && Arrays.equals(lambdas, o.lambdas) && Arrays.equals(pdf, o.pdf);
}
@Override
public int hashCode() {
return 31 * Arrays.hashCode(lambdas) + Arrays.hashCode(pdf);
}
@Override
public @NotNull String toString() {
return "SampledWavelengths[lambdas=" + Arrays.toString(lambdas) + ", pdf=" + Arrays.toString(pdf) + "]";
}
/*
* Conversions
*/
@NotNull
ColorXYZ toXYZ(@NotNull SampledSpectrum spectrum) {
var x = Spectra.X.sample(this);

@ -1,4 +1,9 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
/**
* A pair of chromaticity coordinates in the xyY color space
* @param x the x coordinate
* @param y the y coordinate
*/
public record Chromaticity(double x, double y) {
}

@ -0,0 +1,153 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
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);
public static @NotNull ColorRGB random(@NotNull Random random) {
return new ColorRGB(random.nextDouble(), random.nextDouble(), random.nextDouble());
}
public static @NotNull ColorRGB random(@NotNull Random random, double min, double max) {
var span = max - min;
return new ColorRGB(
Math.fma(random.nextDouble(), span, min),
Math.fma(random.nextDouble(), span, min),
Math.fma(random.nextDouble(), span, min)
);
}
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);
}
public ColorRGB {
if (DEBUG && (!Double.isFinite(r) || !Double.isFinite(g) || !Double.isFinite(b))) {
throw new IllegalArgumentException("r, g and b must be finite");
}
}
/*
* Math
*/
public static @NotNull ColorRGB average(@NotNull ColorRGB current, @NotNull ColorRGB next, int index) {
return lerp(current, next, 1d / index);
}
public static @NotNull ColorRGB lerp(@NotNull ColorRGB a, @NotNull ColorRGB b, double t) {
if (t < 0) return a;
if (t > 1) return b;
return new ColorRGB(
Math.fma(t, b.r - a.r, a.r),
Math.fma(t, b.g - a.g, a.g),
Math.fma(t, b.b - a.b, a.b)
);
}
public static @NotNull ColorRGB fma(@NotNull ColorRGB a, @NotNull ColorRGB b, @NotNull ColorRGB c) {
return new ColorRGB(
Math.fma(a.r, b.r, c.r),
Math.fma(a.g, b.g, c.g),
Math.fma(a.b, b.b, c.b)
);
}
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);
}
@Override
public @NotNull ColorRGB minus(@NotNull ColorRGB other) {
return new ColorRGB(r - other.r, g - other.g, b - other.b);
}
@Override
public @NotNull ColorRGB times(double d) {
return new ColorRGB(r * d, g * d, b * d);
}
@Override
public @NotNull ColorRGB times(@NotNull ColorRGB other) {
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
*/
public int red() {
return toInt(r);
}
public int green() {
return toInt(g);
}
public int blue() {
return toInt(b);
}
private static int toInt(double value) {
return Math.clamp((int) (255.99 * value), 0, 255);
}
@Override
public double component1() {
return r;
}
@Override
public double component2() {
return g;
}
@Override
public double component3() {
return b;
}
}

@ -1,12 +1,11 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
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.texture.Color;
import org.jetbrains.annotations.NotNull;
import java.util.NoSuchElementException;
import java.util.Objects;
/**
@ -19,6 +18,11 @@ public final class ColorSpace {
private final @NotNull Chromaticity w;
private final @NotNull DenselySampledSpectrum illuminant;
private final @NotNull ColorXYZ R;
private final @NotNull ColorXYZ G;
private final @NotNull ColorXYZ B;
private final @NotNull ColorXYZ W;
private final @NotNull Matrix3 XYZfromRGB;
private final @NotNull Matrix3 RGBfromXYZ;
private final @NotNull SpectrumTable RGBtoSpectrumTable;
@ -28,17 +32,17 @@ public final class ColorSpace {
@NotNull Spectrum illuminant, @NotNull SpectrumTable table
) {
this.r = Objects.requireNonNull(r, "r");
this.g = Objects.requireNonNull(r, "g");
this.b = Objects.requireNonNull(r, "b");
this.g = Objects.requireNonNull(g, "g");
this.b = Objects.requireNonNull(b, "b");
this.illuminant = new DenselySampledSpectrum(illuminant);
this.RGBtoSpectrumTable = table;
var W = illuminant.toXYZ();
this.W = illuminant.toXYZ();
this.w = W.xy();
var R = new ColorXYZ(r);
var G = new ColorXYZ(g);
var B = new ColorXYZ(b);
this.R = new ColorXYZ(r);
this.G = new ColorXYZ(g);
this.B = new ColorXYZ(b);
var rgb = new Matrix3(
R.x(), G.x(), B.x(),
R.y(), G.y(), B.y(),
@ -50,18 +54,39 @@ public final class ColorSpace {
this.RGBfromXYZ = XYZfromRGB.invert();
}
public @NotNull Color toRGB(@NotNull ColorXYZ xyz) {
public @NotNull ColorRGB toRGB(@NotNull ColorXYZ xyz) {
var out = RGBfromXYZ.times(xyz.toVec3());
return new Color(out.x(), out.y(), out.z());
return new ColorRGB(out.x(), out.y(), out.z());
}
public @NotNull ColorXYZ toXYZ(@NotNull Color rgb) {
public @NotNull ColorXYZ toXYZ(@NotNull ColorRGB rgb) {
var out = XYZfromRGB.times(rgb.toVec3());
return new ColorXYZ(out);
return ColorXYZ.fromVec3(out);
}
public @NotNull Vec3 toCIELab(@NotNull ColorRGB rgb) {
return toCIELab(toXYZ(rgb));
}
public @NotNull Vec3 toCIELab(@NotNull ColorXYZ xyz) {
return new Vec3(
116 * cieLabCbrt(xyz.y() / W.y()) - 16,
500 * (cieLabCbrt(xyz.x() / W.x()) - cieLabCbrt(xyz.y() / W.y())),
200 * (cieLabCbrt(xyz.y() / W.y()) - cieLabCbrt(xyz.z() / W.z()))
);
}
private static double cieLabCbrt(double x) {
var delta = 6.0 / 29.0;
if (x > delta * delta * delta) {
return Math.cbrt(x);
} else {
return x / (delta * delta * 3.0) + (4.0 / 29.0);
}
}
public @NotNull SigmoidPolynomial toSpectrum(@NotNull Color rgb) {
return RGBtoSpectrumTable.get(new Color(
public @NotNull SigmoidPolynomial toSpectrum(@NotNull ColorRGB rgb) {
return RGBtoSpectrumTable.get(new ColorRGB(
Math.max(0, rgb.r()),
Math.max(0, rgb.g()),
Math.max(0, rgb.b())

@ -3,26 +3,41 @@ package eu.jonahbauer.raytracing.render.spectral.colors;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Objects;
public final class ColorSpaces {
// Rec. ITU-R BT.709.3
public static final @NotNull ColorSpace sRGB = new ColorSpace(
new Chromaticity(0.6400, 0.3300),
new Chromaticity(0.3000, 0.6000),
new Chromaticity(0.1500, 0.0600),
Spectra.D65, null
Spectra.D65, read("sRGB_spectrum.bin")
);
// 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, null
Spectra.D65, read("DCI_P3_spectrum.bin")
);
// 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, null
Spectra.D65, read("Rec2020_spectrum.bin")
);
private static @NotNull SpectrumTable read(@NotNull String name) {
try (var in = ColorSpaces.class.getResourceAsStream("/eu/jonahbauer/raytracing/colorspace/" + name)) {
return SpectrumTable.read(Objects.requireNonNull(in));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private ColorSpaces() {
throw new UnsupportedOperationException();
}

@ -1,14 +1,18 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
import eu.jonahbauer.raytracing.math.IVec3;
import eu.jonahbauer.raytracing.math.Vec3;
import org.jetbrains.annotations.NotNull;
/**
* A CIE XYZ color
*/
public record ColorXYZ(double x, double y, double z) {
public record ColorXYZ(double x, double y, double z) implements IVec3<ColorXYZ> {
public static final double CIE_Y_INTEGRAL = 106.85689500000002;
public static final @NotNull ColorXYZ BLACK = new ColorXYZ(0, 0, 0);
public static final @NotNull ColorXYZ WHITE = new ColorXYZ(0, 1, 0);
public ColorXYZ(@NotNull Chromaticity chromaticity) {
this(chromaticity, 1);
}
@ -21,10 +25,69 @@ public record ColorXYZ(double x, double y, double z) {
);
}
public ColorXYZ(@NotNull Vec3 vec) {
this(vec.x(), vec.y(), vec.z());
/*
* Math
*/
public static @NotNull ColorXYZ average(@NotNull ColorXYZ a, @NotNull ColorXYZ b, int index) {
return lerp(a, b, 1d / index);
}
public static @NotNull ColorXYZ lerp(@NotNull ColorXYZ a, @NotNull ColorXYZ b, double t) {
if (t < 0) return a;
if (t > 1) return b;
return new ColorXYZ(
Math.fma(t, b.x - a.x, a.x),
Math.fma(t, b.y - a.y, a.y),
Math.fma(t, b.z - a.z, a.z)
);
}
public static @NotNull ColorXYZ fma(@NotNull ColorXYZ a, @NotNull ColorXYZ b, @NotNull ColorXYZ c) {
return new ColorXYZ(
Math.fma(a.x, b.x, c.x),
Math.fma(a.y, b.y, c.y),
Math.fma(a.z, b.z, c.z)
);
}
@Override
public @NotNull ColorXYZ plus(@NotNull ColorXYZ other) {
return new ColorXYZ(x + other.x, y + other.y, z + other.z);
}
@Override
public @NotNull ColorXYZ minus(@NotNull ColorXYZ other) {
return new ColorXYZ(x - other.x, y - other.y, z - other.z);
}
@Override
public @NotNull ColorXYZ times(@NotNull ColorXYZ other) {
return new ColorXYZ(x * other.x, y * other.y, z * other.z);
}
@Override
public @NotNull ColorXYZ times(double d) {
return new ColorXYZ(x * d, y * d, z * d);
}
/*
* Vec3
*/
@Override
public @NotNull Vec3 toVec3() {
return new Vec3(x, y, z);
}
public static @NotNull ColorXYZ fromVec3(@NotNull Vec3 vec) {
return new ColorXYZ(vec.x(), vec.y(), vec.z());
}
/*
* Accessors
*/
public double average() {
return (x + y + z) / 3;
}
@ -34,19 +97,18 @@ public record ColorXYZ(double x, double y, double z) {
return new Chromaticity(factor * x, factor * y);
}
public @NotNull Vec3 toVec3() {
return new Vec3(x, y, z);
}
public static @NotNull ColorXYZ multiply(@NotNull ColorXYZ a, @NotNull ColorXYZ b) {
return new ColorXYZ(a.x * b.x, a.y * b.y, a.z * b.z);
@Override
public double component1() {
return x;
}
public static @NotNull ColorXYZ multiply(@NotNull ColorXYZ a, double b) {
return new ColorXYZ(a.x * b, a.y * b, a.z * b);
@Override
public double component2() {
return y;
}
public static @NotNull ColorXYZ add(@NotNull ColorXYZ a, @NotNull ColorXYZ b) {
return new ColorXYZ(a.x + b.x, a.y + b.y, a.z + b.z);
@Override
public double component3() {
return z;
}
}

@ -15,9 +15,9 @@ import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
public record SigmoidPolynomial(double c0, double c1, double c2) {
public double get(double x) {
var p = Math.fma(x, Math.fma(x, c0, c1), c2);
var p = Math.fma(Math.fma(c0, x, c1), x, c2);
if (!Double.isFinite(p)) return p > 0 ? 1 : 0;
return .5 + x / (p * Math.sqrt(1 + p * p));
return Math.fma(.5 * p, 1 / Math.sqrt(Math.fma(p, p, 1)), .5);
}
public double max() {

@ -1,8 +1,8 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
import eu.jonahbauer.raytracing.render.texture.Color;
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.util.Arrays;
/**
@ -13,11 +13,11 @@ import java.util.Arrays;
* {@code y} and {@code z} are all in the range [0, 1] which allows for better use of the samples in a fixed grid.
* The {@code z} coordinate is additionally remapped using {@link #zNodes} to improve sampling at both ends of the scale.
* <p>
* The coefficients of the sigmoid functions are stored in a five-dimensional array with indices as described in
* {@link #coefficients}.
* The coefficients of the sigmoid functions are stored in a flattened five-dimensional array with indices as described
* in {@link #coefficients}.
*/
public final class SpectrumTable {
private static final int RESOLUTION = 64;
private final int resolution;
/**
* the remapped {@code z} values
@ -25,7 +25,7 @@ public final class SpectrumTable {
private final double[] zNodes;
/**
* the stored coefficients, with the following indices
* the stored coefficients as a flattened five-dimensional array with the following indices
* <ol>
* <li>the component index of the biggest rgb component</li>
* <li>the {@code z} coordinate</li>
@ -34,14 +34,58 @@ public final class SpectrumTable {
* <li>the coefficient index</li>
* </ol>
*/
private final double[][][][][] coefficients;
private final double[] coefficients;
public SpectrumTable(double[] zNodes, double[][][][][] coefficients) {
public static void write(@NotNull SpectrumTable table, @NotNull OutputStream out) throws IOException {
var dos = new DataOutputStream(out);
dos.writeInt(table.resolution);
for (double z : table.zNodes) {
dos.writeDouble(z);
}
for (double c : table.coefficients) {
dos.writeDouble(c);
}
dos.flush();
}
public static @NotNull SpectrumTable read(@NotNull InputStream in) throws IOException {
var dis = new DataInputStream(in);
var resolution = dis.readInt();
var nodes = new double[resolution];
for (int i = 0; i < resolution; i++) {
nodes[i] = dis.readDouble();
}
var table = new double[3 * resolution * resolution * resolution * 3];
for (int i = 0; i < table.length; i++) {
table[i] = dis.readDouble();
}
return new SpectrumTable(resolution, nodes, table);
}
SpectrumTable(int resolution, double @NotNull[] zNodes, double[] coefficients) {
this.resolution = resolution;
this.zNodes = zNodes;
this.coefficients = coefficients;
// check input array lengths
if (zNodes.length != resolution) {
throw new IllegalArgumentException("length of zNodes must be equal to the RESOLUTION");
}
if (coefficients.length != 3 * resolution * resolution * resolution * 3) {
throw new IllegalArgumentException("coefficients length must be 3 * RESOLUTION * RESOLUTION * RESOLUTION * 3");
}
// check ascending zNodes
for (int i = 1; i < resolution; i++) {
if (zNodes[i - 1] >= zNodes[i]) {
throw new IllegalArgumentException("zNodes must be in increasing order");
}
}
if (zNodes[0] != 0.0 || zNodes[zNodes.length - 1] != 1.0) {
throw new IllegalArgumentException("zNodes must start with 0.0 and end with 1.0");
}
}
public @NotNull SigmoidPolynomial get(@NotNull Color color) {
public @NotNull SigmoidPolynomial get(@NotNull ColorRGB color) {
// handle uniform rgb values
if (color.r() == color.g() && color.g() == color.b()) {
return new SigmoidPolynomial(0, 0, (color.r() - .5) / Math.sqrt(color.r() * (1 - color.r())));
@ -52,16 +96,19 @@ public final class SpectrumTable {
? (color.r() > color.b() ? 0 : 2)
: (color.g() > color.b() ? 1 : 2);
var z = color.component(max);
var x = color.component((max + 1) % 3) * (RESOLUTION - 1) / z;
var y = color.component((max + 2) % 3) * (RESOLUTION - 1) / z;
var z = color.get(max);
var x = color.get((max + 1) % 3) * (resolution - 1) / z;
var y = color.get((max + 2) % 3) * (resolution - 1) / z;
// compute integer indices and offsets for coefficient interpolation
int xi = Math.min((int) x, RESOLUTION - 2);
int yi = Math.min((int) y, RESOLUTION - 2);
int xi = Math.min((int) x, resolution - 2);
int yi = Math.min((int) y, resolution - 2);
int zi = Arrays.binarySearch(zNodes, z);
if (zi < 0) zi = -zi - 1;
zi++;
if (zi < 0) {
zi = -zi - 2;
} else if (zi > 0) {
zi = zi - 1;
}
var dx = x - xi;
var dy = y -yi;
@ -72,12 +119,12 @@ public final class SpectrumTable {
for (int i = 0; i < 3; i++) {
c[i] = lerp(dz,
lerp(dy,
lerp(dx, coefficients[max][zi + 0][yi + 0][xi + 0][i], coefficients[max][zi + 0][yi + 0][xi + 1][i]),
lerp(dx, coefficients[max][zi + 0][yi + 1][xi + 0][i], coefficients[max][zi + 0][yi + 1][xi + 1][i])
lerp(dx, get(max, zi + 0, yi + 0, xi + 0, i), get(max, zi + 0, yi + 0, xi + 1, i)),
lerp(dx, get(max, zi + 0, yi + 1, xi + 0, i), get(max, zi + 0, yi + 1, xi + 1, i))
),
lerp(dy,
lerp(dx, coefficients[max][zi + 1][yi + 0][xi + 0][i], coefficients[max][zi + 1][yi + 0][xi + 1][i]),
lerp(dx, coefficients[max][zi + 1][yi + 1][xi + 0][i], coefficients[max][zi + 1][yi + 1][xi + 1][i])
lerp(dx, get(max, zi + 1, yi + 0, xi + 0, i), get(max, zi + 1, yi + 0, xi + 1, i)),
lerp(dx, get(max, zi + 1, yi + 1, xi + 0, i), get(max, zi + 1, yi + 1, xi + 1, i))
)
);
}
@ -85,6 +132,10 @@ public final class SpectrumTable {
return new SigmoidPolynomial(c[0], c[1], c[2]);
}
private double get(int l, int z, int y, int x, int i) {
return coefficients[(((l * resolution + z) * resolution + y) * resolution + x) * 3 + i];
}
private static double lerp(double t, double a, double b) {
return Math.fma(t, b, Math.fma(-t, a, a));
}

@ -0,0 +1,204 @@
package eu.jonahbauer.raytracing.render.spectral.colors;
import eu.jonahbauer.raytracing.math.Matrix3;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;
import java.util.stream.IntStream;
/**
* Generates a lookup table for RGB to spectrum conversion.
* <p>
* The spectrum for each RGB value is a {@link SigmoidPolynomial} with coefficients such that the round trip error
* from converting the RGB value to a spectrum and back is minimized.
* <p>
* <img src="doc-files/rgb2spectrum.png">
*/
public final class SpectrumTableGenerator {
private static final double EPSILON = 1e-4;
private static final int ITERATIONS = 15;
private final int resolution = 64;
private final @NotNull ColorSpace cs;
public static void main(String[] args) throws IOException {
var generator = new SpectrumTableGenerator(ColorSpaces.DCI_P3);
var table = generator.generate();
try (var out = Files.newOutputStream(Path.of("DCI_P3_spectrum.bin"))) {
SpectrumTable.write(table, out);
}
}
public SpectrumTableGenerator(@NotNull ColorSpace cs) {
this.cs = Objects.requireNonNull(cs);
}
public @NotNull SpectrumTable generate() {
var scale = new double[resolution];
for (int i = 0; i < scale.length; i++) {
var t = (double) i / (resolution - 1);
scale[i] = smoothstep(smoothstep(t));
}
var table = new double[3 * resolution * resolution * resolution * 3];
for (int l0 = 0; l0 < 3; l0++) {
var l = l0;
IntStream.range(0, resolution).parallel().forEach(i -> {
System.out.println("l = " + l + ", i = " + i);
var x = (double) i / (resolution - 1);
for (int j = 0; j < resolution; j++) {
var y = (double) j / (resolution - 1);
var start = resolution / 5;
var c = new double[3];
for (int k = start; k < resolution; k++) {
var z = scale[k];
var idx = ((((l * resolution + k) * resolution) + j) * resolution + i) * 3;
var color = getColor(l, x, y, z);
generate(color, c, table, idx);
}
Arrays.fill(c, 0);
for (int k = start; k >= 0; --k) {
var z = scale[k];
var idx = ((((l * resolution + k) * resolution) + j) * resolution + i) * 3;
var color = getColor(l, x, y, z);
generate(color, c, table, idx);
}
}
});
}
return new SpectrumTable(resolution, scale, table);
}
private void generate(@NotNull ColorRGB rgb, double @NotNull[] c, double @NotNull[] out, int offset) {
gaussNewton(rgb, c, ITERATIONS);
double c0 = 360.0, c1 = 1.0 / (830.0 - 360.0);
double A = c[0], B = c[1], C = c[2];
out[offset] = A * c1 * c1;
out[offset + 1] = B * c1 - 2 * A * c0 * c1 * c1;
out[offset + 2] = C - B * c0 * c1 + A * c0 * c0 * c1 * c1;
}
/**
* Use Gauss-Newton algorithm to calculate coefficients {@code c} of a {@link SigmoidPolynomial} such that the round
* trip error from converting the {@code rgb} value to a spectrum and back is minimized.
* @param rgb the input color
* @param c the coefficients, used as initial values and output
* @param it the number of iterations
*/
private void gaussNewton(@NotNull ColorRGB rgb, double @NotNull[] c, int it) {
var bestQuality = Double.POSITIVE_INFINITY;
var bestCoefficients = new double[3];
for (int i = 0; i < it; ++i) {
var polynomial = new SigmoidPolynomial(c[0], c[1], c[2]);
var residual = getResidual(rgb, polynomial);
var jacobian = getJacobian(rgb, polynomial);
var delta = jacobian.decompose(1e-15).solve(residual);
for (int j = 0; j < 3; ++j) {
c[j] -= delta.get(j);
}
// catch runaway
double max = Math.max(Math.max(c[0], c[1]), c[2]);
if (max > 200) {
for (int j = 0; j < 3; ++j) {
c[j] *= 200 / max;
}
}
var quality = residual.squared();
if (quality <= 1e-6) {
return;
} else if (quality < bestQuality) {
bestQuality = quality;
System.arraycopy(c, 0, bestCoefficients, 0, 3);
}
}
System.arraycopy(bestCoefficients, 0, c, 0, 3);
}
/**
* Calculates the Jacobian matrix of the {@code polynomial}.
*/
private @NotNull Matrix3 getJacobian(@NotNull ColorRGB rgb, @NotNull SigmoidPolynomial polynomial) {
var jac = new double[3][3];
// central finite difference coefficients for first derivative with sixth-order accuracy
var factors = new double[] { -1d/60, 3d/20, -3d/4, 0, 3d/4, -3d/20, 1d/60 };
for (int i = 0; i < 3; i++) {
var derivative = Vec3.ZERO;
for (int d = - factors.length / 2, j = 0; j < factors.length; d++, j++) {
if (factors[j] == 0) continue;
var tmp = switch (i) {
case 0 -> new SigmoidPolynomial(polynomial.c0() + d * EPSILON, polynomial.c1(), polynomial.c2());
case 1 -> new SigmoidPolynomial(polynomial.c0(), polynomial.c1() + d * EPSILON, polynomial.c2());
case 2 -> new SigmoidPolynomial(polynomial.c0(), polynomial.c1(), polynomial.c2() + d * EPSILON);
default -> throw new AssertionError();
};
var r = getResidual(rgb, tmp);
derivative = Vec3.fma(factors[j], r, derivative);
}
for (int j = 0; j < 3; j++) {
jac[j][i] = derivative.get(j) / EPSILON;
}
}
return new Matrix3(
jac[0][0], jac[0][1], jac[0][2],
jac[1][0], jac[1][1], jac[1][2],
jac[2][0], jac[2][1], jac[2][2]
);
}
/**
* Calculates the difference between the RGB color and the result of converting the RGB color to a spectrum using
* the given coefficients, illuminating it with the color space's standard illuminant, and converting it back to an
* RBG color. The output is a vector in CIE Lab color space.
*/
private @NotNull Vec3 getResidual(@NotNull ColorRGB rgb, @NotNull SigmoidPolynomial polynomial) {
var out = new SigmoidPolynomialSpectrum(polynomial, cs).toXYZ();
return cs.toCIELab(rgb).minus(cs.toCIELab(out));
}
private static double smoothstep(double x) {
return x * x * (3.0 - 2.0 * x);
}
private static @NotNull ColorRGB getColor(int l, double x, double y, double z) {
var rgb = new double[3];
rgb[l] = z;
rgb[(l + 1) % 3] = x * z;
rgb[(l + 2) % 3] = y * z;
return new ColorRGB(rgb[0], rgb[1], rgb[2]);
}
private record SigmoidPolynomialSpectrum(@NotNull SigmoidPolynomial polynomial, @NotNull ColorSpace cs) implements Spectrum {
@Override
public double max() {
return polynomial.max();
}
@Override
public double get(double lambda) {
var l = (lambda - Spectrum.LAMBDA_MIN) / (Spectrum.LAMBDA_MAX - Spectrum.LAMBDA_MIN);
return polynomial.get(l) * cs.illuminant().get(lambda);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

@ -21,10 +21,10 @@ public final class DenselySampledSpectrum implements Spectrum {
if (max - min + 1 <= 0) throw new IllegalArgumentException("samples must not be empty");
this.samples = new double[max - min + 1];
var maxValue = 0d;
for (int lambda = min; lambda <= max; lambda++) {
for (int lambda = min, i = 0; lambda <= max; lambda++, i++) {
var sample = spectrum.get(lambda);
if (sample > maxValue) maxValue = sample;
this.samples[lambda] = sample;
this.samples[i] = sample;
}
this.min = min;
this.max = maxValue;

@ -2,13 +2,13 @@ package eu.jonahbauer.raytracing.render.spectral.spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import org.jetbrains.annotations.NotNull;
public final class RGBAlbedoSpectrum implements Spectrum {
private final @NotNull SigmoidPolynomial polynomial;
public RGBAlbedoSpectrum(@NotNull ColorSpace cs, @NotNull Color rgb) {
public RGBAlbedoSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
if (rgb.r() < 0 || rgb.r() > 1 || rgb.g() < 0 || rgb.g() > 1 || rgb.b() < 0 || rgb.b() > 1) {
throw new IllegalArgumentException();
}

@ -2,21 +2,25 @@ package eu.jonahbauer.raytracing.render.spectral.spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import org.jetbrains.annotations.NotNull;
/**
* A spectrum based on an RGB color used as an illuminant. The spectrum is adjusted to account for the color space's
* standard illuminant.
*/
public final class RGBIlluminantSpectrum implements Spectrum {
private final double scale;
private final @NotNull SigmoidPolynomial polynomial;
private final @NotNull DenselySampledSpectrum illuminant;
private final @NotNull Spectrum illuminant;
public RGBIlluminantSpectrum(@NotNull ColorSpace cs, @NotNull Color rgb) {
public RGBIlluminantSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
if (rgb.r() < 0 || rgb.g() < 0 || rgb.b() < 0) {
throw new IllegalArgumentException();
}
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
this.scale = 2 * max;
this.polynomial = cs.toSpectrum(scale == 0 ? Color.multiply(rgb, scale) : Color.BLACK);
this.polynomial = cs.toSpectrum(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
this.illuminant = cs.illuminant();
}

@ -2,20 +2,20 @@ package eu.jonahbauer.raytracing.render.spectral.spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import org.jetbrains.annotations.NotNull;
public final class RGBUnboundedSpectrum implements Spectrum {
private final double scale;
private final @NotNull SigmoidPolynomial polynomial;
public RGBUnboundedSpectrum(@NotNull ColorSpace cs, @NotNull Color rgb) {
public RGBUnboundedSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
if (rgb.r() < 0 || rgb.g() < 0 || rgb.b() < 0) {
throw new IllegalArgumentException();
}
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
this.scale = 2 * max;
this.polynomial = cs.toSpectrum(scale == 0 ? Color.multiply(rgb, scale) : Color.BLACK);
this.polynomial = cs.toSpectrum(scale == 0 ? rgb.div(scale) : ColorRGB.BLACK);
}
@Override

@ -0,0 +1,15 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
import org.jetbrains.annotations.NotNull;
public record ScaledSpectrum(@NotNull Spectrum spectrum, double scale) implements Spectrum {
@Override
public double max() {
return spectrum.max() * scale;
}
@Override
public double get(double lambda) {
return spectrum.get(lambda) * scale;
}
}

@ -1,5 +1,6 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
import org.jetbrains.annotations.NotNull;
import java.io.BufferedReader;
@ -8,79 +9,402 @@ import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public final class Spectra {
private static final String PATH_PREFIX = "/eu/jonahbauer/raytracing/spectrum/";
/**
* the CIE XYZ color matching curve for X
*/
public static final Spectrum X;
public static final Spectrum X = new DenselySampledSpectrum(new PiecewiseLinearSpectrum(CIE_XYZ.CIE_lambda, CIE_XYZ.CIE_X));
/**
* the CIE XYZ color matching curve for Y
*/
public static final Spectrum Y;
public static final Spectrum Y = new DenselySampledSpectrum(new PiecewiseLinearSpectrum(CIE_XYZ.CIE_lambda, CIE_XYZ.CIE_Y));;
/**
* the CIE XYZ color matching curve for Z
*/
public static final Spectrum Z;
public static final Spectrum Z = new DenselySampledSpectrum(new PiecewiseLinearSpectrum(CIE_XYZ.CIE_lambda, CIE_XYZ.CIE_Z));;
/**
* the CIE standard illuminant D50
* @see <a href="https://doi.org/10.25039/CIE.DS.hjfjmt59">CIE 2022, CIE standard illuminant D65, International Commission on Illumination (CIE), Vienna, Austria, DOI: 10.25039/CIE.DS.hjfjmt59</a>
*/
public static final Spectrum D50;
public static final Spectrum D50 = read("CIE_std_illum_D50.csv", true);
/**
* the CIE standard illuminant D65
* @see <a href="https://doi.org/10.25039/CIE.DS.etgmuqt5">CIE 2022, Relative spectral power distributions of CIE standard illuminants A, D65 and D50 (wavelengths in standard air) (data table), International Commission on Illumination (CIE), Vienna, Austria, DOI:10.25039/CIE.DS.etgmuqt5</a>
*/
public static final Spectrum D65;
public static final Spectrum D65 = read("CIE_std_illum_D65.csv", true);
static {
var xyz = read("/eu/jonahbauer/raytracing/spectrum/CIE_xyz_1931_2deg.csv");
X = xyz[0];
Y = xyz[1];
Z = xyz[2];
D50 = read("/eu/jonahbauer/raytracing/spectrum/CIE_std_illum_D50.csv")[0];
D65 = read("/eu/jonahbauer/raytracing/spectrum/CIE_std_illum_D65.csv")[0];
}
public static final Spectrum BLACK = new ConstantSpectrum(0);
public static final Spectrum WHITE = new ConstantSpectrum(1);
private static @NotNull DenselySampledSpectrum[] read(@NotNull String path) {
private static @NotNull Spectrum read(@NotNull String path, boolean normalize) {
var lambda = new ArrayList<Double>();
var values = new ArrayList<List<Double>>();
var values = new ArrayList<Double>();
try (
var is = Spectra.class.getResourceAsStream(path);
var is = Spectra.class.getResourceAsStream(PATH_PREFIX + path);
var in = new BufferedReader(new InputStreamReader(is, StandardCharsets.US_ASCII))
) {
int count = -1;
String line;
while ((line = in.readLine()) != null) {
var parts = line.split(",");
if (count == -1) {
count = parts.length - 1;
for (int i = 0; i < count; i++) values.add(new ArrayList<>());
} else if (count != parts.length - 1) {
throw new IllegalStateException("invalid spectrum file: " + path);
}
lambda.add(Double.parseDouble(parts[0]));
for (int i = 0; i < count; i++) {
values.get(i).add(Double.parseDouble(parts[i + 1]));
}
values.add(Double.parseDouble(parts[1]));
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
var out = new DenselySampledSpectrum[values.size()];
var lambdaArray = lambda.stream().mapToDouble(Double::doubleValue).toArray();
for (int i = 0; i < values.size(); i++) {
var valueArray = values.get(i).stream().mapToDouble(Double::doubleValue).toArray();
var s = new PiecewiseLinearSpectrum(lambdaArray, valueArray);
out[i] = new DenselySampledSpectrum(s);
var pls = new PiecewiseLinearSpectrum(
lambda.stream().mapToDouble(Double::doubleValue).toArray(),
values.stream().mapToDouble(Double::doubleValue).toArray()
);
if (normalize) {
return pls.scale(ColorXYZ.CIE_Y_INTEGRAL / Util.innerProduct(pls, Spectra.Y));
} else {
return pls;
}
return out;
}
private Spectra() {
throw new UnsupportedOperationException();
}
/**
* @see <a href="https://doi.org/10.25039/CIE.DS.xvudnb9b">CIE 2018, CIE 1931 colour-matching functions , 2 degree observer (data table),
* International Commission on Illumination (CIE), Vienna, Austria,
* DOI:10.25039/CIE.DS.xvudnb9b</a>
*/
private static final class CIE_XYZ {
private static final double[] CIE_lambda = {
360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376,
377, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393,
394, 395, 396, 397, 398, 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410,
411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 424, 425, 426, 427,
428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, 444,
445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, 460, 461,
462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478,
479, 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495,
496, 497, 498, 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512,
513, 514, 515, 516, 517, 518, 519, 520, 521, 522, 523, 524, 525, 526, 527, 528, 529,
530, 531, 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, 543, 544, 545, 546,
547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, 560, 561, 562, 563,
564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580,
581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597,
598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, 609, 610, 611, 612, 613, 614,
615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, 631,
632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, 648,
649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, 665,
666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682,
683, 684, 685, 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, 697, 698, 699,
700, 701, 702, 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716,
717, 718, 719, 720, 721, 722, 723, 724, 725, 726, 727, 728, 729, 730, 731, 732, 733,
734, 735, 736, 737, 738, 739, 740, 741, 742, 743, 744, 745, 746, 747, 748, 749, 750,
751, 752, 753, 754, 755, 756, 757, 758, 759, 760, 761, 762, 763, 764, 765, 766, 767,
768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, 779, 780, 781, 782, 783, 784,
785, 786, 787, 788, 789, 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, 801,
802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812, 813, 814, 815, 816, 817, 818,
819, 820, 821, 822, 823, 824, 825, 826, 827, 828, 829, 830
};
private static final double[] CIE_X = {
// CIE X function values
0.0001299000, 0.0001458470, 0.0001638021, 0.0001840037, 0.0002066902,
0.0002321000, 0.0002607280, 0.0002930750, 0.0003293880, 0.0003699140,
0.0004149000, 0.0004641587, 0.0005189860, 0.0005818540, 0.0006552347,
0.0007416000, 0.0008450296, 0.0009645268, 0.001094949, 0.001231154,
0.001368000, 0.001502050, 0.001642328, 0.001802382, 0.001995757,
0.002236000, 0.002535385, 0.002892603, 0.003300829, 0.003753236,
0.004243000, 0.004762389, 0.005330048, 0.005978712, 0.006741117,
0.007650000, 0.008751373, 0.01002888, 0.01142170, 0.01286901,
0.01431000, 0.01570443, 0.01714744, 0.01878122, 0.02074801,
0.02319000, 0.02620736, 0.02978248, 0.03388092, 0.03846824,
0.04351000, 0.04899560, 0.05502260, 0.06171880, 0.06921200,
0.07763000, 0.08695811, 0.09717672, 0.1084063, 0.1207672,
0.1343800, 0.1493582, 0.1653957, 0.1819831, 0.1986110,
0.2147700, 0.2301868, 0.2448797, 0.2587773, 0.2718079,
0.2839000, 0.2949438, 0.3048965, 0.3137873, 0.3216454,
0.3285000, 0.3343513, 0.3392101, 0.3431213, 0.3461296,
0.3482800, 0.3495999, 0.3501474, 0.3500130, 0.3492870,
0.3480600, 0.3463733, 0.3442624, 0.3418088, 0.3390941,
0.3362000, 0.3331977, 0.3300411, 0.3266357, 0.3228868,
0.3187000, 0.3140251, 0.3088840, 0.3032904, 0.2972579,
0.2908000, 0.2839701, 0.2767214, 0.2689178, 0.2604227,
0.2511000, 0.2408475, 0.2298512, 0.2184072, 0.2068115,
0.1953600, 0.1842136, 0.1733273, 0.1626881, 0.1522833,
0.1421000, 0.1321786, 0.1225696, 0.1132752, 0.1042979,
0.09564000, 0.08729955, 0.07930804, 0.07171776, 0.06458099,
0.05795001, 0.05186211, 0.04628152, 0.04115088, 0.03641283,
0.03201000, 0.02791720, 0.02414440, 0.02068700, 0.01754040,
0.01470000, 0.01216179, 0.009919960, 0.007967240, 0.006296346,
0.004900000, 0.003777173, 0.002945320, 0.002424880, 0.002236293,
0.002400000, 0.002925520, 0.003836560, 0.005174840, 0.006982080,
0.009300000, 0.01214949, 0.01553588, 0.01947752, 0.02399277,
0.02910000, 0.03481485, 0.04112016, 0.04798504, 0.05537861,
0.06327000, 0.07163501, 0.08046224, 0.08973996, 0.09945645,
0.1096000, 0.1201674, 0.1311145, 0.1423679, 0.1538542,
0.1655000, 0.1772571, 0.1891400, 0.2011694, 0.2133658,
0.2257499, 0.2383209, 0.2510668, 0.2639922, 0.2771017,
0.2904000, 0.3038912, 0.3175726, 0.3314384, 0.3454828,
0.3597000, 0.3740839, 0.3886396, 0.4033784, 0.4183115,
0.4334499, 0.4487953, 0.4643360, 0.4800640, 0.4959713,
0.5120501, 0.5282959, 0.5446916, 0.5612094, 0.5778215,
0.5945000, 0.6112209, 0.6279758, 0.6447602, 0.6615697,
0.6784000, 0.6952392, 0.7120586, 0.7288284, 0.7455188,
0.7621000, 0.7785432, 0.7948256, 0.8109264, 0.8268248,
0.8425000, 0.8579325, 0.8730816, 0.8878944, 0.9023181,
0.9163000, 0.9297995, 0.9427984, 0.9552776, 0.9672179,
0.9786000, 0.9893856, 0.9995488, 1.0090892, 1.0180064,
1.0263000, 1.0339827, 1.0409860, 1.0471880, 1.0524667,
1.0567000, 1.0597944, 1.0617992, 1.0628068, 1.0629096,
1.0622000, 1.0607352, 1.0584436, 1.0552244, 1.0509768,
1.0456000, 1.0390369, 1.0313608, 1.0226662, 1.0130477,
1.0026000, 0.9913675, 0.9793314, 0.9664916, 0.9528479,
0.9384000, 0.9231940, 0.9072440, 0.8905020, 0.8729200,
0.8544499, 0.8350840, 0.8149460, 0.7941860, 0.7729540,
0.7514000, 0.7295836, 0.7075888, 0.6856022, 0.6638104,
0.6424000, 0.6215149, 0.6011138, 0.5811052, 0.5613977,
0.5419000, 0.5225995, 0.5035464, 0.4847436, 0.4661939,
0.4479000, 0.4298613, 0.4120980, 0.3946440, 0.3775333,
0.3608000, 0.3444563, 0.3285168, 0.3130192, 0.2980011,
0.2835000, 0.2695448, 0.2561184, 0.2431896, 0.2307272,
0.2187000, 0.2070971, 0.1959232, 0.1851708, 0.1748323,
0.1649000, 0.1553667, 0.1462300, 0.1374900, 0.1291467,
0.1212000, 0.1136397, 0.1064650, 0.09969044, 0.09333061,
0.08740000, 0.08190096, 0.07680428, 0.07207712, 0.06768664,
0.06360000, 0.05980685, 0.05628216, 0.05297104, 0.04981861,
0.04677000, 0.04378405, 0.04087536, 0.03807264, 0.03540461,
0.03290000, 0.03056419, 0.02838056, 0.02634484, 0.02445275,
0.02270000, 0.02108429, 0.01959988, 0.01823732, 0.01698717,
0.01584000, 0.01479064, 0.01383132, 0.01294868, 0.01212920,
0.01135916, 0.01062935, 0.009938846, 0.009288422, 0.008678854,
0.008110916, 0.007582388, 0.007088746, 0.006627313, 0.006195408,
0.005790346, 0.005409826, 0.005052583, 0.004717512, 0.004403507,
0.004109457, 0.003833913, 0.003575748, 0.003334342, 0.003109075,
0.002899327, 0.002704348, 0.002523020, 0.002354168, 0.002196616,
0.002049190, 0.001910960, 0.001781438, 0.001660110, 0.001546459,
0.001439971, 0.001340042, 0.001246275, 0.001158471, 0.001076430,
0.0009999493, 0.0009287358, 0.0008624332, 0.0008007503, 0.0007433960,
0.0006900786, 0.0006405156, 0.0005945021, 0.0005518646, 0.0005124290,
0.0004760213, 0.0004424536, 0.0004115117, 0.0003829814, 0.0003566491,
0.0003323011, 0.0003097586, 0.0002888871, 0.0002695394, 0.0002515682,
0.0002348261, 0.0002191710, 0.0002045258, 0.0001908405, 0.0001780654,
0.0001661505, 0.0001550236, 0.0001446219, 0.0001349098, 0.0001258520,
0.0001174130, 0.0001095515, 0.0001022245, 0.00009539445, 0.00008902390,
0.00008307527, 0.00007751269, 0.00007231304, 0.00006745778, 0.00006292844,
0.00005870652, 0.00005477028, 0.00005109918, 0.00004767654, 0.00004448567,
0.00004150994, 0.00003873324, 0.00003614203, 0.00003372352, 0.00003146487,
0.00002935326, 0.00002737573, 0.00002552433, 0.00002379376, 0.00002217870,
0.00002067383, 0.00001927226, 0.00001796640, 0.00001674991, 0.00001561648,
0.00001455977, 0.00001357387, 0.00001265436, 0.00001179723, 0.00001099844,
0.00001025398, 0.000009559646, 0.000008912044, 0.000008308358, 0.000007745769,
0.000007221456, 0.000006732475, 0.000006276423, 0.000005851304, 0.000005455118,
0.000005085868, 0.000004741466, 0.000004420236, 0.000004120783, 0.000003841716,
0.000003581652, 0.000003339127, 0.000003112949, 0.000002902121, 0.000002705645,
0.000002522525, 0.000002351726, 0.000002192415, 0.000002043902, 0.000001905497,
0.000001776509, 0.000001656215, 0.000001544022, 0.000001439440, 0.000001341977,
0.000001251141
};
private static final double[] CIE_Y = {
// CIE Y function values
0.000003917000, 0.000004393581, 0.000004929604, 0.000005532136, 0.000006208245,
0.000006965000, 0.000007813219, 0.000008767336, 0.000009839844, 0.00001104323,
0.00001239000, 0.00001388641, 0.00001555728, 0.00001744296, 0.00001958375,
0.00002202000, 0.00002483965, 0.00002804126, 0.00003153104, 0.00003521521,
0.00003900000, 0.00004282640, 0.00004691460, 0.00005158960, 0.00005717640,
0.00006400000, 0.00007234421, 0.00008221224, 0.00009350816, 0.0001061361,
0.0001200000, 0.0001349840, 0.0001514920, 0.0001702080, 0.0001918160,
0.0002170000, 0.0002469067, 0.0002812400, 0.0003185200, 0.0003572667,
0.0003960000, 0.0004337147, 0.0004730240, 0.0005178760, 0.0005722187,
0.0006400000, 0.0007245600, 0.0008255000, 0.0009411600, 0.001069880,
0.001210000, 0.001362091, 0.001530752, 0.001720368, 0.001935323,
0.002180000, 0.002454800, 0.002764000, 0.003117800, 0.003526400,
0.004000000, 0.004546240, 0.005159320, 0.005829280, 0.006546160,
0.007300000, 0.008086507, 0.008908720, 0.009767680, 0.01066443,
0.01160000, 0.01257317, 0.01358272, 0.01462968, 0.01571509,
0.01684000, 0.01800736, 0.01921448, 0.02045392, 0.02171824,
0.02300000, 0.02429461, 0.02561024, 0.02695857, 0.02835125,
0.02980000, 0.03131083, 0.03288368, 0.03452112, 0.03622571,
0.03800000, 0.03984667, 0.04176800, 0.04376600, 0.04584267,
0.04800000, 0.05024368, 0.05257304, 0.05498056, 0.05745872,
0.06000000, 0.06260197, 0.06527752, 0.06804208, 0.07091109,
0.07390000, 0.07701600, 0.08026640, 0.08366680, 0.08723280,
0.09098000, 0.09491755, 0.09904584, 0.1033674, 0.1078846,
0.1126000, 0.1175320, 0.1226744, 0.1279928, 0.1334528,
0.1390200, 0.1446764, 0.1504693, 0.1564619, 0.1627177,
0.1693000, 0.1762431, 0.1835581, 0.1912735, 0.1994180,
0.2080200, 0.2171199, 0.2267345, 0.2368571, 0.2474812,
0.2586000, 0.2701849, 0.2822939, 0.2950505, 0.3085780,
0.3230000, 0.3384021, 0.3546858, 0.3716986, 0.3892875,
0.4073000, 0.4256299, 0.4443096, 0.4633944, 0.4829395,
0.5030000, 0.5235693, 0.5445120, 0.5656900, 0.5869653,
0.6082000, 0.6293456, 0.6503068, 0.6708752, 0.6908424,
0.7100000, 0.7281852, 0.7454636, 0.7619694, 0.7778368,
0.7932000, 0.8081104, 0.8224962, 0.8363068, 0.8494916,
0.8620000, 0.8738108, 0.8849624, 0.8954936, 0.9054432,
0.9148501, 0.9237348, 0.9320924, 0.9399226, 0.9472252,
0.9540000, 0.9602561, 0.9660074, 0.9712606, 0.9760225,
0.9803000, 0.9840924, 0.9874812, 0.9903128, 0.9928116,
0.9949501, 0.9967108, 0.9980983, 0.9991120, 0.9997482,
1.0000000, 0.9998567, 0.9993046, 0.9983255, 0.9968987,
0.9950000, 0.9926005, 0.9897426, 0.9864444, 0.9827241,
0.9786000, 0.9740837, 0.9691712, 0.9638568, 0.9581349,
0.9520000, 0.9454504, 0.9384992, 0.9311628, 0.9234576,
0.9154000, 0.9070064, 0.8982772, 0.8892048, 0.8797816,
0.8700000, 0.8598613, 0.8493920, 0.8386220, 0.8275813,
0.8163000, 0.8047947, 0.7930820, 0.7811920, 0.7691547,
0.7570000, 0.7447541, 0.7324224, 0.7200036, 0.7074965,
0.6949000, 0.6822192, 0.6694716, 0.6566744, 0.6438448,
0.6310000, 0.6181555, 0.6053144, 0.5924756, 0.5796379,
0.5668000, 0.5539611, 0.5411372, 0.5283528, 0.5156323,
0.5030000, 0.4904688, 0.4780304, 0.4656776, 0.4534032,
0.4412000, 0.4290800, 0.4170360, 0.4050320, 0.3930320,
0.3810000, 0.3689184, 0.3568272, 0.3447768, 0.3328176,
0.3210000, 0.3093381, 0.2978504, 0.2865936, 0.2756245,
0.2650000, 0.2547632, 0.2448896, 0.2353344, 0.2260528,
0.2170000, 0.2081616, 0.1995488, 0.1911552, 0.1829744,
0.1750000, 0.1672235, 0.1596464, 0.1522776, 0.1451259,
0.1382000, 0.1315003, 0.1250248, 0.1187792, 0.1127691,
0.1070000, 0.1014762, 0.09618864, 0.09112296, 0.08626485,
0.08160000, 0.07712064, 0.07282552, 0.06871008, 0.06476976,
0.06100000, 0.05739621, 0.05395504, 0.05067376, 0.04754965,
0.04458000, 0.04175872, 0.03908496, 0.03656384, 0.03420048,
0.03200000, 0.02996261, 0.02807664, 0.02632936, 0.02470805,
0.02320000, 0.02180077, 0.02050112, 0.01928108, 0.01812069,
0.01700000, 0.01590379, 0.01483718, 0.01381068, 0.01283478,
0.01192000, 0.01106831, 0.01027339, 0.009533311, 0.008846157,
0.008210000, 0.007623781, 0.007085424, 0.006591476, 0.006138485,
0.005723000, 0.005343059, 0.004995796, 0.004676404, 0.004380075,
0.004102000, 0.003838453, 0.003589099, 0.003354219, 0.003134093,
0.002929000, 0.002738139, 0.002559876, 0.002393244, 0.002237275,
0.002091000, 0.001953587, 0.001824580, 0.001703580, 0.001590187,
0.001484000, 0.001384496, 0.001291268, 0.001204092, 0.001122744,
0.001047000, 0.0009765896, 0.0009111088, 0.0008501332, 0.0007932384,
0.0007400000, 0.0006900827, 0.0006433100, 0.0005994960, 0.0005584547,
0.0005200000, 0.0004839136, 0.0004500528, 0.0004183452, 0.0003887184,
0.0003611000, 0.0003353835, 0.0003114404, 0.0002891656, 0.0002684539,
0.0002492000, 0.0002313019, 0.0002146856, 0.0001992884, 0.0001850475,
0.0001719000, 0.0001597781, 0.0001486044, 0.0001383016, 0.0001287925,
0.0001200000, 0.0001118595, 0.0001043224, 0.00009733560, 0.00009084587,
0.00008480000, 0.00007914667, 0.00007385800, 0.00006891600, 0.00006430267,
0.00006000000, 0.00005598187, 0.00005222560, 0.00004871840, 0.00004544747,
0.00004240000, 0.00003956104, 0.00003691512, 0.00003444868, 0.00003214816,
0.00003000000, 0.00002799125, 0.00002611356, 0.00002436024, 0.00002272461,
0.00002120000, 0.00001977855, 0.00001845285, 0.00001721687, 0.00001606459,
0.00001499000, 0.00001398728, 0.00001305155, 0.00001217818, 0.00001136254,
0.00001060000, 0.000009885877, 0.000009217304, 0.000008592362, 0.000008009133,
0.000007465700, 0.000006959567, 0.000006487995, 0.000006048699, 0.000005639396,
0.000005257800, 0.000004901771, 0.000004569720, 0.000004260194, 0.000003971739,
0.000003702900, 0.000003452163, 0.000003218302, 0.000003000300, 0.000002797139,
0.000002607800, 0.000002431220, 0.000002266531, 0.000002113013, 0.000001969943,
0.000001836600, 0.000001712230, 0.000001596228, 0.000001488090, 0.000001387314,
0.000001293400, 0.000001205820, 0.000001124143, 0.000001048009, 0.0000009770578,
0.0000009109300, 0.0000008492513, 0.0000007917212, 0.0000007380904, 0.0000006881098,
0.0000006415300, 0.0000005980895, 0.0000005575746, 0.0000005198080, 0.0000004846123,
0.0000004518100
};
private static final double[] CIE_Z = {
// CIE Z function values
0.0006061000, 0.0006808792, 0.0007651456, 0.0008600124, 0.0009665928,
0.001086000, 0.001220586, 0.001372729, 0.001543579, 0.001734286,
0.001946000, 0.002177777, 0.002435809, 0.002731953, 0.003078064,
0.003486000, 0.003975227, 0.004540880, 0.005158320, 0.005802907,
0.006450001, 0.007083216, 0.007745488, 0.008501152, 0.009414544,
0.01054999, 0.01196580, 0.01365587, 0.01558805, 0.01773015,
0.02005001, 0.02251136, 0.02520288, 0.02827972, 0.03189704,
0.03621000, 0.04143771, 0.04750372, 0.05411988, 0.06099803,
0.06785001, 0.07448632, 0.08136156, 0.08915364, 0.09854048,
0.1102000, 0.1246133, 0.1417017, 0.1613035, 0.1832568,
0.2074000, 0.2336921, 0.2626114, 0.2947746, 0.3307985,
0.3713000, 0.4162091, 0.4654642, 0.5196948, 0.5795303,
0.6456000, 0.7184838, 0.7967133, 0.8778459, 0.9594390,
1.0390501, 1.1153673, 1.1884971, 1.2581233, 1.3239296,
1.3856000, 1.4426352, 1.4948035, 1.5421903, 1.5848807,
1.6229600, 1.6564048, 1.6852959, 1.7098745, 1.7303821,
1.7470600, 1.7600446, 1.7696233, 1.7762637, 1.7804334,
1.7826000, 1.7829682, 1.7816998, 1.7791982, 1.7758671,
1.7721100, 1.7682589, 1.7640390, 1.7589438, 1.7524663,
1.7441000, 1.7335595, 1.7208581, 1.7059369, 1.6887372,
1.6692000, 1.6475287, 1.6234127, 1.5960223, 1.5645280,
1.5281000, 1.4861114, 1.4395215, 1.3898799, 1.3387362,
1.2876400, 1.2374223, 1.1878243, 1.1387611, 1.0901480,
1.0419000, 0.9941976, 0.9473473, 0.9014531, 0.8566193,
0.8129501, 0.7705173, 0.7294448, 0.6899136, 0.6521049,
0.6162000, 0.5823286, 0.5504162, 0.5203376, 0.4919673,
0.4651800, 0.4399246, 0.4161836, 0.3938822, 0.3729459,
0.3533000, 0.3348578, 0.3175521, 0.3013375, 0.2861686,
0.2720000, 0.2588171, 0.2464838, 0.2347718, 0.2234533,
0.2123000, 0.2011692, 0.1901196, 0.1792254, 0.1685608,
0.1582000, 0.1481383, 0.1383758, 0.1289942, 0.1200751,
0.1117000, 0.1039048, 0.09666748, 0.08998272, 0.08384531,
0.07824999, 0.07320899, 0.06867816, 0.06456784, 0.06078835,
0.05725001, 0.05390435, 0.05074664, 0.04775276, 0.04489859,
0.04216000, 0.03950728, 0.03693564, 0.03445836, 0.03208872,
0.02984000, 0.02771181, 0.02569444, 0.02378716, 0.02198925,
0.02030000, 0.01871805, 0.01724036, 0.01586364, 0.01458461,
0.01340000, 0.01230723, 0.01130188, 0.01037792, 0.009529306,
0.008749999, 0.008035200, 0.007381600, 0.006785400, 0.006242800,
0.005749999, 0.005303600, 0.004899800, 0.004534200, 0.004202400,
0.003900000, 0.003623200, 0.003370600, 0.003141400, 0.002934800,
0.002749999, 0.002585200, 0.002438600, 0.002309400, 0.002196800,
0.002100000, 0.002017733, 0.001948200, 0.001889800, 0.001840933,
0.001800000, 0.001766267, 0.001737800, 0.001711200, 0.001683067,
0.001650001, 0.001610133, 0.001564400, 0.001513600, 0.001458533,
0.001400000, 0.001336667, 0.001270000, 0.001205000, 0.001146667,
0.001100000, 0.001068800, 0.001049400, 0.001035600, 0.001021200,
0.001000000, 0.0009686400, 0.0009299200, 0.0008868800, 0.0008425600,
0.0008000000, 0.0007609600, 0.0007236800, 0.0006859200, 0.0006454400,
0.0006000000, 0.0005478667, 0.0004916000, 0.0004354000, 0.0003834667,
0.0003400000, 0.0003072533, 0.0002831600, 0.0002654400, 0.0002518133,
0.0002400000, 0.0002295467, 0.0002206400, 0.0002119600, 0.0002021867,
0.0001900000, 0.0001742133, 0.0001556400, 0.0001359600, 0.0001168533,
0.0001000000, 0.00008613333, 0.00007460000, 0.00006500000, 0.00005693333,
0.00004999999, 0.00004416000, 0.00003948000, 0.00003572000, 0.00003264000,
0.00003000000, 0.00002765333, 0.00002556000, 0.00002364000, 0.00002181333,
0.00002000000, 0.00001813333, 0.00001620000, 0.00001420000, 0.00001213333,
0.00001000000, 0.000007733333, 0.000005400000, 0.000003200000, 0.000001333333,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000, 0.000000000000,
0.000000000000
};
}
}

@ -1,16 +1,20 @@
package eu.jonahbauer.raytracing.render.spectral.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.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.texture.Texture;
import eu.jonahbauer.raytracing.scene.HitResult;
import eu.jonahbauer.raytracing.scene.SkyBox;
import org.jetbrains.annotations.NotNull;
public interface Spectrum {
public interface Spectrum extends Texture, SkyBox {
int LAMBDA_MIN = 360;
int LAMBDA_MAX = 830;
int SAMPLES = 4;
/**
* {@return the maximum value of <code>this</code> spectrum over the range of wavelengths}
@ -23,27 +27,51 @@ public interface Spectrum {
*/
double get(double lambda);
default @NotNull Spectrum scale(double scale) {
return new ScaledSpectrum(this, scale);
}
default @NotNull SampledSpectrum sample(@NotNull SampledWavelengths lambdas) {
return new SampledSpectrum(lambdas, this);
}
default @NotNull ColorXYZ toXYZ() {
return new ColorXYZ(
innerProduct(Spectra.X, this) / ColorXYZ.CIE_Y_INTEGRAL,
innerProduct(Spectra.Y, this) / ColorXYZ.CIE_Y_INTEGRAL,
innerProduct(Spectra.Z, this) / ColorXYZ.CIE_Y_INTEGRAL
Util.innerProduct(Spectra.X, this) / ColorXYZ.CIE_Y_INTEGRAL,
Util.innerProduct(Spectra.Y, this) / ColorXYZ.CIE_Y_INTEGRAL,
Util.innerProduct(Spectra.Z, this) / ColorXYZ.CIE_Y_INTEGRAL
);
}
default @NotNull Color toRGB(@NotNull ColorSpace cs) {
default @NotNull ColorRGB toRGB(@NotNull ColorSpace cs) {
return cs.toRGB(toXYZ());
}
private static double innerProduct(@NotNull Spectrum f, @NotNull Spectrum g) {
var integral = 0.0;
for (var lambda = LAMBDA_MIN; lambda <= LAMBDA_MAX; lambda++) {
integral += f.get(lambda) * g.get(lambda);
}
return integral;
/*
* Texture
*/
@Override
default @NotNull Spectrum get(@NotNull HitResult hit) {
return this;
}
@Override
default @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
return this;
}
@Override
default boolean isUVRequired() {
return false;
}
/*
* SkyBox
*/
@Override
default @NotNull SampledSpectrum getColor(@NotNull Ray ray) {
return this.sample(ray.lambda());
}
}

@ -0,0 +1,17 @@
package eu.jonahbauer.raytracing.render.spectral.spectrum;
import org.jetbrains.annotations.NotNull;
final class Util {
private Util() {
throw new UnsupportedOperationException();
}
public static double innerProduct(@NotNull Spectrum f, @NotNull Spectrum g) {
var integral = 0.0;
for (var lambda = Spectrum.LAMBDA_MIN; lambda <= Spectrum.LAMBDA_MAX; lambda++) {
integral += f.get(lambda) * g.get(lambda);
}
return integral;
}
}

@ -1,12 +1,13 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull;
public record CheckerTexture(double scale, @NotNull Texture even, @NotNull Texture odd) implements Texture {
@Override
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
public @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
var x = (int) Math.floor(p.x() / scale);
var y = (int) Math.floor(p.y() / scale);
var z = (int) Math.floor(p.z() / scale);

@ -1,148 +0,0 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.scene.SkyBox;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.Random;
import static eu.jonahbauer.raytracing.Main.DEBUG;
public record Color(double r, double g, double b) implements Texture, SkyBox {
public static final @NotNull Color BLACK = new Color(0.0, 0.0, 0.0);
public static final @NotNull Color WHITE = new Color(1.0, 1.0, 1.0);
public static final @NotNull Color RED = new Color(1.0, 0.0, 0.0);
public static final @NotNull Color GREEN = new Color(0.0, 1.0, 0.0);
public static final @NotNull Color BLUE = new Color(0.0, 0.0, 1.0);
public static @NotNull Color lerp(@NotNull Color a, @NotNull Color b, double t) {
if (t < 0) return a;
if (t > 1) return b;
return new Color(
Math.fma(t, b.r, Math.fma(-t, a.r, a.r)),
Math.fma(t, b.g, Math.fma(-t, a.g, a.g)),
Math.fma(t, b.b, Math.fma(-t, a.b, a.b))
);
}
public static @NotNull Color multiply(@NotNull Color a, @NotNull Color b) {
return new Color(a.r() * b.r(), a.g() * b.g(), a.b() * b.b());
}
public static @NotNull Color multiply(@NotNull Color a, double b) {
return new Color(a.r() * b, a.g() * b, a.b() * b);
}
public static @NotNull Color add(@NotNull Color a, @NotNull Color b) {
return new Color(a.r() + b.r(), a.g() + b.g(), a.b() + b.b());
}
public static @NotNull Color random(@NotNull Random random) {
return new Color(random.nextDouble(), random.nextDouble(), random.nextDouble());
}
public static @NotNull Color random(@NotNull Random random, double min, double max) {
var span = max - min;
return new Color(
Math.fma(random.nextDouble(), span, min),
Math.fma(random.nextDouble(), span, min),
Math.fma(random.nextDouble(), span, min)
);
}
public static @NotNull Color average(@NotNull Color current, @NotNull Color next, int index) {
var factor = 1d / index;
return new Color(
Math.fma(factor, next.r() - current.r(), current.r()),
Math.fma(factor, next.g() - current.g(), current.g()),
Math.fma(factor, next.b() - current.b(), current.b())
);
}
public static @NotNull Color gamma(@NotNull Color color, double gamma) {
if (gamma == 1.0) {
return color;
} else if (gamma == 2.0) {
return new Color(
Math.sqrt(color.r()),
Math.sqrt(color.g()),
Math.sqrt(color.b())
);
} else {
return new Color(
Math.pow(color.r(), 1 / gamma),
Math.pow(color.g(), 1 / gamma),
Math.pow(color.b(), 1 / gamma)
);
}
}
public Color(int rgb) {
this((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
}
public Color(int red, int green, int blue) {
this(red / 255f, green / 255f, blue / 255f);
}
public Color(@NotNull Vec3 vec) {
this(vec.x(), vec.y(), vec.z());
}
public Color {
if (DEBUG) {
if (!Double.isFinite(r) || !Double.isFinite(g) || !Double.isFinite(b)) {
throw new IllegalArgumentException("r, g and b must be finite");
}
if (r < 0 || g < 0 || b < 0) {
throw new IllegalArgumentException("r, g and b must be non-negative");
}
}
}
public int red() {
return toInt(r);
}
public int green() {
return toInt(g);
}
public int blue() {
return toInt(b);
}
public double component(int i) {
return switch (i) {
case 0 -> r;
case 1 -> g;
case 2 -> b;
default -> throw new IndexOutOfBoundsException(i);
};
}
@Override
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
return this;
}
@Override
public @NotNull Color getColor(@NotNull Ray ray) {
return this;
}
@Override
public boolean isUVRequired() {
return false;
}
public @NotNull Vec3 toVec3() {
return new Vec3(r, g, b);
}
private static int toInt(double value) {
return Math.clamp((int) (255.99 * value), 0, 255);
}
}

@ -1,7 +1,11 @@
package eu.jonahbauer.raytracing.render.texture;
import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.canvas.Image;
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 org.jetbrains.annotations.NotNull;
import javax.imageio.ImageIO;
@ -10,22 +14,33 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Objects;
public record ImageTexture(@NotNull Image image) implements Texture {
public final class ImageTexture implements Texture {
private static final String PATH_PREFIX = "/eu/jonahbauer/raytracing/textures/";
public ImageTexture {
Objects.requireNonNull(image, "image");
}
private final int width;
private final int height;
private final @NotNull Spectrum[][] spectra;
public ImageTexture(@NotNull BufferedImage image, @NotNull ColorSpace cs, @NotNull Type type, boolean gamma) {
this.width = image.getWidth();
this.height = image.getHeight();
this.spectra = new Spectrum[height][width];
public ImageTexture(@NotNull BufferedImage image) {
this(new Image(image));
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);
}
}
}
public ImageTexture(@NotNull String path) {
this(read(path));
public ImageTexture(@NotNull String path, @NotNull ColorSpace cs) {
this(read(path), cs, Type.ALBEDO, true);
}
private static @NotNull BufferedImage read(@NotNull String path) {
try (var in = Objects.requireNonNull(ImageTexture.class.getResourceAsStream(path))) {
try (var in = Objects.requireNonNull(ImageTexture.class.getResourceAsStream(PATH_PREFIX + path))) {
return ImageIO.read(in);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
@ -33,11 +48,29 @@ public record ImageTexture(@NotNull Image image) implements Texture {
}
@Override
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
public @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
u = Math.clamp(u, 0, 1);
v = 1 - Math.clamp(v, 0, 1);
int x = (int) (u * (image.getWidth() - 1));
int y = (int) (v * (image.getHeight() - 1));
return image.get(x, y);
int x = (int) (u * (width - 1));
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,6 +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 org.jetbrains.annotations.NotNull;
import java.util.Objects;
@ -11,11 +13,11 @@ import java.util.random.RandomGenerator;
public final class PerlinTexture implements Texture {
private static final int POINT_COUNT = 256;
private static final @NotNull Random RANDOM = new Random();
private static final @NotNull DoubleFunction<Color> GREYSCALE = t -> new Color(t, t, t);
private static final @NotNull DoubleFunction<Spectrum> GREYSCALE = Spectra.WHITE::scale;
private final double scale;
private final int turbulence;
private final @NotNull DoubleFunction<Color> color;
private final @NotNull DoubleFunction<Spectrum> color;
private final int mask;
private final Vec3[] randvec;
@ -35,12 +37,12 @@ public final class PerlinTexture implements Texture {
this(scale, turbulence, GREYSCALE);
}
public PerlinTexture(double scale, int turbulence, @NotNull DoubleFunction<Color> color) {
public PerlinTexture(double scale, int turbulence, @NotNull DoubleFunction<Spectrum> color) {
this(scale, turbulence, color, POINT_COUNT, RANDOM);
}
public PerlinTexture(
double scale, int turbulence, @NotNull DoubleFunction<Color> color,
double scale, int turbulence, @NotNull DoubleFunction<Spectrum> color,
int count, @NotNull RandomGenerator random
) {
if ((count & (count - 1)) != 0) throw new IllegalArgumentException("count must be a power of two");
@ -118,7 +120,7 @@ public final class PerlinTexture implements Texture {
}
@Override
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
public @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
var noise = getNoise(p, turbulence);
var t = Math.fma(0.5, Math.sin(Math.PI * noise), 0.5);
return color.apply(t);

@ -1,6 +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.scene.HitResult;
import org.jetbrains.annotations.NotNull;
@ -8,7 +9,7 @@ public interface Texture {
/**
* {@return the color of <code>this</code> texture for a hit}
*/
default @NotNull Color get(@NotNull HitResult hit) {
default @NotNull Spectrum get(@NotNull HitResult hit) {
return get(hit.u(), hit.v(), hit.position());
}
@ -18,7 +19,7 @@ public interface Texture {
* @param v the texture v coordinate
* @param p the position
*/
@NotNull Color get(double u, double v, @NotNull Vec3 p);
@NotNull Spectrum get(double u, double v, @NotNull Vec3 p);
/**
* Returns whether {@link #get(double, double, Vec3)} uses the {@code u} and/or {@code v} parameters.

@ -2,7 +2,8 @@ package eu.jonahbauer.raytracing.scene;
import eu.jonahbauer.raytracing.math.AABB;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
import eu.jonahbauer.raytracing.scene.util.HittableCollection;
import org.jetbrains.annotations.NotNull;
@ -23,7 +24,7 @@ public final class Scene extends HittableCollection {
}
public Scene(@NotNull List<? extends @NotNull Hittable> objects, @Nullable List<? extends @NotNull Target> targets) {
this(Color.BLACK, objects, targets);
this(Spectra.BLACK, objects, targets);
}
public Scene(@NotNull SkyBox background, @NotNull List<? extends @NotNull Hittable> objects) {
@ -54,7 +55,7 @@ public final class Scene extends HittableCollection {
return targets;
}
public @NotNull Color getBackgroundColor(@NotNull Ray ray) {
public @NotNull SampledSpectrum getBackgroundColor(@NotNull Ray ray) {
return background.getColor(ray);
}
}

@ -1,21 +1,27 @@
package eu.jonahbauer.raytracing.scene;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
import org.jetbrains.annotations.NotNull;
@FunctionalInterface
public interface SkyBox {
@NotNull Color getColor(@NotNull Ray ray);
@NotNull SampledSpectrum getColor(@NotNull Ray ray);
static @NotNull SkyBox gradient(@NotNull Color top, @NotNull Color bottom) {
static @NotNull SkyBox gradient(@NotNull Spectrum top, @NotNull Spectrum bottom) {
return ray -> {
// altitude from -pi/2 to pi/2
var alt = Math.copySign(
Math.acos(ray.direction().withY(0).unit().times(ray.direction().unit())),
Math.acos(ray.direction().withY(0).unit().dot(ray.direction().unit())),
ray.direction().y()
);
return Color.lerp(bottom, top, alt / Math.PI + 0.5);
return SampledSpectrum.lerp(
top.sample(ray.lambda()),
bottom.sample(ray.lambda()),
alt / Math.PI + 0.5
);
};
}
}

@ -31,23 +31,23 @@ public abstract class Hittable2D implements Hittable {
var n = u.cross(v);
if (n.squared() < 1e-8) throw new IllegalArgumentException();
this.normal = n.unit();
this.d = origin.times(normal);
this.d = origin.dot(normal);
this.w = n.div(n.squared());
}
@Override
public @NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range) {
var denominator = ray.direction().times(normal);
var denominator = ray.direction().dot(normal);
if (Math.abs(denominator) < 1e-8) return Optional.empty(); // parallel
var t = (d - ray.origin().times(normal)) / denominator;
var t = (d - ray.origin().dot(normal)) / denominator;
if (!range.surrounds(t)) return Optional.empty();
var position = ray.at(t);
var p = position.minus(origin);
var alpha = w.times(p.cross(v));
var beta = w.times(u.cross(p));
var alpha = w.dot(p.cross(v));
var beta = w.dot(u.cross(p));
if (!isInterior(alpha, beta)) return Optional.empty();
var frontFace = denominator < 0;
@ -58,10 +58,10 @@ public abstract class Hittable2D implements Hittable {
}
protected double hit0(@NotNull Ray ray, @NotNull Range range) {
var denominator = ray.direction().times(normal);
var denominator = ray.direction().dot(normal);
if (Math.abs(denominator) < 1e-8) return Double.NaN; // parallel
var t = (d - ray.origin().times(normal)) / denominator;
var t = (d - ray.origin().dot(normal)) / denominator;
if (!range.surrounds(t)) return Double.NaN;
var position = ray.at(t);

@ -46,7 +46,7 @@ public final class Sphere implements Hittable, Target {
var position = ray.at(t);
var normal = Vec3.fma(invRadius, position, normalizedCenter);
var frontFace = normal.times(ray.direction()) < 0;
var frontFace = normal.dot(ray.direction()) < 0;
double u;
double v;
@ -70,7 +70,7 @@ public final class Sphere implements Hittable, Target {
var oc = ray.origin().minus(center);
var a = ray.direction().squared();
var h = ray.direction().times(oc);
var h = ray.direction().dot(oc);
var c = oc.squared() - radius * radius;
var discriminant = h * h - a * c;

@ -62,7 +62,7 @@ public sealed class RotateY extends Transform {
var newOrigin = transform(origin);
var newDirection = transform(direction);
return new Ray(newOrigin, newDirection);
return new Ray(newOrigin, newDirection, ray.lambda());
}
@Override

@ -35,7 +35,7 @@ public sealed class Translate extends Transform {
@Override
protected final @NotNull Ray transform(@NotNull Ray ray) {
return new Ray(ray.origin().minus(offset), ray.direction());
return new Ray(ray.origin().minus(offset), ray.direction(), ray.lambda());
}
@Override

@ -13,7 +13,7 @@ public final class PdfUtil {
* must be unit vectors.
*/
public static double getSolidAngle(@NotNull Vec3 a, @NotNull Vec3 b, @NotNull Vec3 c) {
var angle = 2 * Math.atan(Math.abs(Vec3.tripleProduct(a, b, c)) / (1 + a.times(b) + b.times(c) + c.times(a)));
var angle = 2 * Math.atan(Math.abs(Vec3.tripleProduct(a, b, c)) / (1 + a.dot(b) + b.dot(c) + c.dot(a)));
return angle < 0 ? 2 * Math.PI + angle : angle;
}
}

@ -1,471 +0,0 @@
360,0.000129900000,0.0000039170000,0.000606100000
361,0.000145847000,0.0000043935810,0.000680879200
362,0.000163802100,0.0000049296040,0.000765145600
363,0.000184003700,0.0000055321360,0.000860012400
364,0.000206690200,0.0000062082450,0.000966592800
365,0.000232100000,0.0000069650000,0.001086000000
366,0.000260728000,0.0000078132190,0.001220586000
367,0.000293075000,0.0000087673360,0.001372729000
368,0.000329388000,0.0000098398440,0.001543579000
369,0.000369914000,0.0000110432300,0.001734286000
370,0.000414900000,0.0000123900000,0.001946000000
371,0.000464158700,0.0000138864100,0.002177777000
372,0.000518986000,0.0000155572800,0.002435809000
373,0.000581854000,0.0000174429600,0.002731953000
374,0.000655234700,0.0000195837500,0.003078064000
375,0.000741600000,0.0000220200000,0.003486000000
376,0.000845029600,0.0000248396500,0.003975227000
377,0.000964526800,0.0000280412600,0.004540880000
378,0.001094949000,0.0000315310400,0.005158320000
379,0.001231154000,0.0000352152100,0.005802907000
380,0.001368000000,0.0000390000000,0.006450001000
381,0.001502050000,0.0000428264000,0.007083216000
382,0.001642328000,0.0000469146000,0.007745488000
383,0.001802382000,0.0000515896000,0.008501152000
384,0.001995757000,0.0000571764000,0.009414544000
385,0.002236000000,0.0000640000000,0.010549990000
386,0.002535385000,0.0000723442100,0.011965800000
387,0.002892603000,0.0000822122400,0.013655870000
388,0.003300829000,0.0000935081600,0.015588050000
389,0.003753236000,0.0001061361000,0.017730150000
390,0.004243000000,0.0001200000000,0.020050010000
391,0.004762389000,0.0001349840000,0.022511360000
392,0.005330048000,0.0001514920000,0.025202880000
393,0.005978712000,0.0001702080000,0.028279720000
394,0.006741117000,0.0001918160000,0.031897040000
395,0.007650000000,0.0002170000000,0.036210000000
396,0.008751373000,0.0002469067000,0.041437710000
397,0.010028880000,0.0002812400000,0.047503720000
398,0.011421700000,0.0003185200000,0.054119880000
399,0.012869010000,0.0003572667000,0.060998030000
400,0.014310000000,0.0003960000000,0.067850010000
401,0.015704430000,0.0004337147000,0.074486320000
402,0.017147440000,0.0004730240000,0.081361560000
403,0.018781220000,0.0005178760000,0.089153640000
404,0.020748010000,0.0005722187000,0.098540480000
405,0.023190000000,0.0006400000000,0.110200000000
406,0.026207360000,0.0007245600000,0.124613300000
407,0.029782480000,0.0008255000000,0.141701700000
408,0.033880920000,0.0009411600000,0.161303500000
409,0.038468240000,0.0010698800000,0.183256800000
410,0.043510000000,0.0012100000000,0.207400000000
411,0.048995600000,0.0013620910000,0.233692100000
412,0.055022600000,0.0015307520000,0.262611400000
413,0.061718800000,0.0017203680000,0.294774600000
414,0.069212000000,0.0019353230000,0.330798500000
415,0.077630000000,0.0021800000000,0.371300000000
416,0.086958110000,0.0024548000000,0.416209100000
417,0.097176720000,0.0027640000000,0.465464200000
418,0.108406300000,0.0031178000000,0.519694800000
419,0.120767200000,0.0035264000000,0.579530300000
420,0.134380000000,0.0040000000000,0.645600000000
421,0.149358200000,0.0045462400000,0.718483800000
422,0.165395700000,0.0051593200000,0.796713300000
423,0.181983100000,0.0058292800000,0.877845900000
424,0.198611000000,0.0065461600000,0.959439000000
425,0.214770000000,0.0073000000000,1.039050100000
426,0.230186800000,0.0080865070000,1.115367300000
427,0.244879700000,0.0089087200000,1.188497100000
428,0.258777300000,0.0097676800000,1.258123300000
429,0.271807900000,0.0106644300000,1.323929600000
430,0.283900000000,0.0116000000000,1.385600000000
431,0.294943800000,0.0125731700000,1.442635200000
432,0.304896500000,0.0135827200000,1.494803500000
433,0.313787300000,0.0146296800000,1.542190300000
434,0.321645400000,0.0157150900000,1.584880700000
435,0.328500000000,0.0168400000000,1.622960000000
436,0.334351300000,0.0180073600000,1.656404800000
437,0.339210100000,0.0192144800000,1.685295900000
438,0.343121300000,0.0204539200000,1.709874500000
439,0.346129600000,0.0217182400000,1.730382100000
440,0.348280000000,0.0230000000000,1.747060000000
441,0.349599900000,0.0242946100000,1.760044600000
442,0.350147400000,0.0256102400000,1.769623300000
443,0.350013000000,0.0269585700000,1.776263700000
444,0.349287000000,0.0283512500000,1.780433400000
445,0.348060000000,0.0298000000000,1.782600000000
446,0.346373300000,0.0313108300000,1.782968200000
447,0.344262400000,0.0328836800000,1.781699800000
448,0.341808800000,0.0345211200000,1.779198200000
449,0.339094100000,0.0362257100000,1.775867100000
450,0.336200000000,0.0380000000000,1.772110000000
451,0.333197700000,0.0398466700000,1.768258900000
452,0.330041100000,0.0417680000000,1.764039000000
453,0.326635700000,0.0437660000000,1.758943800000
454,0.322886800000,0.0458426700000,1.752466300000
455,0.318700000000,0.0480000000000,1.744100000000
456,0.314025100000,0.0502436800000,1.733559500000
457,0.308884000000,0.0525730400000,1.720858100000
458,0.303290400000,0.0549805600000,1.705936900000
459,0.297257900000,0.0574587200000,1.688737200000
460,0.290800000000,0.0600000000000,1.669200000000
461,0.283970100000,0.0626019700000,1.647528700000
462,0.276721400000,0.0652775200000,1.623412700000
463,0.268917800000,0.0680420800000,1.596022300000
464,0.260422700000,0.0709110900000,1.564528000000
465,0.251100000000,0.0739000000000,1.528100000000
466,0.240847500000,0.0770160000000,1.486111400000
467,0.229851200000,0.0802664000000,1.439521500000
468,0.218407200000,0.0836668000000,1.389879900000
469,0.206811500000,0.0872328000000,1.338736200000
470,0.195360000000,0.0909800000000,1.287640000000
471,0.184213600000,0.0949175500000,1.237422300000
472,0.173327300000,0.0990458400000,1.187824300000
473,0.162688100000,0.1033674000000,1.138761100000
474,0.152283300000,0.1078846000000,1.090148000000
475,0.142100000000,0.1126000000000,1.041900000000
476,0.132178600000,0.1175320000000,0.994197600000
477,0.122569600000,0.1226744000000,0.947347300000
478,0.113275200000,0.1279928000000,0.901453100000
479,0.104297900000,0.1334528000000,0.856619300000
480,0.095640000000,0.1390200000000,0.812950100000
481,0.087299550000,0.1446764000000,0.770517300000
482,0.079308040000,0.1504693000000,0.729444800000
483,0.071717760000,0.1564619000000,0.689913600000
484,0.064580990000,0.1627177000000,0.652104900000
485,0.057950010000,0.1693000000000,0.616200000000
486,0.051862110000,0.1762431000000,0.582328600000
487,0.046281520000,0.1835581000000,0.550416200000
488,0.041150880000,0.1912735000000,0.520337600000
489,0.036412830000,0.1994180000000,0.491967300000
490,0.032010000000,0.2080200000000,0.465180000000
491,0.027917200000,0.2171199000000,0.439924600000
492,0.024144400000,0.2267345000000,0.416183600000
493,0.020687000000,0.2368571000000,0.393882200000
494,0.017540400000,0.2474812000000,0.372945900000
495,0.014700000000,0.2586000000000,0.353300000000
496,0.012161790000,0.2701849000000,0.334857800000
497,0.009919960000,0.2822939000000,0.317552100000
498,0.007967240000,0.2950505000000,0.301337500000
499,0.006296346000,0.3085780000000,0.286168600000
500,0.004900000000,0.3230000000000,0.272000000000
501,0.003777173000,0.3384021000000,0.258817100000
502,0.002945320000,0.3546858000000,0.246483800000
503,0.002424880000,0.3716986000000,0.234771800000
504,0.002236293000,0.3892875000000,0.223453300000
505,0.002400000000,0.4073000000000,0.212300000000
506,0.002925520000,0.4256299000000,0.201169200000
507,0.003836560000,0.4443096000000,0.190119600000
508,0.005174840000,0.4633944000000,0.179225400000
509,0.006982080000,0.4829395000000,0.168560800000
510,0.009300000000,0.5030000000000,0.158200000000
511,0.012149490000,0.5235693000000,0.148138300000
512,0.015535880000,0.5445120000000,0.138375800000
513,0.019477520000,0.5656900000000,0.128994200000
514,0.023992770000,0.5869653000000,0.120075100000
515,0.029100000000,0.6082000000000,0.111700000000
516,0.034814850000,0.6293456000000,0.103904800000
517,0.041120160000,0.6503068000000,0.096667480000
518,0.047985040000,0.6708752000000,0.089982720000
519,0.055378610000,0.6908424000000,0.083845310000
520,0.063270000000,0.7100000000000,0.078249990000
521,0.071635010000,0.7281852000000,0.073208990000
522,0.080462240000,0.7454636000000,0.068678160000
523,0.089739960000,0.7619694000000,0.064567840000
524,0.099456450000,0.7778368000000,0.060788350000
525,0.109600000000,0.7932000000000,0.057250010000
526,0.120167400000,0.8081104000000,0.053904350000
527,0.131114500000,0.8224962000000,0.050746640000
528,0.142367900000,0.8363068000000,0.047752760000
529,0.153854200000,0.8494916000000,0.044898590000
530,0.165500000000,0.8620000000000,0.042160000000
531,0.177257100000,0.8738108000000,0.039507280000
532,0.189140000000,0.8849624000000,0.036935640000
533,0.201169400000,0.8954936000000,0.034458360000
534,0.213365800000,0.9054432000000,0.032088720000
535,0.225749900000,0.9148501000000,0.029840000000
536,0.238320900000,0.9237348000000,0.027711810000
537,0.251066800000,0.9320924000000,0.025694440000
538,0.263992200000,0.9399226000000,0.023787160000
539,0.277101700000,0.9472252000000,0.021989250000
540,0.290400000000,0.9540000000000,0.020300000000
541,0.303891200000,0.9602561000000,0.018718050000
542,0.317572600000,0.9660074000000,0.017240360000
543,0.331438400000,0.9712606000000,0.015863640000
544,0.345482800000,0.9760225000000,0.014584610000
545,0.359700000000,0.9803000000000,0.013400000000
546,0.374083900000,0.9840924000000,0.012307230000
547,0.388639600000,0.9874182000000,0.011301880000
548,0.403378400000,0.9903128000000,0.010377920000
549,0.418311500000,0.9928116000000,0.009529306000
550,0.433449900000,0.9949501000000,0.008749999000
551,0.448795300000,0.9967108000000,0.008035200000
552,0.464336000000,0.9980983000000,0.007381600000
553,0.480064000000,0.9991120000000,0.006785400000
554,0.495971300000,0.9997482000000,0.006242800000
555,0.512050100000,1.0000000000000,0.005749999000
556,0.528295900000,0.9998567000000,0.005303600000
557,0.544691600000,0.9993046000000,0.004899800000
558,0.561209400000,0.9983255000000,0.004534200000
559,0.577821500000,0.9968987000000,0.004202400000
560,0.594500000000,0.9950000000000,0.003900000000
561,0.611220900000,0.9926005000000,0.003623200000
562,0.627975800000,0.9897426000000,0.003370600000
563,0.644760200000,0.9864444000000,0.003141400000
564,0.661569700000,0.9827241000000,0.002934800000
565,0.678400000000,0.9786000000000,0.002749999000
566,0.695239200000,0.9740837000000,0.002585200000
567,0.712058600000,0.9691712000000,0.002438600000
568,0.728828400000,0.9638568000000,0.002309400000
569,0.745518800000,0.9581349000000,0.002196800000
570,0.762100000000,0.9520000000000,0.002100000000
571,0.778543200000,0.9454504000000,0.002017733000
572,0.794825600000,0.9384992000000,0.001948200000
573,0.810926400000,0.9311628000000,0.001889800000
574,0.826824800000,0.9234576000000,0.001840933000
575,0.842500000000,0.9154000000000,0.001800000000
576,0.857932500000,0.9070064000000,0.001766267000
577,0.873081600000,0.8982772000000,0.001737800000
578,0.887894400000,0.8892048000000,0.001711200000
579,0.902318100000,0.8797816000000,0.001683067000
580,0.916300000000,0.8700000000000,0.001650001000
581,0.929799500000,0.8598613000000,0.001610133000
582,0.942798400000,0.8493920000000,0.001564400000
583,0.955277600000,0.8386220000000,0.001513600000
584,0.967217900000,0.8275813000000,0.001458533000
585,0.978600000000,0.8163000000000,0.001400000000
586,0.989385600000,0.8047947000000,0.001336667000
587,0.999548800000,0.7930820000000,0.001270000000
588,1.009089200000,0.7811920000000,0.001205000000
589,1.018006400000,0.7691547000000,0.001146667000
590,1.026300000000,0.7570000000000,0.001100000000
591,1.033982700000,0.7447541000000,0.001068800000
592,1.040986000000,0.7324224000000,0.001049400000
593,1.047188000000,0.7200036000000,0.001035600000
594,1.052466700000,0.7074965000000,0.001021200000
595,1.056700000000,0.6949000000000,0.001000000000
596,1.059794400000,0.6822192000000,0.000968640000
597,1.061799200000,0.6694716000000,0.000929920000
598,1.062806800000,0.6566744000000,0.000886880000
599,1.062909600000,0.6438448000000,0.000842560000
600,1.062200000000,0.6310000000000,0.000800000000
601,1.060735200000,0.6181555000000,0.000760960000
602,1.058443600000,0.6053144000000,0.000723680000
603,1.055224400000,0.5924756000000,0.000685920000
604,1.050976800000,0.5796379000000,0.000645440000
605,1.045600000000,0.5668000000000,0.000600000000
606,1.039036900000,0.5539611000000,0.000547866700
607,1.031360800000,0.5411372000000,0.000491600000
608,1.022666200000,0.5283528000000,0.000435400000
609,1.013047700000,0.5156323000000,0.000383466700
610,1.002600000000,0.5030000000000,0.000340000000
611,0.991367500000,0.4904688000000,0.000307253300
612,0.979331400000,0.4780304000000,0.000283160000
613,0.966491600000,0.4656776000000,0.000265440000
614,0.952847900000,0.4534032000000,0.000251813300
615,0.938400000000,0.4412000000000,0.000240000000
616,0.923194000000,0.4290800000000,0.000229546700
617,0.907244000000,0.4170360000000,0.000220640000
618,0.890502000000,0.4050320000000,0.000211960000
619,0.872920000000,0.3930320000000,0.000202186700
620,0.854449900000,0.3810000000000,0.000190000000
621,0.835084000000,0.3689184000000,0.000174213300
622,0.814946000000,0.3568272000000,0.000155640000
623,0.794186000000,0.3447768000000,0.000135960000
624,0.772954000000,0.3328176000000,0.000116853300
625,0.751400000000,0.3210000000000,0.000100000000
626,0.729583600000,0.3093381000000,0.000086133330
627,0.707588800000,0.2978504000000,0.000074600000
628,0.685602200000,0.2865936000000,0.000065000000
629,0.663810400000,0.2756245000000,0.000056933330
630,0.642400000000,0.2650000000000,0.000049999990
631,0.621514900000,0.2547632000000,0.000044160000
632,0.601113800000,0.2448896000000,0.000039480000
633,0.581105200000,0.2353344000000,0.000035720000
634,0.561397700000,0.2260528000000,0.000032640000
635,0.541900000000,0.2170000000000,0.000030000000
636,0.522599500000,0.2081616000000,0.000027653330
637,0.503546400000,0.1995488000000,0.000025560000
638,0.484743600000,0.1911552000000,0.000023640000
639,0.466193900000,0.1829744000000,0.000021813330
640,0.447900000000,0.1750000000000,0.000020000000
641,0.429861300000,0.1672235000000,0.000018133330
642,0.412098000000,0.1596464000000,0.000016200000
643,0.394644000000,0.1522776000000,0.000014200000
644,0.377533300000,0.1451259000000,0.000012133330
645,0.360800000000,0.1382000000000,0.000010000000
646,0.344456300000,0.1315003000000,0.000007733333
647,0.328516800000,0.1250248000000,0.000005400000
648,0.313019200000,0.1187792000000,0.000003200000
649,0.298001100000,0.1127691000000,0.000001333333
650,0.283500000000,0.1070000000000,0.000000000000
651,0.269544800000,0.1014762000000,0.000000000000
652,0.256118400000,0.0961886400000,0.000000000000
653,0.243189600000,0.0911229600000,0.000000000000
654,0.230727200000,0.0862648500000,0.000000000000
655,0.218700000000,0.0816000000000,0.000000000000
656,0.207097100000,0.0771206400000,0.000000000000
657,0.195923200000,0.0728255200000,0.000000000000
658,0.185170800000,0.0687100800000,0.000000000000
659,0.174832300000,0.0647697600000,0.000000000000
660,0.164900000000,0.0610000000000,0.000000000000
661,0.155366700000,0.0573962100000,0.000000000000
662,0.146230000000,0.0539550400000,0.000000000000
663,0.137490000000,0.0506737600000,0.000000000000
664,0.129146700000,0.0475496500000,0.000000000000
665,0.121200000000,0.0445800000000,0.000000000000
666,0.113639700000,0.0417587200000,0.000000000000
667,0.106465000000,0.0390849600000,0.000000000000
668,0.099690440000,0.0365638400000,0.000000000000
669,0.093330610000,0.0342004800000,0.000000000000
670,0.087400000000,0.0320000000000,0.000000000000
671,0.081900960000,0.0299626100000,0.000000000000
672,0.076804280000,0.0280766400000,0.000000000000
673,0.072077120000,0.0263293600000,0.000000000000
674,0.067686640000,0.0247080500000,0.000000000000
675,0.063600000000,0.0232000000000,0.000000000000
676,0.059806850000,0.0218007700000,0.000000000000
677,0.056282160000,0.0205011200000,0.000000000000
678,0.052971040000,0.0192810800000,0.000000000000
679,0.049818610000,0.0181206900000,0.000000000000
680,0.046770000000,0.0170000000000,0.000000000000
681,0.043784050000,0.0159037900000,0.000000000000
682,0.040875360000,0.0148371800000,0.000000000000
683,0.038072640000,0.0138106800000,0.000000000000
684,0.035404610000,0.0128347800000,0.000000000000
685,0.032900000000,0.0119200000000,0.000000000000
686,0.030564190000,0.0110683100000,0.000000000000
687,0.028380560000,0.0102733900000,0.000000000000
688,0.026344840000,0.0095333110000,0.000000000000
689,0.024452750000,0.0088461570000,0.000000000000
690,0.022700000000,0.0082100000000,0.000000000000
691,0.021084290000,0.0076237810000,0.000000000000
692,0.019599880000,0.0070854240000,0.000000000000
693,0.018237320000,0.0065914760000,0.000000000000
694,0.016987170000,0.0061384850000,0.000000000000
695,0.015840000000,0.0057230000000,0.000000000000
696,0.014790640000,0.0053430590000,0.000000000000
697,0.013831320000,0.0049957960000,0.000000000000
698,0.012948680000,0.0046764040000,0.000000000000
699,0.012129200000,0.0043800750000,0.000000000000
700,0.011359160000,0.0041020000000,0.000000000000
701,0.010629350000,0.0038384530000,0.000000000000
702,0.009938846000,0.0035890990000,0.000000000000
703,0.009288422000,0.0033542190000,0.000000000000
704,0.008678854000,0.0031340930000,0.000000000000
705,0.008110916000,0.0029290000000,0.000000000000
706,0.007582388000,0.0027381390000,0.000000000000
707,0.007088746000,0.0025598760000,0.000000000000
708,0.006627313000,0.0023932440000,0.000000000000
709,0.006195408000,0.0022372750000,0.000000000000
710,0.005790346000,0.0020910000000,0.000000000000
711,0.005409826000,0.0019535870000,0.000000000000
712,0.005052583000,0.0018245800000,0.000000000000
713,0.004717512000,0.0017035800000,0.000000000000
714,0.004403507000,0.0015901870000,0.000000000000
715,0.004109457000,0.0014840000000,0.000000000000
716,0.003833913000,0.0013844960000,0.000000000000
717,0.003575748000,0.0012912680000,0.000000000000
718,0.003334342000,0.0012040920000,0.000000000000
719,0.003109075000,0.0011227440000,0.000000000000
720,0.002899327000,0.0010470000000,0.000000000000
721,0.002704348000,0.0009765896000,0.000000000000
722,0.002523020000,0.0009111088000,0.000000000000
723,0.002354168000,0.0008501332000,0.000000000000
724,0.002196616000,0.0007932384000,0.000000000000
725,0.002049190000,0.0007400000000,0.000000000000
726,0.001910960000,0.0006900827000,0.000000000000
727,0.001781438000,0.0006433100000,0.000000000000
728,0.001660110000,0.0005994960000,0.000000000000
729,0.001546459000,0.0005584547000,0.000000000000
730,0.001439971000,0.0005200000000,0.000000000000
731,0.001340042000,0.0004839136000,0.000000000000
732,0.001246275000,0.0004500528000,0.000000000000
733,0.001158471000,0.0004183452000,0.000000000000
734,0.001076430000,0.0003887184000,0.000000000000
735,0.000999949300,0.0003611000000,0.000000000000
736,0.000928735800,0.0003353835000,0.000000000000
737,0.000862433200,0.0003114404000,0.000000000000
738,0.000800750300,0.0002891656000,0.000000000000
739,0.000743396000,0.0002684539000,0.000000000000
740,0.000690078600,0.0002492000000,0.000000000000
741,0.000640515600,0.0002313019000,0.000000000000
742,0.000594502100,0.0002146856000,0.000000000000
743,0.000551864600,0.0001992884000,0.000000000000
744,0.000512429000,0.0001850475000,0.000000000000
745,0.000476021300,0.0001719000000,0.000000000000
746,0.000442453600,0.0001597781000,0.000000000000
747,0.000411511700,0.0001486044000,0.000000000000
748,0.000382981400,0.0001383016000,0.000000000000
749,0.000356649100,0.0001287925000,0.000000000000
750,0.000332301100,0.0001200000000,0.000000000000
751,0.000309758600,0.0001118595000,0.000000000000
752,0.000288887100,0.0001043224000,0.000000000000
753,0.000269539400,0.0000973356000,0.000000000000
754,0.000251568200,0.0000908458700,0.000000000000
755,0.000234826100,0.0000848000000,0.000000000000
756,0.000219171000,0.0000791466700,0.000000000000
757,0.000204525800,0.0000738580000,0.000000000000
758,0.000190840500,0.0000689160000,0.000000000000
759,0.000178065400,0.0000643026700,0.000000000000
760,0.000166150500,0.0000600000000,0.000000000000
761,0.000155023600,0.0000559818700,0.000000000000
762,0.000144621900,0.0000522256000,0.000000000000
763,0.000134909800,0.0000487184000,0.000000000000
764,0.000125852000,0.0000454474700,0.000000000000
765,0.000117413000,0.0000424000000,0.000000000000
766,0.000109551500,0.0000395610400,0.000000000000
767,0.000102224500,0.0000369151200,0.000000000000
768,0.000095394450,0.0000344486800,0.000000000000
769,0.000089023900,0.0000321481600,0.000000000000
770,0.000083075270,0.0000300000000,0.000000000000
771,0.000077512690,0.0000279912500,0.000000000000
772,0.000072313040,0.0000261135600,0.000000000000
773,0.000067457780,0.0000243602400,0.000000000000
774,0.000062928440,0.0000227246100,0.000000000000
775,0.000058706520,0.0000212000000,0.000000000000
776,0.000054770280,0.0000197785500,0.000000000000
777,0.000051099180,0.0000184528500,0.000000000000
778,0.000047676540,0.0000172168700,0.000000000000
779,0.000044485670,0.0000160645900,0.000000000000
780,0.000041509940,0.0000149900000,0.000000000000
781,0.000038733240,0.0000139872800,0.000000000000
782,0.000036142030,0.0000130515500,0.000000000000
783,0.000033723520,0.0000121781800,0.000000000000
784,0.000031464870,0.0000113625400,0.000000000000
785,0.000029353260,0.0000106000000,0.000000000000
786,0.000027375730,0.0000098858770,0.000000000000
787,0.000025524330,0.0000092173040,0.000000000000
788,0.000023793760,0.0000085923620,0.000000000000
789,0.000022178700,0.0000080091330,0.000000000000
790,0.000020673830,0.0000074657000,0.000000000000
791,0.000019272260,0.0000069595670,0.000000000000
792,0.000017966400,0.0000064879950,0.000000000000
793,0.000016749910,0.0000060486990,0.000000000000
794,0.000015616480,0.0000056393960,0.000000000000
795,0.000014559770,0.0000052578000,0.000000000000
796,0.000013573870,0.0000049017710,0.000000000000
797,0.000012654360,0.0000045697200,0.000000000000
798,0.000011797230,0.0000042601940,0.000000000000
799,0.000010998440,0.0000039717390,0.000000000000
800,0.000010253980,0.0000037029000,0.000000000000
801,0.000009559646,0.0000034521630,0.000000000000
802,0.000008912044,0.0000032183020,0.000000000000
803,0.000008308358,0.0000030003000,0.000000000000
804,0.000007745769,0.0000027971390,0.000000000000
805,0.000007221456,0.0000026078000,0.000000000000
806,0.000006732475,0.0000024312200,0.000000000000
807,0.000006276423,0.0000022665310,0.000000000000
808,0.000005851304,0.0000021130130,0.000000000000
809,0.000005455118,0.0000019699430,0.000000000000
810,0.000005085868,0.0000018366000,0.000000000000
811,0.000004741466,0.0000017122300,0.000000000000
812,0.000004420236,0.0000015962280,0.000000000000
813,0.000004120783,0.0000014880900,0.000000000000
814,0.000003841716,0.0000013873140,0.000000000000
815,0.000003581652,0.0000012934000,0.000000000000
816,0.000003339127,0.0000012058200,0.000000000000
817,0.000003112949,0.0000011241430,0.000000000000
818,0.000002902121,0.0000010480090,0.000000000000
819,0.000002705645,0.0000009770578,0.000000000000
820,0.000002522525,0.0000009109300,0.000000000000
821,0.000002351726,0.0000008492513,0.000000000000
822,0.000002192415,0.0000007917212,0.000000000000
823,0.000002043902,0.0000007380904,0.000000000000
824,0.000001905497,0.0000006881098,0.000000000000
825,0.000001776509,0.0000006415300,0.000000000000
826,0.000001656215,0.0000005980895,0.000000000000
827,0.000001544022,0.0000005575746,0.000000000000
828,0.000001439440,0.0000005198080,0.000000000000
829,0.000001341977,0.0000004846123,0.000000000000
830,0.000001251141,0.0000004518100,0.000000000000
1 360 0.000129900000 0.0000039170000 0.000606100000
2 361 0.000145847000 0.0000043935810 0.000680879200
3 362 0.000163802100 0.0000049296040 0.000765145600
4 363 0.000184003700 0.0000055321360 0.000860012400
5 364 0.000206690200 0.0000062082450 0.000966592800
6 365 0.000232100000 0.0000069650000 0.001086000000
7 366 0.000260728000 0.0000078132190 0.001220586000
8 367 0.000293075000 0.0000087673360 0.001372729000
9 368 0.000329388000 0.0000098398440 0.001543579000
10 369 0.000369914000 0.0000110432300 0.001734286000
11 370 0.000414900000 0.0000123900000 0.001946000000
12 371 0.000464158700 0.0000138864100 0.002177777000
13 372 0.000518986000 0.0000155572800 0.002435809000
14 373 0.000581854000 0.0000174429600 0.002731953000
15 374 0.000655234700 0.0000195837500 0.003078064000
16 375 0.000741600000 0.0000220200000 0.003486000000
17 376 0.000845029600 0.0000248396500 0.003975227000
18 377 0.000964526800 0.0000280412600 0.004540880000
19 378 0.001094949000 0.0000315310400 0.005158320000
20 379 0.001231154000 0.0000352152100 0.005802907000
21 380 0.001368000000 0.0000390000000 0.006450001000
22 381 0.001502050000 0.0000428264000 0.007083216000
23 382 0.001642328000 0.0000469146000 0.007745488000
24 383 0.001802382000 0.0000515896000 0.008501152000
25 384 0.001995757000 0.0000571764000 0.009414544000
26 385 0.002236000000 0.0000640000000 0.010549990000
27 386 0.002535385000 0.0000723442100 0.011965800000
28 387 0.002892603000 0.0000822122400 0.013655870000
29 388 0.003300829000 0.0000935081600 0.015588050000
30 389 0.003753236000 0.0001061361000 0.017730150000
31 390 0.004243000000 0.0001200000000 0.020050010000
32 391 0.004762389000 0.0001349840000 0.022511360000
33 392 0.005330048000 0.0001514920000 0.025202880000
34 393 0.005978712000 0.0001702080000 0.028279720000
35 394 0.006741117000 0.0001918160000 0.031897040000
36 395 0.007650000000 0.0002170000000 0.036210000000
37 396 0.008751373000 0.0002469067000 0.041437710000
38 397 0.010028880000 0.0002812400000 0.047503720000
39 398 0.011421700000 0.0003185200000 0.054119880000
40 399 0.012869010000 0.0003572667000 0.060998030000
41 400 0.014310000000 0.0003960000000 0.067850010000
42 401 0.015704430000 0.0004337147000 0.074486320000
43 402 0.017147440000 0.0004730240000 0.081361560000
44 403 0.018781220000 0.0005178760000 0.089153640000
45 404 0.020748010000 0.0005722187000 0.098540480000
46 405 0.023190000000 0.0006400000000 0.110200000000
47 406 0.026207360000 0.0007245600000 0.124613300000
48 407 0.029782480000 0.0008255000000 0.141701700000
49 408 0.033880920000 0.0009411600000 0.161303500000
50 409 0.038468240000 0.0010698800000 0.183256800000
51 410 0.043510000000 0.0012100000000 0.207400000000
52 411 0.048995600000 0.0013620910000 0.233692100000
53 412 0.055022600000 0.0015307520000 0.262611400000
54 413 0.061718800000 0.0017203680000 0.294774600000
55 414 0.069212000000 0.0019353230000 0.330798500000
56 415 0.077630000000 0.0021800000000 0.371300000000
57 416 0.086958110000 0.0024548000000 0.416209100000
58 417 0.097176720000 0.0027640000000 0.465464200000
59 418 0.108406300000 0.0031178000000 0.519694800000
60 419 0.120767200000 0.0035264000000 0.579530300000
61 420 0.134380000000 0.0040000000000 0.645600000000
62 421 0.149358200000 0.0045462400000 0.718483800000
63 422 0.165395700000 0.0051593200000 0.796713300000
64 423 0.181983100000 0.0058292800000 0.877845900000
65 424 0.198611000000 0.0065461600000 0.959439000000
66 425 0.214770000000 0.0073000000000 1.039050100000
67 426 0.230186800000 0.0080865070000 1.115367300000
68 427 0.244879700000 0.0089087200000 1.188497100000
69 428 0.258777300000 0.0097676800000 1.258123300000
70 429 0.271807900000 0.0106644300000 1.323929600000
71 430 0.283900000000 0.0116000000000 1.385600000000
72 431 0.294943800000 0.0125731700000 1.442635200000
73 432 0.304896500000 0.0135827200000 1.494803500000
74 433 0.313787300000 0.0146296800000 1.542190300000
75 434 0.321645400000 0.0157150900000 1.584880700000
76 435 0.328500000000 0.0168400000000 1.622960000000
77 436 0.334351300000 0.0180073600000 1.656404800000
78 437 0.339210100000 0.0192144800000 1.685295900000
79 438 0.343121300000 0.0204539200000 1.709874500000
80 439 0.346129600000 0.0217182400000 1.730382100000
81 440 0.348280000000 0.0230000000000 1.747060000000
82 441 0.349599900000 0.0242946100000 1.760044600000
83 442 0.350147400000 0.0256102400000 1.769623300000
84 443 0.350013000000 0.0269585700000 1.776263700000
85 444 0.349287000000 0.0283512500000 1.780433400000
86 445 0.348060000000 0.0298000000000 1.782600000000
87 446 0.346373300000 0.0313108300000 1.782968200000
88 447 0.344262400000 0.0328836800000 1.781699800000
89 448 0.341808800000 0.0345211200000 1.779198200000
90 449 0.339094100000 0.0362257100000 1.775867100000
91 450 0.336200000000 0.0380000000000 1.772110000000
92 451 0.333197700000 0.0398466700000 1.768258900000
93 452 0.330041100000 0.0417680000000 1.764039000000
94 453 0.326635700000 0.0437660000000 1.758943800000
95 454 0.322886800000 0.0458426700000 1.752466300000
96 455 0.318700000000 0.0480000000000 1.744100000000
97 456 0.314025100000 0.0502436800000 1.733559500000
98 457 0.308884000000 0.0525730400000 1.720858100000
99 458 0.303290400000 0.0549805600000 1.705936900000
100 459 0.297257900000 0.0574587200000 1.688737200000
101 460 0.290800000000 0.0600000000000 1.669200000000
102 461 0.283970100000 0.0626019700000 1.647528700000
103 462 0.276721400000 0.0652775200000 1.623412700000
104 463 0.268917800000 0.0680420800000 1.596022300000
105 464 0.260422700000 0.0709110900000 1.564528000000
106 465 0.251100000000 0.0739000000000 1.528100000000
107 466 0.240847500000 0.0770160000000 1.486111400000
108 467 0.229851200000 0.0802664000000 1.439521500000
109 468 0.218407200000 0.0836668000000 1.389879900000
110 469 0.206811500000 0.0872328000000 1.338736200000
111 470 0.195360000000 0.0909800000000 1.287640000000
112 471 0.184213600000 0.0949175500000 1.237422300000
113 472 0.173327300000 0.0990458400000 1.187824300000
114 473 0.162688100000 0.1033674000000 1.138761100000
115 474 0.152283300000 0.1078846000000 1.090148000000
116 475 0.142100000000 0.1126000000000 1.041900000000
117 476 0.132178600000 0.1175320000000 0.994197600000
118 477 0.122569600000 0.1226744000000 0.947347300000
119 478 0.113275200000 0.1279928000000 0.901453100000
120 479 0.104297900000 0.1334528000000 0.856619300000
121 480 0.095640000000 0.1390200000000 0.812950100000
122 481 0.087299550000 0.1446764000000 0.770517300000
123 482 0.079308040000 0.1504693000000 0.729444800000
124 483 0.071717760000 0.1564619000000 0.689913600000
125 484 0.064580990000 0.1627177000000 0.652104900000
126 485 0.057950010000 0.1693000000000 0.616200000000
127 486 0.051862110000 0.1762431000000 0.582328600000
128 487 0.046281520000 0.1835581000000 0.550416200000
129 488 0.041150880000 0.1912735000000 0.520337600000
130 489 0.036412830000 0.1994180000000 0.491967300000
131 490 0.032010000000 0.2080200000000 0.465180000000
132 491 0.027917200000 0.2171199000000 0.439924600000
133 492 0.024144400000 0.2267345000000 0.416183600000
134 493 0.020687000000 0.2368571000000 0.393882200000
135 494 0.017540400000 0.2474812000000 0.372945900000
136 495 0.014700000000 0.2586000000000 0.353300000000
137 496 0.012161790000 0.2701849000000 0.334857800000
138 497 0.009919960000 0.2822939000000 0.317552100000
139 498 0.007967240000 0.2950505000000 0.301337500000
140 499 0.006296346000 0.3085780000000 0.286168600000
141 500 0.004900000000 0.3230000000000 0.272000000000
142 501 0.003777173000 0.3384021000000 0.258817100000
143 502 0.002945320000 0.3546858000000 0.246483800000
144 503 0.002424880000 0.3716986000000 0.234771800000
145 504 0.002236293000 0.3892875000000 0.223453300000
146 505 0.002400000000 0.4073000000000 0.212300000000
147 506 0.002925520000 0.4256299000000 0.201169200000
148 507 0.003836560000 0.4443096000000 0.190119600000
149 508 0.005174840000 0.4633944000000 0.179225400000
150 509 0.006982080000 0.4829395000000 0.168560800000
151 510 0.009300000000 0.5030000000000 0.158200000000
152 511 0.012149490000 0.5235693000000 0.148138300000
153 512 0.015535880000 0.5445120000000 0.138375800000
154 513 0.019477520000 0.5656900000000 0.128994200000
155 514 0.023992770000 0.5869653000000 0.120075100000
156 515 0.029100000000 0.6082000000000 0.111700000000
157 516 0.034814850000 0.6293456000000 0.103904800000
158 517 0.041120160000 0.6503068000000 0.096667480000
159 518 0.047985040000 0.6708752000000 0.089982720000
160 519 0.055378610000 0.6908424000000 0.083845310000
161 520 0.063270000000 0.7100000000000 0.078249990000
162 521 0.071635010000 0.7281852000000 0.073208990000
163 522 0.080462240000 0.7454636000000 0.068678160000
164 523 0.089739960000 0.7619694000000 0.064567840000
165 524 0.099456450000 0.7778368000000 0.060788350000
166 525 0.109600000000 0.7932000000000 0.057250010000
167 526 0.120167400000 0.8081104000000 0.053904350000
168 527 0.131114500000 0.8224962000000 0.050746640000
169 528 0.142367900000 0.8363068000000 0.047752760000
170 529 0.153854200000 0.8494916000000 0.044898590000
171 530 0.165500000000 0.8620000000000 0.042160000000
172 531 0.177257100000 0.8738108000000 0.039507280000
173 532 0.189140000000 0.8849624000000 0.036935640000
174 533 0.201169400000 0.8954936000000 0.034458360000
175 534 0.213365800000 0.9054432000000 0.032088720000
176 535 0.225749900000 0.9148501000000 0.029840000000
177 536 0.238320900000 0.9237348000000 0.027711810000
178 537 0.251066800000 0.9320924000000 0.025694440000
179 538 0.263992200000 0.9399226000000 0.023787160000
180 539 0.277101700000 0.9472252000000 0.021989250000
181 540 0.290400000000 0.9540000000000 0.020300000000
182 541 0.303891200000 0.9602561000000 0.018718050000
183 542 0.317572600000 0.9660074000000 0.017240360000
184 543 0.331438400000 0.9712606000000 0.015863640000
185 544 0.345482800000 0.9760225000000 0.014584610000
186 545 0.359700000000 0.9803000000000 0.013400000000
187 546 0.374083900000 0.9840924000000 0.012307230000
188 547 0.388639600000 0.9874182000000 0.011301880000
189 548 0.403378400000 0.9903128000000 0.010377920000
190 549 0.418311500000 0.9928116000000 0.009529306000
191 550 0.433449900000 0.9949501000000 0.008749999000
192 551 0.448795300000 0.9967108000000 0.008035200000
193 552 0.464336000000 0.9980983000000 0.007381600000
194 553 0.480064000000 0.9991120000000 0.006785400000
195 554 0.495971300000 0.9997482000000 0.006242800000
196 555 0.512050100000 1.0000000000000 0.005749999000
197 556 0.528295900000 0.9998567000000 0.005303600000
198 557 0.544691600000 0.9993046000000 0.004899800000
199 558 0.561209400000 0.9983255000000 0.004534200000
200 559 0.577821500000 0.9968987000000 0.004202400000
201 560 0.594500000000 0.9950000000000 0.003900000000
202 561 0.611220900000 0.9926005000000 0.003623200000
203 562 0.627975800000 0.9897426000000 0.003370600000
204 563 0.644760200000 0.9864444000000 0.003141400000
205 564 0.661569700000 0.9827241000000 0.002934800000
206 565 0.678400000000 0.9786000000000 0.002749999000
207 566 0.695239200000 0.9740837000000 0.002585200000
208 567 0.712058600000 0.9691712000000 0.002438600000
209 568 0.728828400000 0.9638568000000 0.002309400000
210 569 0.745518800000 0.9581349000000 0.002196800000
211 570 0.762100000000 0.9520000000000 0.002100000000
212 571 0.778543200000 0.9454504000000 0.002017733000
213 572 0.794825600000 0.9384992000000 0.001948200000
214 573 0.810926400000 0.9311628000000 0.001889800000
215 574 0.826824800000 0.9234576000000 0.001840933000
216 575 0.842500000000 0.9154000000000 0.001800000000
217 576 0.857932500000 0.9070064000000 0.001766267000
218 577 0.873081600000 0.8982772000000 0.001737800000
219 578 0.887894400000 0.8892048000000 0.001711200000
220 579 0.902318100000 0.8797816000000 0.001683067000
221 580 0.916300000000 0.8700000000000 0.001650001000
222 581 0.929799500000 0.8598613000000 0.001610133000
223 582 0.942798400000 0.8493920000000 0.001564400000
224 583 0.955277600000 0.8386220000000 0.001513600000
225 584 0.967217900000 0.8275813000000 0.001458533000
226 585 0.978600000000 0.8163000000000 0.001400000000
227 586 0.989385600000 0.8047947000000 0.001336667000
228 587 0.999548800000 0.7930820000000 0.001270000000
229 588 1.009089200000 0.7811920000000 0.001205000000
230 589 1.018006400000 0.7691547000000 0.001146667000
231 590 1.026300000000 0.7570000000000 0.001100000000
232 591 1.033982700000 0.7447541000000 0.001068800000
233 592 1.040986000000 0.7324224000000 0.001049400000
234 593 1.047188000000 0.7200036000000 0.001035600000
235 594 1.052466700000 0.7074965000000 0.001021200000
236 595 1.056700000000 0.6949000000000 0.001000000000
237 596 1.059794400000 0.6822192000000 0.000968640000
238 597 1.061799200000 0.6694716000000 0.000929920000
239 598 1.062806800000 0.6566744000000 0.000886880000
240 599 1.062909600000 0.6438448000000 0.000842560000
241 600 1.062200000000 0.6310000000000 0.000800000000
242 601 1.060735200000 0.6181555000000 0.000760960000
243 602 1.058443600000 0.6053144000000 0.000723680000
244 603 1.055224400000 0.5924756000000 0.000685920000
245 604 1.050976800000 0.5796379000000 0.000645440000
246 605 1.045600000000 0.5668000000000 0.000600000000
247 606 1.039036900000 0.5539611000000 0.000547866700
248 607 1.031360800000 0.5411372000000 0.000491600000
249 608 1.022666200000 0.5283528000000 0.000435400000
250 609 1.013047700000 0.5156323000000 0.000383466700
251 610 1.002600000000 0.5030000000000 0.000340000000
252 611 0.991367500000 0.4904688000000 0.000307253300
253 612 0.979331400000 0.4780304000000 0.000283160000
254 613 0.966491600000 0.4656776000000 0.000265440000
255 614 0.952847900000 0.4534032000000 0.000251813300
256 615 0.938400000000 0.4412000000000 0.000240000000
257 616 0.923194000000 0.4290800000000 0.000229546700
258 617 0.907244000000 0.4170360000000 0.000220640000
259 618 0.890502000000 0.4050320000000 0.000211960000
260 619 0.872920000000 0.3930320000000 0.000202186700
261 620 0.854449900000 0.3810000000000 0.000190000000
262 621 0.835084000000 0.3689184000000 0.000174213300
263 622 0.814946000000 0.3568272000000 0.000155640000
264 623 0.794186000000 0.3447768000000 0.000135960000
265 624 0.772954000000 0.3328176000000 0.000116853300
266 625 0.751400000000 0.3210000000000 0.000100000000
267 626 0.729583600000 0.3093381000000 0.000086133330
268 627 0.707588800000 0.2978504000000 0.000074600000
269 628 0.685602200000 0.2865936000000 0.000065000000
270 629 0.663810400000 0.2756245000000 0.000056933330
271 630 0.642400000000 0.2650000000000 0.000049999990
272 631 0.621514900000 0.2547632000000 0.000044160000
273 632 0.601113800000 0.2448896000000 0.000039480000
274 633 0.581105200000 0.2353344000000 0.000035720000
275 634 0.561397700000 0.2260528000000 0.000032640000
276 635 0.541900000000 0.2170000000000 0.000030000000
277 636 0.522599500000 0.2081616000000 0.000027653330
278 637 0.503546400000 0.1995488000000 0.000025560000
279 638 0.484743600000 0.1911552000000 0.000023640000
280 639 0.466193900000 0.1829744000000 0.000021813330
281 640 0.447900000000 0.1750000000000 0.000020000000
282 641 0.429861300000 0.1672235000000 0.000018133330
283 642 0.412098000000 0.1596464000000 0.000016200000
284 643 0.394644000000 0.1522776000000 0.000014200000
285 644 0.377533300000 0.1451259000000 0.000012133330
286 645 0.360800000000 0.1382000000000 0.000010000000
287 646 0.344456300000 0.1315003000000 0.000007733333
288 647 0.328516800000 0.1250248000000 0.000005400000
289 648 0.313019200000 0.1187792000000 0.000003200000
290 649 0.298001100000 0.1127691000000 0.000001333333
291 650 0.283500000000 0.1070000000000 0.000000000000
292 651 0.269544800000 0.1014762000000 0.000000000000
293 652 0.256118400000 0.0961886400000 0.000000000000
294 653 0.243189600000 0.0911229600000 0.000000000000
295 654 0.230727200000 0.0862648500000 0.000000000000
296 655 0.218700000000 0.0816000000000 0.000000000000
297 656 0.207097100000 0.0771206400000 0.000000000000
298 657 0.195923200000 0.0728255200000 0.000000000000
299 658 0.185170800000 0.0687100800000 0.000000000000
300 659 0.174832300000 0.0647697600000 0.000000000000
301 660 0.164900000000 0.0610000000000 0.000000000000
302 661 0.155366700000 0.0573962100000 0.000000000000
303 662 0.146230000000 0.0539550400000 0.000000000000
304 663 0.137490000000 0.0506737600000 0.000000000000
305 664 0.129146700000 0.0475496500000 0.000000000000
306 665 0.121200000000 0.0445800000000 0.000000000000
307 666 0.113639700000 0.0417587200000 0.000000000000
308 667 0.106465000000 0.0390849600000 0.000000000000
309 668 0.099690440000 0.0365638400000 0.000000000000
310 669 0.093330610000 0.0342004800000 0.000000000000
311 670 0.087400000000 0.0320000000000 0.000000000000
312 671 0.081900960000 0.0299626100000 0.000000000000
313 672 0.076804280000 0.0280766400000 0.000000000000
314 673 0.072077120000 0.0263293600000 0.000000000000
315 674 0.067686640000 0.0247080500000 0.000000000000
316 675 0.063600000000 0.0232000000000 0.000000000000
317 676 0.059806850000 0.0218007700000 0.000000000000
318 677 0.056282160000 0.0205011200000 0.000000000000
319 678 0.052971040000 0.0192810800000 0.000000000000
320 679 0.049818610000 0.0181206900000 0.000000000000
321 680 0.046770000000 0.0170000000000 0.000000000000
322 681 0.043784050000 0.0159037900000 0.000000000000
323 682 0.040875360000 0.0148371800000 0.000000000000
324 683 0.038072640000 0.0138106800000 0.000000000000
325 684 0.035404610000 0.0128347800000 0.000000000000
326 685 0.032900000000 0.0119200000000 0.000000000000
327 686 0.030564190000 0.0110683100000 0.000000000000
328 687 0.028380560000 0.0102733900000 0.000000000000
329 688 0.026344840000 0.0095333110000 0.000000000000
330 689 0.024452750000 0.0088461570000 0.000000000000
331 690 0.022700000000 0.0082100000000 0.000000000000
332 691 0.021084290000 0.0076237810000 0.000000000000
333 692 0.019599880000 0.0070854240000 0.000000000000
334 693 0.018237320000 0.0065914760000 0.000000000000
335 694 0.016987170000 0.0061384850000 0.000000000000
336 695 0.015840000000 0.0057230000000 0.000000000000
337 696 0.014790640000 0.0053430590000 0.000000000000
338 697 0.013831320000 0.0049957960000 0.000000000000
339 698 0.012948680000 0.0046764040000 0.000000000000
340 699 0.012129200000 0.0043800750000 0.000000000000
341 700 0.011359160000 0.0041020000000 0.000000000000
342 701 0.010629350000 0.0038384530000 0.000000000000
343 702 0.009938846000 0.0035890990000 0.000000000000
344 703 0.009288422000 0.0033542190000 0.000000000000
345 704 0.008678854000 0.0031340930000 0.000000000000
346 705 0.008110916000 0.0029290000000 0.000000000000
347 706 0.007582388000 0.0027381390000 0.000000000000
348 707 0.007088746000 0.0025598760000 0.000000000000
349 708 0.006627313000 0.0023932440000 0.000000000000
350 709 0.006195408000 0.0022372750000 0.000000000000
351 710 0.005790346000 0.0020910000000 0.000000000000
352 711 0.005409826000 0.0019535870000 0.000000000000
353 712 0.005052583000 0.0018245800000 0.000000000000
354 713 0.004717512000 0.0017035800000 0.000000000000
355 714 0.004403507000 0.0015901870000 0.000000000000
356 715 0.004109457000 0.0014840000000 0.000000000000
357 716 0.003833913000 0.0013844960000 0.000000000000
358 717 0.003575748000 0.0012912680000 0.000000000000
359 718 0.003334342000 0.0012040920000 0.000000000000
360 719 0.003109075000 0.0011227440000 0.000000000000
361 720 0.002899327000 0.0010470000000 0.000000000000
362 721 0.002704348000 0.0009765896000 0.000000000000
363 722 0.002523020000 0.0009111088000 0.000000000000
364 723 0.002354168000 0.0008501332000 0.000000000000
365 724 0.002196616000 0.0007932384000 0.000000000000
366 725 0.002049190000 0.0007400000000 0.000000000000
367 726 0.001910960000 0.0006900827000 0.000000000000
368 727 0.001781438000 0.0006433100000 0.000000000000
369 728 0.001660110000 0.0005994960000 0.000000000000
370 729 0.001546459000 0.0005584547000 0.000000000000
371 730 0.001439971000 0.0005200000000 0.000000000000
372 731 0.001340042000 0.0004839136000 0.000000000000
373 732 0.001246275000 0.0004500528000 0.000000000000
374 733 0.001158471000 0.0004183452000 0.000000000000
375 734 0.001076430000 0.0003887184000 0.000000000000
376 735 0.000999949300 0.0003611000000 0.000000000000
377 736 0.000928735800 0.0003353835000 0.000000000000
378 737 0.000862433200 0.0003114404000 0.000000000000
379 738 0.000800750300 0.0002891656000 0.000000000000
380 739 0.000743396000 0.0002684539000 0.000000000000
381 740 0.000690078600 0.0002492000000 0.000000000000
382 741 0.000640515600 0.0002313019000 0.000000000000
383 742 0.000594502100 0.0002146856000 0.000000000000
384 743 0.000551864600 0.0001992884000 0.000000000000
385 744 0.000512429000 0.0001850475000 0.000000000000
386 745 0.000476021300 0.0001719000000 0.000000000000
387 746 0.000442453600 0.0001597781000 0.000000000000
388 747 0.000411511700 0.0001486044000 0.000000000000
389 748 0.000382981400 0.0001383016000 0.000000000000
390 749 0.000356649100 0.0001287925000 0.000000000000
391 750 0.000332301100 0.0001200000000 0.000000000000
392 751 0.000309758600 0.0001118595000 0.000000000000
393 752 0.000288887100 0.0001043224000 0.000000000000
394 753 0.000269539400 0.0000973356000 0.000000000000
395 754 0.000251568200 0.0000908458700 0.000000000000
396 755 0.000234826100 0.0000848000000 0.000000000000
397 756 0.000219171000 0.0000791466700 0.000000000000
398 757 0.000204525800 0.0000738580000 0.000000000000
399 758 0.000190840500 0.0000689160000 0.000000000000
400 759 0.000178065400 0.0000643026700 0.000000000000
401 760 0.000166150500 0.0000600000000 0.000000000000
402 761 0.000155023600 0.0000559818700 0.000000000000
403 762 0.000144621900 0.0000522256000 0.000000000000
404 763 0.000134909800 0.0000487184000 0.000000000000
405 764 0.000125852000 0.0000454474700 0.000000000000
406 765 0.000117413000 0.0000424000000 0.000000000000
407 766 0.000109551500 0.0000395610400 0.000000000000
408 767 0.000102224500 0.0000369151200 0.000000000000
409 768 0.000095394450 0.0000344486800 0.000000000000
410 769 0.000089023900 0.0000321481600 0.000000000000
411 770 0.000083075270 0.0000300000000 0.000000000000
412 771 0.000077512690 0.0000279912500 0.000000000000
413 772 0.000072313040 0.0000261135600 0.000000000000
414 773 0.000067457780 0.0000243602400 0.000000000000
415 774 0.000062928440 0.0000227246100 0.000000000000
416 775 0.000058706520 0.0000212000000 0.000000000000
417 776 0.000054770280 0.0000197785500 0.000000000000
418 777 0.000051099180 0.0000184528500 0.000000000000
419 778 0.000047676540 0.0000172168700 0.000000000000
420 779 0.000044485670 0.0000160645900 0.000000000000
421 780 0.000041509940 0.0000149900000 0.000000000000
422 781 0.000038733240 0.0000139872800 0.000000000000
423 782 0.000036142030 0.0000130515500 0.000000000000
424 783 0.000033723520 0.0000121781800 0.000000000000
425 784 0.000031464870 0.0000113625400 0.000000000000
426 785 0.000029353260 0.0000106000000 0.000000000000
427 786 0.000027375730 0.0000098858770 0.000000000000
428 787 0.000025524330 0.0000092173040 0.000000000000
429 788 0.000023793760 0.0000085923620 0.000000000000
430 789 0.000022178700 0.0000080091330 0.000000000000
431 790 0.000020673830 0.0000074657000 0.000000000000
432 791 0.000019272260 0.0000069595670 0.000000000000
433 792 0.000017966400 0.0000064879950 0.000000000000
434 793 0.000016749910 0.0000060486990 0.000000000000
435 794 0.000015616480 0.0000056393960 0.000000000000
436 795 0.000014559770 0.0000052578000 0.000000000000
437 796 0.000013573870 0.0000049017710 0.000000000000
438 797 0.000012654360 0.0000045697200 0.000000000000
439 798 0.000011797230 0.0000042601940 0.000000000000
440 799 0.000010998440 0.0000039717390 0.000000000000
441 800 0.000010253980 0.0000037029000 0.000000000000
442 801 0.000009559646 0.0000034521630 0.000000000000
443 802 0.000008912044 0.0000032183020 0.000000000000
444 803 0.000008308358 0.0000030003000 0.000000000000
445 804 0.000007745769 0.0000027971390 0.000000000000
446 805 0.000007221456 0.0000026078000 0.000000000000
447 806 0.000006732475 0.0000024312200 0.000000000000
448 807 0.000006276423 0.0000022665310 0.000000000000
449 808 0.000005851304 0.0000021130130 0.000000000000
450 809 0.000005455118 0.0000019699430 0.000000000000
451 810 0.000005085868 0.0000018366000 0.000000000000
452 811 0.000004741466 0.0000017122300 0.000000000000
453 812 0.000004420236 0.0000015962280 0.000000000000
454 813 0.000004120783 0.0000014880900 0.000000000000
455 814 0.000003841716 0.0000013873140 0.000000000000
456 815 0.000003581652 0.0000012934000 0.000000000000
457 816 0.000003339127 0.0000012058200 0.000000000000
458 817 0.000003112949 0.0000011241430 0.000000000000
459 818 0.000002902121 0.0000010480090 0.000000000000
460 819 0.000002705645 0.0000009770578 0.000000000000
461 820 0.000002522525 0.0000009109300 0.000000000000
462 821 0.000002351726 0.0000008492513 0.000000000000
463 822 0.000002192415 0.0000007917212 0.000000000000
464 823 0.000002043902 0.0000007380904 0.000000000000
465 824 0.000001905497 0.0000006881098 0.000000000000
466 825 0.000001776509 0.0000006415300 0.000000000000
467 826 0.000001656215 0.0000005980895 0.000000000000
468 827 0.000001544022 0.0000005575746 0.000000000000
469 828 0.000001439440 0.0000005198080 0.000000000000
470 829 0.000001341977 0.0000004846123 0.000000000000
471 830 0.000001251141 0.0000004518100 0.000000000000

@ -21,10 +21,10 @@ class Vec3Test {
}
@Test
void timesVec() {
void dotVec() {
var a = new Vec3(1, 2, 3);
var b = new Vec3(-1, 1, -2);
assertEquals(-5, a.times(b));
assertEquals(-5, a.dot(b));
}
@Test

@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.render.canvas;
import eu.jonahbauer.raytracing.render.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.ImageFormat;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@ -13,11 +13,11 @@ import java.util.Objects;
import static org.junit.jupiter.api.Assertions.assertEquals;
class ImageTest {
class RGBCanvasTest {
@Test
void test(@TempDir Path dir) throws IOException {
var image = new Image(256, 256);
var image = new RGBCanvas(256, 256);
for (var y = 0; y < image.getHeight(); y++) {
for (var x = 0; x < image.getWidth(); x++) {
@ -25,7 +25,7 @@ class ImageTest {
var g = (double) y / (image.getHeight() - 1);
var b = 0;
image.set(x, y, new Color(r, g, b));
image.set(x, y, new ColorRGB(r, g, b));
}
}
@ -35,7 +35,7 @@ class ImageTest {
String expected;
String actual;
try (var in = Objects.requireNonNull(ImageTest.class.getResourceAsStream("simple_image.ppm"))) {
try (var in = Objects.requireNonNull(RGBCanvasTest.class.getResourceAsStream("simple_image.ppm"))) {
expected = new String(in.readAllBytes(), StandardCharsets.US_ASCII);
}

@ -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.texture.Color;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import eu.jonahbauer.raytracing.render.material.LambertianMaterial;
import org.junit.jupiter.api.Test;
@ -15,7 +15,7 @@ class SphereTest {
void hit() {
var center = new Vec3(1, 2, 3);
var radius = 5;
var sphere = new Sphere(center, radius, new LambertianMaterial(Color.WHITE));
var sphere = new Sphere(center, radius, new LambertianMaterial(ColorRGB.WHITE));
var origin = new Vec3(6, 7, 8);
var direction = new Vec3(-1, -1, -1);

Loading…
Cancel
Save