← Blog
PhysicsSimulationCanvas APIMath

波動方程式をブラウザで可視化する — 干渉・回折・共鳴の世界

更新 2026年5月27日6 分で読める

このトピックを深掘りする

クリエイティブコーディング

ブラウザでの数学的シミュレーション・自然現象の可視化・生成アートの実装技法

全12本中 5 本目。

このトピックの記事一覧

シリーズ全体の流れを見ながら、次に読む記事へ進めます。 初めての方はホームへ →

波動方程式の離散化

連続的な波動方程式 ∂²u/∂t² = c²∇²u を有限差分法で離散化する。時間と空間を格子に分割し、差分で微分を近似する。

1D波動方程式

const N = 500
const c = 2.0 // 波の伝播速度
const dt = 0.01
const dx = 1.0

const u = new Float32Array(N)     // 現在
const uPrev = new Float32Array(N) // 1ステップ前
const uNext = new Float32Array(N) // 次のステップ

function step1D() {
  const r = (c * dt / dx) ** 2

  for (let i = 1; i < N - 1; i++) {
    uNext[i] = 2 * u[i] - uPrev[i] + r * (u[i + 1] - 2 * u[i] + u[i - 1])
  }

  // 境界条件(固定端)
  uNext[0] = 0
  uNext[N - 1] = 0

  // バッファ入れ替え
  uPrev.set(u)
  u.set(uNext)
}

r = (c*dt/dx)² はCFLクーランド数。r ≤ 1 でないと数値的に不安定になる(解が発散する)。

2D波動方程式

WaveEquationとChladniPlateで使う:

const W = 200
const H = 200

const u2d = new Float32Array(W * H)
const u2dPrev = new Float32Array(W * H)
const u2dNext = new Float32Array(W * H)
const damping = 0.999

function step2D() {
  const r = (c * dt / dx) ** 2

  for (let y = 1; y < H - 1; y++) {
    for (let x = 1; x < W - 1; x++) {
      const idx = y * W + x
      const laplacian =
        u2d[idx + 1] + u2d[idx - 1] +
        u2d[idx + W] + u2d[idx - W] -
        4 * u2d[idx]

      u2dNext[idx] = (2 * u2d[idx] - u2dPrev[idx] + r * laplacian) * damping
    }
  }

  u2dPrev.set(u2d)
  u2d.set(u2dNext)
}

damping = 0.999 はエネルギー減衰。これがないと波が永遠に反射し続けてカオスになる。

WaveInterference — 干渉パターン

2つの波源から同心円状に波を放射し、干渉パターンを可視化する:

function addSource(u: Float32Array, x: number, y: number, amplitude: number, t: number) {
  const freq = 5.0
  const idx = y * W + x
  u[idx] += amplitude * Math.sin(2 * Math.PI * freq * t * dt)
}

2つの波源を近くに置くと、強め合いと弱め合いのストライプ(ヤングの干渉縞)が現れる。波源間の距離と波長の比で縞の間隔が決まる。

ChladniPlate — 共鳴パターン

クラドニ図形は薄板の共鳴モードを可視化したもの。2D波動方程式で特定の周波数で振動させると、振幅ゼロの線(節線)が現れる:

function renderChladni(ctx: CanvasRenderingContext2D, u: Float32Array) {
  const imageData = ctx.createImageData(W, H)

  for (let i = 0; i < W * H; i++) {
    const amplitude = Math.abs(u[i])
    // 節線(振幅ゼロ付近)を白く、腹を暗く
    const brightness = amplitude < 0.01 ? 255 : Math.max(0, 255 - amplitude * 500)
    const px = i * 4
    imageData.data[px] = brightness
    imageData.data[px + 1] = brightness
    imageData.data[px + 2] = brightness
    imageData.data[px + 3] = 255
  }

  ctx.putImageData(imageData, 0, 0)
}

物理的なクラドニ実験では金属板に砂をまいて弓で弾く。砂が節線に集まり、美しい幾何学模様が現れる。それをブラウザで再現した。

SoundWavesとの関係

SoundWavesコンポーネントはWeb Audio APIAnalyserNode でリアルタイムの波形データを取得して描画する。波動方程式のシミュレーションとは異なり、実際の音声信号の可視化:

function drawWaveform(ctx: CanvasRenderingContext2D, dataArray: Uint8Array) {
  ctx.beginPath()
  const sliceWidth = ctx.canvas.width / dataArray.length

  for (let i = 0; i < dataArray.length; i++) {
    const v = dataArray[i] / 128.0
    const y = (v * ctx.canvas.height) / 2
    if (i === 0) ctx.moveTo(0, y)
    else ctx.lineTo(i * sliceWidth, y)
  }

  ctx.stroke()
}

「波」という共通テーマでも、物理シミュレーション(WaveEquation)と信号処理(SoundWaves)では実装がまったく異なる。

パフォーマンス最適化

200×200グリッドの2Dシミュレーションは毎フレーム40,000セルを更新する。Float32Arrayを使う理由:

  1. メモリ連続性: TypedArrayはC言語の配列と同じメモリレイアウトで、キャッシュヒット率が高い
  2. 数値型固定: JITコンパイラが型推論を省略できる
  3. GC不要: サイズ固定でガベージコレクションが走らない

通常の number[] と比較して、ループ処理が2-3倍速くなるケースもある。

まとめ:波動方程式で使った技術と道具

波動現象の数学的背景を理解するなら、道具棚の書籍が助けになる。差分法の安定性条件やCFL条件は、シミュレーション全般で必須の知識。