Compare commits

...

6 Commits

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

@ -1,13 +1,11 @@
package eu.jonahbauer.raytracing; package eu.jonahbauer.raytracing;
import eu.jonahbauer.raytracing.render.ImageFormat;
import eu.jonahbauer.raytracing.render.canvas.Canvas; import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.render.canvas.LiveCanvas; import eu.jonahbauer.raytracing.render.canvas.LiveCanvas;
import eu.jonahbauer.raytracing.render.canvas.XYZCanvas; import eu.jonahbauer.raytracing.render.canvas.XYZCanvas;
import eu.jonahbauer.raytracing.render.image.PNGImageWriter;
import eu.jonahbauer.raytracing.render.renderer.SimpleRenderer; import eu.jonahbauer.raytracing.render.renderer.SimpleRenderer;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB; import eu.jonahbauer.raytracing.render.color.ColorSpaces;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.IOException; import java.io.IOException;
@ -44,7 +42,7 @@ public class Main {
renderer.render(camera, scene, canvas); renderer.render(camera, scene, canvas);
System.out.printf("rendering finished after %dms", (System.nanoTime() - time) / 1_000_000); System.out.printf("rendering finished after %dms", (System.nanoTime() - time) / 1_000_000);
ImageFormat.PNG.write(canvas, config.path); PNGImageWriter.sRGB.write(canvas, config.path);
} }
private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int depth) { private record Config(@NotNull Example example, @NotNull Path path, boolean preview, boolean iterative, boolean parallel, int samples, int depth) {

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

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

@ -1,6 +1,6 @@
package eu.jonahbauer.raytracing.math; package eu.jonahbauer.raytracing.math;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths; import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;

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

@ -2,7 +2,7 @@ package eu.jonahbauer.raytracing.render.camera;
import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.math.Vec3; import eu.jonahbauer.raytracing.math.Vec3;
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths; import eu.jonahbauer.raytracing.render.spectrum.SampledWavelengths;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -4,7 +4,7 @@ import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.material.Material; import eu.jonahbauer.raytracing.render.material.Material;
import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction; import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction; import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum; import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.camera.Camera; import eu.jonahbauer.raytracing.render.camera.Camera;
import eu.jonahbauer.raytracing.render.canvas.Canvas; import eu.jonahbauer.raytracing.render.canvas.Canvas;
import eu.jonahbauer.raytracing.scene.Scene; import eu.jonahbauer.raytracing.scene.Scene;

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

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

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

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

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

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

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

@ -1,10 +1,9 @@
package eu.jonahbauer.raytracing.render.spectral; package eu.jonahbauer.raytracing.render.spectrum;
import eu.jonahbauer.raytracing.math.IVec; import eu.jonahbauer.raytracing.math.IVec;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace; import eu.jonahbauer.raytracing.render.color.ColorSpace;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ; import eu.jonahbauer.raytracing.render.color.ColorXYZ;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum; import eu.jonahbauer.raytracing.render.color.ColorRGB;
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Arrays; import java.util.Arrays;

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

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

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

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

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

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

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

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

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

@ -2,8 +2,8 @@ package eu.jonahbauer.raytracing.scene;
import eu.jonahbauer.raytracing.math.AABB; import eu.jonahbauer.raytracing.math.AABB;
import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum; import eu.jonahbauer.raytracing.render.spectrum.SampledSpectrum;
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra; import eu.jonahbauer.raytracing.render.spectrum.Spectra;
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree; import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
import eu.jonahbauer.raytracing.scene.util.HittableCollection; import eu.jonahbauer.raytracing.scene.util.HittableCollection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;

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

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

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

Loading…
Cancel
Save