# Answers: three pieces of an ocean shader

Try `exercises.js` first. These functions are solved in `reference-answers.js`. They are small JavaScript counterparts to ideas used by the GLSL shader; passing their tests does not by itself prove that GLSL compiled or an image rendered.

## 1. A single wave / 一つの波

```js
export function singleWave(x, time, { amplitude, frequency, speed }) {
  return amplitude * Math.sin(frequency * x - speed * time);
}
```

`amplitude` sets the largest absolute height. `frequency` is the spatial angular frequency in radians per local unit, so the wavelength is `2π / frequency`. `speed` multiplies time inside the phase; it is a phase rate in radians per second. For this single wave its crest travels in the positive x direction at `speed / frequency` local units per second. Raising frequency therefore also changes that travel speed if the phase rate stays fixed. The UI's “Speed” label is a short name for the phase-rate control, not a measurement of ocean flow.

振幅は上下の高さの上限です。周波数は空間の角周波数で、山の間隔は`2π / frequency`です。速度の値は時間に掛ける位相の速さです。この一波では、山がxの正方向へ`speed / frequency`の速さで進みます。周波数だけ変えると間隔だけでなく移動速度も変わるため、間隔の比較は停止して行います。水の流速を計算する式ではありません。

Check `amplitude = 0` (all heights zero), `x = 0, time = 0` (height zero), and `frequency*x - speed*time = π/2` (height equals amplitude). The working scene adds a second wave along z and divides the sum by 1.5:

```text
h(x,z,t) = A × [sin(kx − st) + 0.5 sin(0.7kz + 0.8st)] / 1.5
```

The opposite time sign in the second term gives another travel direction. This is a chosen visual pattern with bounded height, rather than a physical coupling between two bodies of water.

## 2. A surface normal / 傾きから法線

```js
export function normalFromGradient(dx, dz) {
  const length = Math.hypot(dx, 1, dz);
  return [-dx / length, 1 / length, -dz / length];
}
```

For a surface `(x, h(x,z), z)`, `dx` is `∂h/∂x` and `dz` is `∂h/∂z`. Tangents along the two axes are `(1, dx, 0)` and `(0, dz, 1)`. An upward perpendicular vector is `(-dx, 1, -dz)`; divide by its length before lighting. A flat patch returns `(0, 1, 0)`.

水面の高さを`h(x,z)`とすると、`dx`と`dz`は左右・前後の傾きです。面に沿う二方向に垂直で、上を向くベクトルが`(-dx, 1, -dz)`になります。長さを1にそろえることで、向きと長さを混同せずに光と比較できます。形が動いても元の平面の法線を使い続けると、波の斜面に照明が追従しません。

For the scene's two waves:

```text
∂h/∂x = A k cos(kx − st) / 1.5
∂h/∂z = A × 0.5 × 0.7 k cos(0.7kz + 0.8st) / 1.5
```

The scene transforms this local normal with `normalMatrix` into view space. It also transforms the world light direction into view space, then normalizes the interpolated normal in the fragment shader. Compare directions in one space. Three.js's normal-matrix definition is documented in [Matrix3](https://threejs.org/docs/pages/Matrix3.html), and object transforms in [Object3D](https://threejs.org/docs/pages/Object3D.html) (checked 2026-10-03 UTC).

## 3. A crest-color mask / 波頭の白さ

```js
export function crestMask(height, amplitude) {
  if (amplitude <= 0) return 0;
  const ratio = height / amplitude;
  const t = Math.max(0, Math.min(1, (ratio - 0.65) / (0.95 - 0.65)));
  return t * t * (3 - 2 * t);
}
```

The thresholds are deliberate artistic choices: no crest color at or below `0.65 × amplitude`, full mask at or above `0.95 × amplitude`, and a smooth transition between. At `0.8 × amplitude`, the mask is `0.5`. The zero-amplitude branch avoids dividing by zero and leaves the flat plane unfoamed.

高さを振幅で割った比率が0.65以下なら白さゼロ、0.95以上なら最大です。間は急に切り替えず、滑らかにつなぎます。中間の0.8では0.5になります。振幅ゼロは除算せずゼロを返します。しきい値は見た目のために選んだ値です。泡の発生や寿命、岸との接触を計算してはいません。

Try lowering the start threshold and predict the change before running it: more of each wave becomes white, while the outline stays unchanged. That is the distinction between a color mask and geometry displacement.

## Keep evidence specific / 観察を具体的に残す

Record one control, its before/after values, stage, paused time, prediction, and observed change. “It looks more realistic” hides the cause. “At the same paused time, increasing amplitude raises the outline while crest spacing stays fixed” explains it. Record browser/device and an actual measurement separately if you later investigate performance. These answers include no frame-rate claims.
