All entries

No still was sent with this entry.

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);
}