Rayity is a library for creating photorealistic renderings of three-dimensional scenes in a web browser. It uses path tracing to simulate light to reproduce effects such as shadows, ambient occlusion, reflection, refraction, depth of field, and subsurface scattering. Objects are rendered using a technique called “sphere tracing” where geometry is defined using “distance functions”. This allows for the rendering of complicated implicit surfaces including recursively-defined geometry like fractals.
Scenes and geometry are defined using TypeScript (or JavaScript). Rayity then uses a code generation technique to convert that scene into a GLSL shader. This allows the scene to be rendered quickly by taking advantage of graphics processing hardware.
viewer(document.getElementById("canvas"),
scene({
camera: orbit({
radius: value(4),
offset: value(0.25, -0.5)
}),
models: [
model({
shape: plane(value(0, 1, 0), value(0.5)),
material: material({
color: value(0.6)
})
}),
model({
shape: translate(value(-0.5, 0, 0),
sphere()),
material: material({
color: value(0.8, 0.4, 0.8)
})
}),
model({
shape: translate(value(0.5, 0, 0),
cube()),
material: material({
color: value(0.8, 0.9, 0.1)
})
})
]));
scene({
air: material({
scatter: value(1000),
}),
camera: orbit({
fieldOfView: value(60 / 180 * Math.PI),
radius: value(10),
aperture: value(0.1),
target: value(0, 0, 0),
offset: value(0.25, -0.5)
}),
models: [
model({
shape: scale(value(1000),
sphere()),
material: spotlight({
color: value(1),
direction: value(1, 1, 1),
spread: value(0.1),
ambient: value(0)
})
}),
model({
shape: intersection(
modulate(value(1, 100, 1), index =>
smoothBox(
expression(`0.9, 1.0 + 5.0 * ${random(index)}.x, 0.9`),
value(0.5))),
plane(value(0, 1, 0), value(-6))),
material: material({
color: value(0.7, 0.6, 0.5),
smoothness: value(0.99)
})
})
]
})
scene({
camera: orbit({
radius: value(1.54),
offset: value(-0.2, -0.5),
}),
models: [
model({
shape: scale(value(1000), sphere()),
material: spotlight({
direction: value(1, 1, 0),
spread: value(0.1),
color: value(0.5)
})
}),
model({
shape: plane(value(0, 1, 0), value(0)),
}),
model({
shape: intersection(
sphere(),
scale(value(0.1),
truchet())),
material: material({
color: value(0.9, 0.8, 0.4),
smoothness: value(0.999)
})
})
]
})
scene({
camera: orbit({
radius: value(2.5),
target: value(0, 0.8, 0),
offset: value(-0.2, -0.2),
}),
models: [
model({
shape: scale(value(10), sphere()),
material: spotlight({
direction: value(1, 1, 0),
spread: value(0.05),
ambient: value(1),
color: value(0.5)
})
}),
model({
shape: plane(value(0, 1, 0), value(0)),
material: material({
color: value(0.7)
})
}),
model({
shape: tree(),
material: material({
color: value(0.7, 0.6, 0.5)
})
})
]
})
function tree(iterations: number = 7, shape?: Shape): Shape {
let factor = 0.6;
let length = 1;
let width = 0.15;
let angle = 30 / 180 * Math.PI;
if (iterations <= 1)
return smoothBox(value(width, length, width), value(width));
else {
shape = tree(iterations - 1, shape);
return union(
shape,
mirror(value(1 / Math.sqrt(2), 0, 1 / Math.sqrt(2)),
mirror(value(1 / Math.sqrt(2), 0, -1 / Math.sqrt(2)),
translate(
value(
length * factor / 2 * Math.sin(angle),
length / 2 * (1 + factor / 2 * Math.cos(angle)),
0),
scale(value(factor),
rotateY(value(0.1),
rotateZ(value(angle),
shape)))))));
}
}
scene({
camera: orbit({
radius: value(3),
offset: value(-0.3, -0.4),
aperture: value(0.1)
}),
models: [
model({
shape: scale(value(1000), sphere()),
material: spotlight({
direction: value(1, 1, 0),
spread: value(0.1),
color: value(0.5),
ambient: value(1)
})
}),
model({
shape: plane(value(0, 1, 0), value(0.3)),
material: material({
color: value(0.5)
})
}),
model({
shape: repeat(value(1.3, 0, 1),
rotateZ(value(-Math.PI / 4), skull())),
material: material({
smoothness: value(0.99),
color: value(0.4, 0.6, 0.8)
})
})
]
})
scene({
camera: orbit({
radius: value(4),
aperture: value(0.05),
target: value(0, 0.6, 0),
offset: value(0.2, -0.3)
}),
models: [
model({
shape: scale(value(1000),
sphere()),
material: spotlight({
color: value(0.3),
direction: value(1, 1, 1),
spread: value(0.01),
ambient: value(0.5, 0.6, 0.9)
})
}),
model({
shape: plane(value(0, 1, 0), value(0.002)),
material: material({
color: value(0.3, 0.35, 0.35),
smoothness: value(0),
})
}),
model({
shape: plane(value(0, 1, 0), value(0.001)),
material: material({
transmittance: value(0.9),
smoothness: value(0.95),
refraction: value(1.1)
})
}),
model({
shape: plane(value(0, 0, 1), value(1.58)),
material: material({
color: value(0.2)
})
}),
model({
shape: translate(value(0, 0.5, -2),
union(
repeat(value(2, 2, 0),
smoothBox(
value(1.95, 0.95, 0.9),
value(0.05))),
translate(value(1, 1, 0),
repeat(value(2, 2, 0),
smoothBox(
value(1.95, 0.95, 0.9),
value(0.05)))))),
material: material({
smoothness: value(0.5),
color: value(0.8)
})
}),
model({
shape: translate(value(0.8, 0.5, 0.25),
difference(
smoothUnion(value(0.1),
intersection(
cylinder(),
cube()),
translate(value(0, 0, 0.5),
rotateZ(value(Math.PI / 2),
scale(value(0.5),
torus())))),
intersection(
scale(value(0.9),
cylinder()),
plane(value(0, -1, 0), value(-0.4))))),
material: material({
color: value(1, 1, 0.99),
smoothness: value(1),
transmittance: value(0.9),
refraction: value(1.5),
scatter: value(0.01)
})
}),
model({
shape: translate(value(-0.8, 0.5, 0),
difference(
intersection(
smoothUnion(value(0.6),
translate(value(0, 0.5, 0),
scale(value(0.9),
sphere())),
translate(value(0, -0.25, 0),
scale(value(0.5),
cube()))),
plane(value(0, 1, 0), value(-0.5))),
intersection(
expand(value(-0.04),
smoothUnion(value(0.6),
translate(value(0, 0.5, 0),
scale(value(0.9),
sphere())),
translate(value(0, -0.25, 0),
scale(value(0.5),
cube())))),
plane(value(0, -1, 0), value(-0.4))))),
material: material({
transmittance: value(0.7),
smoothness: value(1),
refraction: value(1.9),
color: value(0.97, 0.97, 0.99),
scatter: value(1)
})
})
]
})