97

Underwater Oceanic Sunbeams

Underwater light simulation illustrating sunlight penetrating deep oceanic waters. Computes Snell's law refraction through dynamic surface waves, creating volumetric cathedral light shafts, surface caustics, and illuminated schooling fish silhouettes.

Playground
60 FPS Canvas 2D
Click + Drag to interact with field
</>

Full Executable Algorithm Code

// 080 - Underwater Oceanic Sunbeams (physics)
// 1:1 Original algorithm engine source
function createUnderwaterOceanicSunbeams() {
  const MARINE_SNOW_COUNT = 90;
  const FISH_COUNT = 45;
  const marineSnow = [];
  const fishSchool = [];

  function initOcean() {
    marineSnow.length = 0;
    for (let i = 0; i < MARINE_SNOW_COUNT; i++) {
      marineSnow.push({
        x: Math.random(),
        y: Math.random(),
        vx: (Math.random() - 0.5) * 0.0006,
        vy: 0.0003 + Math.random() * 0.0007,
        size: 0.8 + Math.random() * 2.0,
        phase: Math.random() * Math.PI * 2,
      });
    }

    fishSchool.length = 0;
    for (let i = 0; i < FISH_COUNT; i++) {
      const randType = Math.random();
      let fSize;
      let isLarge = false;
      if (randType < 0.18) {
        fSize = 16.0 + Math.random() * 10.0;
        isLarge = true;
      } else if (randType < 0.5) {
        fSize = 8.5 + Math.random() * 6.5;
      } else {
        fSize = 3.8 + Math.random() * 4.2;
      }
      fishSchool.push({
        x: Math.random(),
        y: 0.32 + Math.random() * 0.54,
        speed: (0.0006 + Math.random() * 0.0012) * (isLarge ? 0.75 : 1.1),
        size: fSize,
        phase: Math.random() * Math.PI * 2,
        depth: Math.random(),
        isLarge,
      });
    }
  }

  return {
    setup() {
      initOcean();
    },
    render(context, timeState, params) {
      const { ctx, width, height } = context;
      const speed = Number(params.speed ?? 1.0);
      const shaftIntensity = Number(params.shaftIntensity ?? 1.2);
      const waterClarity = Number(params.waterClarity ?? 1.0);
      const causticSpeed = Number(params.causticSpeed ?? 1.3);
      const kelpHeight = Number(params.kelpHeight ?? 1.0);

      const t = timeState.time * speed;
      if (marineSnow.length === 0) initOcean();

      // Deep Ocean Water
      const waterGrad = ctx.createLinearGradient(0, 0, 0, height);
      waterGrad.addColorStop(0, '#044368');
      waterGrad.addColorStop(0.25, '#022949');
      waterGrad.addColorStop(0.6, '#01162d');
      waterGrad.addColorStop(1.0, '#000814');
      ctx.fillStyle = waterGrad;
      ctx.fillRect(0, 0, width, height);

      const lightX = width * 0.5 + Math.sin(t * 0.3) * (width * 0.04);
      const lightY = height * 0.08;

      // 2. Surface Caustics & Water Ripple Band (Soft, Gentle Refraction)
      ctx.save();
      ctx.globalCompositeOperation = 'screen';
      const CAUSTIC_CURVES = 14;
      for (let c = 0; c < CAUSTIC_CURVES; c++) {
        const normC = c / CAUSTIC_CURVES;
        const cy0 = normC * (height * 0.10);
        ctx.beginPath();
        const steps = 60;
        for (let s = 0; s <= steps; s++) {
          const nx = s / steps;
          const x = nx * width;
          const wave1 = Math.sin(nx * 14 + t * causticSpeed * 0.7 + c) * 5;
          const wave2 = Math.cos(nx * 24 - t * causticSpeed * 0.5) * 3;
          const y = cy0 + wave1 + wave2;
          if (s === 0) ctx.moveTo(x, y);
          else ctx.lineTo(x, y);
        }
        const causticAlpha = (1 - normC) * 0.18 * waterClarity;
        ctx.strokeStyle = `rgba(160, 230, 255, ${causticAlpha})`;
        ctx.lineWidth = 1.0 + (1 - normC) * 1.5;
        ctx.stroke();
      }
      ctx.restore();

      // Volumetric Sun Shafts
      ctx.save();
      ctx.globalCompositeOperation = 'screen';
      const SHAFT_COUNT = 38;
      const maxShaftLen = height * 1.25;

      for (let i = 0; i < SHAFT_COUNT; i++) {
        const normI = i / SHAFT_COUNT;
        const baseAngle = Math.PI * 0.18 + normI * (Math.PI * 0.64);
        const waveWarp = Math.sin(baseAngle * 7 + t * 0.8) * 0.05 + Math.cos(baseAngle * 13 - t * 1.1) * 0.03;
        const shaftAngle = baseAngle + waveWarp;
        const distFromCenter = Math.abs(normI - 0.5) * 2;
        const beamIntensity = Math.pow(Math.max(0, 1 - distFromCenter * 0.8), 1.8);
        const pulse = 0.65 + 0.35 * Math.sin(i * 2.3 + t * 1.8);
        const alpha = Math.min(0.65, 0.32 * beamIntensity * pulse * shaftIntensity);

        if (alpha > 0.02) {
          const beamWidth = 0.035 + (1 - beamIntensity) * 0.02;
          const shaftGrad = ctx.createRadialGradient(lightX, lightY, 0, lightX, lightY, maxShaftLen);
          shaftGrad.addColorStop(0, `rgba(255, 255, 255, ${alpha * 1.4})`);
          shaftGrad.addColorStop(0.12, `rgba(190, 245, 255, ${alpha})`);
          shaftGrad.addColorStop(0.45, `rgba(40, 180, 230, ${alpha * 0.45})`);
          shaftGrad.addColorStop(0.85, `rgba(10, 80, 150, ${alpha * 0.15})`);
          shaftGrad.addColorStop(1.0, 'rgba(0, 30, 80, 0)');

          ctx.beginPath();
          ctx.moveTo(lightX, lightY);
          ctx.lineTo(lightX + Math.cos(shaftAngle - beamWidth) * maxShaftLen, lightY + Math.sin(shaftAngle - beamWidth) * maxShaftLen);
          ctx.lineTo(lightX + Math.cos(shaftAngle + beamWidth) * maxShaftLen, lightY + Math.sin(shaftAngle + beamWidth) * maxShaftLen);
          ctx.closePath();
          ctx.fillStyle = shaftGrad;
          ctx.fill();
        }
      }
      ctx.restore();

      // Core Glare
      ctx.save();
      ctx.globalCompositeOperation = 'screen';
      const sunCoreGrad = ctx.createRadialGradient(lightX, lightY, 0, lightX, lightY, width * 0.4);
      sunCoreGrad.addColorStop(0, 'rgba(255, 255, 255, 1.0)');
      sunCoreGrad.addColorStop(0.08, 'rgba(220, 250, 255, 0.9)');
      sunCoreGrad.addColorStop(0.28, 'rgba(80, 210, 255, 0.45)');
      sunCoreGrad.addColorStop(1.0, 'rgba(0, 0, 0, 0)');
      ctx.fillStyle = sunCoreGrad;
      ctx.beginPath();
      ctx.arc(lightX, lightY, width * 0.4, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();

      // Fish School
      for (let f = 0; f < fishSchool.length; f++) {
        const fish = fishSchool[f];
        fish.x = (fish.x + fish.speed + 1) % 1;
        const fx = fish.x * width;
        const fy = fish.y * height + Math.sin(t * 2.2 + fish.phase) * (fish.isLarge ? 8 : 4);
        const distFromLight = Math.abs(fx - lightX) / (width * 0.45);
        const inBeamFactor = Math.max(0, 1 - distFromLight);
        const hue = inBeamFactor > 0.35 ? 42 + (1 - inBeamFactor) * 55 : 188;

        ctx.save();
        ctx.fillStyle = `hsla(${hue}, 95%, ${inBeamFactor > 0.35 ? 65 : 35}%, 0.85)`;
        ctx.beginPath();
        ctx.ellipse(fx, fy, fish.size, fish.size * 0.42, 0, 0, Math.PI * 2);
        ctx.fill();

        const tailWag = Math.sin(t * (fish.isLarge ? 9 : 15) + fish.phase) * (fish.size * 0.35);
        ctx.beginPath();
        ctx.moveTo(fx - fish.size * 0.75, fy);
        ctx.lineTo(fx - fish.size * 1.6, fy - fish.size * 0.45 + tailWag);
        ctx.lineTo(fx - fish.size * 1.6, fy + fish.size * 0.45 + tailWag);
        ctx.closePath();
        ctx.fill();
        ctx.restore();
      }
    }
  };
}

const defaultParams = [
  { key: "speed", label: "Ocean Current Cadence", type: "range", min: 0.2, max: 2.5, step: 0.1, defaultValue: 1.0 },
  { key: "shaftIntensity", label: "Cathedral Sunbeam Intensity", type: "range", min: 0.4, max: 2.2, step: 0.1, defaultValue: 1.2 },
  { key: "waterClarity", label: "Water Optical Clarity", type: "range", min: 0.3, max: 2.0, step: 0.1, defaultValue: 1.0 },
  { key: "causticSpeed", label: "Surface Caustic Refraction", type: "range", min: 0.4, max: 2.5, step: 0.1, defaultValue: 1.3 },
  { key: "kelpHeight", label: "Kelp Canopy Elevation", type: "range", min: 0.4, max: 1.6, step: 0.1, defaultValue: 1.0 }
];

if (!window.__art_instances) window.__art_instances = {};
if (!window.__art_instances['underwater-oceanic-sunbeams']) {
  const inst = typeof createUnderwaterOceanicSunbeams === 'function' ? createUnderwaterOceanicSunbeams() : null;
  if (inst && inst.setup) {
    inst.setup({ ctx, width, height, dpr: 1, aspectRatio: width / height }, defaultParams);
  }
  window.__art_instances['underwater-oceanic-sunbeams'] = inst;
}

const instance = window.__art_instances['underwater-oceanic-sunbeams'];
if (instance && instance.render) {
  instance.render(
    { ctx, width, height, dpr: 1, aspectRatio: width / height },
    { time, deltaTime: dt, frameCount: Math.floor(time * 60), fps: 60 },
    defaultParams
  );
}
Edit in Interactive Playground Zero Dependencies • Standalone Canvas 2D
ƒ

Mathematical Formulation

high
Analytical Equation
n1sinθ1=n2sinθ2,I(z)=I0ecz,C(x,y,t)=det(Jrefract(x,y,t))1n_1 \sin \theta_1 = n_2 \sin \theta_2, \quad I(z) = I_0 e^{-c z}, \quad \mathcal{C}(x, y, t) = \left| \det \left( \mathbf{J}_{\text{refract}}(x, y, t) \right) \right|^{-1}
Click to expand
Compact Formula
n_air sin(θ_i) = n_water sin(θ_t), I(d) = I_0 exp(-αd) · wave_caustics(x, t)

Mathematical Tags

#underwater #sunbeams #caustics #snells-law #ocean #refraction #god-rays #marine-optics
Author: Marine Hydrodynamics Lab Target: 60 FPS

Export & Embed: Underwater Oceanic Sunbeams

4K PNG Snapshot

High-resolution single frame render

WebM Video (5s Loop)

60 FPS browser-captured stream

Standalone JS Script

Complete executable Canvas 2D algorithm

HTML Iframe Embed

<iframe src="https://art.fazleyrabbi.xyz/embed/underwater-oceanic-sunbeams" width="500" height="500" frameborder="0" loading="lazy"></iframe>
ESC
↑↓ Navigate Select
101 Mathematical Artworks

Support the Project

Keep mathematical creative coding alive & open source

Or via Direct Payoneer ($0 Fee)
Payoneer Customer ID: $0 Fee Direct
24076084

💡 Payoneer app: Go to Pay → Pay to recipient → Enter ID 24076084.

Thank you for supporting generative mathematical animations! ✨