use a more performant RandomGenerator
This commit is contained in:
parent
5bf3108ab4
commit
1fe5731dcf
@ -3,6 +3,7 @@ package eu.jonahbauer.raytracing.math;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public record Vec3(double x, double y, double z) {
|
public record Vec3(double x, double y, double z) {
|
||||||
public static final Vec3 ZERO = new Vec3(0, 0, 0);
|
public static final Vec3 ZERO = new Vec3(0, 0, 0);
|
||||||
@ -16,17 +17,17 @@ public record Vec3(double x, double y, double z) {
|
|||||||
assert Double.isFinite(x) && Double.isFinite(y) && Double.isFinite(z) : "x, y and z must be finite";
|
assert Double.isFinite(x) && Double.isFinite(y) && Double.isFinite(z) : "x, y and z must be finite";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull Vec3 random() {
|
public static @NotNull Vec3 random(@NotNull RandomGenerator random) {
|
||||||
return random(false);
|
return random(random, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull Vec3 random(boolean unit) {
|
public static @NotNull Vec3 random(@NotNull RandomGenerator random, boolean unit) {
|
||||||
var random = new Vec3(
|
var vec = new Vec3(
|
||||||
2 * Math.random() - 1,
|
2 * random.nextDouble() - 1,
|
||||||
2 * Math.random() - 1,
|
2 * random.nextDouble() - 1,
|
||||||
2 * Math.random() - 1
|
2 * random.nextDouble() - 1
|
||||||
);
|
);
|
||||||
return unit ? random.unit() : random;
|
return unit ? vec.unit() : vec;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NotNull Vec3 reflect(@NotNull Vec3 vec, @NotNull Vec3 normal) {
|
public static @NotNull Vec3 reflect(@NotNull Vec3 vec, @NotNull Vec3 normal) {
|
||||||
|
@ -3,6 +3,8 @@ package eu.jonahbauer.raytracing.render.camera;
|
|||||||
import eu.jonahbauer.raytracing.math.Ray;
|
import eu.jonahbauer.raytracing.math.Ray;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public interface Camera {
|
public interface Camera {
|
||||||
/**
|
/**
|
||||||
* {@return the width of this camera in pixels}
|
* {@return the width of this camera in pixels}
|
||||||
@ -18,5 +20,5 @@ public interface Camera {
|
|||||||
* Casts a ray through the given pixel.
|
* Casts a ray through the given pixel.
|
||||||
* @return a new ray
|
* @return a new ray
|
||||||
*/
|
*/
|
||||||
@NotNull Ray cast(int x, int y);
|
@NotNull Ray cast(int x, int y, @NotNull RandomGenerator random);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ 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.random.RandomGenerator;
|
||||||
|
|
||||||
public final class SimpleCamera implements Camera {
|
public final class SimpleCamera implements Camera {
|
||||||
// image size
|
// image size
|
||||||
@ -79,12 +80,12 @@ public final class SimpleCamera implements Camera {
|
|||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
public @NotNull Ray cast(int x, int y) {
|
public @NotNull Ray cast(int x, int y, @NotNull RandomGenerator random) {
|
||||||
Objects.checkIndex(x, width);
|
Objects.checkIndex(x, width);
|
||||||
Objects.checkIndex(y, height);
|
Objects.checkIndex(y, height);
|
||||||
|
|
||||||
var origin = getRayOrigin();
|
var origin = getRayOrigin(random);
|
||||||
var target = getRayTarget(x, y);
|
var target = getRayTarget(x, y, random);
|
||||||
return new Ray(origin, target.minus(origin));
|
return new Ray(origin, target.minus(origin));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,12 +94,12 @@ public final class SimpleCamera implements Camera {
|
|||||||
* radius {@link #blurRadius} centered on the camera position and perpendicular to the direction to simulate depth
|
* radius {@link #blurRadius} centered on the camera position and perpendicular to the direction to simulate depth
|
||||||
* of field.
|
* of field.
|
||||||
*/
|
*/
|
||||||
private @NotNull Vec3 getRayOrigin() {
|
private @NotNull Vec3 getRayOrigin(@NotNull RandomGenerator random) {
|
||||||
if (blurRadius <= 0) return origin;
|
if (blurRadius <= 0) return origin;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var du = 2 * Math.random() - 1;
|
var du = 2 * random.nextDouble() - 1;
|
||||||
var dv = 2 * Math.random() - 1;
|
var dv = 2 * random.nextDouble() - 1;
|
||||||
if (du * du + dv * dv >= 1) continue;
|
if (du * du + dv * dv >= 1) continue;
|
||||||
|
|
||||||
var ru = blurRadius * du;
|
var ru = blurRadius * du;
|
||||||
@ -115,9 +116,9 @@ public final class SimpleCamera implements Camera {
|
|||||||
/**
|
/**
|
||||||
* {@return the target vector for a ray through the given pixel} The position is randomized within the pixel.
|
* {@return the target vector for a ray through the given pixel} The position is randomized within the pixel.
|
||||||
*/
|
*/
|
||||||
private @NotNull Vec3 getRayTarget(int x, int y) {
|
private @NotNull Vec3 getRayTarget(int x, int y, @NotNull RandomGenerator random) {
|
||||||
double dx = x + Math.random() - 0.5;
|
double dx = x + random.nextDouble() - 0.5;
|
||||||
double dy = y + Math.random() - 0.5;
|
double dy = y + random.nextDouble() - 0.5;
|
||||||
return pixel00.plus(pixelU.times(dx)).plus(pixelV.times(dy));
|
return pixel00.plus(pixelU.times(dx)).plus(pixelV.times(dy));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ 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 record DielectricMaterial(double refractionIndex, @NotNull Color albedo) implements Material {
|
public record DielectricMaterial(double refractionIndex, @NotNull Color albedo) implements Material {
|
||||||
public DielectricMaterial(double refractionIndex) {
|
public DielectricMaterial(double refractionIndex) {
|
||||||
@ -19,12 +20,12 @@ public record DielectricMaterial(double refractionIndex, @NotNull Color albedo)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit) {
|
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||||
var ri = hit.frontFace() ? (1 / refractionIndex) : refractionIndex;
|
var ri = hit.frontFace() ? (1 / refractionIndex) : refractionIndex;
|
||||||
|
|
||||||
var cosTheta = Math.min(- ray.direction().unit().times(hit.normal()), 1.0);
|
var cosTheta = Math.min(- ray.direction().unit().times(hit.normal()), 1.0);
|
||||||
var reflectance = reflectance(cosTheta);
|
var reflectance = reflectance(cosTheta);
|
||||||
var reflect = reflectance > Math.random();
|
var reflect = reflectance > random.nextDouble();
|
||||||
|
|
||||||
var newDirection = (reflect ? Optional.<Vec3>empty() : Vec3.refract(ray.direction(), hit.normal(), ri))
|
var newDirection = (reflect ? Optional.<Vec3>empty() : Vec3.refract(ray.direction(), hit.normal(), ri))
|
||||||
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
|
.orElseGet(() -> Vec3.reflect(ray.direction(), hit.normal()));
|
||||||
|
@ -6,10 +6,11 @@ import eu.jonahbauer.raytracing.scene.HitResult;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public record DiffuseLight(@NotNull Color emit) implements Material {
|
public record DiffuseLight(@NotNull Color emit) implements Material {
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit) {
|
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,10 +7,11 @@ import eu.jonahbauer.raytracing.scene.HitResult;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
|
|
||||||
public record IsotropicMaterial(@NotNull Color albedo) implements Material{
|
public record IsotropicMaterial(@NotNull Color albedo) implements Material{
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit) {
|
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(true)), albedo()));
|
return Optional.of(new ScatterResult(new Ray(hit.position(), Vec3.random(random, true)), albedo()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ 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 record LambertianMaterial(@NotNull Color albedo) implements Material {
|
public record LambertianMaterial(@NotNull Color albedo) implements Material {
|
||||||
public LambertianMaterial {
|
public LambertianMaterial {
|
||||||
@ -15,8 +16,8 @@ public record LambertianMaterial(@NotNull Color albedo) implements Material {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit) {
|
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||||
var newDirection = hit.normal().plus(Vec3.random(true));
|
var newDirection = hit.normal().plus(Vec3.random(random, true));
|
||||||
if (newDirection.isNearZero()) newDirection = hit.normal();
|
if (newDirection.isNearZero()) newDirection = hit.normal();
|
||||||
|
|
||||||
var scattered = new Ray(hit.position(), newDirection);
|
var scattered = new Ray(hit.position(), newDirection);
|
||||||
|
@ -7,10 +7,11 @@ 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 interface Material {
|
public interface Material {
|
||||||
|
|
||||||
@NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit);
|
@NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random);
|
||||||
|
|
||||||
default @NotNull Color emitted(@NotNull HitResult hit) {
|
default @NotNull Color emitted(@NotNull HitResult hit) {
|
||||||
return Color.BLACK;
|
return Color.BLACK;
|
||||||
|
@ -8,6 +8,7 @@ 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 record MetallicMaterial(@NotNull Color albedo, double fuzz) implements Material {
|
public record MetallicMaterial(@NotNull Color albedo, double fuzz) implements Material {
|
||||||
|
|
||||||
@ -21,10 +22,10 @@ public record MetallicMaterial(@NotNull Color albedo, double fuzz) implements Ma
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit) {
|
public @NotNull Optional<ScatterResult> scatter(@NotNull Ray ray, @NotNull HitResult hit, @NotNull RandomGenerator random) {
|
||||||
var newDirection = Vec3.reflect(ray.direction(), hit.normal());
|
var newDirection = Vec3.reflect(ray.direction(), hit.normal());
|
||||||
if (fuzz > 0) {
|
if (fuzz > 0) {
|
||||||
newDirection = newDirection.unit().plus(Vec3.random(true).times(fuzz));
|
newDirection = newDirection.unit().plus(Vec3.random(random, true).times(fuzz));
|
||||||
}
|
}
|
||||||
return Optional.of(new ScatterResult(new Ray(hit.position(), newDirection), albedo));
|
return Optional.of(new ScatterResult(new Ray(hit.position(), newDirection), albedo));
|
||||||
}
|
}
|
||||||
|
@ -8,9 +8,10 @@ import eu.jonahbauer.raytracing.render.canvas.Canvas;
|
|||||||
import eu.jonahbauer.raytracing.scene.Scene;
|
import eu.jonahbauer.raytracing.scene.Scene;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.Random;
|
||||||
|
import java.util.SplittableRandom;
|
||||||
|
import java.util.random.RandomGenerator;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.LongStream;
|
|
||||||
|
|
||||||
public final class SimpleRenderer implements Renderer {
|
public final class SimpleRenderer implements Renderer {
|
||||||
private final int samplesPerPixel;
|
private final int samplesPerPixel;
|
||||||
@ -44,36 +45,40 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (iterative) {
|
if (iterative) {
|
||||||
|
var random = new Random();
|
||||||
|
|
||||||
// render one sample after the other
|
// render one sample after the other
|
||||||
for (int i = 1 ; i <= samplesPerPixel; i++) {
|
for (int i = 1 ; i <= samplesPerPixel; i++) {
|
||||||
var sample = i;
|
var sample = i;
|
||||||
getPixelStream(camera.getWidth(), camera.getHeight(), parallel).forEach(pixel -> {
|
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
|
||||||
var y = (int) (pixel >> 32);
|
for (int x = 0; x < camera.getWidth(); x++) {
|
||||||
var x = (int) pixel;
|
var ray = camera.cast(x, y, random);
|
||||||
var ray = camera.cast(x, y);
|
var c = getColor(scene, ray, random);
|
||||||
var c = getColor(scene, ray);
|
canvas.set(x, y, Color.average(canvas.get(x, y), c, sample));
|
||||||
canvas.set(x, y, Color.average(canvas.get(x, y), c, sample));
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply gamma correction
|
// apply gamma correction
|
||||||
getPixelStream(camera.getWidth(), camera.getHeight(), parallel).forEach(pixel -> {
|
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
|
||||||
var y = (int) (pixel >> 32);
|
for (int x = 0; x < camera.getWidth(); x++) {
|
||||||
var x = (int) pixel;
|
canvas.set(x, y, Color.gamma(canvas.get(x, y), gamma));
|
||||||
canvas.set(x, y, Color.gamma(canvas.get(x, y), gamma));
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
var splittable = new SplittableRandom();
|
||||||
// render one pixel after the other
|
// render one pixel after the other
|
||||||
getPixelStream(camera.getWidth(), camera.getHeight(), parallel).forEach(pixel -> {
|
getScanlineStream(camera.getHeight(), parallel).forEach(y -> {
|
||||||
var y = (int) (pixel >> 32);
|
var random = splittable.split();
|
||||||
var x = (int) pixel;
|
for (int x = 0; x < camera.getWidth(); x++) {
|
||||||
|
var color = Color.BLACK;
|
||||||
var color = Color.BLACK;
|
for (int i = 1; i <= samplesPerPixel; i++) {
|
||||||
for (int i = 1; i <= samplesPerPixel; i++) {
|
var ray = camera.cast(x, y, random);
|
||||||
var ray = camera.cast(x, y);
|
var c = getColor(scene, ray, random);
|
||||||
var c = getColor(scene, ray);
|
color = Color.average(color, c, i);
|
||||||
color = Color.average(color, c, i);
|
}
|
||||||
|
canvas.set(x, y, Color.gamma(color, gamma));
|
||||||
}
|
}
|
||||||
canvas.set(x, y, Color.gamma(color, gamma));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,11 +86,11 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
/**
|
/**
|
||||||
* {@return the color of the given ray in the given scene}
|
* {@return the color of the given ray in the given scene}
|
||||||
*/
|
*/
|
||||||
private @NotNull Color getColor(@NotNull Scene scene, @NotNull Ray ray) {
|
private @NotNull Color getColor(@NotNull Scene scene, @NotNull Ray ray, @NotNull RandomGenerator random) {
|
||||||
return getColor0(scene, ray, maxDepth);
|
return getColor0(scene, ray, maxDepth, random);
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NotNull Color getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth) {
|
private @NotNull Color getColor0(@NotNull Scene scene, @NotNull Ray ray, int depth, @NotNull RandomGenerator random) {
|
||||||
var color = Color.BLACK;
|
var color = Color.BLACK;
|
||||||
var attenuation = Color.WHITE;
|
var attenuation = Color.WHITE;
|
||||||
|
|
||||||
@ -99,7 +104,7 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
var hit = optional.get();
|
var hit = optional.get();
|
||||||
var material = hit.material();
|
var material = hit.material();
|
||||||
var emitted = material.emitted(hit);
|
var emitted = material.emitted(hit);
|
||||||
var scatter = material.scatter(ray, hit);
|
var scatter = 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;
|
if (scatter.isEmpty()) break;
|
||||||
@ -114,10 +119,8 @@ public final class SimpleRenderer implements Renderer {
|
|||||||
* {@return a stream of the pixels in a canvas with the given size} The pixels {@code x} and {@code y} coordinate
|
* {@return a stream of the pixels in a canvas with the given size} The pixels {@code x} and {@code y} coordinate
|
||||||
* are encoded in the longs lower and upper 32 bits respectively.
|
* are encoded in the longs lower and upper 32 bits respectively.
|
||||||
*/
|
*/
|
||||||
private static @NotNull LongStream getPixelStream(int width, int height, boolean parallel) {
|
private static @NotNull IntStream getScanlineStream(int height, boolean parallel) {
|
||||||
var stream = IntStream.range(0, height)
|
var stream = IntStream.range(0, height);
|
||||||
.mapToObj(y -> IntStream.range(0, width).mapToLong(x -> (long) y << 32 | x))
|
|
||||||
.flatMapToLong(Function.identity());
|
|
||||||
return parallel ? stream.parallel() : stream;
|
return parallel ? stream.parallel() : stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user