add probability density function based materials
This commit is contained in:
parent
5f1e816edd
commit
6b47f44ad2
@ -32,7 +32,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 ScatterResult(new Ray(hit.position(), newDirection), attenuation));
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection)));
|
||||
}
|
||||
|
||||
private double reflectance(double cos) {
|
||||
|
@ -18,6 +18,7 @@ public final class DirectionalMaterial implements Material {
|
||||
private final @NotNull Texture texture;
|
||||
|
||||
public DirectionalMaterial(@Nullable Material front, @Nullable Material back) {
|
||||
if (front == null && back == null) throw new IllegalArgumentException("front and back must not both be null");
|
||||
this.front = front;
|
||||
this.back = back;
|
||||
this.texture = new DirectionalTexture(
|
||||
@ -39,7 +40,7 @@ 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 ScatterResult(new Ray(ray.at(hit.t()), ray.direction()), Color.WHITE));
|
||||
return Optional.of(new SpecularScatterResult(Color.WHITE, new Ray(ray.at(hit.t()), ray.direction())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -49,7 +50,7 @@ public final class DirectionalMaterial implements Material {
|
||||
} else {
|
||||
if (back != null) return back.emitted(hit);
|
||||
}
|
||||
return Color.BLACK;
|
||||
return Material.super.emitted(hit);
|
||||
}
|
||||
|
||||
private record DirectionalTexture(@Nullable Texture front, @Nullable Texture back) implements Texture {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.SphereProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
@ -13,7 +13,7 @@ import java.util.random.RandomGenerator;
|
||||
public record IsotropicMaterial(@NotNull Color albedo) implements Material {
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
return Optional.of(new ScatterResult(new Ray(hit.position(), Vec3.random(random, true)), albedo()));
|
||||
return Optional.of(new PdfScatterResult(albedo(), new SphereProbabilityDensityFunction()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,7 +1,7 @@
|
||||
package eu.jonahbauer.raytracing.render.material;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.CosineProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@ -17,11 +17,7 @@ public record LambertianMaterial(@NotNull Texture texture) implements Material {
|
||||
|
||||
@Override
|
||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||
var newDirection = hit.normal().plus(Vec3.random(random, true));
|
||||
if (newDirection.isNearZero()) newDirection = hit.normal();
|
||||
|
||||
var attenuation = texture.get(hit);
|
||||
var scattered = new Ray(hit.position(), newDirection);
|
||||
return Optional.of(new ScatterResult(scattered, attenuation));
|
||||
return Optional.of(new PdfScatterResult(attenuation, new CosineProbabilityDensityFunction(hit.normal())));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
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.texture.Texture;
|
||||
import eu.jonahbauer.raytracing.scene.HitResult;
|
||||
@ -12,18 +13,59 @@ import java.util.random.RandomGenerator;
|
||||
|
||||
public interface Material {
|
||||
|
||||
/**
|
||||
* {@return the texture associated with this material}
|
||||
*/
|
||||
@NotNull Texture texture();
|
||||
|
||||
/**
|
||||
* Scatters a light ray after it hit a surface.
|
||||
* @param ray the incoming light ray
|
||||
* @param hit information about the light ray hitting some object
|
||||
* @param random a random number generator
|
||||
* @return a {@code ScatterResult} if the ray is scattered or an {@linkplain Optional#empty() empty optional} if the
|
||||
* ray is absorbed.
|
||||
*/
|
||||
@NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random);
|
||||
|
||||
/**
|
||||
* {@return the color emitted for a given hit}
|
||||
* @implSpec the default implementation returns {@linkplain Color#BLACK black}, i.e. no emission
|
||||
*/
|
||||
default @NotNull Color emitted(@NotNull HitResult hit) {
|
||||
return Color.BLACK;
|
||||
}
|
||||
|
||||
record ScatterResult(@NotNull Ray ray, @NotNull Color attenuation) {
|
||||
public ScatterResult {
|
||||
Objects.requireNonNull(ray, "ray");
|
||||
/**
|
||||
* The result of a {@linkplain Material#scatter(Ray, HitResult, RandomGenerator) scattering operation}.
|
||||
*/
|
||||
sealed interface ScatterResult {}
|
||||
|
||||
/**
|
||||
* The result of a specular {@linkplain #scatter(Ray, HitResult, RandomGenerator) scattering operation}. A
|
||||
* specular is a scattering operation with a very small number of possible scattered rays (like a
|
||||
* perfect reflection which only has one possible scattered ray).
|
||||
* @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 {
|
||||
public SpecularScatterResult {
|
||||
Objects.requireNonNull(attenuation, "attenuation");
|
||||
Objects.requireNonNull(ray, "ray");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The result of a probability density function based
|
||||
* {@linkplain #scatter(Ray, HitResult, RandomGenerator) scattering operation}. A probability density function
|
||||
* based scattering operation uses a probability density function to determine the scatter direction.
|
||||
* @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 {
|
||||
public PdfScatterResult {
|
||||
Objects.requireNonNull(attenuation, "attenuation");
|
||||
Objects.requireNonNull(pdf, "pdf");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,6 @@ public record MetallicMaterial(@NotNull Texture texture, double fuzz) implements
|
||||
newDirection = newDirection.unit().plus(Vec3.random(random, true).times(fuzz));
|
||||
}
|
||||
var attenuation = texture.get(hit);
|
||||
return Optional.of(new ScatterResult(new Ray(hit.position(), newDirection), attenuation));
|
||||
return Optional.of(new SpecularScatterResult(attenuation, new Ray(hit.position(), newDirection)));
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@ package eu.jonahbauer.raytracing.render.renderer;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Range;
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.render.material.Material;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.HittableProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.renderer.pdf.MixtureProbabilityDensityFunction;
|
||||
import eu.jonahbauer.raytracing.render.texture.Color;
|
||||
import eu.jonahbauer.raytracing.render.camera.Camera;
|
||||
import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
||||
@ -131,12 +134,24 @@ public final class SimpleRenderer implements Renderer {
|
||||
var hit = optional.get();
|
||||
var material = hit.material();
|
||||
var emitted = material.emitted(hit);
|
||||
var scatter = material.scatter(ray, hit, random);
|
||||
var result = material.scatter(ray, hit, random);
|
||||
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;
|
||||
|
||||
switch (result.get()) {
|
||||
case Material.SpecularScatterResult(var a, var scattered) -> {
|
||||
attenuation = Color.multiply(attenuation, a);
|
||||
ray = scattered;
|
||||
}
|
||||
case Material.PdfScatterResult(var a, var pdf) -> {
|
||||
attenuation = Color.multiply(attenuation, a);
|
||||
ray = new Ray(hit.position(), pdf.generate(random));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
|
@ -0,0 +1,27 @@
|
||||
package eu.jonahbauer.raytracing.render.renderer.pdf;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public record CosineProbabilityDensityFunction(@NotNull Vec3 normal) implements ProbabilityDensityFunction {
|
||||
|
||||
public CosineProbabilityDensityFunction {
|
||||
Objects.requireNonNull(normal, "normal");
|
||||
normal = normal.unit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double value(@NotNull Vec3 direction) {
|
||||
var cos = normal.times(direction.unit());
|
||||
return Math.max(0, cos / Math.PI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Vec3 generate(@NotNull RandomGenerator random) {
|
||||
var out = normal().plus(Vec3.random(random, true));
|
||||
return out.isNearZero() ? normal() : out;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package eu.jonahbauer.raytracing.render.renderer.pdf;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public record MixtureProbabilityDensityFunction(
|
||||
@NotNull ProbabilityDensityFunction a,
|
||||
@NotNull ProbabilityDensityFunction b,
|
||||
double weight
|
||||
) implements ProbabilityDensityFunction {
|
||||
public MixtureProbabilityDensityFunction(@NotNull ProbabilityDensityFunction a, @NotNull ProbabilityDensityFunction b) {
|
||||
this(a, b, 0.5);
|
||||
}
|
||||
|
||||
public MixtureProbabilityDensityFunction {
|
||||
Objects.requireNonNull(a);
|
||||
Objects.requireNonNull(b);
|
||||
weight = Math.clamp(weight, 0, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double value(@NotNull Vec3 direction) {
|
||||
return weight * a.value(direction) + (1 - weight) * b.value(direction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Vec3 generate(@NotNull RandomGenerator random) {
|
||||
if (random.nextDouble() < weight) {
|
||||
return a.generate(random);
|
||||
} else {
|
||||
return b.generate(random);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package eu.jonahbauer.raytracing.render.renderer.pdf;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public interface ProbabilityDensityFunction {
|
||||
double value(@NotNull Vec3 direction);
|
||||
@NotNull Vec3 generate(@NotNull RandomGenerator random);
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package eu.jonahbauer.raytracing.render.renderer.pdf;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.random.RandomGenerator;
|
||||
|
||||
public record SphereProbabilityDensityFunction() implements ProbabilityDensityFunction {
|
||||
|
||||
@Override
|
||||
public double value(@NotNull Vec3 direction) {
|
||||
return 1 / (4 * Math.PI);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Vec3 generate(@NotNull RandomGenerator random) {
|
||||
return Vec3.random(random, true);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user