import { Color } from "./Color" import type { Framebuffer } from "./Framebuffer" import { Camera } from "../scene/Camera" import { Vec3 } from "../math/Vec3" /** Procedural sky: a vertical gradient plus a sun disc. No texture needed. */ export type SkyConfig = { zenith: Color horizon: Color sun: Color /** World-space direction toward the sun (need not be normalized). */ sunDir: Vec3 /** Angular radius of the sun's core, in radians. */ sunSize: number } const UP: Vec3 = { x: 0, y: 1, z: 0 } export namespace Sky { /** * Fill the whole framebuffer with the sky and reset depth to 0. Run first each * frame in place of Framebuffer.clear; opaque geometry then overwrites the sky * wherever it is nearer. * * Per pixel it reconstructs the view ray from the camera basis, shades a * horizon->zenith gradient by the ray's elevation (so it pans with pitch and * yaw), and brightens toward `sun` where the ray points near `sunDir`. */ export function render(fb: Framebuffer, camera: Camera, sky: SkyConfig): void { const { width, height, color, depth } = fb const forward = Camera.forward(camera) const right = Vec3.normalize(Vec3.cross(forward, UP)) const up = Vec3.cross(right, forward) const tanY = Math.tan(camera.fov / 2) const tanX = tanY * (width / height) const sun = Vec3.normalize(sky.sunDir) const cosSun = Math.cos(sky.sunSize) for (let y = 0; y < height; y++) { const ndcY = 1 - ((y + 0.5) / height) * 2 for (let x = 0; x < width; x++) { const ndcX = ((x + 0.5) / width) * 2 - 1 // View ray = forward + right*ndcX*tanX + up*ndcY*tanY, then normalized. let dx = forward.x + right.x * ndcX * tanX + up.x * ndcY * tanY let dy = forward.y + right.y * ndcX * tanX + up.y * ndcY * tanY let dz = forward.z + right.z * ndcX * tanX + up.z * ndcY * tanY const inv = 1 / Math.hypot(dx, dy, dz) dx *= inv dy *= inv dz *= inv // dy is the ray elevation: 0 at the horizon, 1 straight up. const t = Math.max(0, Math.min(1, dy)) let c = Color.lerp(sky.horizon, sky.zenith, t) const facing = dx * sun.x + dy * sun.y + dz * sun.z if (facing > cosSun) { const glow = Math.min(1, ((facing - cosSun) / (1 - cosSun)) * 1.5) c = Color.lerp(c, sky.sun, glow) } const i = y * width + x color[i] = c depth[i] = 0 } } } }