add camera, scene and skybox
parent
028d19b118
commit
7875befa94
@ -1,7 +1,18 @@
|
||||
package eu.jonahbauer.raytracing;
|
||||
|
||||
import eu.jonahbauer.raytracing.render.Camera;
|
||||
import eu.jonahbauer.raytracing.render.ImageIO;
|
||||
import eu.jonahbauer.raytracing.render.Scene;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello world!");
|
||||
public static void main(String[] args) throws IOException {
|
||||
var scene = new Scene();
|
||||
var camera = new Camera(256, 2, 16 / 9d);
|
||||
|
||||
var image = scene.render(camera);
|
||||
ImageIO.write(image, Path.of("scene-" + System.currentTimeMillis() + ".ppm"));
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package eu.jonahbauer.raytracing.render;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import eu.jonahbauer.raytracing.math.Vec3;
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new camera with the given dimensions at the origin facing towards positive z with a focal length of 1.
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new camera with the given dimensions at the origin facing towards positive z with a focal length of 1.
|
||||
* @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);
|
||||
}
|
||||
|
||||
public @NotNull Stream<Pixel> pixels() {
|
||||
// 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
|
||||
|
||||
var pixelU = viewportU.div(width);
|
||||
var pixelV = viewportV.div(height);
|
||||
|
||||
var 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) {}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package eu.jonahbauer.raytracing.render;
|
||||
|
||||
import eu.jonahbauer.raytracing.math.Ray;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public record Scene() {
|
||||
|
||||
public @NotNull Image render(@NotNull Camera camera) {
|
||||
var image = new Image(camera.width(), camera.height());
|
||||
|
||||
camera.pixels().forEach(pixel -> {
|
||||
var x = pixel.x();
|
||||
var y = pixel.y();
|
||||
var ray = pixel.ray();
|
||||
|
||||
image.set(x, y, getSkyboxColor(ray));
|
||||
});
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private @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…
Reference in New Issue