Published
Edited
Jun 25, 2020
1 fork
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
async function renderImage(size, getPixelColor, visibility) {
await visibility();
const ctx = DOM.context2d(size.w, size.h, 1);
const imgData = ctx.getImageData(0, 0, size.w, size.h);
const pxs = imgData.data;
for (let i = 0; i < pxs.length; i += 4) {
const [x, y] = [(i/4)%size.w, Math.floor((i/4)/size.w)];
const [u, v] = [x/size.w, y/size.h];
const color = getPixelColor(u, v);
const [r, g, b] = vecToRgb(color);
[pxs[i], pxs[i + 1], pxs[i + 2], pxs[i + 3]] = [r, g, b, 255]
}
ctx.putImageData(imgData, 0, 0);
return ctx.canvas;
}
Insert cell
Insert cell
renderImage(lowQuality, (u, v) => new Vec(u, v, 0.2), visibility)
Insert cell
Insert cell
function castRay(u, v) {
const origin = new Vec(0, 1, -8);
const target = new Vec(-4 + 8*u, 1 + 9/4 - 9/2*v, 0);
const direction = target.sub(origin).norm();
return { origin, direction };
}
Insert cell
function intersectRaySphere(ray, sphere) {
const oc = ray.origin.sub(sphere.center);
const b = Vec.dot(oc, ray.direction);
const c = Vec.dot(oc, oc) - sphere.radius*sphere.radius;
const h = b*b - c;
if (h < 0) return null;
const hSqrt = Math.sqrt(h);
const [t1, t2] = [-b - hSqrt, -b + hSqrt];
if (t1 < t2 && t1 > eps && t1 > 0) return t1;
if (t2 > eps) return t2;
return null;
}
Insert cell
Insert cell
renderImage(lowQuality, (u, v) => {
const ray = castRay(u, v);
const isect = intersectRaySphere(ray, sphere);
return isect !== null ? new Vec(1, 0, 1) : new Vec(0, 0, 0);
}, visibility);
Insert cell
Insert cell
renderImage(lowQuality, (u, v) => {
const ray = castRay(u, v);
const t = intersectRaySphere(ray, sphere);
if (t === null) return new Vec(0, 0, 0);

const p = ray.origin.add(ray.direction.mult(t));
const norm = p.sub(sphere.center).norm();
return norm.add(new Vec(1, 1, 1)).div(2);
}, visibility);
Insert cell
Insert cell
function intersectRaySphereArray(ray, spheres) {
let isect = { t: Infinity };
spheres.forEach(sphere => {
const t = intersectRaySphere(ray, sphere);
if (t === null || t > isect.t) return;
const p = ray.origin.add(ray.direction.mult(t));
const norm = p.sub(sphere.center).norm();
isect = { t, p, norm, sphere };
});
return isect.t < Infinity ? isect : null;
}
Insert cell
Insert cell
renderImage(lowQuality, (u, v) => {
const ray = castRay(u, v);
const isect = intersectRaySphereArray(ray, spheres);
return isect === null
? new Vec(0, 0, 0)
: isect.norm.add(new Vec(1, 1, 1)).div(2);
}, visibility);
Insert cell
Insert cell
background = ray => {
const t = (ray.direction.y + 1)/2;
return new Vec(0.09, 0.42, 0.59).mult(1 - t).add(new Vec(0.79, 0.53, 0.38).mult(t));
}
Insert cell
renderImage(lowQuality, (u, v) => {
const ray = castRay(u, v);
const isect = intersectRaySphereArray(ray, spheres);
return isect === null
? background(ray)
: isect.norm.add(new Vec(1, 1, 1)).div(2);
}, visibility);
Insert cell
Insert cell
function getColorSpecular(spheres, ray, depth = 0) {
if (depth > 10) return new Vec(0, 0, 0);
const isect = intersectRaySphereArray(ray, spheres);
if (isect === null) return background(ray);
const reflected = Vec.reflect(ray.direction, isect.norm);
return getColorSpecular(spheres, { origin: isect.p, direction: reflected }, depth + 1).div(2);
}
Insert cell
renderImage(lowQuality, (u, v) => {
const ray = castRay(u, v);
return getColorSpecular(spheres, ray);
}, visibility);
Insert cell
Insert cell
function randomVecInUnitSphere() {
while (true) {
const p = new Vec(2*Math.random() - 1, 2*Math.random() - 1, 2*Math.random() - 1);
if (p.lengthSquared() > 1) continue;
return p;
}
}
Insert cell
function getColorLambertian(spheres, ray, depth = 0) {
if (depth > 10) return new Vec(0, 0, 0);
const isect = intersectRaySphereArray(ray, spheres);
if (isect === null) return background(ray);
const scattered = isect.norm.add(randomVecInUnitSphere()).norm();
return getColorLambertian(spheres, { origin: isect.p, direction: scattered }, depth + 1).div(2);
}
Insert cell
Insert cell
Insert cell
correctGamma = v => new Vec(Math.sqrt(v.x), Math.sqrt(v.y), Math.sqrt(v.z))
Insert cell
renderImage(lowQuality, (u, v) => {
const ray = castRay(u, v);
const color = getColorLambertian(spheres, ray);
return correctGamma(color);
}, visibility);
Insert cell
Insert cell
Insert cell
renderImage(lowQuality, (u, v) => {
let sum = new Vec(0, 0, 0);
for (let i = 0; i < samples; i++) {
const ray = castRay(u, v);
sum = sum.add(getColorLambertian(spheres, ray));
}
const color = sum.div(samples);
return correctGamma(color);
}, visibility);
Insert cell
Insert cell
Insert cell
function getColorLambertianColor(spheres, ray, depth = 0) {
if (depth > 10) return new Vec(0, 0, 0);
const isect = intersectRaySphereArray(ray, spheres);
if (isect === null) return background(ray);
const scattered = isect.norm.add(randomVecInUnitSphere()).norm();
return isect.sphere.color.multVec(getColorLambertian(spheres, { origin: isect.p, direction: scattered }, depth + 1));
}
Insert cell
Insert cell
Insert cell
renderImage(lowQuality, (u, v) => {
let sum = new Vec(0, 0, 0);
for (let i = 0; i < samples; i++) {
const ray = castRay(u + Math.random()/320, v + Math.random()/180);
sum = sum.add(getColorLambertianColor(spheres, ray));
}
const color = sum.div(samples);
return correctGamma(color);
}, visibility);
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more