No still was sent with this entry.
mandelbulb
Source
"use typeshade";
// **************************************************
// origin: https://www.shadertoy.com/view/MdXSWn
//
// Created by evilryu
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
//
// **************************************************
class Uniforms {
time: f32;
resolution: vec2;
mouse: vec2;
}
declare const u: uniform<Uniforms>;
class Geometry {
static rotateY(p: vec3, angle: f32): vec3 {
const c = cos(angle);
const s = sin(angle);
return vec3(c * p.x + s * p.z, p.y, -s * p.x + c * p.z);
}
}
class Mandelbulb {
static evaluate(input: vec3): vec3 {
const p = input.xzy;
const power = 8.0;
let z = p;
let radius = 0.0;
let derivative = 1.0;
let orbitTrap = 1.0;
for (let i = 0; i < 7; i++) {
radius = length(z);
if (radius <= 2.0) {
let theta = atan(z.y / z.x);
let phi = asin(z.z / radius);
derivative =
pow(radius, power - 1.0) * derivative * power + 1.0;
radius = pow(radius, power);
theta = theta * power;
phi = phi * power;
z =
radius *
vec3(
cos(theta) * cos(phi),
sin(theta) * cos(phi),
sin(phi),
) +
p;
orbitTrap = min(orbitTrap, radius);
}
}
const distance = 0.5 * log(radius) * radius / derivative;
return vec3(distance, orbitTrap, 0.0);
}
}
class Scene {
static sample(p: vec3): vec3 {
const rotated = Geometry.rotateY(p, u.time * 0.2);
return Mandelbulb.evaluate(rotated);
}
}
class Surface {
static normalAt(pos: vec3): vec3 {
const epsilon = 0.001;
const xOffset = vec3(epsilon, 0.0, 0.0);
const yOffset = vec3(0.0, epsilon, 0.0);
const zOffset = vec3(0.0, 0.0, epsilon);
const dx =
Scene.sample(pos + xOffset).x -
Scene.sample(pos - xOffset).x;
const dy =
Scene.sample(pos + yOffset).x -
Scene.sample(pos - yOffset).x;
const dz =
Scene.sample(pos + zOffset).x -
Scene.sample(pos - zOffset).x;
return normalize(vec3(dx, dy, dz));
}
static softShadow(
origin: vec3,
direction: vec3,
hardness: f32,
): f32 {
let shadow = 1.0;
let distance = 0.01;
let blocked = false;
for (let i = 0; i < 50; i++) {
if (!blocked) {
const height =
Scene.sample(origin + direction * distance).x;
if (height < 0.001) {
shadow = 0.02;
blocked = true;
} else {
shadow = min(shadow, hardness * height / distance);
distance = distance + clamp(height, 0.01, 2.0);
}
}
}
return shadow;
}
}
class RayMarcher {
static trace(
origin: vec3,
direction: vec3,
pixelSize: f32,
): vec3 {
let travel = 1.0;
let hitDistance = 0.0;
let hitData = vec3(0.0, 0.0, 0.0);
let bestError = 1000.0;
let error = 1000.0;
let distance = 1.0;
let previousDistance = 100.0;
let overstep = 0.0;
let stepSize = 0.0;
for (let i = 0; i < 48; i++) {
const finished =
error < pixelSize * 0.5 ||
travel > 20.0;
if (!finished) {
const sample =
Scene.sample(origin + direction * travel);
distance = sample.x;
if (distance > overstep) {
overstep =
0.4 * distance * distance / previousDistance;
stepSize = distance + overstep;
previousDistance = distance;
} else {
stepSize = -overstep;
overstep = 0.0;
previousDistance = 100.0;
distance = 1.0;
}
error = distance / travel;
if (error < bestError) {
bestError = error;
hitDistance = travel;
hitData = sample;
}
travel = travel + stepSize;
}
}
if (travel > 20.0) {
hitDistance = -1.0;
}
return vec3(hitDistance, hitData.y, hitData.z);
}
}
class Camera {
static origin(time: f32): vec3 {
const sineMotion =
0.7 + 0.3 * sin(time * 0.4);
const cosineMotion =
0.7 + 0.3 * cos(time * 0.4);
const orbit = sineMotion * cosineMotion;
return vec3(
0.0,
3.0 * orbit,
3.0 * (1.0 - orbit),
);
}
static ray(origin: vec3, screen: vec2): vec3 {
const lookAt = vec3(0.0, 0.0, 0.0);
const forward = normalize(lookAt - origin);
const right = normalize(
cross(forward, vec3(0.0, 1.0, 0.0)),
);
const up = normalize(cross(right, forward));
return normalize(
screen.x * right +
screen.y * up +
3.0 * forward,
);
}
}
class Material {
static color(orbitTrap: f32): vec3 {
const material =
pow(clamp(orbitTrap, 0.0, 1.0), 0.55);
const phase =
3.0 + 4.2 * material;
return (
vec3(0.5, 0.5, 0.5) +
0.5 *
sin(
vec3(
phase,
phase + 0.5,
phase + 1.0,
),
)
);
}
}
class Lighting {
static sunDirection(): vec3 {
return normalize(vec3(0.1, 0.8, 0.6));
}
static shade(
pos: vec3,
rayDirection: vec3,
orbitTrap: f32,
): vec3 {
const normal = Surface.normalAt(pos);
const sunDirection = Lighting.sunDirection();
const sunColor = vec3(1.64, 1.27, 0.99);
const skyColor = vec3(0.6, 1.5, 1.0);
const baseColor = vec3(0.9, 0.8, 0.6);
const shadow =
Surface.softShadow(pos, sunDirection, 10.0);
const diffuse =
max(0.0, dot(normal, sunDirection));
const sky =
0.6 +
0.4 *
max(
0.0,
dot(normal, vec3(0.0, 1.0, 0.0)),
);
const backDirection =
vec3(-sunDirection.x, -1.0, -sunDirection.z);
const backLight =
max(
0.3 + 0.7 * dot(backDirection, normal),
0.0,
);
const reflectedRay =
reflect(rayDirection, normal);
const specular =
pow(
clamp(
dot(sunDirection, reflectedRay),
0.0,
1.0,
),
10.0,
);
const diffuseLight =
4.5 * sunColor * diffuse * shadow;
const backLightColor =
0.8 * backLight * sunColor;
const skyLight =
0.6 * sky * skyColor * shadow;
const specularLight =
vec3(3.0 * specular * shadow);
const light =
diffuseLight +
backLightColor +
skyLight +
specularLight;
const tint =
Material.color(orbitTrap);
return light * baseColor * 0.2 * tint;
}
}
class Background {
static base(screen: vec2): vec3 {
return (
exp(screen.y - 2.0) *
vec3(0.4, 1.6, 1.0)
);
}
static color(
screen: vec2,
cameraOrigin: vec3,
rayDirection: vec3,
): vec3 {
const baseColor =
Background.base(screen);
const halo =
clamp(
dot(normalize(-cameraOrigin), rayDirection),
0.0,
1.0,
);
const haloColor =
vec3(1.0, 0.8, 0.4) *
pow(halo, 17.0);
return baseColor + haloColor;
}
}
class Fog {
static amount(distance: f32): f32 {
return (
1.0 -
exp(-0.001 * distance * distance)
);
}
static apply(
color: vec3,
fogColor: vec3,
distance: f32,
): vec3 {
return mix(
color,
fogColor,
Fog.amount(distance),
);
}
}
class PostProcessing {
static gamma(color: vec3): vec3 {
return pow(
clamp(
color,
vec3(0.0, 0.0, 0.0),
vec3(1.0, 1.0, 1.0),
),
vec3(0.45, 0.45, 0.45),
);
}
static contrast(color: vec3): vec3 {
return (
color * 0.6 +
0.4 *
color *
color *
(vec3(3.0, 3.0, 3.0) - 2.0 * color)
);
}
static saturation(color: vec3): vec3 {
const gray =
dot(color, vec3(0.33, 0.33, 0.33));
return mix(
color,
vec3(gray, gray, gray),
-0.5,
);
}
static vignette(
color: vec3,
uv: vec2,
): vec3 {
const amount =
pow(
16.0 *
uv.x *
uv.y *
(1.0 - uv.x) *
(1.0 - uv.y),
0.7,
);
return color * (0.5 + 0.5 * amount);
}
static apply(
inputColor: vec3,
uv: vec2,
): vec3 {
let color =
PostProcessing.gamma(inputColor);
color =
PostProcessing.contrast(color);
color =
PostProcessing.saturation(color);
return PostProcessing.vignette(color, uv);
}
}
class Renderer {
static screenCoordinate(
uv: vec2,
resolution: vec2,
): vec2 {
const aspect =
resolution.x /
max(resolution.y, 1.0);
return (
(uv * 2.0 - vec2(1.0, 1.0)) *
vec2(aspect, 1.0)
);
}
static pixelSize(resolution: vec2): f32 {
return 1.0 / (resolution.x * 3.0);
}
static alpha(hitDistance: f32): f32 {
return smoothstep(
0.55,
0.76,
1.0 - hitDistance / 5.0,
);
}
static render(uv: vec2): vec4 {
const screen =
Renderer.screenCoordinate(uv, u.resolution);
const pixelSize =
Renderer.pixelSize(u.resolution);
const cameraOrigin =
Camera.origin(u.time);
const rayDirection =
Camera.ray(cameraOrigin, screen);
const backgroundColor =
Background.color(
screen,
cameraOrigin,
rayDirection,
);
const hit =
RayMarcher.trace(
cameraOrigin,
rayDirection,
pixelSize,
);
let color = backgroundColor;
if (hit.x > 0.0) {
const hitPosition =
cameraOrigin + hit.x * rayDirection;
const surfaceColor =
Lighting.shade(
hitPosition,
rayDirection,
hit.y,
);
const fogColor =
Background.base(screen);
color =
Fog.apply(
surfaceColor,
fogColor,
hit.x,
);
}
const finalColor =
PostProcessing.apply(color, uv);
return vec4(
finalColor,
Renderer.alpha(hit.x),
);
}
}
@fragment
export function main(
@location(0) uv: vec2,
): vec4 {
return Renderer.render(uv);
}