From 2e5659a04af1c66cd7262a6fe32a20ab2aadf45f Mon Sep 17 00:00:00 2001 From: jbb01 <32650546+jbb01@users.noreply.github.com> Date: Sat, 3 Aug 2024 21:19:01 +0200 Subject: [PATCH] add custom Materials --- .../java/eu/jonahbauer/raytracing/Main.java | 6 +++-- .../material/LambertianMaterial.java | 25 +++++++++++++++++++ .../raytracing/material/Material.java | 21 ++++++++++++++++ .../eu/jonahbauer/raytracing/math/Vec3.java | 5 ++++ .../jonahbauer/raytracing/render/Camera.java | 11 ++++---- .../jonahbauer/raytracing/render/Color.java | 6 +++++ .../raytracing/scene/HitResult.java | 8 +++++- .../jonahbauer/raytracing/scene/Sphere.java | 15 +++++------ 8 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 src/main/java/eu/jonahbauer/raytracing/material/LambertianMaterial.java create mode 100644 src/main/java/eu/jonahbauer/raytracing/material/Material.java diff --git a/src/main/java/eu/jonahbauer/raytracing/Main.java b/src/main/java/eu/jonahbauer/raytracing/Main.java index 9334b10..725228a 100644 --- a/src/main/java/eu/jonahbauer/raytracing/Main.java +++ b/src/main/java/eu/jonahbauer/raytracing/Main.java @@ -1,6 +1,8 @@ package eu.jonahbauer.raytracing; +import eu.jonahbauer.raytracing.material.LambertianMaterial; import eu.jonahbauer.raytracing.render.Camera; +import eu.jonahbauer.raytracing.render.Color; import eu.jonahbauer.raytracing.render.ImageFormat; import eu.jonahbauer.raytracing.scene.Scene; import eu.jonahbauer.raytracing.scene.Sphere; @@ -11,8 +13,8 @@ import java.nio.file.Path; public class Main { public static void main(String[] args) throws IOException { var scene = new Scene( - new Sphere(0, 0, 1, 0.5), - new Sphere(0, -100.5, 1, 100) + new Sphere(0, 0, 1, 0.5, new LambertianMaterial(Color.RED)), + new Sphere(0, -100.5, 1, 100, new LambertianMaterial(Color.BLUE)) ); var camera = new Camera(512, 2, 16 / 9d); diff --git a/src/main/java/eu/jonahbauer/raytracing/material/LambertianMaterial.java b/src/main/java/eu/jonahbauer/raytracing/material/LambertianMaterial.java new file mode 100644 index 0000000..20a097a --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/material/LambertianMaterial.java @@ -0,0 +1,25 @@ +package eu.jonahbauer.raytracing.material; + +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.render.Color; +import eu.jonahbauer.raytracing.scene.HitResult; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Optional; + +public record LambertianMaterial(@NotNull Color albedo) implements Material { + public LambertianMaterial { + Objects.requireNonNull(albedo, "albedo"); + } + + @Override + public @NotNull Optional scatter(@NotNull Ray ray, @NotNull HitResult hit) { + var newDirection = hit.normal().plus(Vec3.random(true)); + if (newDirection.isNearZero()) newDirection = hit.normal(); + + var scattered = new Ray(hit.position(), newDirection); + return Optional.of(new ScatterResult(scattered, albedo)); + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/material/Material.java b/src/main/java/eu/jonahbauer/raytracing/material/Material.java new file mode 100644 index 0000000..8c4b74e --- /dev/null +++ b/src/main/java/eu/jonahbauer/raytracing/material/Material.java @@ -0,0 +1,21 @@ +package eu.jonahbauer.raytracing.material; + +import eu.jonahbauer.raytracing.math.Ray; +import eu.jonahbauer.raytracing.render.Color; +import eu.jonahbauer.raytracing.scene.HitResult; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.Optional; + +public interface Material { + + @NotNull Optional scatter(@NotNull Ray ray, @NotNull HitResult hit); + + record ScatterResult(@NotNull Ray ray, @NotNull Color attenuation) { + public ScatterResult { + Objects.requireNonNull(ray, "ray"); + Objects.requireNonNull(attenuation, "attenuation"); + } + } +} diff --git a/src/main/java/eu/jonahbauer/raytracing/math/Vec3.java b/src/main/java/eu/jonahbauer/raytracing/math/Vec3.java index 580bd76..fbd1713 100644 --- a/src/main/java/eu/jonahbauer/raytracing/math/Vec3.java +++ b/src/main/java/eu/jonahbauer/raytracing/math/Vec3.java @@ -62,6 +62,11 @@ public record Vec3(double x, double y, double z) { public double length() { return Math.sqrt(squared()); } + + public boolean isNearZero() { + var s = 1e-8; + return Math.abs(x) < s && Math.abs(y) < s && Math.abs(z) < s; + } public @NotNull Vec3 unit() { return div(length()); diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Camera.java b/src/main/java/eu/jonahbauer/raytracing/render/Camera.java index beca859..93f6d7f 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Camera.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/Camera.java @@ -137,12 +137,11 @@ public final class Camera { var optional = scene.hit(ray, new Range(0.001, Double.POSITIVE_INFINITY)); if (optional.isPresent()) { - var result = optional.get(); - - var newDirection = result.normal().plus(Vec3.random(true)); - var scattered = new Ray(result.position(), newDirection); - - return Color.lerp(Color.BLACK, getColor(scene, scattered, depth - 1), 0.5); + var hit = optional.get(); + var material = hit.material(); + return material.scatter(ray, hit) + .map(scatter -> Color.multiply(scatter.attenuation(), getColor(scene, scatter.ray(), depth - 1))) + .orElse(Color.BLACK); } else { return getSkyboxColor(ray); } diff --git a/src/main/java/eu/jonahbauer/raytracing/render/Color.java b/src/main/java/eu/jonahbauer/raytracing/render/Color.java index dee19d1..1dc1211 100644 --- a/src/main/java/eu/jonahbauer/raytracing/render/Color.java +++ b/src/main/java/eu/jonahbauer/raytracing/render/Color.java @@ -7,6 +7,8 @@ public record Color(double r, double g, double b) { public static final @NotNull Color WHITE = new Color(1.0, 1.0, 1.0); public static final @NotNull Color SKY = new Color(0.5, 0.7, 1.0); public static final @NotNull Color RED = new Color(1.0, 0.0, 0.0); + public static final @NotNull Color GREEN = new Color(0.0, 1.0, 0.0); + public static final @NotNull Color BLUE = new Color(0.0, 0.0, 1.0); public static @NotNull Color lerp(@NotNull Color a, @NotNull Color b, double t) { if (t < 0) return a; @@ -18,6 +20,10 @@ public record Color(double r, double g, double b) { ); } + public static @NotNull Color multiply(@NotNull Color a, @NotNull Color b) { + return new Color(a.r() * b.r(), a.g() * b.g(), a.b() * b.b()); + } + public Color { if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) { throw new IllegalArgumentException("r, g and b must be in the range 0 to 1"); diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java b/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java index ef6d281..ad2dd79 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/HitResult.java @@ -1,11 +1,17 @@ package eu.jonahbauer.raytracing.scene; import eu.jonahbauer.raytracing.math.Vec3; +import eu.jonahbauer.raytracing.material.Material; import org.jetbrains.annotations.NotNull; import java.util.Objects; -public record HitResult(double t, @NotNull Vec3 position, @NotNull Vec3 normal) implements Comparable { +public record HitResult( + double t, + @NotNull Vec3 position, + @NotNull Vec3 normal, + @NotNull Material material +) implements Comparable { public HitResult { if (t < 0 || !Double.isFinite(t)) throw new IllegalArgumentException("t must be non-negative"); Objects.requireNonNull(position, "position"); diff --git a/src/main/java/eu/jonahbauer/raytracing/scene/Sphere.java b/src/main/java/eu/jonahbauer/raytracing/scene/Sphere.java index e136bb1..d795ce5 100644 --- a/src/main/java/eu/jonahbauer/raytracing/scene/Sphere.java +++ b/src/main/java/eu/jonahbauer/raytracing/scene/Sphere.java @@ -1,5 +1,6 @@ package eu.jonahbauer.raytracing.scene; +import eu.jonahbauer.raytracing.material.Material; import eu.jonahbauer.raytracing.math.Range; import eu.jonahbauer.raytracing.math.Ray; import eu.jonahbauer.raytracing.math.Vec3; @@ -8,16 +9,16 @@ import org.jetbrains.annotations.NotNull; import java.util.Objects; import java.util.Optional; -public record Sphere(@NotNull Vec3 center, double radius) implements Hittable { - public static final @NotNull Sphere UNIT = new Sphere(Vec3.ZERO, 1.0); +public record Sphere(@NotNull Vec3 center, double radius, @NotNull Material material) implements Hittable { public Sphere { Objects.requireNonNull(center, "center"); + Objects.requireNonNull(material, "material"); if (radius <= 0 || !Double.isFinite(radius)) throw new IllegalArgumentException("radius must be positive"); } - public Sphere(double x, double y, double z, double r) { - this(new Vec3(x, y, z), r); + public Sphere(double x, double y, double z, double r, @NotNull Material material) { + this(new Vec3(x, y, z), r, material); } @Override @@ -38,11 +39,11 @@ public record Sphere(@NotNull Vec3 center, double radius) implements Hittable { if (!range.surrounds(t)) return Optional.empty(); var position = ray.at(t); - return Optional.of(new HitResult(t, position, position.minus(center))); + return Optional.of(new HitResult(t, position, position.minus(center), material)); } public @NotNull Sphere withCenter(@NotNull Vec3 center) { - return new Sphere(center, radius); + return new Sphere(center, radius, material); } public @NotNull Sphere withCenter(double x, double y, double z) { @@ -50,6 +51,6 @@ public record Sphere(@NotNull Vec3 center, double radius) implements Hittable { } public @NotNull Sphere withRadius(double radius) { - return new Sphere(center, radius); + return new Sphere(center, radius, material); } }