refactor Camera

main
jbb01 6 months ago
parent 672dc6af8b
commit af3dc8dac7

@ -7,22 +7,24 @@ import eu.jonahbauer.raytracing.scene.Scene;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public record Camera(
int width, int height,
double viewportWidth, double viewportHeight,
@NotNull Vec3 origin, @NotNull Vec3 direction
) {
public Camera {
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");
Objects.requireNonNull(origin, "origin");
Objects.requireNonNull(direction, "direction");
}
public final class Camera {
// image size
private final int width;
private final int height;
// viewport size
private final double viewportWidth;
private final double viewportHeight;
// camera position and orientation
private final @NotNull Vec3 origin;
private final @NotNull Vec3 direction;
// internal properties
private final @NotNull Vec3 pixelU;
private final @NotNull Vec3 pixelV;
private final @NotNull Vec3 pixel00;
/**
* Creates a new camera with the given dimensions at the origin facing towards positive z with a focal length of 1.
@ -45,27 +47,76 @@ public record Camera(
this(width, height, viewportWidth, viewportHeight, Vec3.ZERO, Vec3.UNIT_Z);
}
public Camera(
int width, int height,
double viewportWidth, double viewportHeight,
@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;
this.height = height;
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
this.origin = Objects.requireNonNull(origin, "origin");
this.direction = Objects.requireNonNull(direction, "direction");
// project direction onto xz-plane
var d = direction.unit();
var dXZ = direction.withY(0).unit();
var viewportU = new Vec3(dXZ.z(), 0, - dXZ.x()); // perpendicular to dXZ in xz-plane
var viewportV = d.cross(viewportU); // perpendicular to viewportU and direction
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)
.minus(viewportU.div(2)).minus(viewportV.div(2))
.plus(pixelU.div(2)).plus(pixelV.div(2));
}
public @NotNull Image render(@NotNull Scene scene) {
var image = new Image(width(), height());
var image = new Image(width, height);
for (int y = 0; y < height; y++) {
var ray = getRay(x, y);
var color = getColor(scene, ray);
image.set(x, y, color);
for (int x = 0; x < width; x++) {
}
}
return image;
}
private @NotNull Ray getRay(int x, int y) {
return new Ray(origin, getPixel(x, y).minus(origin));
}
pixels().forEach(pixel -> {
var x = pixel.x();
var y = pixel.y();
var ray = pixel.ray();
private @NotNull Vec3 getPixel(int x, int y) {
Objects.checkIndex(x, width);
Objects.checkIndex(y, height);
return pixel00.plus(pixelU.times(x)).plus(pixelV.times(y));
}
private @NotNull Color getColor(@NotNull Scene scene, @NotNull Ray ray) {
var result = scene.hit(ray, Range.NON_NEGATIVE);
if (result.isPresent()) {
var normal = result.get().normal();
image.set(x, y, getNormalColor(normal));
return getNormalColor(normal);
} else {
image.set(x, y, scene.getSkyboxColor(ray));
return getSkyboxColor(ray);
}
});
return image;
}
private @NotNull Color getNormalColor(@NotNull Vec3 normal) {
private static @NotNull Color getNormalColor(@NotNull Vec3 normal) {
return new Color(
0.5 * (normal.x() + 1),
0.5 * (normal.y() + 1),
@ -73,31 +124,38 @@ public record Camera(
);
}
public @NotNull Stream<Pixel> pixels() {
// project direction onto xz-plane
var d = direction.unit();
var dXZ = direction.withY(0).unit();
private static @NotNull Color getSkyboxColor(@NotNull Ray ray) {
// altitude from -pi/2 to pi/2
var alt = Math.copySign(
Math.acos(ray.direction().withY(0).unit().times(ray.direction().unit())),
ray.direction().y()
);
return Color.lerp(Color.WHITE, Color.SKY, alt / Math.PI + 0.5);
}
var viewportU = new Vec3(dXZ.z(), 0, - dXZ.x()); // perpendicular to dXZ in xz-plane
var viewportV = d.cross(viewportU); // perpendicular to viewportU and direction
// getters
viewportU = viewportU.times(viewportWidth); // vector along the width of the viewport
viewportV = viewportV.times(- viewportHeight); // vector along the height of the viewport
public int width() {
return width;
}
var pixelU = viewportU.div(width);
var pixelV = viewportV.div(height);
public int height() {
return height;
}
var pixel00 = origin.plus(direction)
.minus(viewportU.div(2)).minus(viewportV.div(2))
.plus(pixelU.div(2)).plus(pixelV.div(2));
public double viewportWidth() {
return viewportWidth;
}
return IntStream.range(0, width * height).mapToObj(i -> {
var y = i / width;
var x = i % width;
var pixel = pixel00.plus(pixelU.times(x)).plus(pixelV.times(y));
return new Pixel(x, y, new Ray(origin, pixel.minus(origin)));
});
public double viewportHeight() {
return viewportHeight;
}
public record Pixel(int x, int y, @NotNull Ray ray) {}
public @NotNull Vec3 origin() {
return origin;
}
public @NotNull Vec3 direction() {
return direction;
}
}

@ -2,7 +2,6 @@ package eu.jonahbauer.raytracing.scene;
import eu.jonahbauer.raytracing.math.Range;
import eu.jonahbauer.raytracing.math.Ray;
import eu.jonahbauer.raytracing.render.Color;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@ -29,13 +28,4 @@ public record Scene(@NotNull List<@NotNull Hittable> objects) implements Hittabl
}
return Optional.ofNullable(result);
}
public @NotNull Color getSkyboxColor(@NotNull Ray ray) {
// altitude from -pi/2 to pi/2
var alt = Math.copySign(
Math.acos(ray.direction().withY(0).unit().times(ray.direction().unit())),
ray.direction().y()
);
return Color.lerp(Color.WHITE, Color.SKY, alt / Math.PI + 0.5);
}
}

Loading…
Cancel
Save