refactor Camera
This commit is contained in:
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,35 +47,23 @@ public record Camera(
|
||||
this(width, height, viewportWidth, viewportHeight, Vec3.ZERO, Vec3.UNIT_Z);
|
||||
}
|
||||
|
||||
public @NotNull Image render(@NotNull Scene scene) {
|
||||
var image = new Image(width(), height());
|
||||
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");
|
||||
|
||||
pixels().forEach(pixel -> {
|
||||
var x = pixel.x();
|
||||
var y = pixel.y();
|
||||
var ray = pixel.ray();
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.viewportWidth = viewportWidth;
|
||||
this.viewportHeight = viewportHeight;
|
||||
this.origin = Objects.requireNonNull(origin, "origin");
|
||||
this.direction = Objects.requireNonNull(direction, "direction");
|
||||
|
||||
var result = scene.hit(ray, Range.NON_NEGATIVE);
|
||||
if (result.isPresent()) {
|
||||
var normal = result.get().normal();
|
||||
image.set(x, y, getNormalColor(normal));
|
||||
} else {
|
||||
image.set(x, y, scene.getSkyboxColor(ray));
|
||||
}
|
||||
});
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private @NotNull Color getNormalColor(@NotNull Vec3 normal) {
|
||||
return new Color(
|
||||
0.5 * (normal.x() + 1),
|
||||
0.5 * (normal.y() + 1),
|
||||
0.5 * (normal.z() + 1)
|
||||
);
|
||||
}
|
||||
|
||||
public @NotNull Stream<Pixel> pixels() {
|
||||
// project direction onto xz-plane
|
||||
var d = direction.unit();
|
||||
var dXZ = direction.withY(0).unit();
|
||||
@ -84,20 +74,88 @@ public record Camera(
|
||||
viewportU = viewportU.times(viewportWidth); // vector along the width of the viewport
|
||||
viewportV = viewportV.times(- viewportHeight); // vector along the height of the viewport
|
||||
|
||||
var pixelU = viewportU.div(width);
|
||||
var pixelV = viewportV.div(height);
|
||||
this.pixelU = viewportU.div(width);
|
||||
this.pixelV = viewportV.div(height);
|
||||
|
||||
var pixel00 = origin.plus(direction)
|
||||
this.pixel00 = origin.plus(direction)
|
||||
.minus(viewportU.div(2)).minus(viewportV.div(2))
|
||||
.plus(pixelU.div(2)).plus(pixelV.div(2));
|
||||
|
||||
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 record Pixel(int x, int y, @NotNull Ray ray) {}
|
||||
public @NotNull Image render(@NotNull Scene scene) {
|
||||
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));
|
||||
}
|
||||
|
||||
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();
|
||||
return getNormalColor(normal);
|
||||
} else {
|
||||
return getSkyboxColor(ray);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
// getters
|
||||
|
||||
public int width() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int height() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public double viewportWidth() {
|
||||
return viewportWidth;
|
||||
}
|
||||
|
||||
public double viewportHeight() {
|
||||
return viewportHeight;
|
||||
}
|
||||
|
||||
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…
x
Reference in New Issue
Block a user