webgpu-pt

monte carlo path tracer
Contents

camera.ts

4.3 kB
  1import { Vec3, Mat4 } from "gl-matrix";
  2
  3export type CameraState = {
  4  position: Float32Array; // [x, y, z]
  5  rotation: Float32Array; // [theta, phi]
  6  view: Float32Array;
  7  inverseView: Float32Array;
  8  projection: Float32Array;
  9  currentViewProj: Float32Array;
 10  previousViewProj: Float32Array;
 11  dirty: boolean;
 12};
 13
 14const moveVec = Vec3.create();
 15const lastPosition = Vec3.create();
 16const lastRotation = new Float32Array(2);
 17const rotateDelta = new Float32Array(2);
 18const keyState = new Set<string>();
 19const sprintMultiplier = 2.5;
 20
 21let isPointerLocked = false;
 22let oldTime = 0;
 23
 24export function createCamera(canvas: HTMLCanvasElement): CameraState {
 25  const position = Vec3.fromValues(0, -75, 20);
 26  const view = Mat4.create();
 27  const projection = Mat4.create();
 28  const inverseView = Mat4.create();
 29
 30  const lookAtTarget = Vec3.fromValues(0, 0, 20);
 31  const up = Vec3.fromValues(0, 0, 1);
 32  Mat4.lookAt(view, position, lookAtTarget, up);
 33  Mat4.invert(inverseView, view);
 34  Mat4.perspectiveZO(projection, Math.PI / 4, canvas.width / canvas.height, 0.1, 100);
 35
 36  const forward = Vec3.create();
 37  Vec3.subtract(forward, lookAtTarget, position);
 38  Vec3.normalize(forward, forward);
 39  const phi = Math.asin(forward[2]);
 40  const theta = Math.atan2(forward[0], forward[1]);
 41
 42  Vec3.copy(lastPosition, position);
 43  lastRotation[0] = theta;
 44  lastRotation[1] = phi;
 45
 46  return {
 47    position,
 48    rotation: new Float32Array([theta, phi]),
 49    view,
 50    inverseView,
 51    projection,
 52    currentViewProj: Mat4.create(),
 53    previousViewProj: Mat4.create(),
 54    dirty: true,
 55  };
 56}
 57
 58export function updateCamera(cam: CameraState): void {
 59  const now = performance.now() * 0.001;
 60  const dt = now - oldTime;
 61  oldTime = now;
 62
 63  const theta = cam.rotation[0] += rotateDelta[0] * dt * 60;
 64  const phi = cam.rotation[1] -= rotateDelta[1] * dt * 60;
 65  cam.rotation[1] = Math.min(88, Math.max(-88, cam.rotation[1]));
 66  rotateDelta[0] = rotateDelta[1] = 0;
 67
 68  updateMovementInput();
 69
 70  const scaledMove = Vec3.create();
 71  Vec3.scale(scaledMove, moveVec, dt);
 72
 73  const moved = Vec3.length(scaledMove) > 1e-5;
 74  const rotated = theta !== lastRotation[0] || phi !== lastRotation[1];
 75  const changedPos = !Vec3.exactEquals(cam.position, lastPosition);
 76  cam.dirty = moved || rotated || changedPos;
 77
 78  if (!cam.dirty) return;
 79
 80  const forwards = Vec3.fromValues(
 81    Math.sin(toRad(theta)) * Math.cos(toRad(phi)),
 82    Math.cos(toRad(theta)) * Math.cos(toRad(phi)),
 83    Math.sin(toRad(phi))
 84  );
 85  const right = Vec3.create();
 86  Vec3.cross(right, forwards, [0, 0, 1]);
 87  Vec3.normalize(right, right);
 88
 89  const up = Vec3.create();
 90  Vec3.cross(up, right, forwards);
 91  Vec3.normalize(up, up);
 92
 93  Vec3.scaleAndAdd(cam.position, cam.position, forwards, scaledMove[0]);
 94  Vec3.scaleAndAdd(cam.position, cam.position, right, scaledMove[1]);
 95  Vec3.scaleAndAdd(cam.position, cam.position, up, scaledMove[2]);
 96
 97  const target = Vec3.create();
 98  Vec3.add(target, cam.position, forwards);
 99  Mat4.lookAt(cam.view, cam.position, target, up);
100  Mat4.invert(cam.inverseView, cam.view);
101
102  Vec3.copy(lastPosition, cam.position);
103  lastRotation[0] = theta;
104  lastRotation[1] = phi;
105}
106
107export function updateMovementInput(): void {
108  moveVec[0] = moveVec[1] = moveVec[2] = 0;
109  const speed = keyState.has("shift") ? 10 * sprintMultiplier : 10;
110
111  if (keyState.has("w")) moveVec[0] += speed;
112  if (keyState.has("s")) moveVec[0] -= speed;
113  if (keyState.has("d")) moveVec[1] += speed;
114  if (keyState.has("a")) moveVec[1] -= speed;
115  if (keyState.has("q")) moveVec[2] += speed;
116  if (keyState.has("e")) moveVec[2] -= speed;
117}
118
119function toRad(deg: number): number {
120  return (deg * Math.PI) / 180;
121}
122
123export function setupCameraInput(canvas: HTMLCanvasElement): void {
124  canvas.addEventListener("click", () => canvas.requestPointerLock());
125
126  document.addEventListener("pointerlockchange", () => {
127    isPointerLocked = document.pointerLockElement != null;
128  });
129
130  window.addEventListener("keydown", e => keyState.add(e.key.toLowerCase()));
131  window.addEventListener("keyup", e => keyState.delete(e.key.toLowerCase()));
132
133  window.addEventListener("mousemove", (e) => {
134    if (!isPointerLocked) return;
135    const sensitivity = 0.5;
136    rotateDelta[0] = e.movementX * sensitivity;
137    rotateDelta[1] = e.movementY * sensitivity;
138  });
139}