adjust rendering pipeline for spectral rendering
This commit is contained in:
parent
ddc861138a
commit
00fbf4e4f1
@ -1,8 +1,11 @@
|
||||
package eu.jonahbauer.raytracing;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.CheckerTexture;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.camera.SimpleCamera;
|
||||
import eu.jonahbauer.raytracing.render.material.*;
|
||||
import eu.jonahbauer.raytracing.render.texture.ImageTexture;
|
||||
@ -49,13 +52,14 @@ public class Examples {
|
||||
|
||||
public static @NotNull Example getSimpleScene(int height) {
|
||||
if (height <= 0) height = 675;
|
||||
var cs = ColorSpaces.sRGB;
|
||||
return new Example(
|
||||
new Scene(getSkyBox(), List.of(
|
||||
new Sphere(new Vec3(0, -100.5, -1.0), 100.0, new LambertianMaterial(new Color(0.8, 0.8, 0.0))),
|
||||
new Sphere(new Vec3(0, 0, -1.2), 0.5, new LambertianMaterial(new Color(0.1, 0.2, 0.5))),
|
||||
new Sphere(new Vec3(0, -100.5, -1.0), 100.0, new LambertianMaterial(new ColorRGB(0.8, 0.8, 0.0), cs)),
|
||||
new Sphere(new Vec3(0, 0, -1.2), 0.5, new LambertianMaterial(new ColorRGB(0.1, 0.2, 0.5), cs)),
|
||||
new Sphere(new Vec3(-1.0, 0, -1.2), 0.5, new DielectricMaterial(1.5)),
|
||||
new Sphere(new Vec3(-1.0, 0, -1.2), 0.4, new DielectricMaterial(1 / 1.5)),
|
||||
new Sphere(new Vec3(1.0, 0, -1.2), 0.5, new MetallicMaterial(new Color(0.8, 0.6, 0.2), 0.0))
|
||||
new Sphere(new Vec3(1.0, 0, -1.2), 0.5, new MetallicMaterial(new ColorRGB(0.8, 0.6, 0.2), cs, 0.0))
|
||||
)),
|
||||
SimpleCamera.builder()
|
||||
.withImage(height * 16 / 9, height)
|
||||
@ -66,11 +70,16 @@ public class Examples {
|
||||
public static @NotNull Example getSpheres(int height) {
|
||||
if (height <= 0) height = 675;
|
||||
|
||||
var cs = ColorSpaces.sRGB;
|
||||
|
||||
var rng = new Random(1);
|
||||
var objects = new ArrayList<Hittable>();
|
||||
objects.add(new Sphere(
|
||||
new Vec3(0, -1000, 0), 1000,
|
||||
new LambertianMaterial(new CheckerTexture(0.32, new Color(.2, .3, .1), new Color(.9, .9, .9)))
|
||||
new LambertianMaterial(new CheckerTexture(0.32,
|
||||
new RGBAlbedoSpectrum(cs, new ColorRGB(.2, .3, .1)),
|
||||
new RGBAlbedoSpectrum(cs, new ColorRGB(.9, .9, .9))
|
||||
))
|
||||
));
|
||||
|
||||
for (int a = -11; a < 11; a++) {
|
||||
@ -82,13 +91,13 @@ public class Examples {
|
||||
var rnd = rng.nextDouble();
|
||||
if (rnd < 0.8) {
|
||||
// diffuse
|
||||
var albedo = Color.random(rng).times(Color.random(rng));
|
||||
material = new LambertianMaterial(albedo);
|
||||
var albedo = ColorRGB.random(rng).times(ColorRGB.random(rng));
|
||||
material = new LambertianMaterial(albedo, cs);
|
||||
} else if (rnd < 0.95) {
|
||||
// metal
|
||||
var albedo = Color.random(rng, 0.5, 1.0);
|
||||
var albedo = ColorRGB.random(rng, 0.5, 1.0);
|
||||
var fuzz = rng.nextDouble() * 0.5;
|
||||
material = new MetallicMaterial(albedo, fuzz);
|
||||
material = new MetallicMaterial(albedo, cs, fuzz);
|
||||
} else {
|
||||
// glass
|
||||
material = new DielectricMaterial(1.5);
|
||||
@ -99,8 +108,8 @@ public class Examples {
|
||||
}
|
||||
|
||||
objects.add(new Sphere(new Vec3(0, 1, 0), 1.0, new DielectricMaterial(1.5)));
|
||||
objects.add(new Sphere(new Vec3(-4, 1, 0), 1.0, new LambertianMaterial(new Color(0.4, 0.2, 0.1))));
|
||||
objects.add(new Sphere(new Vec3(4, 1, 0), 1.0, new MetallicMaterial(new Color(0.7, 0.6, 0.5))));
|
||||
objects.add(new Sphere(new Vec3(-4, 1, 0), 1.0, new LambertianMaterial(new ColorRGB(0.4, 0.2, 0.1), cs)));
|
||||
objects.add(new Sphere(new Vec3(4, 1, 0), 1.0, new MetallicMaterial(new ColorRGB(0.7, 0.6, 0.5), cs)));
|
||||
|
||||
var camera = SimpleCamera.builder()
|
||||
.withImage(height * 16 / 9, height)
|
||||
@ -116,13 +125,14 @@ public class Examples {
|
||||
|
||||
public static @NotNull Example getSquares(int height) {
|
||||
if (height <= 0) height = 600;
|
||||
var cs = ColorSpaces.sRGB;
|
||||
return new Example(
|
||||
new Scene(getSkyBox(), List.of(
|
||||
new Parallelogram(new Vec3(-3, -2, 5), new Vec3(0, 0, -4), new Vec3(0, 4, 0), new LambertianMaterial(new Color(1.0, 0.2, 0.2))),
|
||||
new Parallelogram(new Vec3(-2, -2, 0), new Vec3(4, 0, 0), new Vec3(0, 4, 0), new LambertianMaterial(new Color(0.2, 1.0, 0.2))),
|
||||
new Parallelogram(new Vec3(3, -2, 1), new Vec3(0, 0, 4), new Vec3(0, 4, 0), new LambertianMaterial(new Color(0.2, 0.2, 1.0))),
|
||||
new Parallelogram(new Vec3(-2, 3, 1), new Vec3(4, 0, 0), new Vec3(0, 0, 4), new LambertianMaterial(new Color(1.0, 0.5, 0.0))),
|
||||
new Parallelogram(new Vec3(-2, -3, 5), new Vec3(4, 0, 0), new Vec3(0, 0, -4), new LambertianMaterial(new Color(0.2, 0.8, 0.8)))
|
||||
new Parallelogram(new Vec3(-3, -2, 5), new Vec3(0, 0, -4), new Vec3(0, 4, 0), new LambertianMaterial(new ColorRGB(1.0, 0.2, 0.2), cs)),
|
||||
new Parallelogram(new Vec3(-2, -2, 0), new Vec3(4, 0, 0), new Vec3(0, 4, 0), new LambertianMaterial(new ColorRGB(0.2, 1.0, 0.2), cs)),
|
||||
new Parallelogram(new Vec3(3, -2, 1), new Vec3(0, 0, 4), new Vec3(0, 4, 0), new LambertianMaterial(new ColorRGB(0.2, 0.2, 1.0), cs)),
|
||||
new Parallelogram(new Vec3(-2, 3, 1), new Vec3(4, 0, 0), new Vec3(0, 0, 4), new LambertianMaterial(new ColorRGB(1.0, 0.5, 0.0), cs)),
|
||||
new Parallelogram(new Vec3(-2, -3, 5), new Vec3(4, 0, 0), new Vec3(0, 0, -4), new LambertianMaterial(new ColorRGB(0.2, 0.8, 0.8), cs))
|
||||
)),
|
||||
SimpleCamera.builder()
|
||||
.withImage(height, height)
|
||||
@ -135,12 +145,13 @@ public class Examples {
|
||||
|
||||
public static @NotNull Example getLight(int height) {
|
||||
if (height <= 0) height = 225;
|
||||
var cs = ColorSpaces.sRGB;
|
||||
return new Example(
|
||||
new Scene(List.of(
|
||||
new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(new Color(0.2, 0.2, 0.2))),
|
||||
new Sphere(new Vec3(0, 2, 0), 2, new LambertianMaterial(new Color(0.2, 0.2, 0.2))),
|
||||
new Parallelogram(new Vec3(3, 1, -2), new Vec3(2, 0, 0), new Vec3(0, 2, 0), new DiffuseLight(new Color(4.0, 4.0, 4.0))),
|
||||
new Sphere(new Vec3(0, 7, 0), 2, new DiffuseLight(new Color(4.0, 4.0, 4.0)))
|
||||
new Sphere(new Vec3(0, -1000, 0), 1000, new LambertianMaterial(new ColorRGB(0.2, 0.2, 0.2), cs)),
|
||||
new Sphere(new Vec3(0, 2, 0), 2, new LambertianMaterial(new ColorRGB(0.2, 0.2, 0.2), cs)),
|
||||
new Parallelogram(new Vec3(3, 1, -2), new Vec3(2, 0, 0), new Vec3(0, 2, 0), new DiffuseLight(new ColorRGB(4.0, 4.0, 4.0), cs)),
|
||||
new Sphere(new Vec3(0, 7, 0), 2, new DiffuseLight(new ColorRGB(4.0, 4.0, 4.0), cs))
|
||||
)),
|
||||
SimpleCamera.builder()
|
||||
.withImage(height * 16 / 9, height)
|
||||
@ -154,10 +165,11 @@ public class Examples {
|
||||
public static @NotNull Example getCornellBox(int height) {
|
||||
if (height <= 0) height = 600;
|
||||
|
||||
var red = new LambertianMaterial(new Color(.65, .05, .05));
|
||||
var white = new LambertianMaterial(new Color(.73, .73, .73));
|
||||
var green = new LambertianMaterial(new Color(.12, .45, .15));
|
||||
var light = new DiffuseLight(new Color(15.0, 15.0, 15.0));
|
||||
var cs = ColorSpaces.sRGB;
|
||||
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs);
|
||||
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
|
||||
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs);
|
||||
var light = new DiffuseLight(new ColorRGB(15.0, 15.0, 15.0), cs);
|
||||
|
||||
return new Example(
|
||||
new Scene(List.of(
|
||||
@ -181,10 +193,11 @@ public class Examples {
|
||||
|
||||
public static @NotNull Example getCornellBoxSmoke(int height) {
|
||||
if (height <= 0) height = 600;
|
||||
var red = new LambertianMaterial(new Color(.65, .05, .05));
|
||||
var white = new LambertianMaterial(new Color(.73, .73, .73));
|
||||
var green = new LambertianMaterial(new Color(.12, .45, .15));
|
||||
var light = new DiffuseLight(new Color(7.0, 7.0, 7.0));
|
||||
var cs = ColorSpaces.sRGB;
|
||||
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs);
|
||||
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
|
||||
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs);
|
||||
var light = new DiffuseLight(new ColorRGB(15.0, 15.0, 15.0), cs);
|
||||
|
||||
return new Example(
|
||||
new Scene(List.of(
|
||||
@ -194,13 +207,13 @@ public class Examples {
|
||||
new Box(new Vec3(0, 0, 0), new Vec3(165, 330, 165), white)
|
||||
.rotateY(Math.toRadians(15))
|
||||
.translate(new Vec3(265, 0, 295)),
|
||||
0.01, new IsotropicMaterial(Color.BLACK)
|
||||
0.01, new IsotropicMaterial(ColorRGB.BLACK, cs)
|
||||
),
|
||||
new ConstantMedium(
|
||||
new Box(new Vec3(0, 0, 0), new Vec3(165, 165, 165), white)
|
||||
.rotateY(Math.toRadians(-18))
|
||||
.translate(new Vec3(130, 0, 65)),
|
||||
0.01, new IsotropicMaterial(Color.WHITE)
|
||||
0.01, new IsotropicMaterial(ColorRGB.WHITE, cs)
|
||||
)
|
||||
)),
|
||||
SimpleCamera.builder()
|
||||
@ -215,10 +228,11 @@ public class Examples {
|
||||
public static @NotNull Example getCornellBoxSphere(int height) {
|
||||
if (height <= 0) height = 600;
|
||||
|
||||
var red = new LambertianMaterial(new Color(.65, .05, .05));
|
||||
var white = new LambertianMaterial(new Color(.73, .73, .73));
|
||||
var green = new LambertianMaterial(new Color(.12, .45, .15));
|
||||
var light = new DiffuseLight(new Color(7.0, 7.0, 7.0));
|
||||
var cs = ColorSpaces.sRGB;
|
||||
var red = new LambertianMaterial(new ColorRGB(.65, .05, .05), cs);
|
||||
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
|
||||
var green = new LambertianMaterial(new ColorRGB(.12, .45, .15), cs);
|
||||
var light = new DiffuseLight(new ColorRGB(7.0, 7.0, 7.0), cs);
|
||||
var glass = new DielectricMaterial(1.5);
|
||||
|
||||
var room = new Box(new Vec3(0, 0, 0), new Vec3(555, 555, 555), white, white, red, green, white, null);
|
||||
@ -242,17 +256,18 @@ public class Examples {
|
||||
public static @NotNull Example getDiagramm(int height) {
|
||||
if (height <= 0) height = 450;
|
||||
|
||||
record Partei(String name, Color color, double stimmen) { }
|
||||
var cs = ColorSpaces.sRGB;
|
||||
record Partei(String name, ColorRGB color, double stimmen) { }
|
||||
var data = List.of(
|
||||
new Partei("CDU", new Color(0x00, 0x4B, 0x76), 18.9),
|
||||
new Partei("SPD", new Color(0xC0, 0x00, 0x3C), 25.7),
|
||||
new Partei("AfD", new Color(0x80, 0xCD, 0xEC), 10.3),
|
||||
new Partei("FDP", new Color(0xF7, 0xBB, 0x3D), 11.5),
|
||||
new Partei("DIE LINKE", new Color(0x5F, 0x31, 0x6E), 4.9),
|
||||
new Partei("GRÜNE", new Color(0x00, 0x85, 0x4A), 14.8),
|
||||
new Partei("CSU", new Color(0x00, 0x77, 0xB6), 5.2)
|
||||
new Partei("CDU", new ColorRGB(0x00, 0x4B, 0x76), 18.9),
|
||||
new Partei("SPD", new ColorRGB(0xC0, 0x00, 0x3C), 25.7),
|
||||
new Partei("AfD", new ColorRGB(0x80, 0xCD, 0xEC), 10.3),
|
||||
new Partei("FDP", new ColorRGB(0xF7, 0xBB, 0x3D), 11.5),
|
||||
new Partei("DIE LINKE", new ColorRGB(0x5F, 0x31, 0x6E), 4.9),
|
||||
new Partei("GRÜNE", new ColorRGB(0x00, 0x85, 0x4A), 14.8),
|
||||
new Partei("CSU", new ColorRGB(0x00, 0x77, 0xB6), 5.2)
|
||||
);
|
||||
var white = new LambertianMaterial(new Color(.99, .99, .99));
|
||||
var white = new LambertianMaterial(new ColorRGB(.99, .99, .99), cs);
|
||||
|
||||
var count = data.size();
|
||||
var size = 75d;
|
||||
@ -272,12 +287,12 @@ public class Examples {
|
||||
objects.add(new Box(
|
||||
new Vec3((i + 1) * spacing + i * size, 0, spacing),
|
||||
new Vec3((i + 1) * spacing + (i + 1) * size, partei.stimmen() * 15, spacing + size),
|
||||
new DielectricMaterial(1.5, partei.color())
|
||||
new DielectricMaterial(1.5, partei.color(), cs)
|
||||
));
|
||||
}
|
||||
|
||||
return new Example(
|
||||
new Scene(new Color(1.25, 1.25, 1.25), objects),
|
||||
new Scene(cs.illuminant().scale(1.25), objects),
|
||||
SimpleCamera.builder()
|
||||
.withImage(height * 16 / 9, height)
|
||||
.withPosition(new Vec3(700, 250, 800))
|
||||
@ -292,7 +307,7 @@ public class Examples {
|
||||
|
||||
return new Example(
|
||||
new Scene(getSkyBox(), List.of(
|
||||
new Sphere(Vec3.ZERO, 2, new LambertianMaterial(new ImageTexture("/eu/jonahbauer/raytracing/textures/earthmap.jpg")))
|
||||
new Sphere(Vec3.ZERO, 2, new LambertianMaterial(new ImageTexture("earthmap.jpg", ColorSpaces.sRGB)))
|
||||
)),
|
||||
SimpleCamera.builder()
|
||||
.withImage(height * 16 / 9, height)
|
||||
@ -325,12 +340,13 @@ public class Examples {
|
||||
public static @NotNull Example getFinal(int height) {
|
||||
if (height <= 0) height = 400;
|
||||
|
||||
var cs = ColorSpaces.sRGB;
|
||||
var objects = new ArrayList<Hittable>();
|
||||
var random = new Random(1);
|
||||
|
||||
// boxes
|
||||
var boxes = new ArrayList<Hittable>();
|
||||
var ground = new LambertianMaterial(new Color(0.48, 0.83, 0.53));
|
||||
var ground = new LambertianMaterial(new ColorRGB(0.48, 0.83, 0.53), cs);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 20; j++) {
|
||||
var w = 100.0;
|
||||
@ -348,31 +364,31 @@ public class Examples {
|
||||
// light
|
||||
objects.add(new Parallelogram(
|
||||
new Vec3(123, 554, 147), new Vec3(300, 0, 0), new Vec3(0, 0, 265),
|
||||
new DiffuseLight(new Color(7., 7., 7.))
|
||||
new DiffuseLight(new ColorRGB(7., 7., 7.), cs)
|
||||
));
|
||||
|
||||
// spheres with different materials
|
||||
objects.add(new Sphere(new Vec3(400, 400, 200), 50, new LambertianMaterial(new Color(0.7, 0.3, 0.1))));
|
||||
objects.add(new Sphere(new Vec3(400, 400, 200), 50, new LambertianMaterial(new ColorRGB(0.7, 0.3, 0.1), cs)));
|
||||
objects.add(new Sphere(new Vec3(260, 150, 45), 50, new DielectricMaterial(1.5)));
|
||||
objects.add(new Sphere(new Vec3(0, 150, 145), 50, new MetallicMaterial(new Color(0.8, 0.8, 0.9), 1.0)));
|
||||
objects.add(new Sphere(new Vec3(0, 150, 145), 50, new MetallicMaterial(new ColorRGB(0.8, 0.8, 0.9), cs, 1.0)));
|
||||
|
||||
// glass sphere filled with gas
|
||||
var boundary = new Sphere(new Vec3(360, 150, 145), 70, new DielectricMaterial(1.5));
|
||||
objects.add(boundary);
|
||||
objects.add(new ConstantMedium(boundary, 0.2, new IsotropicMaterial(new Color(0.2, 0.4, 0.9))));
|
||||
objects.add(new ConstantMedium(boundary, 0.2, new IsotropicMaterial(new ColorRGB(0.2, 0.4, 0.9), cs)));
|
||||
|
||||
// put the world in a glass sphere
|
||||
objects.add(new ConstantMedium(
|
||||
new Sphere(new Vec3(0, 0, 0), 5000, new DielectricMaterial(1.5)),
|
||||
0.0001, new IsotropicMaterial(new Color(1., 1., 1.))
|
||||
0.0001, new IsotropicMaterial(new ColorRGB(1., 1., 1.), cs)
|
||||
));
|
||||
|
||||
// textures spheres
|
||||
objects.add(new Sphere(new Vec3(400, 200, 400), 100, new LambertianMaterial(new ImageTexture("/eu/jonahbauer/raytracing/textures/earthmap.jpg"))));
|
||||
objects.add(new Sphere(new Vec3(400, 200, 400), 100, new LambertianMaterial(new ImageTexture("earthmap.jpg", cs))));
|
||||
objects.add(new Sphere(new Vec3(220, 280, 300), 80, new LambertianMaterial(new PerlinTexture(0.2))));
|
||||
|
||||
// box from spheres
|
||||
var white = new LambertianMaterial(new Color(.73, .73, .73));
|
||||
var white = new LambertianMaterial(new ColorRGB(.73, .73, .73), cs);
|
||||
var spheres = new ArrayList<Hittable>();
|
||||
for (int j = 0; j < 1000; j++) {
|
||||
spheres.add(new Sphere(new Vec3(random.nextDouble(165), random.nextDouble(165), random.nextDouble(165)), 10, white));
|
||||
@ -391,6 +407,9 @@ public class Examples {
|
||||
}
|
||||
|
||||
private static @NotNull SkyBox getSkyBox() {
|
||||
return SkyBox.gradient(new Color(0.5, 0.7, 1.0), Color.WHITE);
|
||||
return SkyBox.gradient(
|
||||
new RGBIlluminantSpectrum(ColorSpaces.sRGB, new ColorRGB(0.5, 0.7, 1.0)),
|
||||
ColorSpaces.sRGB.illuminant()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,12 @@ package eu.jonahbauer.raytracing;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.ImageFormat;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Image;
|
||||
import eu.jonahbauer.raytracing.render.canvas.LiveCanvas;
|
||||
import eu.jonahbauer.raytracing.render.canvas.XYZCanvas;
|
||||
import eu.jonahbauer.raytracing.render.renderer.SimpleRenderer;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -30,11 +33,11 @@ public class Main {
|
||||
|
||||
Canvas canvas;
|
||||
if (config.preview) {
|
||||
var image = new LiveCanvas(new Image(camera.getWidth(), camera.getHeight()));
|
||||
var image = new LiveCanvas(new XYZCanvas(camera.getWidth(), camera.getHeight()), ColorSpaces.sRGB);
|
||||
image.preview();
|
||||
canvas = image;
|
||||
} else {
|
||||
canvas = new Image(camera.getWidth(), camera.getHeight());
|
||||
canvas = new XYZCanvas(camera.getWidth(), camera.getHeight());
|
||||
}
|
||||
|
||||
long time = System.nanoTime();
|
||||
|
@ -1,13 +1,19 @@
|
||||
package eu.jonahbauer.raytracing.math;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||
public record Ray(@NotNull Vec3 origin, @NotNull Vec3 direction, @NotNull SampledWavelengths lambda) {
|
||||
public Ray {
|
||||
Objects.requireNonNull(origin, "origin");
|
||||
Objects.requireNonNull(direction, "direction");
|
||||
Objects.requireNonNull(lambda, "lambda");
|
||||
}
|
||||
|
||||
public Ray(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||
this(origin, direction, SampledWavelengths.EMPTY);
|
||||
}
|
||||
|
||||
public @NotNull Vec3 at(double t) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package eu.jonahbauer.raytracing.render;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Image;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpaces;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
@ -11,7 +11,6 @@ import java.nio.file.Path;
|
||||
import java.util.zip.CRC32;
|
||||
import java.util.zip.CheckedOutputStream;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
public enum ImageFormat {
|
||||
PPM {
|
||||
@ -24,9 +23,9 @@ public enum ImageFormat {
|
||||
writer.write(String.valueOf(image.getHeight()));
|
||||
writer.write("\n255\n");
|
||||
|
||||
var it = image.pixels().iterator();
|
||||
while (it.hasNext()) {
|
||||
var color = it.next();
|
||||
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()));
|
||||
@ -36,6 +35,7 @@ public enum ImageFormat {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
PNG {
|
||||
private static final byte[] MAGIC = new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
||||
@ -84,15 +84,16 @@ public enum ImageFormat {
|
||||
idat.writeInt(IDAT_TYPE);
|
||||
|
||||
try (var deflate = new DataOutputStream(new DeflaterOutputStream(idat))) {
|
||||
var pixels = image.pixels().iterator();
|
||||
for (int i = 0; pixels.hasNext(); i = (i + 1) % image.getWidth()) {
|
||||
if (i == 0) deflate.writeByte(0); // filter type
|
||||
var pixel = pixels.next();
|
||||
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
|
||||
|
@ -2,6 +2,7 @@ package eu.jonahbauer.raytracing.render.camera;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@ -93,7 +94,7 @@ public final class SimpleCamera implements Camera {
|
||||
|
||||
var origin = getRayOrigin(random);
|
||||
var target = getRayTarget(x, y, i, j, n, random);
|
||||
return new Ray(origin, target.minus(origin));
|
||||
return new Ray(origin, target.minus(origin), SampledWavelengths.uniform(random.nextDouble()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,22 +1,37 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface Canvas {
|
||||
/**
|
||||
* {@return the width of this canvas}
|
||||
*/
|
||||
int getWidth();
|
||||
|
||||
/**
|
||||
* {@return the height of this canvas}
|
||||
*/
|
||||
int getHeight();
|
||||
|
||||
void set(int x, int y, @NotNull Color color);
|
||||
@NotNull Color get(int x, int y);
|
||||
/**
|
||||
* Adds a sample to this canvas
|
||||
* @param x the pixel x coordinate
|
||||
* @param y the pixel y coordinate
|
||||
* @param n the index of the sample
|
||||
* @param spectrum the sampled spectrum
|
||||
* @param lambda the sampled wavelengths
|
||||
*/
|
||||
void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda);
|
||||
|
||||
default @NotNull Stream<Color> pixels() {
|
||||
return IntStream.range(0, getHeight())
|
||||
.mapToObj(y -> IntStream.range(0, getWidth()).mapToObj(x -> get(x, y)))
|
||||
.flatMap(Function.identity());
|
||||
}
|
||||
/**
|
||||
* {@return the color at a given pixel}
|
||||
* @param x the pixel x coordinate
|
||||
* @param y the pixel y coordinate
|
||||
* @param cs the color space of the output
|
||||
*/
|
||||
@NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs);
|
||||
}
|
||||
|
@ -1,58 +0,0 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class Image implements Canvas {
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
private final Color[][] data;
|
||||
|
||||
public Image(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
||||
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
||||
|
||||
this.data = new Color[height][width];
|
||||
}
|
||||
|
||||
public Image(@NotNull BufferedImage image) {
|
||||
this(image.getWidth(), image.getHeight());
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
this.data[y][x] = new Color(image.getRGB(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color get(int x, int y) {
|
||||
Objects.checkIndex(x, width);
|
||||
Objects.checkIndex(y, height);
|
||||
return Objects.requireNonNullElse(this.data[y][x], Color.BLACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int x, int y, @NotNull Color color) {
|
||||
Objects.checkIndex(x, width);
|
||||
Objects.checkIndex(y, height);
|
||||
this.data[y][x] = Objects.requireNonNull(color);
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
@ -12,10 +15,12 @@ import java.awt.image.BufferedImage;
|
||||
public final class LiveCanvas implements Canvas {
|
||||
private final @NotNull Canvas delegate;
|
||||
private final @NotNull BufferedImage image;
|
||||
private final @NotNull ColorSpace cs;
|
||||
|
||||
public LiveCanvas(@NotNull Canvas delegate) {
|
||||
public LiveCanvas(@NotNull Canvas delegate, @NotNull ColorSpace cs) {
|
||||
this.delegate = delegate;
|
||||
this.image = new BufferedImage(delegate.getWidth(), delegate.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||
this.cs = cs;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -29,15 +34,16 @@ public final class LiveCanvas implements Canvas {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int x, int y, @NotNull Color color) {
|
||||
delegate.set(x, y, color);
|
||||
public void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda) {
|
||||
delegate.add(x, y, n, spectrum, lambda);
|
||||
var color = ColorRGB.gamma(delegate.getRGB(x, y, cs));
|
||||
var rgb = color.red() << 16 | color.green() << 8 | color.blue();
|
||||
image.setRGB(x, y, rgb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color get(int x, int y) {
|
||||
return delegate.get(x, y);
|
||||
public @NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs) {
|
||||
return delegate.getRGB(x, y, cs);
|
||||
}
|
||||
|
||||
public @NotNull Thread preview() {
|
||||
|
@ -0,0 +1,76 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class RGBCanvas implements Canvas {
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
private final @NotNull ColorSpace cs;
|
||||
private final @NotNull ColorRGB[][] data;
|
||||
|
||||
public RGBCanvas(int width, int height, @NotNull ColorSpace cs) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.cs = Objects.requireNonNull(cs);
|
||||
|
||||
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
||||
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
||||
|
||||
this.data = new ColorRGB[height][width];
|
||||
}
|
||||
|
||||
public RGBCanvas(@NotNull BufferedImage image, @NotNull ColorSpace cs) {
|
||||
this(image.getWidth(), image.getHeight(), cs);
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
this.data[y][x] = new ColorRGB(image.getRGB(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda) {
|
||||
assert x < width;
|
||||
assert y < height;
|
||||
|
||||
var rgb = spectrum.toRGB(lambda, cs);
|
||||
data[y][x] = ColorRGB.average(data[y][x], rgb, n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs) {
|
||||
if (cs == this.cs) return get(x, y);
|
||||
return cs.toRGB(this.cs.toXYZ(get(x, y)));
|
||||
}
|
||||
|
||||
public @NotNull ColorRGB get(int x, int y) {
|
||||
assert x < width;
|
||||
assert y < height;
|
||||
return Objects.requireNonNullElse(data[y][x], ColorRGB.BLACK);
|
||||
}
|
||||
|
||||
public void set(int x, int y, @NotNull ColorRGB color) {
|
||||
assert x < width;
|
||||
assert y < height;
|
||||
data[y][x] = Objects.requireNonNull(color);
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class XYZCanvas implements Canvas {
|
||||
private final int width;
|
||||
private final int height;
|
||||
|
||||
private final @NotNull ColorXYZ[][] data;
|
||||
|
||||
public XYZCanvas(int width, int height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
|
||||
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
||||
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
||||
|
||||
this.data = new ColorXYZ[height][width];
|
||||
}
|
||||
|
||||
public XYZCanvas(@NotNull BufferedImage image, @NotNull ColorSpace cs) {
|
||||
this(image.getWidth(), image.getHeight());
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
data[y][x] = cs.toXYZ(new ColorRGB(image.getRGB(x, y)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int x, int y, int n, @NotNull SampledSpectrum spectrum, @NotNull SampledWavelengths lambda) {
|
||||
assert x < width;
|
||||
assert y < height;
|
||||
|
||||
var xyz = spectrum.toXYZ(lambda);
|
||||
data[y][x] = ColorXYZ.average(get(x, y), xyz, n);
|
||||
}
|
||||
|
||||
public @NotNull ColorXYZ get(int x, int y) {
|
||||
assert x < width;
|
||||
assert y < height;
|
||||
return Objects.requireNonNullElse(data[y][x], ColorXYZ.BLACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ColorRGB getRGB(int x, int y, @NotNull ColorSpace cs) {
|
||||
return cs.toRGB(get(x, y));
|
||||
}
|
||||
|
||||
public void set(int x, int y, @NotNull ColorXYZ color) {
|
||||
assert x < width;
|
||||
assert y < height;
|
||||
data[y][x] = Objects.requireNonNull(color);
|
||||
}
|
||||
}
|
@ -2,7 +2,10 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -13,13 +16,17 @@ import java.util.random.RandomGenerator;
|
||||
|
||||
public record DielectricMaterial(double refractionIndex, @NotNull Texture texture) implements Material {
|
||||
public DielectricMaterial(double refractionIndex) {
|
||||
this(refractionIndex, Color.WHITE);
|
||||
this(refractionIndex, Spectra.WHITE);
|
||||
}
|
||||
|
||||
public DielectricMaterial {
|
||||
Objects.requireNonNull(texture, "texture");
|
||||
}
|
||||
|
||||
public DielectricMaterial(double refractionIndex, @NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(refractionIndex, new RGBAlbedoSpectrum(cs, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
var ri = hit.isFrontFace() ? (1 / refractionIndex) : refractionIndex;
|
||||
@ -32,7 +39,7 @@ public record DielectricMaterial(double refractionIndex, @NotNull Texture textur
|
||||
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
|
||||
|
||||
var attenuation = texture.get(hit);
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection)));
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
|
||||
}
|
||||
|
||||
private double reflectance(double cos) {
|
||||
|
@ -1,22 +1,34 @@
|
||||
package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public record DiffuseLight(@NotNull Texture texture) implements Material {
|
||||
public DiffuseLight {
|
||||
Objects.requireNonNull(texture, "texture");
|
||||
}
|
||||
|
||||
public DiffuseLight(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(new RGBIlluminantSpectrum(cs, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color emitted(@NotNull HitResult hit) {
|
||||
public @NotNull Spectrum emitted(@NotNull HitResult hit) {
|
||||
return texture.get(hit);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,8 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -40,11 +41,11 @@ public final class DirectionalMaterial implements Material {
|
||||
if (back != null) return back.scatter(ray, hit, random);
|
||||
}
|
||||
// let the ray pass through without obstruction
|
||||
return Optional.of(new SpecularScatterResult(Color.WHITE, new Ray(ray.at(hit.t()), ray.direction())));
|
||||
return Optional.of(new SpecularScatterResult(Spectra.WHITE, new Ray(ray.at(hit.t()), ray.direction(), ray.lambda())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color emitted(@NotNull HitResult hit) {
|
||||
public @NotNull Spectrum emitted(@NotNull HitResult hit) {
|
||||
if (hit.isFrontFace()) {
|
||||
if (front != null) return front.emitted(hit);
|
||||
} else {
|
||||
@ -56,7 +57,7 @@ public final class DirectionalMaterial implements Material {
|
||||
private record DirectionalTexture(@Nullable Texture front, @Nullable Texture back) implements Texture {
|
||||
|
||||
@Override
|
||||
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
|
||||
public @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
@ -2,15 +2,27 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.SphereProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public record IsotropicMaterial(@NotNull Color albedo) implements Material {
|
||||
public record IsotropicMaterial(@NotNull Spectrum albedo) implements Material {
|
||||
public IsotropicMaterial {
|
||||
Objects.requireNonNull(albedo, "albedo");
|
||||
}
|
||||
|
||||
public IsotropicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(new RGBAlbedoSpectrum(cs, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
return Optional.of(new PdfScatterResult(albedo(), new SphereProbabilityDensityFunction()));
|
||||
|
@ -2,6 +2,9 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.CosineProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -15,6 +18,10 @@ public record LambertianMaterial(@NotNull Texture texture) implements Material {
|
||||
Objects.requireNonNull(texture, "texture");
|
||||
}
|
||||
|
||||
public LambertianMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(new RGBAlbedoSpectrum(cs, color));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
var attenuation = texture.get(hit);
|
||||
|
@ -2,7 +2,9 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.ProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -30,10 +32,10 @@ public interface Material {
|
||||
|
||||
/**
|
||||
* {@return the color emitted for a given hit}
|
||||
* @implSpec the default implementation returns {@linkplain Color#BLACK black}, i.e. no emission
|
||||
* @implSpec the default implementation returns {@linkplain ColorRGB#BLACK black}, i.e. no emission
|
||||
*/
|
||||
default @NotNull Color emitted(@NotNull HitResult hit) {
|
||||
return Color.BLACK;
|
||||
default @NotNull Spectrum emitted(@NotNull HitResult hit) {
|
||||
return Spectra.BLACK;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,7 +50,7 @@ public interface Material {
|
||||
* @param attenuation the attenuation of the scattered light ray
|
||||
* @param ray the scattered light ray
|
||||
*/
|
||||
record SpecularScatterResult(@NotNull Color attenuation, @NotNull Ray ray) implements ScatterResult {
|
||||
record SpecularScatterResult(@NotNull Spectrum attenuation, @NotNull Ray ray) implements ScatterResult {
|
||||
public SpecularScatterResult {
|
||||
Objects.requireNonNull(attenuation, "attenuation");
|
||||
Objects.requireNonNull(ray, "ray");
|
||||
@ -62,7 +64,7 @@ public interface Material {
|
||||
* @param attenuation the attenuation of the scattered light ray
|
||||
* @param pdf the probability density function
|
||||
*/
|
||||
record PdfScatterResult(@NotNull Color attenuation, @NotNull ProbabilityDensityFunction pdf) implements ScatterResult {
|
||||
record PdfScatterResult(@NotNull Spectrum attenuation, @NotNull ProbabilityDensityFunction pdf) implements ScatterResult {
|
||||
public PdfScatterResult {
|
||||
Objects.requireNonNull(attenuation, "attenuation");
|
||||
Objects.requireNonNull(pdf, "pdf");
|
||||
|
@ -2,6 +2,9 @@ package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -16,6 +19,14 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
|
||||
this(texture, 0);
|
||||
}
|
||||
|
||||
public MetallicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs) {
|
||||
this(color, cs, 0);
|
||||
}
|
||||
|
||||
public MetallicMaterial(@NotNull ColorRGB color, @NotNull ColorSpace cs, double fuzz) {
|
||||
this(new RGBAlbedoSpectrum(cs, color), fuzz);
|
||||
}
|
||||
|
||||
public MetallicMaterial {
|
||||
Objects.requireNonNull(texture, "texture");
|
||||
if (fuzz < 0 || !Double.isFinite(fuzz)) throw new IllegalArgumentException("fuzz must be non-negative");
|
||||
@ -28,6 +39,6 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
|
||||
newDirection = Vec3.fma(fuzz, Vec3.random(random), newDirection.unit());
|
||||
}
|
||||
var attenuation = texture.get(hit);
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection)));
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection, ray.lambda())));
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ package eu.jonahbauer.raytracing.render.renderer;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.camera.Camera;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Image;
|
||||
import eu.jonahbauer.raytracing.render.canvas.XYZCanvas;
|
||||
import eu.jonahbauer.raytracing.scene.Scene;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface Renderer {
|
||||
default @NotNull Image render(@NotNull Camera camera, @NotNull Scene scene) {
|
||||
var image = new Image(camera.getWidth(), camera.getHeight());
|
||||
default @NotNull Canvas render(@NotNull Camera camera, @NotNull Scene scene) {
|
||||
var image = new XYZCanvas(camera.getWidth(), camera.getHeight());
|
||||
render(camera, scene, image);
|
||||
return image;
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.material.Material;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.camera.Camera;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||
import eu.jonahbauer.raytracing.scene.Scene;
|
||||
@ -76,18 +76,11 @@ public final class SimpleRenderer implements Renderer {
|
||||
for (int x = 0; x < camera.getWidth(); x++) {
|
||||
var ray = camera.cast(x, y, sif, sjf, sqrtSamplesPerPixel, random);
|
||||
var c = getColor(scene, ray, random);
|
||||
canvas.set(x, y, Color.average(canvas.get(x, y), c, sample));
|
||||
canvas.add(x, y, sample, c, ray.lambda());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// apply gamma correction
|
||||
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
|
||||
for (int x = 0; x < camera.getWidth(); x++) {
|
||||
canvas.set(x, y, Color.gamma(canvas.get(x, y), gamma));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +93,6 @@ public final class SimpleRenderer implements Renderer {
|
||||
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
|
||||
var random = splittable.split();
|
||||
for (int x = 0; x < camera.getWidth(); x++) {
|
||||
var color = Color.BLACK;
|
||||
int i = 0;
|
||||
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
|
||||
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
|
||||
@ -109,10 +101,9 @@ public final class SimpleRenderer implements Renderer {
|
||||
System.out.println("Casting ray " + ray + " through pixel (" + x + "," + y + ") at subpixel (" + si + "," + sj + ")...");
|
||||
}
|
||||
var c = getColor(scene, ray, random);
|
||||
color = Color.average(color, c, ++i);
|
||||
canvas.add(x, y, ++i, c, ray.lambda());
|
||||
}
|
||||
}
|
||||
canvas.set(x, y, Color.gamma(color, gamma));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -120,19 +111,19 @@ public final class SimpleRenderer implements Renderer {
|
||||
/**
|
||||
* {@return the color of the given ray in the given scene}
|
||||
*/
|
||||
private @NotNull Color getColor(@NotNull Scene scene, @NotNull Ray ray, @NotNull RandomGenerator random) {
|
||||
private @NotNull SampledSpectrum getColor(@NotNull Scene scene, @NotNull Ray ray, @NotNull RandomGenerator random) {
|
||||
return getColor0(scene, ray, maxDepth, random);
|
||||
}
|
||||
|
||||
private @NotNull Color getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
|
||||
var color = Color.BLACK;
|
||||
var attenuation = Color.WHITE;
|
||||
private @NotNull SampledSpectrum getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
|
||||
var color = SampledSpectrum.BLACK;
|
||||
var attenuation = SampledSpectrum.WHITE;
|
||||
|
||||
while (depth-- > 0) {
|
||||
var optional = scene.hit(ray);
|
||||
if (optional.isEmpty()) {
|
||||
var background = scene.getBackgroundColor(ray);
|
||||
color = Color.fma(attenuation, background, color);
|
||||
color = SampledSpectrum.fma(attenuation, background, color);
|
||||
if (DEBUG) {
|
||||
System.out.println(" Hit background: " + background);
|
||||
}
|
||||
@ -144,13 +135,13 @@ public final class SimpleRenderer implements Renderer {
|
||||
System.out.println(" Hit " + hit.target() + " at t=" + hit.t() + " (" + hit.position() + ")");
|
||||
}
|
||||
var material = hit.material();
|
||||
var emitted = material.emitted(hit);
|
||||
if (DEBUG && !Color.BLACK.equals(emitted)) {
|
||||
var emitted = material.emitted(hit).sample(ray.lambda());
|
||||
if (DEBUG && !SampledSpectrum.BLACK.equals(emitted)) {
|
||||
System.out.println(" Emitted: " + emitted);
|
||||
}
|
||||
|
||||
var result = material.scatter(ray, hit, random);
|
||||
color = Color.fma(attenuation, emitted, color);
|
||||
color = SampledSpectrum.fma(attenuation, emitted, color);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
if (DEBUG) {
|
||||
@ -161,7 +152,7 @@ public final class SimpleRenderer implements Renderer {
|
||||
|
||||
switch (result.get()) {
|
||||
case Material.SpecularScatterResult(var a, var scattered) -> {
|
||||
attenuation = attenuation.times(a);
|
||||
attenuation = attenuation.times(a.sample(ray.lambda()));
|
||||
ray = scattered;
|
||||
|
||||
if (DEBUG) {
|
||||
@ -170,8 +161,8 @@ public final class SimpleRenderer implements Renderer {
|
||||
}
|
||||
case Material.PdfScatterResult(var a, var pdf) -> {
|
||||
if (scene.getTargets() == null) {
|
||||
attenuation = attenuation.times(a);
|
||||
ray = new Ray(hit.position(), pdf.generate(random));
|
||||
attenuation = attenuation.times(a.sample(ray.lambda()));
|
||||
ray = new Ray(hit.position(), pdf.generate(random), ray.lambda());
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println(" Pdf scattering with albedo " + a);
|
||||
@ -186,8 +177,8 @@ public final class SimpleRenderer implements Renderer {
|
||||
|
||||
var factor = idealPdf / actualPdf;
|
||||
|
||||
attenuation = attenuation.times(a.times(factor));
|
||||
ray = new Ray(hit.position(), direction);
|
||||
attenuation = attenuation.times(a.sample(ray.lambda()).times(factor));
|
||||
ray = new Ray(hit.position(), direction, ray.lambda());
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println(" Pdf scattering with albedo " + a + " and factor " + factor);
|
||||
@ -215,7 +206,7 @@ public final class SimpleRenderer implements Renderer {
|
||||
* are encoded in the longs lower and upper 32 bits respectively.
|
||||
*/
|
||||
private static @NotNull IntStream getScanlineStream(int height, boolean parallel) {
|
||||
var stream = IntStream.range(0, height);
|
||||
var stream = IntStream.range(0, height).map(i -> height - i - 1);
|
||||
return parallel ? stream.parallel() : stream;
|
||||
}
|
||||
|
||||
|
@ -4,11 +4,12 @@ import eu.jonahbauer.raytracing.math.IVec;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO use Vector API to parallelize operations
|
||||
public final class SampledSpectrum implements IVec<SampledSpectrum> {
|
||||
public static final SampledSpectrum BLACK;
|
||||
public static final SampledSpectrum WHITE;
|
||||
@ -137,7 +138,7 @@ public final class SampledSpectrum implements IVec<SampledSpectrum> {
|
||||
return lambdas.toXYZ(this);
|
||||
}
|
||||
|
||||
public @NotNull Color toRGB(@NotNull SampledWavelengths lambdas, @NotNull ColorSpace cs) {
|
||||
public @NotNull ColorRGB toRGB(@NotNull SampledWavelengths lambdas, @NotNull ColorSpace cs) {
|
||||
return cs.toRGB(toXYZ(lambdas));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,153 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.colors;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.IVec3;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static eu.jonahbauer.raytracing.Main.DEBUG;
|
||||
|
||||
public record ColorRGB(double r, double g, double b) implements IVec3<ColorRGB> {
|
||||
public static final @NotNull ColorRGB BLACK = new ColorRGB(0.0, 0.0, 0.0);
|
||||
public static final @NotNull ColorRGB WHITE = new ColorRGB(1.0, 1.0, 1.0);
|
||||
|
||||
public static @NotNull ColorRGB random(@NotNull Random random) {
|
||||
return new ColorRGB(random.nextDouble(), random.nextDouble(), random.nextDouble());
|
||||
}
|
||||
|
||||
public static @NotNull ColorRGB random(@NotNull Random random, double min, double max) {
|
||||
var span = max - min;
|
||||
return new ColorRGB(
|
||||
Math.fma(random.nextDouble(), span, min),
|
||||
Math.fma(random.nextDouble(), span, min),
|
||||
Math.fma(random.nextDouble(), span, min)
|
||||
);
|
||||
}
|
||||
|
||||
public ColorRGB(int rgb) {
|
||||
this((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
|
||||
}
|
||||
|
||||
public ColorRGB(int red, int green, int blue) {
|
||||
this(red / 255f, green / 255f, blue / 255f);
|
||||
}
|
||||
|
||||
public ColorRGB {
|
||||
if (DEBUG && (!Double.isFinite(r) || !Double.isFinite(g) || !Double.isFinite(b))) {
|
||||
throw new IllegalArgumentException("r, g and b must be finite");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Math
|
||||
*/
|
||||
|
||||
public static @NotNull ColorRGB average(@NotNull ColorRGB current, @NotNull ColorRGB next, int index) {
|
||||
return lerp(current, next, 1d / index);
|
||||
}
|
||||
|
||||
public static @NotNull ColorRGB lerp(@NotNull ColorRGB a, @NotNull ColorRGB b, double t) {
|
||||
if (t < 0) return a;
|
||||
if (t > 1) return b;
|
||||
return new ColorRGB(
|
||||
Math.fma(t, b.r - a.r, a.r),
|
||||
Math.fma(t, b.g - a.g, a.g),
|
||||
Math.fma(t, b.b - a.b, a.b)
|
||||
);
|
||||
}
|
||||
|
||||
public static @NotNull ColorRGB fma(@NotNull ColorRGB a, @NotNull ColorRGB b, @NotNull ColorRGB c) {
|
||||
return new ColorRGB(
|
||||
Math.fma(a.r, b.r, c.r),
|
||||
Math.fma(a.g, b.g, c.g),
|
||||
Math.fma(a.b, b.b, c.b)
|
||||
);
|
||||
}
|
||||
|
||||
public static @NotNull ColorRGB gamma(@NotNull ColorRGB color) {
|
||||
return new ColorRGB(gamma(color.r), gamma(color.g), gamma(color.b));
|
||||
}
|
||||
|
||||
public static @NotNull ColorRGB inverseGamma(@NotNull ColorRGB color) {
|
||||
return new ColorRGB(inverseGamma(color.r), inverseGamma(color.g), inverseGamma(color.b));
|
||||
}
|
||||
|
||||
private static double gamma(double value) {
|
||||
if (value <= 0.0031308) return 12.92 * value;
|
||||
return 1.055 * Math.pow(value, 1. / 2.4) - 0.055;
|
||||
}
|
||||
|
||||
private static double inverseGamma(double value) {
|
||||
if (value <= 0.04045) return value / 12.92;
|
||||
return Math.pow((value + 0.055) / 1.055, 2.4d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ColorRGB plus(@NotNull ColorRGB other) {
|
||||
return new ColorRGB(r + other.r, g + other.g, b + other.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ColorRGB minus(@NotNull ColorRGB other) {
|
||||
return new ColorRGB(r - other.r, g - other.g, b - other.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ColorRGB times(double d) {
|
||||
return new ColorRGB(r * d, g * d, b * d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ColorRGB times(@NotNull ColorRGB other) {
|
||||
return new ColorRGB(r * other.r, g * other.g, b * other.b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Vec3
|
||||
*/
|
||||
|
||||
@Override
|
||||
public @NotNull Vec3 toVec3() {
|
||||
return new Vec3(r, g, b);
|
||||
}
|
||||
|
||||
public static @NotNull ColorRGB fromVec3(@NotNull Vec3 vec) {
|
||||
return new ColorRGB(vec.x(), vec.y(), vec.z());
|
||||
}
|
||||
|
||||
/*
|
||||
* Accessors
|
||||
*/
|
||||
|
||||
public int red() {
|
||||
return toInt(r);
|
||||
}
|
||||
|
||||
public int green() {
|
||||
return toInt(g);
|
||||
}
|
||||
|
||||
public int blue() {
|
||||
return toInt(b);
|
||||
}
|
||||
|
||||
private static int toInt(double value) {
|
||||
return Math.clamp((int) (255.99 * value), 0, 255);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double component1() {
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double component2() {
|
||||
return g;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double component3() {
|
||||
return b;
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ 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;
|
||||
@ -55,17 +54,17 @@ public final class ColorSpace {
|
||||
this.RGBfromXYZ = XYZfromRGB.invert();
|
||||
}
|
||||
|
||||
public @NotNull Color toRGB(@NotNull ColorXYZ xyz) {
|
||||
public @NotNull ColorRGB toRGB(@NotNull ColorXYZ xyz) {
|
||||
var out = RGBfromXYZ.times(xyz.toVec3());
|
||||
return new Color(out.x(), out.y(), out.z());
|
||||
return new ColorRGB(out.x(), out.y(), out.z());
|
||||
}
|
||||
|
||||
public @NotNull ColorXYZ toXYZ(@NotNull Color rgb) {
|
||||
public @NotNull ColorXYZ toXYZ(@NotNull ColorRGB rgb) {
|
||||
var out = XYZfromRGB.times(rgb.toVec3());
|
||||
return ColorXYZ.fromVec3(out);
|
||||
}
|
||||
|
||||
public @NotNull Vec3 toCIELab(@NotNull Color rgb) {
|
||||
public @NotNull Vec3 toCIELab(@NotNull ColorRGB rgb) {
|
||||
return toCIELab(toXYZ(rgb));
|
||||
}
|
||||
|
||||
@ -86,8 +85,8 @@ public final class ColorSpace {
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull SigmoidPolynomial toSpectrum(@NotNull Color rgb) {
|
||||
return RGBtoSpectrumTable.get(new Color(
|
||||
public @NotNull SigmoidPolynomial toSpectrum(@NotNull ColorRGB rgb) {
|
||||
return RGBtoSpectrumTable.get(new ColorRGB(
|
||||
Math.max(0, rgb.r()),
|
||||
Math.max(0, rgb.g()),
|
||||
Math.max(0, rgb.b())
|
||||
|
@ -1,6 +1,5 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.colors;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.*;
|
||||
@ -86,7 +85,7 @@ public final class SpectrumTable {
|
||||
}
|
||||
}
|
||||
|
||||
public @NotNull SigmoidPolynomial get(@NotNull Color color) {
|
||||
public @NotNull SigmoidPolynomial get(@NotNull ColorRGB color) {
|
||||
// handle uniform rgb values
|
||||
if (color.r() == color.g() && color.g() == color.b()) {
|
||||
return new SigmoidPolynomial(0, 0, (color.r() - .5) / Math.sqrt(color.r() * (1 - color.r())));
|
||||
|
@ -3,7 +3,6 @@ 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;
|
||||
@ -81,7 +80,7 @@ public final class SpectrumTableGenerator {
|
||||
return new SpectrumTable(resolution, scale, table);
|
||||
}
|
||||
|
||||
private void generate(@NotNull Color rgb, double @NotNull[] c, double @NotNull[] out, int offset) {
|
||||
private void generate(@NotNull ColorRGB rgb, double @NotNull[] c, double @NotNull[] out, int offset) {
|
||||
gaussNewton(rgb, c, ITERATIONS);
|
||||
double c0 = 360.0, c1 = 1.0 / (830.0 - 360.0);
|
||||
double A = c[0], B = c[1], C = c[2];
|
||||
@ -97,7 +96,7 @@ public final class SpectrumTableGenerator {
|
||||
* @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) {
|
||||
private void gaussNewton(@NotNull ColorRGB rgb, double @NotNull[] c, int it) {
|
||||
var bestQuality = Double.POSITIVE_INFINITY;
|
||||
var bestCoefficients = new double[3];
|
||||
|
||||
@ -134,7 +133,7 @@ public final class SpectrumTableGenerator {
|
||||
/**
|
||||
* Calculates the Jacobian matrix of the {@code polynomial}.
|
||||
*/
|
||||
private @NotNull Matrix3 getJacobian(@NotNull Color rgb, @NotNull SigmoidPolynomial polynomial) {
|
||||
private @NotNull Matrix3 getJacobian(@NotNull ColorRGB rgb, @NotNull SigmoidPolynomial polynomial) {
|
||||
var jac = new double[3][3];
|
||||
|
||||
// central finite difference coefficients for first derivative with sixth-order accuracy
|
||||
@ -171,7 +170,7 @@ public final class SpectrumTableGenerator {
|
||||
* 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) {
|
||||
private @NotNull Vec3 getResidual(@NotNull ColorRGB rgb, @NotNull SigmoidPolynomial polynomial) {
|
||||
var out = new SigmoidPolynomialSpectrum(polynomial, cs).toXYZ();
|
||||
return cs.toCIELab(rgb).minus(cs.toCIELab(out));
|
||||
}
|
||||
@ -181,12 +180,12 @@ public final class SpectrumTableGenerator {
|
||||
return x * x * (3.0 - 2.0 * x);
|
||||
}
|
||||
|
||||
private static @NotNull Color getColor(int l, double x, double y, double z) {
|
||||
private static @NotNull ColorRGB getColor(int l, double x, double y, double z) {
|
||||
var rgb = new double[3];
|
||||
rgb[l] = z;
|
||||
rgb[(l + 1) % 3] = x * z;
|
||||
rgb[(l + 2) % 3] = y * z;
|
||||
return new Color(rgb[0], rgb[1], rgb[2]);
|
||||
return new ColorRGB(rgb[0], rgb[1], rgb[2]);
|
||||
}
|
||||
|
||||
private record SigmoidPolynomialSpectrum(@NotNull SigmoidPolynomial polynomial, @NotNull ColorSpace cs) implements Spectrum {
|
||||
|
@ -37,14 +37,6 @@ public final class DenselySampledSpectrum implements Spectrum {
|
||||
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;
|
||||
|
@ -1,7 +1,5 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class PiecewiseLinearSpectrum implements Spectrum {
|
||||
@ -31,14 +29,6 @@ public final class PiecewiseLinearSpectrum implements Spectrum {
|
||||
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;
|
||||
|
@ -2,13 +2,13 @@ package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class RGBAlbedoSpectrum implements Spectrum {
|
||||
private final @NotNull SigmoidPolynomial polynomial;
|
||||
|
||||
public RGBAlbedoSpectrum(@NotNull ColorSpace cs, @NotNull Color rgb) {
|
||||
public RGBAlbedoSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
||||
if (rgb.r() < 0 || rgb.r() > 1 || rgb.g() < 0 || rgb.g() > 1 || rgb.b() < 0 || rgb.b() > 1) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@ -14,13 +14,13 @@ public final class RGBIlluminantSpectrum implements Spectrum {
|
||||
private final @NotNull SigmoidPolynomial polynomial;
|
||||
private final @NotNull Spectrum illuminant;
|
||||
|
||||
public RGBIlluminantSpectrum(@NotNull ColorSpace cs, @NotNull Color rgb) {
|
||||
public RGBIlluminantSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
||||
if (rgb.r() < 0 || rgb.g() < 0 || rgb.b() < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
|
||||
this.scale = 2 * max;
|
||||
this.polynomial = cs.toSpectrum(scale == 0 ? rgb.div(scale) : Color.BLACK);
|
||||
this.polynomial = cs.toSpectrum(scale != 0 ? rgb.div(scale) : ColorRGB.BLACK);
|
||||
this.illuminant = cs.illuminant();
|
||||
}
|
||||
|
||||
|
@ -2,20 +2,20 @@ package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.SigmoidPolynomial;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class RGBUnboundedSpectrum implements Spectrum {
|
||||
private final double scale;
|
||||
private final @NotNull SigmoidPolynomial polynomial;
|
||||
|
||||
public RGBUnboundedSpectrum(@NotNull ColorSpace cs, @NotNull Color rgb) {
|
||||
public RGBUnboundedSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
||||
if (rgb.r() < 0 || rgb.g() < 0 || rgb.b() < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
var max = Math.max(rgb.r(), Math.max(rgb.g(), rgb.b()));
|
||||
this.scale = 2 * max;
|
||||
this.polynomial = cs.toSpectrum(scale == 0 ? rgb.div(scale) : Color.BLACK);
|
||||
this.polynomial = cs.toSpectrum(scale == 0 ? rgb.div(scale) : ColorRGB.BLACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,15 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record ScaledSpectrum(@NotNull Spectrum spectrum, double scale) implements Spectrum {
|
||||
@Override
|
||||
public double max() {
|
||||
return spectrum.max() * scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double get(double lambda) {
|
||||
return spectrum.get(lambda) * scale;
|
||||
}
|
||||
}
|
@ -36,6 +36,9 @@ public final class Spectra {
|
||||
*/
|
||||
public static final Spectrum D65 = read("CIE_std_illum_D65.csv", true);
|
||||
|
||||
public static final Spectrum BLACK = new ConstantSpectrum(0);
|
||||
public static final Spectrum WHITE = new ConstantSpectrum(1);
|
||||
|
||||
private static @NotNull Spectrum read(@NotNull String path, boolean normalize) {
|
||||
var lambda = new ArrayList<Double>();
|
||||
var values = new ArrayList<Double>();
|
||||
|
@ -1,13 +1,18 @@
|
||||
package eu.jonahbauer.raytracing.render.spectral.spectrum;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorXYZ;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledWavelengths;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import eu.jonahbauer.raytracing.scene.SkyBox;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface Spectrum {
|
||||
public interface Spectrum extends Texture, SkyBox {
|
||||
int LAMBDA_MIN = 360;
|
||||
int LAMBDA_MAX = 830;
|
||||
|
||||
@ -22,6 +27,10 @@ public interface Spectrum {
|
||||
*/
|
||||
double get(double lambda);
|
||||
|
||||
default @NotNull Spectrum scale(double scale) {
|
||||
return new ScaledSpectrum(this, scale);
|
||||
}
|
||||
|
||||
default @NotNull SampledSpectrum sample(@NotNull SampledWavelengths lambdas) {
|
||||
return new SampledSpectrum(lambdas, this);
|
||||
}
|
||||
@ -34,8 +43,35 @@ public interface Spectrum {
|
||||
);
|
||||
}
|
||||
|
||||
default @NotNull Color toRGB(@NotNull ColorSpace cs) {
|
||||
default @NotNull ColorRGB toRGB(@NotNull ColorSpace cs) {
|
||||
return cs.toRGB(toXYZ());
|
||||
}
|
||||
|
||||
/*
|
||||
* Texture
|
||||
*/
|
||||
|
||||
@Override
|
||||
default @NotNull Spectrum get(@NotNull HitResult hit) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean isUVRequired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* SkyBox
|
||||
*/
|
||||
|
||||
@Override
|
||||
default @NotNull SampledSpectrum getColor(@NotNull Ray ray) {
|
||||
return this.sample(ray.lambda());
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
package eu.jonahbauer.raytracing.render.texture;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record CheckerTexture(double scale, @NotNull Texture even, @NotNull Texture odd) implements Texture {
|
||||
|
||||
@Override
|
||||
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
|
||||
public @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
|
||||
var x = (int) Math.floor(p.x() / scale);
|
||||
var y = (int) Math.floor(p.y() / scale);
|
||||
var z = (int) Math.floor(p.z() / scale);
|
||||
|
@ -1,178 +0,0 @@
|
||||
package eu.jonahbauer.raytracing.render.texture;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.IVec3;
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.scene.SkyBox;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static eu.jonahbauer.raytracing.Main.DEBUG;
|
||||
|
||||
public record Color(double r, double g, double b) implements Texture, SkyBox, IVec3<Color> {
|
||||
public static final @NotNull Color BLACK = new Color(0.0, 0.0, 0.0);
|
||||
public static final @NotNull Color WHITE = new Color(1.0, 1.0, 1.0);
|
||||
|
||||
public static @NotNull Color random(@NotNull Random random) {
|
||||
return new Color(random.nextDouble(), random.nextDouble(), random.nextDouble());
|
||||
}
|
||||
|
||||
public static @NotNull Color random(@NotNull Random random, double min, double max) {
|
||||
var span = max - min;
|
||||
return new Color(
|
||||
Math.fma(random.nextDouble(), span, min),
|
||||
Math.fma(random.nextDouble(), span, min),
|
||||
Math.fma(random.nextDouble(), span, min)
|
||||
);
|
||||
}
|
||||
|
||||
public static @NotNull Color gamma(@NotNull Color color, double gamma) {
|
||||
if (gamma == 1.0) {
|
||||
return color;
|
||||
} else if (gamma == 2.0) {
|
||||
return new Color(
|
||||
Math.sqrt(color.r()),
|
||||
Math.sqrt(color.g()),
|
||||
Math.sqrt(color.b())
|
||||
);
|
||||
} else {
|
||||
return new Color(
|
||||
Math.pow(color.r(), 1 / gamma),
|
||||
Math.pow(color.g(), 1 / gamma),
|
||||
Math.pow(color.b(), 1 / gamma)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public Color(int rgb) {
|
||||
this((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, rgb & 0xFF);
|
||||
}
|
||||
|
||||
public Color(int red, int green, int blue) {
|
||||
this(red / 255f, green / 255f, blue / 255f);
|
||||
}
|
||||
|
||||
public Color {
|
||||
if (DEBUG && (!Double.isFinite(r) || !Double.isFinite(g) || !Double.isFinite(b))) {
|
||||
throw new IllegalArgumentException("r, g and b must be finite");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Math
|
||||
*/
|
||||
|
||||
public static @NotNull Color average(@NotNull Color current, @NotNull Color next, int index) {
|
||||
return lerp(current, next, 1d / index);
|
||||
}
|
||||
|
||||
public static @NotNull Color lerp(@NotNull Color a, @NotNull Color b, double t) {
|
||||
if (t < 0) return a;
|
||||
if (t > 1) return b;
|
||||
return new Color(
|
||||
Math.fma(t, b.r - a.r, a.r),
|
||||
Math.fma(t, b.g - a.g, a.g),
|
||||
Math.fma(t, b.b - a.b, a.b)
|
||||
);
|
||||
}
|
||||
|
||||
public static @NotNull Color fma(@NotNull Color a, @NotNull Color b, @NotNull Color c) {
|
||||
return new Color(
|
||||
Math.fma(a.r, b.r, c.r),
|
||||
Math.fma(a.g, b.g, c.g),
|
||||
Math.fma(a.b, b.b, c.b)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color plus(@NotNull Color other) {
|
||||
return new Color(r + other.r, g + other.g, b + other.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color minus(@NotNull Color other) {
|
||||
return new Color(r - other.r, g - other.g, b - other.b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color times(double d) {
|
||||
return new Color(r * d, g * d, b * d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color times(@NotNull Color other) {
|
||||
return new Color(r * other.r, g * other.g, b * other.b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Vec3
|
||||
*/
|
||||
|
||||
@Override
|
||||
public @NotNull Vec3 toVec3() {
|
||||
return new Vec3(r, g, b);
|
||||
}
|
||||
|
||||
public static @NotNull Color fromVec3(@NotNull Vec3 vec) {
|
||||
return new Color(vec.x(), vec.y(), vec.z());
|
||||
}
|
||||
|
||||
/*
|
||||
* Accessors
|
||||
*/
|
||||
|
||||
public int red() {
|
||||
return toInt(r);
|
||||
}
|
||||
|
||||
public int green() {
|
||||
return toInt(g);
|
||||
}
|
||||
|
||||
public int blue() {
|
||||
return toInt(b);
|
||||
}
|
||||
|
||||
private static int toInt(double value) {
|
||||
return Math.clamp((int) (255.99 * value), 0, 255);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double component1() {
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double component2() {
|
||||
return g;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double component3() {
|
||||
return b;
|
||||
}
|
||||
|
||||
/*
|
||||
* Texture
|
||||
*/
|
||||
|
||||
@Override
|
||||
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUVRequired() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* SkyBox
|
||||
*/
|
||||
|
||||
@Override
|
||||
public @NotNull Color getColor(@NotNull Ray ray) {
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
package eu.jonahbauer.raytracing.render.texture;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Image;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorSpace;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBAlbedoSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.RGBIlluminantSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@ -10,22 +14,33 @@ import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Objects;
|
||||
|
||||
public record ImageTexture(@NotNull Image image) implements Texture {
|
||||
public final class ImageTexture implements Texture {
|
||||
private static final String PATH_PREFIX = "/eu/jonahbauer/raytracing/textures/";
|
||||
|
||||
public ImageTexture {
|
||||
Objects.requireNonNull(image, "image");
|
||||
private final int width;
|
||||
private final int height;
|
||||
private final @NotNull Spectrum[][] spectra;
|
||||
|
||||
public ImageTexture(@NotNull BufferedImage image, @NotNull ColorSpace cs, @NotNull Type type, boolean gamma) {
|
||||
this.width = image.getWidth();
|
||||
this.height = image.getHeight();
|
||||
this.spectra = new Spectrum[height][width];
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
var rgb = new ColorRGB(image.getRGB(x, y));
|
||||
if (gamma) rgb = ColorRGB.inverseGamma(rgb);
|
||||
spectra[y][x] = type.newSpectrum(cs, rgb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ImageTexture(@NotNull BufferedImage image) {
|
||||
this(new Image(image));
|
||||
}
|
||||
|
||||
public ImageTexture(@NotNull String path) {
|
||||
this(read(path));
|
||||
public ImageTexture(@NotNull String path, @NotNull ColorSpace cs) {
|
||||
this(read(path), cs, Type.ALBEDO, true);
|
||||
}
|
||||
|
||||
private static @NotNull BufferedImage read(@NotNull String path) {
|
||||
try (var in = Objects.requireNonNull(ImageTexture.class.getResourceAsStream(path))) {
|
||||
try (var in = Objects.requireNonNull(ImageTexture.class.getResourceAsStream(PATH_PREFIX + path))) {
|
||||
return ImageIO.read(in);
|
||||
} catch (IOException ex) {
|
||||
throw new UncheckedIOException(ex);
|
||||
@ -33,11 +48,29 @@ public record ImageTexture(@NotNull Image image) implements Texture {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
|
||||
public @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
|
||||
u = Math.clamp(u, 0, 1);
|
||||
v = 1 - Math.clamp(v, 0, 1);
|
||||
int x = (int) (u * (image.getWidth() - 1));
|
||||
int y = (int) (v * (image.getHeight() - 1));
|
||||
return image.get(x, y);
|
||||
int x = (int) (u * (width - 1));
|
||||
int y = (int) (v * (height - 1));
|
||||
return spectra[y][x];
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
ALBEDO {
|
||||
@Override
|
||||
protected @NotNull Spectrum newSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
||||
return new RGBAlbedoSpectrum(cs, rgb);
|
||||
}
|
||||
},
|
||||
ILLUMINANT {
|
||||
@Override
|
||||
protected @NotNull Spectrum newSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb) {
|
||||
return new RGBIlluminantSpectrum(cs, rgb);
|
||||
}
|
||||
},
|
||||
;
|
||||
|
||||
protected abstract @NotNull Spectrum newSpectrum(@NotNull ColorSpace cs, @NotNull ColorRGB rgb);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package eu.jonahbauer.raytracing.render.texture;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
@ -11,11 +13,11 @@ import java.util.random.RandomGenerator;
|
||||
public final class PerlinTexture implements Texture {
|
||||
private static final int POINT_COUNT = 256;
|
||||
private static final @NotNull Random RANDOM = new Random();
|
||||
private static final @NotNull DoubleFunction<Color> GREYSCALE = t -> new Color(t, t, t);
|
||||
private static final @NotNull DoubleFunction<Spectrum> GREYSCALE = Spectra.WHITE::scale;
|
||||
|
||||
private final double scale;
|
||||
private final int turbulence;
|
||||
private final @NotNull DoubleFunction<Color> color;
|
||||
private final @NotNull DoubleFunction<Spectrum> color;
|
||||
|
||||
private final int mask;
|
||||
private final Vec3[] randvec;
|
||||
@ -35,12 +37,12 @@ public final class PerlinTexture implements Texture {
|
||||
this(scale, turbulence, GREYSCALE);
|
||||
}
|
||||
|
||||
public PerlinTexture(double scale, int turbulence, @NotNull DoubleFunction<Color> color) {
|
||||
public PerlinTexture(double scale, int turbulence, @NotNull DoubleFunction<Spectrum> color) {
|
||||
this(scale, turbulence, color, POINT_COUNT, RANDOM);
|
||||
}
|
||||
|
||||
public PerlinTexture(
|
||||
double scale, int turbulence, @NotNull DoubleFunction<Color> color,
|
||||
double scale, int turbulence, @NotNull DoubleFunction<Spectrum> color,
|
||||
int count, @NotNull RandomGenerator random
|
||||
) {
|
||||
if ((count & (count - 1)) != 0) throw new IllegalArgumentException("count must be a power of two");
|
||||
@ -118,7 +120,7 @@ public final class PerlinTexture implements Texture {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Color get(double u, double v, @NotNull Vec3 p) {
|
||||
public @NotNull Spectrum get(double u, double v, @NotNull Vec3 p) {
|
||||
var noise = getNoise(p, turbulence);
|
||||
var t = Math.fma(0.5, Math.sin(Math.PI * noise), 0.5);
|
||||
return color.apply(t);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package eu.jonahbauer.raytracing.render.texture;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@ -8,7 +9,7 @@ public interface Texture {
|
||||
/**
|
||||
* {@return the color of <code>this</code> texture for a hit}
|
||||
*/
|
||||
default @NotNull Color get(@NotNull HitResult hit) {
|
||||
default @NotNull Spectrum get(@NotNull HitResult hit) {
|
||||
return get(hit.u(), hit.v(), hit.position());
|
||||
}
|
||||
|
||||
@ -18,7 +19,7 @@ public interface Texture {
|
||||
* @param v the texture v coordinate
|
||||
* @param p the position
|
||||
*/
|
||||
@NotNull Color get(double u, double v, @NotNull Vec3 p);
|
||||
@NotNull Spectrum get(double u, double v, @NotNull Vec3 p);
|
||||
|
||||
/**
|
||||
* Returns whether {@link #get(double, double, Vec3)} uses the {@code u} and/or {@code v} parameters.
|
||||
|
@ -2,7 +2,8 @@ package eu.jonahbauer.raytracing.scene;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.AABB;
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectra;
|
||||
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
|
||||
import eu.jonahbauer.raytracing.scene.util.HittableCollection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -23,7 +24,7 @@ public final class Scene extends HittableCollection {
|
||||
}
|
||||
|
||||
public Scene(@NotNull List<? extends @NotNull Hittable> objects, @Nullable List<? extends @NotNull Target> targets) {
|
||||
this(Color.BLACK, objects, targets);
|
||||
this(Spectra.BLACK, objects, targets);
|
||||
}
|
||||
|
||||
public Scene(@NotNull SkyBox background, @NotNull List<? extends @NotNull Hittable> objects) {
|
||||
@ -54,7 +55,7 @@ public final class Scene extends HittableCollection {
|
||||
return targets;
|
||||
}
|
||||
|
||||
public @NotNull Color getBackgroundColor(@NotNull Ray ray) {
|
||||
public @NotNull SampledSpectrum getBackgroundColor(@NotNull Ray ray) {
|
||||
return background.getColor(ray);
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,27 @@
|
||||
package eu.jonahbauer.raytracing.scene;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.SampledSpectrum;
|
||||
import eu.jonahbauer.raytracing.render.spectral.spectrum.Spectrum;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface SkyBox {
|
||||
@NotNull Color getColor(@NotNull Ray ray);
|
||||
@NotNull SampledSpectrum getColor(@NotNull Ray ray);
|
||||
|
||||
static @NotNull SkyBox gradient(@NotNull Color top, @NotNull Color bottom) {
|
||||
static @NotNull SkyBox gradient(@NotNull Spectrum top, @NotNull Spectrum bottom) {
|
||||
return ray -> {
|
||||
// altitude from -pi/2 to pi/2
|
||||
var alt = Math.copySign(
|
||||
Math.acos(ray.direction().withY(0).unit().dot(ray.direction().unit())),
|
||||
ray.direction().y()
|
||||
);
|
||||
return Color.lerp(bottom, top, alt / Math.PI + 0.5);
|
||||
|
||||
return SampledSpectrum.lerp(
|
||||
top.sample(ray.lambda()),
|
||||
bottom.sample(ray.lambda()),
|
||||
alt / Math.PI + 0.5
|
||||
);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ public sealed class RotateY extends Transform {
|
||||
|
||||
var newOrigin = transform(origin);
|
||||
var newDirection = transform(direction);
|
||||
return new Ray(newOrigin, newDirection);
|
||||
return new Ray(newOrigin, newDirection, ray.lambda());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -35,7 +35,7 @@ public sealed class Translate extends Transform {
|
||||
|
||||
@Override
|
||||
protected final @NotNull Ray transform(@NotNull Ray ray) {
|
||||
return new Ray(ray.origin().minus(offset), ray.direction());
|
||||
return new Ray(ray.origin().minus(offset), ray.direction(), ray.lambda());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,6 @@
|
||||
package eu.jonahbauer.raytracing.render.canvas;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.ImageFormat;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
@ -13,11 +13,11 @@ import java.util.Objects;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class ImageTest {
|
||||
class RGBCanvasTest {
|
||||
|
||||
@Test
|
||||
void test(@TempDir Path dir) throws IOException {
|
||||
var image = new Image(256, 256);
|
||||
var image = new RGBCanvas(256, 256);
|
||||
|
||||
for (var y = 0; y < image.getHeight(); y++) {
|
||||
for (var x = 0; x < image.getWidth(); x++) {
|
||||
@ -25,7 +25,7 @@ class ImageTest {
|
||||
var g = (double) y / (image.getHeight() - 1);
|
||||
var b = 0;
|
||||
|
||||
image.set(x, y, new Color(r, g, b));
|
||||
image.set(x, y, new ColorRGB(r, g, b));
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ class ImageTest {
|
||||
String expected;
|
||||
String actual;
|
||||
|
||||
try (var in = Objects.requireNonNull(ImageTest.class.getResourceAsStream("simple_image.ppm"))) {
|
||||
try (var in = Objects.requireNonNull(RGBCanvasTest.class.getResourceAsStream("simple_image.ppm"))) {
|
||||
expected = new String(in.readAllBytes(), StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package eu.jonahbauer.raytracing.scene.hittable3d;
|
||||
import eu.jonahbauer.raytracing.math.Range;
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.spectral.colors.ColorRGB;
|
||||
import eu.jonahbauer.raytracing.render.material.LambertianMaterial;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -15,7 +15,7 @@ class SphereTest {
|
||||
void hit() {
|
||||
var center = new Vec3(1, 2, 3);
|
||||
var radius = 5;
|
||||
var sphere = new Sphere(center, radius, new LambertianMaterial(Color.WHITE));
|
||||
var sphere = new Sphere(center, radius, new LambertianMaterial(ColorRGB.WHITE));
|
||||
|
||||
var origin = new Vec3(6, 7, 8);
|
||||
var direction = new Vec3(-1, -1, -1);
|
||||
|
Loading…
x
Reference in New Issue
Block a user