add targeting probability density function
This commit is contained in:
parent
6b47f44ad2
commit
09831c4231
@ -35,6 +35,7 @@ public class Examples {
|
|||||||
register("LIGHT", Examples::getLight);
|
register("LIGHT", Examples::getLight);
|
||||||
register("CORNELL", Examples::getCornellBox);
|
register("CORNELL", Examples::getCornellBox);
|
||||||
register("CORNELL_SMOKE", Examples::getCornellBoxSmoke);
|
register("CORNELL_SMOKE", Examples::getCornellBoxSmoke);
|
||||||
|
register("CORNELL_SPHERE", Examples::getCornellBoxSphere);
|
||||||
register("DIAGRAMM", Examples::getDiagramm);
|
register("DIAGRAMM", Examples::getDiagramm);
|
||||||
register("EARTH", Examples::getEarth);
|
register("EARTH", Examples::getEarth);
|
||||||
register("PERLIN", Examples::getPerlin);
|
register("PERLIN", Examples::getPerlin);
|
||||||
@ -212,6 +213,38 @@ 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 aluminum = new MetallicMaterial(new Color(0.8, 0.85, 0.88));
|
||||||
|
var glass = new DielectricMaterial(1.5);
|
||||||
|
|
||||||
|
return new Example(
|
||||||
|
new Scene(
|
||||||
|
new Box(
|
||||||
|
new AABB(new Vec3(0, 0, 0), new Vec3(555, 555, 555)),
|
||||||
|
white, white, red, green, white, null
|
||||||
|
),
|
||||||
|
new Parallelogram(new Vec3(343, 554, 332), new Vec3(-130, 0, 0), new Vec3(0, 0, -105), light),
|
||||||
|
new Box(
|
||||||
|
new AABB(new Vec3(0, 0, 0), new Vec3(165, 330, 165)),
|
||||||
|
white, white, white, white, white, aluminum
|
||||||
|
).rotateY(Math.toRadians(15)).translate(new Vec3(265, 0, 295)),
|
||||||
|
new Sphere(new Vec3(190, 90, 190), 90, glass)
|
||||||
|
),
|
||||||
|
SimpleCamera.builder()
|
||||||
|
.withImage(height, height)
|
||||||
|
.withFieldOfView(Math.toRadians(40))
|
||||||
|
.withPosition(new Vec3(278, 278, -800))
|
||||||
|
.withTarget(new Vec3(278, 278, 0))
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static @NotNull Example getDiagramm(int height) {
|
public static @NotNull Example getDiagramm(int height) {
|
||||||
if (height <= 0) height = 450;
|
if (height <= 0) height = 450;
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ package eu.jonahbauer.raytracing.render.renderer;
|
|||||||
import eu.jonahbauer.raytracing.math.Range;
|
import eu.jonahbauer.raytracing.math.Range;
|
||||||
import eu.jonahbauer.raytracing.math.Ray;
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
import eu.jonahbauer.raytracing.render.material.Material;
|
import eu.jonahbauer.raytracing.render.material.Material;
|
||||||
import eu.jonahbauer.raytracing.render.renderer.pdf.HittableProbabilityDensityFunction;
|
import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction;
|
||||||
import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction;
|
import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction;
|
||||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||||
import eu.jonahbauer.raytracing.render.camera.Camera;
|
import eu.jonahbauer.raytracing.render.camera.Camera;
|
||||||
@ -137,9 +137,6 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
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 (scatter.isEmpty()) break;
|
|
||||||
attenuation = Color.multiply(attenuation, scatter.get().attenuation());
|
|
||||||
ray = scatter.get().ray();
|
|
||||||
if (result.isEmpty()) break;
|
if (result.isEmpty()) break;
|
||||||
|
|
||||||
switch (result.get()) {
|
switch (result.get()) {
|
||||||
@ -148,8 +145,16 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
ray = scattered;
|
ray = scattered;
|
||||||
}
|
}
|
||||||
case Material.PdfScatterResult(var a, var pdf) -> {
|
case Material.PdfScatterResult(var a, var pdf) -> {
|
||||||
attenuation = Color.multiply(attenuation, a);
|
if (scene.getLights() == null) {
|
||||||
ray = new Ray(hit.position(), pdf.generate(random));
|
attenuation = Color.multiply(attenuation, a);
|
||||||
|
ray = new Ray(hit.position(), pdf.generate(random));
|
||||||
|
} else {
|
||||||
|
var mixed = new MixtureProbabilityDensityFunction(new TargetingProbabilityDensityFunction(hit.position(), scene.getLights()), pdf);
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package eu.jonahbauer.raytracing.render.renderer.pdf;
|
||||||
|
|
||||||
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
|
import eu.jonahbauer.raytracing.scene.Target;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A probability density function targeting a target.
|
||||||
|
*/
|
||||||
|
public record TargetingProbabilityDensityFunction(@NotNull Vec3 origin, @NotNull Target target) implements ProbabilityDensityFunction {
|
||||||
|
public TargetingProbabilityDensityFunction {
|
||||||
|
Objects.requireNonNull(origin, "origin");
|
||||||
|
Objects.requireNonNull(target, "target");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double value(@NotNull Vec3 direction) {
|
||||||
|
return target.getProbabilityDensity(origin, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Vec3 generate(@NotNull RandomGenerator random) {
|
||||||
|
return target.getTargetingDirection(origin, random);
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,10 @@ public record Color(double r, double g, double b) implements Texture {
|
|||||||
return new Color(a.r() * b.r(), a.g() * b.g(), a.b() * b.b());
|
return new Color(a.r() * b.r(), a.g() * b.g(), a.b() * b.b());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NotNull Color multiply(@NotNull Color a, double b) {
|
||||||
|
return new Color(a.r() * b, a.g() * b, a.b() * b);
|
||||||
|
}
|
||||||
|
|
||||||
public static @NotNull Color add(@NotNull Color a, @NotNull Color b) {
|
public static @NotNull Color add(@NotNull Color a, @NotNull Color b) {
|
||||||
return new Color(a.r() + b.r(), a.g() + b.g(), a.b() + b.b());
|
return new Color(a.r() + b.r(), a.g() + b.g(), a.b() + b.b());
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,9 @@ public interface Hittable {
|
|||||||
*/
|
*/
|
||||||
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range);
|
@NotNull Optional<HitResult> hit(@NotNull Ray ray, @NotNull Range range);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return the axis-aligned bounding box of this hittable}
|
||||||
|
*/
|
||||||
@NotNull AABB getBoundingBox();
|
@NotNull AABB getBoundingBox();
|
||||||
|
|
||||||
default @NotNull Hittable translate(@NotNull Vec3 offset) {
|
default @NotNull Hittable translate(@NotNull Vec3 offset) {
|
||||||
|
@ -6,6 +6,7 @@ import eu.jonahbauer.raytracing.render.texture.Color;
|
|||||||
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
|
import eu.jonahbauer.raytracing.scene.util.HittableBinaryTree;
|
||||||
import eu.jonahbauer.raytracing.scene.util.HittableCollection;
|
import eu.jonahbauer.raytracing.scene.util.HittableCollection;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -14,6 +15,8 @@ public final class Scene extends HittableCollection {
|
|||||||
private final @NotNull HittableCollection objects;
|
private final @NotNull HittableCollection objects;
|
||||||
private final @NotNull SkyBox background;
|
private final @NotNull SkyBox background;
|
||||||
|
|
||||||
|
private final @Nullable Target light;
|
||||||
|
|
||||||
public Scene(@NotNull List<? extends @NotNull Hittable> objects) {
|
public Scene(@NotNull List<? extends @NotNull Hittable> objects) {
|
||||||
this(Color.BLACK, objects);
|
this(Color.BLACK, objects);
|
||||||
}
|
}
|
||||||
@ -25,6 +28,8 @@ public final class Scene extends HittableCollection {
|
|||||||
public Scene(@NotNull SkyBox background, @NotNull List<? extends @NotNull Hittable> objects) {
|
public Scene(@NotNull SkyBox background, @NotNull List<? extends @NotNull Hittable> objects) {
|
||||||
this.objects = new HittableBinaryTree(objects);
|
this.objects = new HittableBinaryTree(objects);
|
||||||
this.background = Objects.requireNonNull(background);
|
this.background = Objects.requireNonNull(background);
|
||||||
|
|
||||||
|
this.light = (Target) objects.get(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Scene(@NotNull Hittable @NotNull... objects) {
|
public Scene(@NotNull Hittable @NotNull... objects) {
|
||||||
@ -49,6 +54,10 @@ public final class Scene extends HittableCollection {
|
|||||||
return objects.getBoundingBox();
|
return objects.getBoundingBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public @Nullable Target getLights() {
|
||||||
|
return light;
|
||||||
|
}
|
||||||
|
|
||||||
public @NotNull Color getBackgroundColor(@NotNull Ray ray) {
|
public @NotNull Color getBackgroundColor(@NotNull Ray ray) {
|
||||||
return background.getColor(ray);
|
return background.getColor(ray);
|
||||||
}
|
}
|
||||||
|
39
src/main/java/eu/jonahbauer/raytracing/scene/Target.java
Normal file
39
src/main/java/eu/jonahbauer/raytracing/scene/Target.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package eu.jonahbauer.raytracing.scene;
|
||||||
|
|
||||||
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
|
import eu.jonahbauer.raytracing.render.renderer.pdf.TargetingProbabilityDensityFunction;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface for objects that can be targeted. A target can construct randomly distributed directions in which
|
||||||
|
* it will be hit from a given origin.
|
||||||
|
* @see TargetingProbabilityDensityFunction
|
||||||
|
*/
|
||||||
|
public interface Target extends Hittable {
|
||||||
|
/**
|
||||||
|
* Returns the probability density for a direction as sampled by {@link #getTargetingDirection(Vec3, RandomGenerator)}.
|
||||||
|
* @param origin the origin
|
||||||
|
* @param direction the direction
|
||||||
|
* @return the probability density for a direction as sampled by {@link #getTargetingDirection(Vec3, RandomGenerator)}
|
||||||
|
*/
|
||||||
|
double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return a vector targeting this hittable from the <code>origin</code>} The vector is chosen randomly.
|
||||||
|
* @param origin the origin
|
||||||
|
* @param random a random number generator
|
||||||
|
*/
|
||||||
|
@NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default @NotNull Target translate(@NotNull Vec3 offset) {
|
||||||
|
return (Target) Hittable.super.translate(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default @NotNull Target rotateY(double angle) {
|
||||||
|
return (Target) Hittable.super.rotateY(angle);
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,17 @@
|
|||||||
package eu.jonahbauer.raytracing.scene.hittable2d;
|
package eu.jonahbauer.raytracing.scene.hittable2d;
|
||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.AABB;
|
import eu.jonahbauer.raytracing.math.AABB;
|
||||||
|
import eu.jonahbauer.raytracing.math.Range;
|
||||||
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.render.material.Material;
|
import eu.jonahbauer.raytracing.render.material.Material;
|
||||||
|
import eu.jonahbauer.raytracing.scene.Target;
|
||||||
|
import eu.jonahbauer.raytracing.scene.util.PdfUtil;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public final class Parallelogram extends Hittable2D {
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
|
public final class Parallelogram extends Hittable2D implements Target {
|
||||||
private final @NotNull AABB bbox;
|
private final @NotNull AABB bbox;
|
||||||
|
|
||||||
public Parallelogram(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) {
|
public Parallelogram(@NotNull Vec3 origin, @NotNull Vec3 u, @NotNull Vec3 v, @NotNull Material material) {
|
||||||
@ -22,4 +28,24 @@ public final class Parallelogram extends Hittable2D {
|
|||||||
public @NotNull AABB getBoundingBox() {
|
public @NotNull AABB getBoundingBox() {
|
||||||
return bbox;
|
return bbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||||
|
var result = hit(new Ray(origin, direction), new Range(0.001, Double.POSITIVE_INFINITY));
|
||||||
|
if (result.isEmpty()) return 0;
|
||||||
|
|
||||||
|
var a = this.origin;
|
||||||
|
var b = this.origin.plus(u);
|
||||||
|
var c = this.origin.plus(v);
|
||||||
|
var d = b.plus(v);
|
||||||
|
var angle = PdfUtil.getSolidAngle(origin, a, b, d) + PdfUtil.getSolidAngle(origin, c, b, d);
|
||||||
|
return 1 / angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) {
|
||||||
|
var alpha = random.nextDouble();
|
||||||
|
var beta = random.nextDouble();
|
||||||
|
return this.origin.plus(u.times(alpha)).plus(v.times(beta)).minus(origin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,13 +7,16 @@ import eu.jonahbauer.raytracing.math.Vec3;
|
|||||||
import eu.jonahbauer.raytracing.render.material.Material;
|
import eu.jonahbauer.raytracing.render.material.Material;
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
|
import eu.jonahbauer.raytracing.scene.Target;
|
||||||
|
import eu.jonahbauer.raytracing.scene.util.PdfUtil;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public final class Box implements Hittable {
|
public final class Box implements Hittable, Target {
|
||||||
private final @NotNull AABB box;
|
private final @NotNull AABB box;
|
||||||
private final @Nullable Material @NotNull[] materials;
|
private final @Nullable Material @NotNull[] materials;
|
||||||
|
|
||||||
@ -115,6 +118,61 @@ public final class Box implements Hittable {
|
|||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||||
|
if (contains(origin)) return 1 / (4 * Math.PI);
|
||||||
|
if (hit(new Ray(origin, direction), new Range(0.001, Double.POSITIVE_INFINITY)).isEmpty()) return 0;
|
||||||
|
|
||||||
|
var solidAngle = 0d;
|
||||||
|
for (var s : Side.values()) {
|
||||||
|
if (!s.isExterior(box, origin)) continue;
|
||||||
|
solidAngle += s.getSolidAngle(box, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1 / solidAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) {
|
||||||
|
if (contains(origin)) return Vec3.random(random, true);
|
||||||
|
|
||||||
|
// determine sides facing the origin and their solid angles
|
||||||
|
int visible = 0;
|
||||||
|
|
||||||
|
// at most three faces are visible
|
||||||
|
Side[] sides = new Side[3];
|
||||||
|
double[] solidAngle = new double[3];
|
||||||
|
|
||||||
|
double accumSolidAngle = 0;
|
||||||
|
for (var s : Side.values()) {
|
||||||
|
if (!s.isExterior(box, origin)) continue;
|
||||||
|
|
||||||
|
var sa = s.getSolidAngle(box, origin);
|
||||||
|
accumSolidAngle += sa;
|
||||||
|
sides[visible] = s;
|
||||||
|
solidAngle[visible] = accumSolidAngle;
|
||||||
|
visible++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// choose a random side facing the origin based on their relative solid angles
|
||||||
|
var r = random.nextDouble() * solidAngle[visible - 1];
|
||||||
|
for (int j = 0; j < visible; j++) {
|
||||||
|
if (r < solidAngle[j]) {
|
||||||
|
// choose a random point on that side
|
||||||
|
var target = sides[j].random(box, random);
|
||||||
|
return target.minus(origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new AssertionError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(@NotNull Vec3 point) {
|
||||||
|
return box.min().x() < point.x() && point.x() < box.max().x()
|
||||||
|
&& box.min().y() < point.y() && point.y() < box.max().y()
|
||||||
|
&& box.min().z() < point.z() && point.z() < box.max().z();
|
||||||
|
}
|
||||||
|
|
||||||
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()),
|
||||||
@ -155,5 +213,78 @@ public final class Box implements Hittable {
|
|||||||
case POS_Y -> (box.max().z() - pos.z()) / (box.max().z() - box.min().z());
|
case POS_Y -> (box.max().z() - pos.z()) / (box.max().z() - box.min().z());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return whether the given position is outside of the box only considering <code>this</code> side}
|
||||||
|
*/
|
||||||
|
public boolean isExterior(@NotNull AABB box, @NotNull Vec3 pos) {
|
||||||
|
return switch (this) {
|
||||||
|
case NEG_X -> pos.x() < box.min().x();
|
||||||
|
case NEG_Y -> pos.y() < box.min().y();
|
||||||
|
case NEG_Z -> pos.z() < box.min().z();
|
||||||
|
case POS_X -> pos.x() > box.max().x();
|
||||||
|
case POS_Y -> pos.y() > box.max().y();
|
||||||
|
case POS_Z -> pos.z() > box.max().z();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return the point on <code>this</code> side of the <code>box</code> with the given <code>u</code>-<code>v</code>-coordinates}
|
||||||
|
*/
|
||||||
|
public @NotNull Vec3 get(@NotNull AABB box, double u, double v) {
|
||||||
|
return switch (this) {
|
||||||
|
case NEG_X -> new Vec3(
|
||||||
|
box.min().x(),
|
||||||
|
Math.fma(v, box.max().y() - box.min().y(), box.min().y()),
|
||||||
|
Math.fma(u, box.max().z() - box.min().z(), box.min().z())
|
||||||
|
);
|
||||||
|
case NEG_Y -> new Vec3(
|
||||||
|
Math.fma(u, box.max().x() - box.min().x(), box.min().x()),
|
||||||
|
box.min().y(),
|
||||||
|
Math.fma(v, box.max().z() - box.min().z(), box.min().z())
|
||||||
|
);
|
||||||
|
case NEG_Z -> new Vec3(
|
||||||
|
Math.fma(u, box.min().x() - box.max().x(), box.max().x()),
|
||||||
|
Math.fma(v, box.max().y() - box.min().y(), box.min().y()),
|
||||||
|
box.min().z()
|
||||||
|
);
|
||||||
|
case POS_X -> new Vec3(
|
||||||
|
box.max().x(),
|
||||||
|
Math.fma(v, box.max().y() - box.min().y(), box.min().y()),
|
||||||
|
Math.fma(u, box.min().z() - box.max().z(), box.max().z())
|
||||||
|
);
|
||||||
|
case POS_Y -> new Vec3(
|
||||||
|
Math.fma(u, box.max().x() - box.min().x(), box.min().x()),
|
||||||
|
box.max().y(),
|
||||||
|
Math.fma(v, box.min().z() - box.max().z(), box.max().z())
|
||||||
|
);
|
||||||
|
case POS_Z -> new Vec3(
|
||||||
|
Math.fma(u, box.max().x() - box.min().x(), box.min().x()),
|
||||||
|
Math.fma(v, box.max().y() - box.min().y(), box.min().y()),
|
||||||
|
box.max().z()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return a random point on <code>this</code> side of the <code>box</code>}
|
||||||
|
*/
|
||||||
|
public @NotNull Vec3 random(@NotNull AABB box, @NotNull RandomGenerator random) {
|
||||||
|
var u = random.nextDouble();
|
||||||
|
var v = random.nextDouble();
|
||||||
|
return get(box, u, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return the solid angle covered by <code>this</code> side of the <code>box</code> when viewed from <code>pos</code>}
|
||||||
|
*/
|
||||||
|
public double getSolidAngle(@NotNull AABB box, @NotNull Vec3 pos) {
|
||||||
|
var a = get(box, 0, 0);
|
||||||
|
var b = get(box, 0, 1);
|
||||||
|
var c = get(box, 1, 1);
|
||||||
|
var d = get(box, 1, 0);
|
||||||
|
|
||||||
|
return PdfUtil.getSolidAngle(pos, a, b, d) + PdfUtil.getSolidAngle(pos, c, b, d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,14 @@ import eu.jonahbauer.raytracing.math.Ray;
|
|||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
|
import eu.jonahbauer.raytracing.scene.Target;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public final class Sphere implements Hittable {
|
public final class Sphere implements Hittable, Target {
|
||||||
private final @NotNull Vec3 center;
|
private final @NotNull Vec3 center;
|
||||||
private final double radius;
|
private final double radius;
|
||||||
private final @NotNull Material material;
|
private final @NotNull Material material;
|
||||||
@ -74,4 +76,25 @@ public final class Sphere implements Hittable {
|
|||||||
public @NotNull AABB getBoundingBox() {
|
public @NotNull AABB getBoundingBox() {
|
||||||
return bbox;
|
return bbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||||
|
if (hit(new Ray(origin, direction), new Range(0.001, Double.POSITIVE_INFINITY)).isEmpty()) return 0;
|
||||||
|
|
||||||
|
var cos = Math.sqrt(1 - radius * radius / (center.minus(origin).squared()));
|
||||||
|
var solidAngle = 2 * Math.PI * (1 - cos);
|
||||||
|
return 1 / solidAngle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) {
|
||||||
|
var direction = center.minus(origin);
|
||||||
|
|
||||||
|
Vec3 target;
|
||||||
|
do {
|
||||||
|
target = Vec3.random(random, true);
|
||||||
|
} while (target.times(direction) >= 0);
|
||||||
|
|
||||||
|
return target.times(radius).plus(center).minus(origin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,11 @@ import eu.jonahbauer.raytracing.math.Ray;
|
|||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
|
import eu.jonahbauer.raytracing.scene.Target;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public final class RotateY extends Transform {
|
public final class RotateY extends Transform {
|
||||||
private final double cos;
|
private final double cos;
|
||||||
private final double sin;
|
private final double sin;
|
||||||
@ -49,41 +52,44 @@ public final class RotateY extends Transform {
|
|||||||
var origin = ray.origin();
|
var origin = ray.origin();
|
||||||
var direction = ray.direction();
|
var direction = ray.direction();
|
||||||
|
|
||||||
var newOrigin = new Vec3(
|
var newOrigin = transform(origin);
|
||||||
cos * origin.x() - sin * origin.z(),
|
var newDirection = transform(direction);
|
||||||
origin.y(),
|
|
||||||
sin * origin.x() + cos * origin.z()
|
|
||||||
);
|
|
||||||
var newDirection = new Vec3(
|
|
||||||
cos * direction.x() - sin * direction.z(),
|
|
||||||
direction.y(),
|
|
||||||
sin * direction.x() + cos * direction.z()
|
|
||||||
);
|
|
||||||
|
|
||||||
return new Ray(newOrigin, newDirection);
|
return new Ray(newOrigin, newDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected @NotNull HitResult transform(@NotNull HitResult result) {
|
protected @NotNull HitResult transform(@NotNull HitResult result) {
|
||||||
var position = result.position();
|
var position = result.position();
|
||||||
var newPosition = new Vec3(
|
var newPosition = untransform(position);
|
||||||
cos * position.x() + sin * position.z(),
|
|
||||||
position.y(),
|
|
||||||
- sin * position.x() + cos * position.z()
|
|
||||||
);
|
|
||||||
|
|
||||||
var normal = result.normal();
|
var normal = result.normal();
|
||||||
var newNormal = new Vec3(
|
var newNormal = untransform(normal);
|
||||||
cos * normal.x() + sin * normal.z(),
|
|
||||||
normal.y(),
|
|
||||||
-sin * normal.x() + cos * normal.z()
|
|
||||||
);
|
|
||||||
|
|
||||||
return result.withPositionAndNormal(newPosition, newNormal);
|
return result.withPositionAndNormal(newPosition, newNormal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @NotNull Vec3 transform(@NotNull Vec3 vec) {
|
||||||
|
return new Vec3(cos * vec.x() - sin * vec.z(), vec.y(), sin * vec.x() + cos * vec.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NotNull Vec3 untransform(@NotNull Vec3 vec) {
|
||||||
|
return new Vec3(cos * vec.x() + sin * vec.z(), vec.y(), - sin * vec.x() + cos * vec.z());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull AABB getBoundingBox() {
|
public @NotNull AABB getBoundingBox() {
|
||||||
return bbox;
|
return bbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||||
|
if (!(object instanceof Target target)) throw new UnsupportedOperationException();
|
||||||
|
return target.getProbabilityDensity(transform(origin), transform(direction));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) {
|
||||||
|
if (!(object instanceof Target target)) throw new UnsupportedOperationException();
|
||||||
|
return untransform(target.getTargetingDirection(transform(origin), random));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,17 @@ package eu.jonahbauer.raytracing.scene.transform;
|
|||||||
|
|
||||||
import eu.jonahbauer.raytracing.math.Range;
|
import eu.jonahbauer.raytracing.math.Range;
|
||||||
import eu.jonahbauer.raytracing.math.Ray;
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
|
import eu.jonahbauer.raytracing.scene.Target;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public abstract class Transform implements Hittable {
|
public abstract class Transform implements Hittable, Target {
|
||||||
protected final @NotNull Hittable object;
|
protected final @NotNull Hittable object;
|
||||||
|
|
||||||
protected Transform(@NotNull Hittable object) {
|
protected Transform(@NotNull Hittable object) {
|
||||||
|
@ -5,8 +5,11 @@ import eu.jonahbauer.raytracing.math.Ray;
|
|||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||||
import eu.jonahbauer.raytracing.scene.Hittable;
|
import eu.jonahbauer.raytracing.scene.Hittable;
|
||||||
|
import eu.jonahbauer.raytracing.scene.Target;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public final class Translate extends Transform {
|
public final class Translate extends Transform {
|
||||||
private final @NotNull Vec3 offset;
|
private final @NotNull Vec3 offset;
|
||||||
private final @NotNull AABB bbox;
|
private final @NotNull AABB bbox;
|
||||||
@ -36,4 +39,16 @@ public final class Translate extends Transform {
|
|||||||
public @NotNull AABB getBoundingBox() {
|
public @NotNull AABB getBoundingBox() {
|
||||||
return bbox;
|
return bbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getProbabilityDensity(@NotNull Vec3 origin, @NotNull Vec3 direction) {
|
||||||
|
if (!(object instanceof Target target)) throw new UnsupportedOperationException();
|
||||||
|
return target.getProbabilityDensity(origin.minus(offset), direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull Vec3 getTargetingDirection(@NotNull Vec3 origin, @NotNull RandomGenerator random) {
|
||||||
|
if (!(object instanceof Target target)) throw new UnsupportedOperationException();
|
||||||
|
return target.getTargetingDirection(origin.minus(offset), random);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package eu.jonahbauer.raytracing.scene.util;
|
||||||
|
|
||||||
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public final class PdfUtil {
|
||||||
|
private PdfUtil() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getSolidAngle(@NotNull Vec3 o, @NotNull Vec3 a, @NotNull Vec3 b, @NotNull Vec3 c) {
|
||||||
|
var i = a.minus(o).unit();
|
||||||
|
var j = b.minus(o).unit();
|
||||||
|
var k = c.minus(o).unit();
|
||||||
|
|
||||||
|
return 2 * Math.atan(Math.abs(i.times(j.cross(k))) / (1 + i.times(j) + j.times(k) + k.times(i)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user