improve debuggability
This commit is contained in:
parent
2a2cf7b642
commit
940e8ebc37
@ -13,6 +13,8 @@ import java.nio.file.Path;
|
|||||||
import java.util.function.IntFunction;
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
|
public static final boolean DEBUG = false;
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
var config = Config.parse(args);
|
var config = Config.parse(args);
|
||||||
var example = config.example;
|
var example = config.example;
|
||||||
|
@ -178,4 +178,9 @@ public record Vec3(double x, double y, double z) {
|
|||||||
public @NotNull Vec3 withZ(double z) {
|
public @NotNull Vec3 withZ(double z) {
|
||||||
return new Vec3(x, y, z);
|
return new Vec3(x, y, z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String toString() {
|
||||||
|
return "(" + x + "," + y + "," + z + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ import java.util.SplittableRandom;
|
|||||||
import java.util.random.RandomGenerator;
|
import java.util.random.RandomGenerator;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.raytracing.Main.DEBUG;
|
||||||
|
|
||||||
public final class SimpleRenderer implements Renderer {
|
public final class SimpleRenderer implements Renderer {
|
||||||
private final int sqrtSamplesPerPixel;
|
private final int sqrtSamplesPerPixel;
|
||||||
private final int maxDepth;
|
private final int maxDepth;
|
||||||
@ -104,6 +106,9 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
|
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
|
||||||
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
|
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
|
||||||
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random);
|
var ray = camera.cast(x, y, si, sj, sqrtSamplesPerPixel, random);
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println("Casting ray " + ray + " through pixel (" + x + "," + y + ") at subpixel (" + si + "," + sj + ")...");
|
||||||
|
}
|
||||||
var c = getColor(scene, ray, random);
|
var c = getColor(scene, ray, random);
|
||||||
color = Color.average(color, c, ++i);
|
color = Color.average(color, c, ++i);
|
||||||
}
|
}
|
||||||
@ -127,36 +132,75 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
while (depth-- > 0) {
|
while (depth-- > 0) {
|
||||||
var optional = scene.hit(ray, new Range(0.001, Double.POSITIVE_INFINITY));
|
var optional = scene.hit(ray, new Range(0.001, Double.POSITIVE_INFINITY));
|
||||||
if (optional.isEmpty()) {
|
if (optional.isEmpty()) {
|
||||||
color = Color.add(color, Color.multiply(attenuation, scene.getBackgroundColor(ray)));
|
var background = scene.getBackgroundColor(ray);
|
||||||
|
color = Color.add(color, Color.multiply(attenuation, background));
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(" Hit background: " + background);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hit = optional.get();
|
var hit = optional.get();
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(" Hit " + hit.target() + " at t=" + hit.t() + " (" + hit.position() + ")");
|
||||||
|
}
|
||||||
var material = hit.material();
|
var material = hit.material();
|
||||||
var emitted = material.emitted(hit);
|
var emitted = material.emitted(hit);
|
||||||
|
if (DEBUG && !Color.BLACK.equals(emitted)) {
|
||||||
|
System.out.println(" Emitted: " + emitted);
|
||||||
|
}
|
||||||
|
|
||||||
var result = material.scatter(ray, hit, random);
|
var result = material.scatter(ray, hit, random);
|
||||||
color = Color.add(color, Color.multiply(attenuation, emitted));
|
color = Color.add(color, Color.multiply(attenuation, emitted));
|
||||||
|
|
||||||
if (result.isEmpty()) break;
|
if (result.isEmpty()) {
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(" Absorbed");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
switch (result.get()) {
|
switch (result.get()) {
|
||||||
case Material.SpecularScatterResult(var a, var scattered) -> {
|
case Material.SpecularScatterResult(var a, var scattered) -> {
|
||||||
attenuation = Color.multiply(attenuation, a);
|
attenuation = Color.multiply(attenuation, a);
|
||||||
ray = scattered;
|
ray = scattered;
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(" Specular scattering with albedo " + a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case Material.PdfScatterResult(var a, var pdf) -> {
|
case Material.PdfScatterResult(var a, var pdf) -> {
|
||||||
if (scene.getLights() == null) {
|
if (scene.getLights() == null) {
|
||||||
attenuation = Color.multiply(attenuation, a);
|
attenuation = Color.multiply(attenuation, a);
|
||||||
ray = new Ray(hit.position(), pdf.generate(random));
|
ray = new Ray(hit.position(), pdf.generate(random));
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(" Pdf scattering with albedo " + a);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
var mixed = new MixtureProbabilityDensityFunction(new TargetingProbabilityDensityFunction(hit.position(), scene.getLights()), pdf);
|
var mixed = new MixtureProbabilityDensityFunction(new TargetingProbabilityDensityFunction(hit.position(), scene.getLights()), pdf, 0.5);
|
||||||
var direction = mixed.generate(random);
|
var direction = mixed.generate(random);
|
||||||
var factor = pdf.value(direction) / mixed.value(direction);
|
var factor = pdf.value(direction) / mixed.value(direction);
|
||||||
attenuation = Color.multiply(attenuation, Color.multiply(a, factor));
|
attenuation = Color.multiply(attenuation, Color.multiply(a, factor));
|
||||||
ray = new Ray(hit.position(), direction);
|
ray = new Ray(hit.position(), direction);
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(" Pdf scattering with albedo " + a + " and factor " + factor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(" Combined color is " + color);
|
||||||
|
System.out.println(" Combined attenuation is " + attenuation);
|
||||||
|
System.out.println(" New ray is " + ray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG) {
|
||||||
|
System.out.println(" Final color is " + color);
|
||||||
}
|
}
|
||||||
|
|
||||||
return color;
|
return color;
|
||||||
|
@ -5,6 +5,8 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static eu.jonahbauer.raytracing.Main.DEBUG;
|
||||||
|
|
||||||
public record Color(double r, double g, double b) implements Texture {
|
public record Color(double r, double g, double b) implements Texture {
|
||||||
public static final @NotNull Color BLACK = new Color(0.0, 0.0, 0.0);
|
public static final @NotNull Color BLACK = new Color(0.0, 0.0, 0.0);
|
||||||
public static final @NotNull Color WHITE = new Color(1.0, 1.0, 1.0);
|
public static final @NotNull Color WHITE = new Color(1.0, 1.0, 1.0);
|
||||||
@ -82,6 +84,17 @@ public record Color(double r, double g, double b) implements Texture {
|
|||||||
this(red / 255f, green / 255f, blue / 255f);
|
this(red / 255f, green / 255f, blue / 255f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Color {
|
||||||
|
if (DEBUG) {
|
||||||
|
if (!Double.isFinite(r) || !Double.isFinite(g) || !Double.isFinite(b)) {
|
||||||
|
throw new IllegalArgumentException("r, g and b must be finite");
|
||||||
|
}
|
||||||
|
if (r < 0 || g < 0 || b < 0) {
|
||||||
|
throw new IllegalArgumentException("r, g and b must be non-negative");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int red() {
|
public int red() {
|
||||||
return toInt(r);
|
return toInt(r);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public record HitResult(
|
public record HitResult(
|
||||||
double t, @NotNull Vec3 position, @NotNull Vec3 normal,
|
double t, @NotNull Vec3 position, @NotNull Vec3 normal, @NotNull Hittable target,
|
||||||
@NotNull Material material, double u, double v, boolean isFrontFace
|
@NotNull Material material, double u, double v, boolean isFrontFace
|
||||||
) implements Comparable<HitResult> {
|
) implements Comparable<HitResult> {
|
||||||
public HitResult {
|
public HitResult {
|
||||||
@ -16,7 +16,7 @@ public record HitResult(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull HitResult withPositionAndNormal(@NotNull Vec3 position, @NotNull Vec3 normal) {
|
public @NotNull HitResult withPositionAndNormal(@NotNull Vec3 position, @NotNull Vec3 normal) {
|
||||||
return new HitResult(t, position, normal, material, u, v, isFrontFace);
|
return new HitResult(t, position, normal, target, material, u, v, isFrontFace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -52,10 +52,15 @@ public abstract class Hittable2D implements Hittable {
|
|||||||
|
|
||||||
var frontFace = denominator < 0;
|
var frontFace = denominator < 0;
|
||||||
return Optional.of(new HitResult(
|
return Optional.of(new HitResult(
|
||||||
t, position, frontFace ? normal : normal.neg(),
|
t, position, frontFace ? normal : normal.neg(), this,
|
||||||
material, alpha, beta, frontFace
|
material, alpha, beta, frontFace
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract boolean isInterior(double alpha, double beta);
|
protected abstract boolean isInterior(double alpha, double beta);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String toString() {
|
||||||
|
return this.getClass().getSimpleName() + "(origin=" + origin + ", u=" + u + ", v=" + v + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ public final class Box implements Hittable, Target {
|
|||||||
var uv = material.texture().isUVRequired();
|
var uv = material.texture().isUVRequired();
|
||||||
var u = uv ? side.getTextureU(box, position) : Double.NaN;
|
var u = uv ? side.getTextureU(box, position) : Double.NaN;
|
||||||
var v = uv ? side.getTextureV(box, position) : Double.NaN;
|
var v = uv ? side.getTextureV(box, position) : Double.NaN;
|
||||||
return Optional.of(new HitResult(t, position, normal, material, u, v, frontFace));
|
return Optional.of(new HitResult(t, position, normal, this, material, u, v, frontFace));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -173,6 +173,11 @@ public final class Box implements Hittable, Target {
|
|||||||
&& box.min().z() < point.z() && point.z() < box.max().z();
|
&& box.min().z() < point.z() && point.z() < box.max().z();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String toString() {
|
||||||
|
return "Box(min=" + box.min() + ", max=" + box.max() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
private enum Side {
|
private enum Side {
|
||||||
NEG_X(Vec3.UNIT_X.neg()),
|
NEG_X(Vec3.UNIT_X.neg()),
|
||||||
NEG_Y(Vec3.UNIT_Y.neg()),
|
NEG_Y(Vec3.UNIT_Y.neg()),
|
||||||
|
@ -32,7 +32,7 @@ public record ConstantMedium(@NotNull Hittable boundary, double density, @NotNul
|
|||||||
if (hitDistance > distance) return Optional.empty();
|
if (hitDistance > distance) return Optional.empty();
|
||||||
|
|
||||||
var t = tmin + hitDistance / length;
|
var t = tmin + hitDistance / length;
|
||||||
return Optional.of(new HitResult(t, ray.at(t), Vec3.UNIT_X, material, 0, 0, true)); // arbitrary normal, u, v and isFrontFace
|
return Optional.of(new HitResult(t, ray.at(t), Vec3.UNIT_X, this, material, 0, 0, true)); // arbitrary normal, u, v and isFrontFace
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -67,7 +67,7 @@ public final class Sphere implements Hittable, Target {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Optional.of(new HitResult(
|
return Optional.of(new HitResult(
|
||||||
t, position, frontFace ? normal : normal.neg(),
|
t, position, frontFace ? normal : normal.neg(), this,
|
||||||
material, u, v, frontFace
|
material, u, v, frontFace
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -97,4 +97,9 @@ public final class Sphere implements Hittable, Target {
|
|||||||
|
|
||||||
return target.times(radius).plus(center).minus(origin);
|
return target.times(radius).plus(center).minus(origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String toString() {
|
||||||
|
return "Sphere(center=" + center + ", radius=" + radius + ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,11 @@ public sealed class RotateY extends Transform {
|
|||||||
return bbox;
|
return bbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String toString() {
|
||||||
|
return object + " rotated by " + Math.toDegrees(Math.atan2(sin, cos)) + "° around the y axis";
|
||||||
|
}
|
||||||
|
|
||||||
private static final class RotateYTarget extends RotateY implements Target {
|
private static final class RotateYTarget extends RotateY implements Target {
|
||||||
|
|
||||||
private RotateYTarget(@NotNull Hittable object, double angle) {
|
private RotateYTarget(@NotNull Hittable object, double angle) {
|
||||||
|
@ -48,6 +48,11 @@ public sealed class Translate extends Transform {
|
|||||||
return bbox;
|
return bbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull String toString() {
|
||||||
|
return object + " translated by " + offset;
|
||||||
|
}
|
||||||
|
|
||||||
private static final class TranslateTarget extends Translate implements Target {
|
private static final class TranslateTarget extends Translate implements Target {
|
||||||
private TranslateTarget(@NotNull Hittable object, @NotNull Vec3 offset) {
|
private TranslateTarget(@NotNull Hittable object, @NotNull Vec3 offset) {
|
||||||
super(object, offset);
|
super(object, offset);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user