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;
|
||||
|
||||
public class Main {
|
||||
public static final boolean DEBUG = false;
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
var config = Config.parse(args);
|
||||
var example = config.example;
|
||||
|
@ -178,4 +178,9 @@ public record Vec3(double x, double y, double z) {
|
||||
public @NotNull Vec3 withZ(double 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.stream.IntStream;
|
||||
|
||||
import static eu.jonahbauer.raytracing.Main.DEBUG;
|
||||
|
||||
public final class SimpleRenderer implements Renderer {
|
||||
private final int sqrtSamplesPerPixel;
|
||||
private final int maxDepth;
|
||||
@ -104,6 +106,9 @@ public final class SimpleRenderer implements Renderer {
|
||||
for (int sj = 0; sj < sqrtSamplesPerPixel; sj++) {
|
||||
for (int si = 0; si < sqrtSamplesPerPixel; si++) {
|
||||
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);
|
||||
color = Color.average(color, c, ++i);
|
||||
}
|
||||
@ -127,38 +132,77 @@ public final class SimpleRenderer implements Renderer {
|
||||
while (depth-- > 0) {
|
||||
var optional = scene.hit(ray, new Range(0.001, Double.POSITIVE_INFINITY));
|
||||
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;
|
||||
}
|
||||
|
||||
var hit = optional.get();
|
||||
if (DEBUG) {
|
||||
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)) {
|
||||
System.out.println(" Emitted: " + emitted);
|
||||
}
|
||||
|
||||
var result = material.scatter(ray, hit, random);
|
||||
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()) {
|
||||
case Material.SpecularScatterResult(var a, var scattered) -> {
|
||||
attenuation = Color.multiply(attenuation, a);
|
||||
ray = scattered;
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println(" Specular scattering with albedo " + a);
|
||||
}
|
||||
}
|
||||
case Material.PdfScatterResult(var a, var pdf) -> {
|
||||
if (scene.getLights() == null) {
|
||||
attenuation = Color.multiply(attenuation, a);
|
||||
ray = new Ray(hit.position(), pdf.generate(random));
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println(" Pdf scattering with albedo " + a);
|
||||
}
|
||||
} 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 factor = pdf.value(direction) / mixed.value(direction);
|
||||
attenuation = Color.multiply(attenuation, Color.multiply(a, factor));
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ 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 {
|
||||
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);
|
||||
@ -82,6 +84,17 @@ public record Color(double r, double g, double b) implements Texture {
|
||||
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() {
|
||||
return toInt(r);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.Objects;
|
||||
|
||||
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
|
||||
) implements Comparable<HitResult> {
|
||||
public HitResult {
|
||||
@ -16,7 +16,7 @@ public record HitResult(
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -52,10 +52,15 @@ public abstract class Hittable2D implements Hittable {
|
||||
|
||||
var frontFace = denominator < 0;
|
||||
return Optional.of(new HitResult(
|
||||
t, position, frontFace ? normal : normal.neg(),
|
||||
t, position, frontFace ? normal : normal.neg(), this,
|
||||
material, alpha, beta, frontFace
|
||||
));
|
||||
}
|
||||
|
||||
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 u = uv ? side.getTextureU(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
|
||||
@ -173,6 +173,11 @@ public final class Box implements Hittable, Target {
|
||||
&& 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 {
|
||||
NEG_X(Vec3.UNIT_X.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();
|
||||
|
||||
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
|
||||
|
@ -67,7 +67,7 @@ public final class Sphere implements Hittable, Target {
|
||||
}
|
||||
|
||||
return Optional.of(new HitResult(
|
||||
t, position, frontFace ? normal : normal.neg(),
|
||||
t, position, frontFace ? normal : normal.neg(), this,
|
||||
material, u, v, frontFace
|
||||
));
|
||||
}
|
||||
@ -97,4 +97,9 @@ public final class Sphere implements Hittable, Target {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@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 RotateYTarget(@NotNull Hittable object, double angle) {
|
||||
|
@ -48,6 +48,11 @@ public sealed class Translate extends Transform {
|
||||
return bbox;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String toString() {
|
||||
return object + " translated by " + offset;
|
||||
}
|
||||
|
||||
private static final class TranslateTarget extends Translate implements Target {
|
||||
private TranslateTarget(@NotNull Hittable object, @NotNull Vec3 offset) {
|
||||
super(object, offset);
|
||||
|
Loading…
x
Reference in New Issue
Block a user