diff --git a/src/main/java/eu/jonahbauer/raytracing/Examples.java b/src/main/java/eu/jonahbauer/raytracing/Examples.java index 401be82..bb545d2 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Examples.java +++ b/src/main/java/eu/jonahbauer/raytracing/Examples.java @@ -1,6 +1,5 @@ package eu.jonahbauer.raytracing; -import eu.jonahbauer.raytracing.math.AABB; import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.render.texture.CheckerTexture; import eu.jonahbauer.raytracing.render.texture.Color; @@ -293,7 +292,7 @@ public class Examples { return new Example( new Scene(getSkyBox(), List.of( - new Sphere(Vec3.ZERO, 2, new LambertianMaterial(new ImageTexture("/earthmap.jpg"))) + new Sphere(Vec3.ZERO, 2, new LambertianMaterial(new ImageTexture("/eu/jonahbauer/raytracing/textures/earthmap.jpg"))) )), SimpleCamera.builder() .withImage(height * 16 / 9, height) @@ -369,7 +368,7 @@ public class Examples { )); // textures spheres - objects.add(new Sphere(new Vec3(400, 200, 400), 100, new LambertianMaterial(new ImageTexture("/earthmap.jpg")))); + 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(220, 280, 300), 80, new LambertianMaterial(new PerlinTexture(0.2)))); // box from spheres diff --git a/src/main/java/eu/jonahbauer/raytracing/math/Matrix3.java b/src/main/java/eu/jonahbauer/raytracing/math/Matrix3.java new file mode 100644 index 0000000..7a40be3 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/math/Matrix3.java @@ -0,0 +1,255 @@ +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); + } + + public Matrix3(double a11, double a22, double a33) { + this(a11, 0, 0, 0, a22, 0, 0, 0, a33); + } + + public @NotNull Matrix3 times(@NotNull Matrix3 other) { + return new Matrix3( + a11 * other.a11 + a12 * other.a21 + a13 * other.a31, + a11 * other.a12 + a12 * other.a22 + a13 * other.a32, + a11 * other.a13 + a12 * other.a23 + a13 * other.a33, + a21 * other.a11 + a22 * other.a21 + a23 * other.a31, + a21 * other.a12 + a22 * other.a22 + a23 * other.a32, + a21 * other.a13 + a22 * other.a23 + a23 * other.a33, + a31 * other.a11 + a32 * other.a21 + a33 * other.a31, + a31 * other.a12 + a32 * other.a22 + a33 * other.a32, + a31 * other.a13 + a32 * other.a23 + a33 * other.a33 + ); + } + + public @NotNull Matrix3 times(double other) { + return new Matrix3( + a11 * other, a12 * other, a13 * other, + a21 * other, a22 * other, a23 * other, + a31 * other, a32 * other, a33 * other + ); + } + + public @NotNull Vec3 times(@NotNull Vec3 other) { + return new Vec3( + a11 * other.x() + a12 * other.y() + a13 * other.z(), + a21 * other.x() + a22 * other.y() + a23 * other.z(), + a31 * other.x() + a32 * other.y() + a33 * other.z() + ); + } + + public @NotNull Matrix3 plus(@NotNull Matrix3 other) { + return new Matrix3( + a11 + other.a11, a12 + other.a12, a13 + other.a13, + a21 + other.a21, a22 + other.a22, a23 + other.a23, + a31 + other.a31, a32 + other.a32, a33 + other.a33 + ); + } + + public double det() { + return a11 * a22 * a33 + a12 * a23 * a31 + a13 * a21 * a32 + - a13 * a22 * a31 - a23 * a32 * a11 - a33 * a12 * a21; + } + + public @NotNull Matrix3 invert() { + var det = det(); + if (det == 0) throw new IllegalStateException(); + var t = 1 / det; + return new Matrix3( + 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 LU decomposition — Wikipedia, The Free Encyclopedia + */ + 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]); + } + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/SampledSpectrum.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/SampledSpectrum.java new file mode 100644 index 0000000..8566fa8 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/SampledSpectrum.java @@ -0,0 +1,71 @@ +package eu.jonahbauer.raytracing.render.spectral; + +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 org.jetbrains.annotations.NotNull; + +public final class SampledSpectrum { + 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++) { + values[i] = spectrum.get(lambdas.get(i)); + } + this.values = values; + } + + private SampledSpectrum(double @NotNull[] values) { + this.values = values; + } + + public static @NotNull SampledSpectrum multiply(@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]; + } + return new SampledSpectrum(out); + } + + public static @NotNull SampledSpectrum multiply(@NotNull SampledSpectrum a, double b) { + var out = new double[a.values.length]; + for (int i = 0; i < a.values.length; i++) { + out[i] = a.values[i] * b; + } + 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]; + } + return new SampledSpectrum(out); + } + + public double get(int index) { + return values[index]; + } + + public int size() { + 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; + } + + public @NotNull ColorXYZ toXYZ(@NotNull SampledWavelengths lambdas) { + return lambdas.toXYZ(this); + } + + public @NotNull Color toRGB(@NotNull SampledWavelengths lambdas, @NotNull ColorSpace cs) { + return cs.toRGB(toXYZ(lambdas)); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/SampledWavelengths.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/SampledWavelengths.java new file mode 100644 index 0000000..8b40b56 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/SampledWavelengths.java @@ -0,0 +1,93 @@ +package eu.jonahbauer.raytracing.render.spectral; + +import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ; +import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra; +import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; + +/** + * A set of sampled wavelength that can be tracked together. + */ +public final class SampledWavelengths { + public static final int SAMPLES = 4; + + private final double @NotNull[] lambdas; + private final double @NotNull[] pdf; + + public static @NotNull SampledWavelengths uniform(double rng) { + return uniform(rng, Spectrum.LAMBDA_MIN, Spectrum.LAMBDA_MAX); + } + + public static @NotNull SampledWavelengths uniform(double rng, double min, double max) { + 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) / 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[SAMPLES]; + Arrays.fill(pdf, 1 / (max - min)); + return new SampledWavelengths(lambdas, pdf); + } + + private SampledWavelengths(double @NotNull[] lambdas, double @NotNull[] pdf) { + this.lambdas = lambdas; + this.pdf = pdf; + } + + public double get(int index) { + return lambdas[index]; + } + + public int size() { + return lambdas.length; + } + + /** + * 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; + } + + @NotNull + ColorXYZ toXYZ(@NotNull SampledSpectrum spectrum) { + var x = Spectra.X.sample(this); + var y = Spectra.Y.sample(this); + var z = Spectra.Z.sample(this); + + return new ColorXYZ( + toXYZ0(spectrum, x) / ColorXYZ.CIE_Y_INTEGRAL, + toXYZ0(spectrum, y) / ColorXYZ.CIE_Y_INTEGRAL, + toXYZ0(spectrum, z) / ColorXYZ.CIE_Y_INTEGRAL + ); + } + + private double toXYZ0(@NotNull SampledSpectrum spectrum, @NotNull SampledSpectrum cie) { + var avg = 0d; + for (int i = 0; i < spectrum.size(); i++) { + var pdf = this.pdf[i]; + double value; + if (pdf == 0) { + value = 0; + } else { + value = spectrum.get(i) * cie.get(i) / pdf; + } + avg = Math.fma(1d / (i + 1), value - avg, avg); + } + return avg; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/Chromaticity.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/Chromaticity.java new file mode 100644 index 0000000..3b9c3de --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/Chromaticity.java @@ -0,0 +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) { +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/ColorSpace.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/ColorSpace.java new file mode 100644 index 0000000..a71f759 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/ColorSpace.java @@ -0,0 +1,116 @@ +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.Objects; + +/** + * An RGB color space. + */ +public final class ColorSpace { + private final @NotNull Chromaticity r; + private final @NotNull Chromaticity g; + private final @NotNull Chromaticity b; + 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; + + public ColorSpace( + @NotNull Chromaticity r, @NotNull Chromaticity g, @NotNull Chromaticity b, + @NotNull Spectrum illuminant, @NotNull SpectrumTable table + ) { + this.r = Objects.requireNonNull(r, "r"); + this.g = Objects.requireNonNull(g, "g"); + this.b = Objects.requireNonNull(b, "b"); + this.illuminant = new DenselySampledSpectrum(illuminant); + this.RGBtoSpectrumTable = table; + + this.W = illuminant.toXYZ(); + this.w = W.xy(); + + 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(), + R.z(), G.z(), B.z() + ); + var C = rgb.invert().times(W.toVec3()); + + this.XYZfromRGB = rgb.times(new Matrix3(C.x(), C.y(), C.z())); + this.RGBfromXYZ = XYZfromRGB.invert(); + } + + public @NotNull Color toRGB(@NotNull ColorXYZ xyz) { + var out = RGBfromXYZ.times(xyz.toVec3()); + return new Color(out.x(), out.y(), out.z()); + } + + public @NotNull ColorXYZ toXYZ(@NotNull Color rgb) { + var out = XYZfromRGB.times(rgb.toVec3()); + return new ColorXYZ(out); + } + + public @NotNull Vec3 toCIELab(@NotNull Color 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( + Math.max(0, rgb.r()), + Math.max(0, rgb.g()), + Math.max(0, rgb.b()) + )); + } + + public @NotNull Chromaticity r() { + return r; + } + + public @NotNull Chromaticity g() { + return g; + } + + public @NotNull Chromaticity b() { + return b; + } + + public @NotNull Chromaticity w() { + return w; + } + + public @NotNull DenselySampledSpectrum illuminant() { + return illuminant; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/ColorSpaces.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/ColorSpaces.java new file mode 100644 index 0000000..7f42d6f --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/ColorSpaces.java @@ -0,0 +1,44 @@ +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, 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, 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, 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(); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/ColorXYZ.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/ColorXYZ.java new file mode 100644 index 0000000..eb0147b --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/ColorXYZ.java @@ -0,0 +1,52 @@ +package eu.jonahbauer.raytracing.render.spectral.colors; + +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 static final double CIE_Y_INTEGRAL = 106.85689500000002; + + public ColorXYZ(@NotNull Chromaticity chromaticity) { + this(chromaticity, 1); + } + + public ColorXYZ(@NotNull Chromaticity chromaticity, double Y) { + this( + chromaticity.y() == 0 ? 0 : Y * chromaticity.x() / chromaticity.y(), + chromaticity.y() == 0 ? 0 : Y, + chromaticity.y() == 0 ? 0 : Y * (1 - chromaticity.x() - chromaticity.y()) / chromaticity.y() + ); + } + + public ColorXYZ(@NotNull Vec3 vec) { + this(vec.x(), vec.y(), vec.z()); + } + + public double average() { + return (x + y + z) / 3; + } + + public @NotNull Chromaticity xy() { + var factor = 1 / (x + y + 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); + } + + public static @NotNull ColorXYZ multiply(@NotNull ColorXYZ a, double b) { + return new ColorXYZ(a.x * b, a.y * b, a.z * b); + } + + 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); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/SigmoidPolynomial.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/SigmoidPolynomial.java new file mode 100644 index 0000000..64c391b --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/SigmoidPolynomial.java @@ -0,0 +1,34 @@ +package eu.jonahbauer.raytracing.render.spectral.colors; + +import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum; + +/** + * A function of the form {@code s(p(x))} where {@code p} is a polynomial of second degree and {@code s} is the sigmoid + * function s(x) = 0.5 + x / (2 * sqrt(1 + x^2)). + *

+ * A function of this form is used to generate a {@link Spectrum} from an RGB value. + * + * @param c0 the coefficient of the quadratic term + * @param c1 the coefficient of the linear term + * @param c2 the coefficient of the constant term + */ +public record SigmoidPolynomial(double c0, double c1, double c2) { + + public double get(double x) { + var p = Math.fma(Math.fma(c0, x, c1), x, c2); + if (!Double.isFinite(p)) return p > 0 ? 1 : 0; + return Math.fma(.5 * p, 1 / Math.sqrt(Math.fma(p, p, 1)), .5); + } + + public double max() { + // evaluate at the edges + var result = Math.max(get(Spectrum.LAMBDA_MIN), get(Spectrum.LAMBDA_MAX)); + var lambda = -c1 / (2 * c0); + if (lambda >= 360 && lambda <= 830) { + // evaluate at the vertex + return Math.max(result, get(lambda)); + } else { + return result; + } + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/SpectrumTable.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/SpectrumTable.java new file mode 100644 index 0000000..e573d07 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/SpectrumTable.java @@ -0,0 +1,143 @@ +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; + +/** + * A table of sigmoid polynomials used to convert between RGB values and spectra. + *

+ * The rgb values are renormalized to xyz coordinates with {@code z} being the largest of the rgb components, and + * {@code x} and {@code y} being the other two rgb components divided by {@code z}. By this construction, {@code x}, + * {@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. + *

+ * 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 final int resolution; + + /** + * the remapped {@code z} values + */ + private final double[] zNodes; + + /** + * the stored coefficients as a flattened five-dimensional array with the following indices + *

    + *
  1. the component index of the biggest rgb component
  2. + *
  3. the {@code z} coordinate
  4. + *
  5. the {@code y} coordinate
  6. + *
  7. the {@code x} coordinate
  8. + *
  9. the coefficient index
  10. + *
+ */ + private final 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) { + // 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()))); + } + + // find maximum component and compute remapped component values + var max = color.r() > color.g() + ? (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; + + // 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 zi = Arrays.binarySearch(zNodes, z); + if (zi < 0) { + zi = -zi - 2; + } else if (zi > 0) { + zi = zi - 1; + } + + var dx = x - xi; + var dy = y -yi; + var dz = (z - zNodes[zi]) / (zNodes[zi + 1] - zNodes[zi]); + + // trilinearly interpolate sigmoid polynomial coefficients + var c = new double[3]; + for (int i = 0; i < 3; i++) { + c[i] = lerp(dz, + lerp(dy, + 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, 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)) + ) + ); + } + + 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)); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/SpectrumTableGenerator.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/SpectrumTableGenerator.java new file mode 100644 index 0000000..f06f7d9 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/SpectrumTableGenerator.java @@ -0,0 +1,205 @@ +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 eu.jonahbauer.raytracing.render.texture.Color; +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. + *

+ * 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. + *

+ * + */ +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 Color 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 Color 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 Color 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 Color 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 Color 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 Color(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); + } + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/doc-files/rgb2spectrum.png b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/doc-files/rgb2spectrum.png new file mode 100644 index 0000000..583edbe Binary files /dev/null and b/src/main/java/eu/jonahbauer/raytracing/render/spectral/colors/doc-files/rgb2spectrum.png differ diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/BlackbodySpectrum.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/BlackbodySpectrum.java new file mode 100644 index 0000000..4a07f8b --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/BlackbodySpectrum.java @@ -0,0 +1,45 @@ +package eu.jonahbauer.raytracing.render.spectral.spectrum; + +public final class BlackbodySpectrum implements Spectrum { + /** + * the speed of light in m/s + */ + private static final double c = 299792458d; + + /** + * the planck constant in m^2*kg/s + */ + private static final double h = 6.62607015E-34; + + /** + * the boltzmann constant in m^2*kg/s^2/K + */ + private static final double k = 1.380649E-23; + + /** + * wien's displacement constant in m*K + */ + private static final double b = 2.897771995E-3; + + private final double T; + private final double factor; + + public BlackbodySpectrum(double T) { + if (T < 0) throw new IllegalArgumentException("T must be non-negative"); + this.T = T; + this.factor = 1 / get(b / T); + } + + @Override + public double max() { + return 1; + } + + @Override + public double get(double lambda) { + lambda *= 1E-9; + var l2 = lambda * lambda; + var x = h * c / (lambda * k * T); + return 2 * h * c * c / (l2 * l2 * lambda) / (Math.exp(x) - 1) * factor; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/ConstantSpectrum.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/ConstantSpectrum.java new file mode 100644 index 0000000..e5348da --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/ConstantSpectrum.java @@ -0,0 +1,17 @@ +package eu.jonahbauer.raytracing.render.spectral.spectrum; + +/** + * A constant spectrum. + * @param c the constant value + */ +public record ConstantSpectrum(double c) implements Spectrum { + @Override + public double max() { + return c; + } + + @Override + public double get(double lambda) { + return c; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/DenselySampledSpectrum.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/DenselySampledSpectrum.java new file mode 100644 index 0000000..c3e24ae --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/DenselySampledSpectrum.java @@ -0,0 +1,59 @@ +package eu.jonahbauer.raytracing.render.spectral.spectrum; + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; + +/** + * A spectrum sampled in one nanometer intervals. + */ +public final class DenselySampledSpectrum implements Spectrum { + private final double[] samples; + private final int min; + + private final double max; + + public DenselySampledSpectrum(@NotNull Spectrum spectrum) { + this(spectrum, LAMBDA_MIN, LAMBDA_MAX); + } + + public DenselySampledSpectrum(@NotNull Spectrum spectrum, int min, int max) { + 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, i = 0; lambda <= max; lambda++, i++) { + var sample = spectrum.get(lambda); + if (sample > maxValue) maxValue = sample; + this.samples[i] = sample; + } + this.min = min; + this.max = maxValue; + } + + public DenselySampledSpectrum(double @NotNull[] samples, int lambdaMin) { + if (samples.length == 0) throw new IllegalArgumentException("samples must not be empty"); + this.samples = Arrays.copyOf(samples, samples.length); + this.min = lambdaMin; + this.max = Arrays.stream(this.samples).max().orElseThrow(); + } + + public @NotNull DenselySampledSpectrum scale(double scale) { + var s = Arrays.copyOf(samples, samples.length); + for (int i = 0; i < s.length; i++) { + s[i] *= scale; + } + return new DenselySampledSpectrum(s, min); + } + + @Override + public double max() { + return max; + } + + @Override + public double get(double lambda) { + int offset = (int) Math.round(lambda) - min; + if (offset < 0 || offset >= samples.length) return 0; + return samples[offset]; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/PiecewiseLinearSpectrum.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/PiecewiseLinearSpectrum.java new file mode 100644 index 0000000..4ff4519 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/PiecewiseLinearSpectrum.java @@ -0,0 +1,58 @@ +package eu.jonahbauer.raytracing.render.spectral.spectrum; + +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; + +public final class PiecewiseLinearSpectrum implements Spectrum { + private final double[] lambdas; + private final double[] values; + private final double max; + + public PiecewiseLinearSpectrum(double[] lambdas, double[] values) { + if (lambdas.length != values.length) { + throw new IllegalArgumentException("lambdas and values must have the same length"); + } + + this.lambdas = Arrays.copyOf(lambdas, lambdas.length); + this.values = Arrays.copyOf(values, values.length); + + var max = 0d; + for (int i = 1; i < this.lambdas.length; i++) { + if (this.lambdas[i] <= this.lambdas[i - 1]) { + throw new IllegalArgumentException("lambdas must be in increasing order"); + } + if (this.values[i] < 0) { + throw new IllegalArgumentException("values must be non-negative"); + } else if (this.values[i] > max) { + max = this.values[i]; + } + } + this.max = max; + } + + public @NotNull PiecewiseLinearSpectrum scale(double scale) { + var v = Arrays.copyOf(values, values.length); + for (int i = 0; i < v.length; i++) { + v[i] *= scale; + } + return new PiecewiseLinearSpectrum(lambdas, v); + } + + @Override + public double max() { + return max; + } + + @Override + public double get(double lambda) { + if (lambdas.length == 0 || lambda < lambdas[0] || lambda > lambdas[lambdas.length - 1]) return 0; + if (lambda == lambdas[lambdas.length - 1]) return values[values.length - 1]; + + var i = Arrays.binarySearch(lambdas, lambda); + if (i < 0) i = -i - 1; + + var t = (lambda - lambdas[i]) / (lambdas[i + 1] - lambdas[i]); + return (1 - t) * values[i] + t * values[i + 1]; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/RGBAlbedoSpectrum.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/RGBAlbedoSpectrum.java new file mode 100644 index 0000000..58b9d21 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/RGBAlbedoSpectrum.java @@ -0,0 +1,27 @@ +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 org.jetbrains.annotations.NotNull; + +public final class RGBAlbedoSpectrum implements Spectrum { + private final @NotNull SigmoidPolynomial polynomial; + + public RGBAlbedoSpectrum(@NotNull ColorSpace cs, @NotNull Color rgb) { + if (rgb.r() < 0 || rgb.r() > 1 || rgb.g() < 0 || rgb.g() > 1 || rgb.b() < 0 || rgb.b() > 1) { + throw new IllegalArgumentException(); + } + this.polynomial = cs.toSpectrum(rgb); + } + + @Override + public double max() { + return polynomial.max(); + } + + @Override + public double get(double lambda) { + return polynomial.get(lambda); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/RGBIlluminantSpectrum.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/RGBIlluminantSpectrum.java new file mode 100644 index 0000000..9ad0421 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/RGBIlluminantSpectrum.java @@ -0,0 +1,36 @@ +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 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 Spectrum illuminant; + + public RGBIlluminantSpectrum(@NotNull ColorSpace cs, @NotNull Color 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.illuminant = cs.illuminant(); + } + + @Override + public double max() { + return scale * polynomial.max() * illuminant.max(); + } + + @Override + public double get(double lambda) { + return scale * polynomial.get(lambda) * illuminant.get(lambda); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/RGBUnboundedSpectrum.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/RGBUnboundedSpectrum.java new file mode 100644 index 0000000..d98654e --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/RGBUnboundedSpectrum.java @@ -0,0 +1,30 @@ +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 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) { + 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); + } + + @Override + public double max() { + return scale * polynomial.max(); + } + + @Override + public double get(double lambda) { + return scale * polynomial.get(lambda); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/Spectra.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/Spectra.java new file mode 100644 index 0000000..e007509 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/Spectra.java @@ -0,0 +1,407 @@ +package eu.jonahbauer.raytracing.render.spectral.spectrum; + +import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ; +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +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 = 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 = 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 = new DenselySampledSpectrum(new PiecewiseLinearSpectrum(CIE_XYZ.CIE_lambda, CIE_XYZ.CIE_Z));; + /** + * the CIE standard illuminant D50 + * @see CIE 2022, CIE standard illuminant D65, International Commission on Illumination (CIE), Vienna, Austria, DOI: 10.25039/CIE.DS.hjfjmt59 + */ + public static final Spectrum D50 = read("CIE_std_illum_D50.csv", true); + /** + * the CIE standard illuminant D65 + * @see 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 + */ + public static final Spectrum D65 = read("CIE_std_illum_D65.csv", true); + + private static @NotNull Spectrum read(@NotNull String path, boolean normalize) { + var lambda = new ArrayList(); + var values = new ArrayList(); + + try ( + var is = Spectra.class.getResourceAsStream(PATH_PREFIX + path); + var in = new BufferedReader(new InputStreamReader(is, StandardCharsets.US_ASCII)) + ) { + String line; + while ((line = in.readLine()) != null) { + var parts = line.split(","); + lambda.add(Double.parseDouble(parts[0])); + values.add(Double.parseDouble(parts[1])); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + 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; + } + } + + private Spectra() { + throw new UnsupportedOperationException(); + } + + /** + * @see 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 + */ + 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 + }; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/Spectrum.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/Spectrum.java new file mode 100644 index 0000000..09fe1da --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/Spectrum.java @@ -0,0 +1,41 @@ +package eu.jonahbauer.raytracing.render.spectral.spectrum; + +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 org.jetbrains.annotations.NotNull; + +public interface Spectrum { + int LAMBDA_MIN = 360; + int LAMBDA_MAX = 830; + + /** + * {@return the maximum value of this spectrum over the range of wavelengths} + */ + double max(); + + /** + * {@return the value of this spectrum at a given wavelength} + * @param lambda the wavelength in nanometers + */ + double get(double lambda); + + default @NotNull SampledSpectrum sample(@NotNull SampledWavelengths lambdas) { + return new SampledSpectrum(lambdas, this); + } + + default @NotNull ColorXYZ toXYZ() { + return new ColorXYZ( + 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) { + return cs.toRGB(toXYZ()); + } + +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/Util.java b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/Util.java new file mode 100644 index 0000000..783e3e0 --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/render/spectral/spectrum/Util.java @@ -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; + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/render/texture/Color.java b/src/main/java/eu/jonahbauer/raytracing/render/texture/Color.java index b7d1672..bfebde3 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/texture/Color.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/texture/Color.java @@ -5,6 +5,7 @@ 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; @@ -20,9 +21,9 @@ public record Color(double r, double g, double b) implements Texture, SkyBox { if (t < 0) return a; if (t > 1) return b; return new Color( - (1 - t) * a.r + t * b.r, - (1 - t) * a.g + t * b.g, - (1 - t) * a.b + t * b.b + 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)) ); } @@ -86,6 +87,10 @@ public record Color(double r, double g, double b) implements Texture, SkyBox { 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)) { @@ -109,6 +114,15 @@ public record Color(double r, double g, double b) implements Texture, SkyBox { 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; @@ -124,6 +138,10 @@ public record Color(double r, double g, double b) implements Texture, SkyBox { 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); } diff --git a/src/main/resources/eu/jonahbauer/raytracing/colorspace/DCI_P3_spectrum.bin b/src/main/resources/eu/jonahbauer/raytracing/colorspace/DCI_P3_spectrum.bin new file mode 100644 index 0000000..9a709bb Binary files /dev/null and b/src/main/resources/eu/jonahbauer/raytracing/colorspace/DCI_P3_spectrum.bin differ diff --git a/src/main/resources/eu/jonahbauer/raytracing/colorspace/Rec2020_spectrum.bin b/src/main/resources/eu/jonahbauer/raytracing/colorspace/Rec2020_spectrum.bin new file mode 100644 index 0000000..94d8c36 Binary files /dev/null and b/src/main/resources/eu/jonahbauer/raytracing/colorspace/Rec2020_spectrum.bin differ diff --git a/src/main/resources/eu/jonahbauer/raytracing/colorspace/sRGB_spectrum.bin b/src/main/resources/eu/jonahbauer/raytracing/colorspace/sRGB_spectrum.bin new file mode 100644 index 0000000..f0f9750 Binary files /dev/null and b/src/main/resources/eu/jonahbauer/raytracing/colorspace/sRGB_spectrum.bin differ diff --git a/src/main/resources/eu/jonahbauer/raytracing/spectrum/CIE_std_illum_D50.csv b/src/main/resources/eu/jonahbauer/raytracing/spectrum/CIE_std_illum_D50.csv new file mode 100644 index 0000000..6469668 --- /dev/null +++ b/src/main/resources/eu/jonahbauer/raytracing/spectrum/CIE_std_illum_D50.csv @@ -0,0 +1,531 @@ +300,0.01922 +301,0.222348 +302,0.425476 +303,0.628604 +304,0.831732 +305,1.03486 +306,1.23799 +307,1.44112 +308,1.64424 +309,1.84737 +310,2.0505 +311,2.62329 +312,3.19608 +313,3.76887 +314,4.34166 +315,4.91445 +316,5.48724 +317,6.06003 +318,6.63282 +319,7.20561 +320,7.7784 +321,8.47531 +322,9.17222 +323,9.86913 +324,10.566 +325,11.263 +326,11.9599 +327,12.6568 +328,13.3537 +329,14.0506 +330,14.7475 +331,15.0676 +332,15.3876 +333,15.7076 +334,16.0277 +335,16.3478 +336,16.6678 +337,16.9878 +338,17.3079 +339,17.628 +340,17.948 +341,18.2542 +342,18.5603 +343,18.8665 +344,19.1727 +345,19.4788 +346,19.785 +347,20.0912 +348,20.3974 +349,20.7035 +350,21.0097 +351,21.3029 +352,21.5961 +353,21.8894 +354,22.1826 +355,22.4758 +356,22.769 +357,23.0622 +358,23.3555 +359,23.6487 +360,23.9419 +361,24.2438 +362,24.5457 +363,24.8475 +364,25.1494 +365,25.4513 +366,25.7532 +367,26.0551 +368,26.3569 +369,26.6588 +370,26.9607 +371,26.7134 +372,26.4661 +373,26.2187 +374,25.9714 +375,25.7241 +376,25.4768 +377,25.2295 +378,24.9821 +379,24.7348 +380,24.4875 +381,25.0258 +382,25.5641 +383,26.1024 +384,26.6407 +385,27.179 +386,27.7174 +387,28.2557 +388,28.794 +389,29.3323 +390,29.8706 +391,31.8144 +392,33.7581 +393,35.7018 +394,37.6456 +395,39.5894 +396,41.5331 +397,43.4768 +398,45.4206 +399,47.3644 +400,49.3081 +401,50.0286 +402,50.749 +403,51.4695 +404,52.19 +405,52.9104 +406,53.6309 +407,54.3514 +408,55.0719 +409,55.7923 +410,56.5128 +411,56.8649 +412,57.217 +413,57.5691 +414,57.9212 +415,58.2733 +416,58.6254 +417,58.9775 +418,59.3296 +419,59.6817 +420,60.0338 +421,59.8122 +422,59.5905 +423,59.3689 +424,59.1473 +425,58.9256 +426,58.704 +427,58.4824 +428,58.2608 +429,58.0391 +430,57.8175 +431,59.5182 +432,61.219 +433,62.9197 +434,64.6205 +435,66.3212 +436,68.0219 +437,69.7227 +438,71.4234 +439,73.1242 +440,74.8249 +441,76.0671 +442,77.3094 +443,78.5516 +444,79.7938 +445,81.036 +446,82.2783 +447,83.5205 +448,84.7627 +449,86.005 +450,87.2472 +451,87.5837 +452,87.9202 +453,88.2567 +454,88.5932 +455,88.9297 +456,89.2662 +457,89.6027 +458,89.9392 +459,90.2757 +460,90.6122 +461,90.6878 +462,90.7634 +463,90.839 +464,90.9146 +465,90.9902 +466,91.0657 +467,91.1413 +468,91.2169 +469,91.2925 +470,91.3681 +471,91.7421 +472,92.1162 +473,92.4902 +474,92.8643 +475,93.2383 +476,93.6123 +477,93.9864 +478,94.3604 +479,94.7345 +480,95.1085 +481,94.7939 +482,94.4793 +483,94.1648 +484,93.8502 +485,93.5356 +486,93.221 +487,92.9064 +488,92.5919 +489,92.2773 +490,91.9627 +491,92.3388 +492,92.7149 +493,93.091 +494,93.4671 +495,93.8432 +496,94.2193 +497,94.5954 +498,94.9715 +499,95.3476 +500,95.7237 +501,95.8127 +502,95.9016 +503,95.9906 +504,96.0795 +505,96.1685 +506,96.2575 +507,96.3464 +508,96.4354 +509,96.5243 +510,96.6133 +511,96.6649 +512,96.7164 +513,96.768 +514,96.8196 +515,96.8712 +516,96.9227 +517,96.9743 +518,97.0259 +519,97.0774 +520,97.129 +521,97.626 +522,98.123 +523,98.62 +524,99.117 +525,99.614 +526,100.111 +527,100.608 +528,101.105 +529,101.602 +530,102.099 +531,101.965 +532,101.83 +533,101.696 +534,101.561 +535,101.427 +536,101.292 +537,101.158 +538,101.024 +539,100.889 +540,100.755 +541,100.911 +542,101.067 +543,101.223 +544,101.38 +545,101.536 +546,101.692 +547,101.848 +548,102.005 +549,102.161 +550,102.317 +551,102.085 +552,101.854 +553,101.622 +554,101.39 +555,101.158 +556,100.927 +557,100.695 +558,100.463 +559,100.232 +560,100 +561,99.7735 +562,99.547 +563,99.3205 +564,99.094 +565,98.8675 +566,98.641 +567,98.4145 +568,98.188 +569,97.9615 +570,97.735 +571,97.8533 +572,97.9716 +573,98.0899 +574,98.2082 +575,98.3265 +576,98.4448 +577,98.5631 +578,98.6814 +579,98.7997 +580,98.918 +581,98.3761 +582,97.8342 +583,97.2922 +584,96.7503 +585,96.2084 +586,95.6665 +587,95.1246 +588,94.5826 +589,94.0407 +590,93.4988 +591,93.9177 +592,94.3366 +593,94.7555 +594,95.1744 +595,95.5933 +596,96.0122 +597,96.4311 +598,96.85 +599,97.2689 +600,97.6878 +601,97.8459 +602,98.0041 +603,98.1622 +604,98.3203 +605,98.4784 +606,98.6366 +607,98.7947 +608,98.9528 +609,99.111 +610,99.2691 +611,99.2463 +612,99.2236 +613,99.2008 +614,99.1781 +615,99.1553 +616,99.1325 +617,99.1098 +618,99.087 +619,99.0643 +620,99.0415 +621,98.7095 +622,98.3776 +623,98.0456 +624,97.7136 +625,97.3816 +626,97.0497 +627,96.7177 +628,96.3857 +629,96.0538 +630,95.7218 +631,96.0353 +632,96.3489 +633,96.6624 +634,96.976 +635,97.2895 +636,97.603 +637,97.9166 +638,98.2301 +639,98.5437 +640,98.8572 +641,98.5382 +642,98.2192 +643,97.9002 +644,97.5812 +645,97.2622 +646,96.9432 +647,96.6242 +648,96.3052 +649,95.9862 +650,95.6672 +651,95.9195 +652,96.1717 +653,96.424 +654,96.6762 +655,96.9285 +656,97.1808 +657,97.433 +658,97.6853 +659,97.9375 +660,98.1898 +661,98.6712 +662,99.1525 +663,99.6339 +664,100.115 +665,100.597 +666,101.078 +667,101.559 +668,102.041 +669,102.522 +670,103.003 +671,102.616 +672,102.229 +673,101.842 +674,101.455 +675,101.068 +676,100.681 +677,100.294 +678,99.9071 +679,99.52 +680,99.133 +681,97.9578 +682,96.7826 +683,95.6074 +684,94.4322 +685,93.257 +686,92.0817 +687,90.9065 +688,89.7313 +689,88.5561 +690,87.3809 +691,87.8032 +692,88.2254 +693,88.6477 +694,89.0699 +695,89.4922 +696,89.9145 +697,90.3367 +698,90.759 +699,91.1812 +700,91.6035 +701,91.732 +702,91.8605 +703,91.989 +704,92.1175 +705,92.246 +706,92.3746 +707,92.5031 +708,92.6316 +709,92.7601 +710,92.8886 +711,91.2852 +712,89.6818 +713,88.0783 +714,86.4749 +715,84.8715 +716,83.2681 +717,81.6647 +718,80.0612 +719,78.4578 +720,76.8544 +721,77.8201 +722,78.7858 +723,79.7514 +724,80.7171 +725,81.6828 +726,82.6485 +727,83.6142 +728,84.5798 +729,85.5455 +730,86.5112 +731,87.1181 +732,87.7249 +733,88.3318 +734,88.9386 +735,89.5455 +736,90.1524 +737,90.7592 +738,91.3661 +739,91.9729 +740,92.5798 +741,91.1448 +742,89.7098 +743,88.2748 +744,86.8398 +745,85.4048 +746,83.9699 +747,82.5349 +748,81.0999 +749,79.6649 +750,78.2299 +751,76.1761 +752,74.1223 +753,72.0685 +754,70.0147 +755,67.9608 +756,65.907 +757,63.8532 +758,61.7994 +759,59.7456 +760,57.6918 +761,60.2149 +762,62.738 +763,65.2612 +764,67.7843 +765,70.3074 +766,72.8305 +767,75.3536 +768,77.8768 +769,80.3999 +770,82.923 +771,82.4581 +772,81.9932 +773,81.5283 +774,81.0634 +775,80.5985 +776,80.1336 +777,79.6687 +778,79.2038 +779,78.7389 +780,78.274 +781,78.402 +782,78.5301 +783,78.6581 +784,78.7862 +785,78.9142 +786,79.0422 +787,79.1703 +788,79.2983 +789,79.4264 +790,79.5544 +791,78.9391 +792,78.3238 +793,77.7085 +794,77.0932 +795,76.478 +796,75.8627 +797,75.2474 +798,74.6321 +799,74.0168 +800,73.4015 +801,72.4534 +802,71.5052 +803,70.5571 +804,69.609 +805,68.6608 +806,67.7127 +807,66.7646 +808,65.8165 +809,64.8683 +810,63.9202 +811,64.6059 +812,65.2916 +813,65.9772 +814,66.6629 +815,67.3486 +816,68.0343 +817,68.72 +818,69.4056 +819,70.0913 +820,70.777 +821,71.1435 +822,71.5099 +823,71.8764 +824,72.2429 +825,72.6094 +826,72.9758 +827,73.3423 +828,73.7088 +829,74.0752 +830,74.4417 diff --git a/src/main/resources/eu/jonahbauer/raytracing/spectrum/CIE_std_illum_D65.csv b/src/main/resources/eu/jonahbauer/raytracing/spectrum/CIE_std_illum_D65.csv new file mode 100644 index 0000000..b6c2c7f --- /dev/null +++ b/src/main/resources/eu/jonahbauer/raytracing/spectrum/CIE_std_illum_D65.csv @@ -0,0 +1,531 @@ +300,0.0341 +301,0.36014 +302,0.68618 +303,1.01222 +304,1.33826 +305,1.6643 +306,1.99034 +307,2.31638 +308,2.64242 +309,2.96846 +310,3.2945 +311,4.98865 +312,6.6828 +313,8.37695 +314,10.0711 +315,11.7652 +316,13.4594 +317,15.1535 +318,16.8477 +319,18.5418 +320,20.236 +321,21.9177 +322,23.5995 +323,25.2812 +324,26.963 +325,28.6447 +326,30.3265 +327,32.0082 +328,33.69 +329,35.3717 +330,37.0535 +331,37.343 +332,37.6326 +333,37.9221 +334,38.2116 +335,38.5011 +336,38.7907 +337,39.0802 +338,39.3697 +339,39.6593 +340,39.9488 +341,40.4451 +342,40.9414 +343,41.4377 +344,41.934 +345,42.4302 +346,42.9265 +347,43.4228 +348,43.9191 +349,44.4154 +350,44.9117 +351,45.0844 +352,45.257 +353,45.4297 +354,45.6023 +355,45.775 +356,45.9477 +357,46.1203 +358,46.293 +359,46.4656 +360,46.6383 +361,47.1834 +362,47.7285 +363,48.2735 +364,48.8186 +365,49.3637 +366,49.9088 +367,50.4539 +368,50.9989 +369,51.544 +370,52.0891 +371,51.8777 +372,51.6664 +373,51.455 +374,51.2437 +375,51.0323 +376,50.8209 +377,50.6096 +378,50.3982 +379,50.1869 +380,49.9755 +381,50.4428 +382,50.91 +383,51.3773 +384,51.8446 +385,52.3118 +386,52.7791 +387,53.2464 +388,53.7137 +389,54.1809 +390,54.6482 +391,57.4589 +392,60.2695 +393,63.0802 +394,65.8909 +395,68.7015 +396,71.5122 +397,74.3229 +398,77.1336 +399,79.9442 +400,82.7549 +401,83.628 +402,84.5011 +403,85.3742 +404,86.2473 +405,87.1204 +406,87.9936 +407,88.8667 +408,89.7398 +409,90.6129 +410,91.486 +411,91.6806 +412,91.8752 +413,92.0697 +414,92.2643 +415,92.4589 +416,92.6535 +417,92.8481 +418,93.0426 +419,93.2372 +420,93.4318 +421,92.7568 +422,92.0819 +423,91.4069 +424,90.732 +425,90.057 +426,89.3821 +427,88.7071 +428,88.0322 +429,87.3572 +430,86.6823 +431,88.5006 +432,90.3188 +433,92.1371 +434,93.9554 +435,95.7736 +436,97.5919 +437,99.4102 +438,101.228 +439,103.047 +440,104.865 +441,106.079 +442,107.294 +443,108.508 +444,109.722 +445,110.936 +446,112.151 +447,113.365 +448,114.579 +449,115.794 +450,117.008 +451,117.088 +452,117.169 +453,117.249 +454,117.33 +455,117.41 +456,117.49 +457,117.571 +458,117.651 +459,117.732 +460,117.812 +461,117.517 +462,117.222 +463,116.927 +464,116.632 +465,116.336 +466,116.041 +467,115.746 +468,115.451 +469,115.156 +470,114.861 +471,114.967 +472,115.073 +473,115.18 +474,115.286 +475,115.392 +476,115.498 +477,115.604 +478,115.711 +479,115.817 +480,115.923 +481,115.212 +482,114.501 +483,113.789 +484,113.078 +485,112.367 +486,111.656 +487,110.945 +488,110.233 +489,109.522 +490,108.811 +491,108.865 +492,108.92 +493,108.974 +494,109.028 +495,109.082 +496,109.137 +497,109.191 +498,109.245 +499,109.3 +500,109.354 +501,109.199 +502,109.044 +503,108.888 +504,108.733 +505,108.578 +506,108.423 +507,108.268 +508,108.112 +509,107.957 +510,107.802 +511,107.501 +512,107.2 +513,106.898 +514,106.597 +515,106.296 +516,105.995 +517,105.694 +518,105.392 +519,105.091 +520,104.79 +521,105.08 +522,105.37 +523,105.66 +524,105.95 +525,106.239 +526,106.529 +527,106.819 +528,107.109 +529,107.399 +530,107.689 +531,107.361 +532,107.032 +533,106.704 +534,106.375 +535,106.047 +536,105.719 +537,105.39 +538,105.062 +539,104.733 +540,104.405 +541,104.369 +542,104.333 +543,104.297 +544,104.261 +545,104.225 +546,104.19 +547,104.154 +548,104.118 +549,104.082 +550,104.046 +551,103.641 +552,103.237 +553,102.832 +554,102.428 +555,102.023 +556,101.618 +557,101.214 +558,100.809 +559,100.405 +560,100 +561,99.6334 +562,99.2668 +563,98.9003 +564,98.5337 +565,98.1671 +566,97.8005 +567,97.4339 +568,97.0674 +569,96.7008 +570,96.3342 +571,96.2796 +572,96.225 +573,96.1703 +574,96.1157 +575,96.0611 +576,96.0065 +577,95.9519 +578,95.8972 +579,95.8426 +580,95.788 +581,95.0778 +582,94.3675 +583,93.6573 +584,92.947 +585,92.2368 +586,91.5266 +587,90.8163 +588,90.1061 +589,89.3958 +590,88.6856 +591,88.8177 +592,88.9497 +593,89.0818 +594,89.2138 +595,89.3459 +596,89.478 +597,89.61 +598,89.7421 +599,89.8741 +600,90.0062 +601,89.9655 +602,89.9248 +603,89.8841 +604,89.8434 +605,89.8026 +606,89.7619 +607,89.7212 +608,89.6805 +609,89.6398 +610,89.5991 +611,89.4091 +612,89.219 +613,89.029 +614,88.8389 +615,88.6489 +616,88.4589 +617,88.2688 +618,88.0788 +619,87.8887 +620,87.6987 +621,87.2577 +622,86.8167 +623,86.3757 +624,85.9347 +625,85.4936 +626,85.0526 +627,84.6116 +628,84.1706 +629,83.7296 +630,83.2886 +631,83.3297 +632,83.3707 +633,83.4118 +634,83.4528 +635,83.4939 +636,83.535 +637,83.576 +638,83.6171 +639,83.6581 +640,83.6992 +641,83.332 +642,82.9647 +643,82.5975 +644,82.2302 +645,81.863 +646,81.4958 +647,81.1285 +648,80.7613 +649,80.394 +650,80.0268 +651,80.0456 +652,80.0644 +653,80.0831 +654,80.1019 +655,80.1207 +656,80.1395 +657,80.1583 +658,80.177 +659,80.1958 +660,80.2146 +661,80.4209 +662,80.6272 +663,80.8336 +664,81.0399 +665,81.2462 +666,81.4525 +667,81.6588 +668,81.8652 +669,82.0715 +670,82.2778 +671,81.8784 +672,81.4791 +673,81.0797 +674,80.6804 +675,80.281 +676,79.8816 +677,79.4823 +678,79.0829 +679,78.6836 +680,78.2842 +681,77.4279 +682,76.5716 +683,75.7153 +684,74.859 +685,74.0027 +686,73.1465 +687,72.2902 +688,71.4339 +689,70.5776 +690,69.7213 +691,69.9101 +692,70.0989 +693,70.2876 +694,70.4764 +695,70.6652 +696,70.854 +697,71.0428 +698,71.2315 +699,71.4203 +700,71.6091 +701,71.8831 +702,72.1571 +703,72.4311 +704,72.7051 +705,72.979 +706,73.253 +707,73.527 +708,73.801 +709,74.075 +710,74.349 +711,73.0745 +712,71.8 +713,70.5255 +714,69.251 +715,67.9765 +716,66.702 +717,65.4275 +718,64.153 +719,62.8785 +720,61.604 +721,62.4322 +722,63.2603 +723,64.0885 +724,64.9166 +725,65.7448 +726,66.573 +727,67.4011 +728,68.2293 +729,69.0574 +730,69.8856 +731,70.4057 +732,70.9259 +733,71.446 +734,71.9662 +735,72.4863 +736,73.0064 +737,73.5266 +738,74.0467 +739,74.5669 +740,75.087 +741,73.9376 +742,72.7881 +743,71.6387 +744,70.4893 +745,69.3398 +746,68.1904 +747,67.041 +748,65.8916 +749,64.7421 +750,63.5927 +751,61.8752 +752,60.1578 +753,58.4403 +754,56.7229 +755,55.0054 +756,53.288 +757,51.5705 +758,49.8531 +759,48.1356 +760,46.4182 +761,48.4569 +762,50.4956 +763,52.5344 +764,54.5731 +765,56.6118 +766,58.6505 +767,60.6892 +768,62.728 +769,64.7667 +770,66.8054 +771,66.4631 +772,66.1209 +773,65.7786 +774,65.4364 +775,65.0941 +776,64.7518 +777,64.4096 +778,64.0673 +779,63.7251 +780,63.3828 +781,63.4749 +782,63.567 +783,63.6592 +784,63.7513 +785,63.8434 +786,63.9355 +787,64.0276 +788,64.1198 +789,64.2119 +790,64.304 +791,63.8188 +792,63.3336 +793,62.8484 +794,62.3632 +795,61.8779 +796,61.3927 +797,60.9075 +798,60.4223 +799,59.9371 +800,59.4519 +801,58.7026 +802,57.9533 +803,57.204 +804,56.4547 +805,55.7054 +806,54.9562 +807,54.2069 +808,53.4576 +809,52.7083 +810,51.959 +811,52.5072 +812,53.0553 +813,53.6035 +814,54.1516 +815,54.6998 +816,55.248 +817,55.7961 +818,56.3443 +819,56.8924 +820,57.4406 +821,57.7278 +822,58.015 +823,58.3022 +824,58.5894 +825,58.8765 +826,59.1637 +827,59.4509 +828,59.7381 +829,60.0253 +830,60.3125 diff --git a/src/main/resources/earthmap.jpg b/src/main/resources/eu/jonahbauer/raytracing/textures/earthmap.jpg similarity index 100% rename from src/main/resources/earthmap.jpg rename to src/main/resources/eu/jonahbauer/raytracing/textures/earthmap.jpg