add Camera.Builder
This commit is contained in:
parent
94231f6a5b
commit
d89d15f1a4
@ -22,11 +22,13 @@ public class Main {
|
|||||||
new Sphere(-1, 0, - 1, 0.4, new DielectricMaterial(1 / 1.5)),
|
new Sphere(-1, 0, - 1, 0.4, new DielectricMaterial(1 / 1.5)),
|
||||||
new Sphere(1, 0, - 1, 0.5, new MetallicMaterial(new Color(0.8, 0.6, 0.2), 1.0))
|
new Sphere(1, 0, - 1, 0.5, new MetallicMaterial(new Color(0.8, 0.6, 0.2), 1.0))
|
||||||
);
|
);
|
||||||
var camera = new Camera(
|
|
||||||
800, 450,
|
var camera = Camera.builder()
|
||||||
16d / 9 * 2, 2d,
|
.withImage(800, 450)
|
||||||
Vec3.ZERO.plus(Vec3.UNIT_Z).plus(Vec3.UNIT_Y).minus(Vec3.UNIT_X), new Vec3(0.25, -0.5, - 1)
|
.withPosition(new Vec3(-2, 2, 1))
|
||||||
);
|
.withTarget(new Vec3(0, 0, -1))
|
||||||
|
.withFieldOfView(Math.toRadians(20))
|
||||||
|
.build();
|
||||||
|
|
||||||
var image = camera.render(scene);
|
var image = camera.render(scene);
|
||||||
ImageFormat.PNG.write(image, Path.of("scene-" + System.currentTimeMillis() + ".png"));
|
ImageFormat.PNG.write(image, Path.of("scene-" + System.currentTimeMillis() + ".png"));
|
||||||
|
@ -5,6 +5,7 @@ import eu.jonahbauer.raytracing.math.Ray;
|
|||||||
import eu.jonahbauer.raytracing.math.Vec3;
|
import eu.jonahbauer.raytracing.math.Vec3;
|
||||||
import eu.jonahbauer.raytracing.scene.Scene;
|
import eu.jonahbauer.raytracing.scene.Scene;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@ -13,77 +14,57 @@ public final class Camera {
|
|||||||
private final int width;
|
private final int width;
|
||||||
private final int height;
|
private final int height;
|
||||||
|
|
||||||
// viewport size
|
|
||||||
private final double viewportWidth;
|
|
||||||
private final double viewportHeight;
|
|
||||||
|
|
||||||
// camera position and orientation
|
// camera position and orientation
|
||||||
private final @NotNull Vec3 origin;
|
private final @NotNull Vec3 origin;
|
||||||
private final @NotNull Vec3 direction;
|
|
||||||
|
|
||||||
// rendering
|
// rendering
|
||||||
private final int samplesPerPixel = 100;
|
private final int samplesPerPixel;
|
||||||
private final int maxDepth = 10;
|
private final int maxDepth;
|
||||||
private final double gamma = 2.0;
|
private final double gamma;
|
||||||
|
|
||||||
// internal properties
|
// internal properties
|
||||||
|
private final @NotNull Vec3 u;
|
||||||
|
private final @NotNull Vec3 v;
|
||||||
|
|
||||||
private final @NotNull Vec3 pixelU;
|
private final @NotNull Vec3 pixelU;
|
||||||
private final @NotNull Vec3 pixelV;
|
private final @NotNull Vec3 pixelV;
|
||||||
private final @NotNull Vec3 pixel00;
|
private final @NotNull Vec3 pixel00;
|
||||||
|
|
||||||
/**
|
public static @NotNull Builder builder() {
|
||||||
* Creates a new camera with the given dimensions at the origin facing towards positive z with a focal length of 1.
|
return new Builder();
|
||||||
* @param height the image height
|
|
||||||
* @param viewportHeight the viewport height
|
|
||||||
* @param aspectRatio the aspect ratio
|
|
||||||
*/
|
|
||||||
public Camera(int height, double viewportHeight, double aspectRatio) {
|
|
||||||
this((int) (height * aspectRatio), height, viewportHeight * aspectRatio, viewportHeight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public Camera() {
|
||||||
* Creates a new camera with the given dimensions at the origin facing towards positive z with a focal length of 1.
|
this(new Builder());
|
||||||
* @param width the image width
|
|
||||||
* @param height the image height
|
|
||||||
* @param viewportWidth the viewport width
|
|
||||||
* @param viewportHeight the viewport height
|
|
||||||
*/
|
|
||||||
public Camera(int width, int height, double viewportWidth, double viewportHeight) {
|
|
||||||
this(width, height, viewportWidth, viewportHeight, Vec3.ZERO, Vec3.UNIT_Z.neg());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Camera(
|
private Camera(@NotNull Builder builder) {
|
||||||
int width, int height,
|
this.width = builder.imageWidth;
|
||||||
double viewportWidth, double viewportHeight,
|
this.height = builder.imageHeight;
|
||||||
@NotNull Vec3 origin, @NotNull Vec3 direction
|
|
||||||
) {
|
|
||||||
if (width <= 0) throw new IllegalArgumentException("width must be positive");
|
|
||||||
if (height <= 0) throw new IllegalArgumentException("height must be positive");
|
|
||||||
if (viewportWidth <= 0 || !Double.isFinite(viewportWidth)) throw new IllegalArgumentException("viewportWidth must be positive");
|
|
||||||
if (viewportHeight <= 0 || !Double.isFinite(viewportHeight)) throw new IllegalArgumentException("viewportHeight must be positive");
|
|
||||||
|
|
||||||
this.width = width;
|
var viewportHeight = 2 * Math.tan(0.5 * builder.fov);
|
||||||
this.height = height;
|
var viewportWidth = viewportHeight * ((double) width / height);
|
||||||
this.viewportWidth = viewportWidth;
|
|
||||||
this.viewportHeight = viewportHeight;
|
|
||||||
this.origin = Objects.requireNonNull(origin, "origin");
|
|
||||||
this.direction = Objects.requireNonNull(direction, "direction");
|
|
||||||
|
|
||||||
// project direction onto xz-plane
|
this.origin = builder.position;
|
||||||
var d = direction.unit();
|
var direction = (builder.direction == null ? builder.target.minus(builder.position).unit() : builder.direction);
|
||||||
|
|
||||||
|
this.samplesPerPixel = builder.samplePerPixel;
|
||||||
|
this.maxDepth = builder.maxDepth;
|
||||||
|
this.gamma = builder.gamma;
|
||||||
|
|
||||||
|
// project direction the horizontal plane
|
||||||
var dXZ = direction.withY(0).unit();
|
var dXZ = direction.withY(0).unit();
|
||||||
|
this.u = Vec3.rotate(
|
||||||
|
new Vec3(- dXZ.z(), 0, dXZ.x()), // perpendicular to d in horizontal plane
|
||||||
|
direction, builder.rotation
|
||||||
|
);
|
||||||
|
this.v = direction.cross(u); // perpendicular to viewportU and direction
|
||||||
|
|
||||||
var viewportU = new Vec3(- dXZ.z(), 0, dXZ.x()); // perpendicular to dXZ in xz-plane
|
this.pixelU = u.times(viewportWidth / width);
|
||||||
var viewportV = d.cross(viewportU); // perpendicular to viewportU and direction
|
this.pixelV = v.times(viewportHeight / height);
|
||||||
|
|
||||||
viewportU = viewportU.times(viewportWidth); // vector along the width of the viewport
|
|
||||||
viewportV = viewportV.times(viewportHeight); // vector along the height of the viewport
|
|
||||||
|
|
||||||
this.pixelU = viewportU.div(width);
|
|
||||||
this.pixelV = viewportV.div(height);
|
|
||||||
|
|
||||||
this.pixel00 = origin.plus(direction)
|
this.pixel00 = origin.plus(direction)
|
||||||
.minus(viewportU.div(2)).minus(viewportV.div(2))
|
.minus(u.times(0.5 * viewportWidth)).minus(v.times(0.5 * viewportHeight))
|
||||||
.plus(pixelU.div(2)).plus(pixelV.div(2));
|
.plus(pixelU.div(2)).plus(pixelV.div(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,14 +128,6 @@ public final class Camera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NotNull Color getNormalColor(@NotNull Vec3 normal) {
|
|
||||||
return new Color(
|
|
||||||
0.5 * (normal.x() + 1),
|
|
||||||
0.5 * (normal.y() + 1),
|
|
||||||
0.5 * (normal.z() + 1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static @NotNull Color getSkyboxColor(@NotNull Ray ray) {
|
private static @NotNull Color getSkyboxColor(@NotNull Ray ray) {
|
||||||
// altitude from -pi/2 to pi/2
|
// altitude from -pi/2 to pi/2
|
||||||
var alt = Math.copySign(
|
var alt = Math.copySign(
|
||||||
@ -164,29 +137,80 @@ public final class Camera {
|
|||||||
return Color.lerp(Color.WHITE, Color.SKY, alt / Math.PI + 0.5);
|
return Color.lerp(Color.WHITE, Color.SKY, alt / Math.PI + 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
// getters
|
public static class Builder {
|
||||||
|
private int imageWidth = 1920;
|
||||||
|
private int imageHeight = 1080;
|
||||||
|
|
||||||
public int width() {
|
private @NotNull Vec3 position = Vec3.ZERO;
|
||||||
return width;
|
private @Nullable Vec3 direction = Vec3.UNIT_Z.neg();
|
||||||
|
private @Nullable Vec3 target = null;
|
||||||
|
private double rotation = 0.0;
|
||||||
|
|
||||||
|
private double fov = 0.5 * Math.PI;
|
||||||
|
|
||||||
|
private int samplePerPixel = 100;
|
||||||
|
private int maxDepth = 10;
|
||||||
|
private double gamma = 2.0;
|
||||||
|
|
||||||
|
private Builder() {}
|
||||||
|
|
||||||
|
public @NotNull Builder withImage(int width, int height) {
|
||||||
|
if (width <= 0 || height <= 0) throw new IllegalArgumentException("width and height must be positive");
|
||||||
|
this.imageWidth = width;
|
||||||
|
this.imageHeight = height;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int height() {
|
public @NotNull Builder withPosition(@NotNull Vec3 position) {
|
||||||
return height;
|
this.position = Objects.requireNonNull(position);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double viewportWidth() {
|
public @NotNull Builder withDirection(@NotNull Vec3 direction) {
|
||||||
return viewportWidth;
|
this.direction = Objects.requireNonNull(direction).unit();
|
||||||
|
this.target = null;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double viewportHeight() {
|
public @NotNull Builder withTarget(@NotNull Vec3 target) {
|
||||||
return viewportHeight;
|
this.target = Objects.requireNonNull(target);
|
||||||
|
this.direction = null;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull Vec3 origin() {
|
public @NotNull Builder withRotation(double rotation) {
|
||||||
return origin;
|
if (!Double.isFinite(rotation)) throw new IllegalArgumentException("rotation must be finite");
|
||||||
|
this.rotation = rotation;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Builder withFieldOfView(double fov) {
|
||||||
|
if (fov <= 0 || fov >= Math.PI || !Double.isFinite(fov)) throw new IllegalArgumentException("fov must be in the range (0, π)");
|
||||||
|
this.fov = fov;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Builder withSamplesPerPixel(int samples) {
|
||||||
|
if (samples <= 0) throw new IllegalArgumentException("samples must be positive");
|
||||||
|
this.samplePerPixel = samples;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Builder withMaxDepth(int depth) {
|
||||||
|
if (depth <= 0) throw new IllegalArgumentException("depth must be positive");
|
||||||
|
this.maxDepth = depth;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Builder withGamma(double gamma) {
|
||||||
|
if (gamma <= 0 || !Double.isFinite(gamma)) throw new IllegalArgumentException("gamma must be positive");
|
||||||
|
this.gamma = gamma;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NotNull Camera build() {
|
||||||
|
return new Camera(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull Vec3 direction() {
|
|
||||||
return direction;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user