# リヴァイア算 技術ノート **この作品の海と体が、どの式・どの値で動いているか。** 解説ページ「リヴァイア算」は高校生にも読めるように書いています。こちらはその裏側で、実際に動いているコードと数値をそのまま載せたものです。作った人:アトリエファクトリー(aTELiER FACTORY)。 - 作品と解説:`/3d/creature/leviathan/` - 実行環境:ブラウザ(WebGL2 / three.js)。サーバ側の計算はありません - この文書の .md ファイル:このページからダウンロードできます --- ## 0. 全体の作り ``` page/ index.html 作品+解説(1 つの住所。中で画面を切り替える) sea.js 海・空・泡・しぶき・描画の中心(約 2,800 行) creature.js 体の形(節・太さ・とげ・ひれ) rig.js 書き出した体(glb)に骨を貼る route.js 体が通る道(見せ場の並び) swim.js うねり・向き・足し込み muscle.js 節どうしのつながり(なめらかさ) glsl.js ノイズと空の色(シェーダから読む) web/ lev_body.glb 体のメッシュ(約 20MB) ``` 計算はすべてブラウザの中。1 コマごとに「道の上の位置 → 体の節 → 水面の変形 → 泡としぶき → 描画」の順で進みます。 --- ## 1. 海の形 ### 1-1. 波は 8 本の足し算 作品の海は、向き・高さ・波長の違う **sin 波 8 本**の重ね合わせです。値はこれだけです(`sea.js` の `WAVES`)。 | # | 向き x | 向き z | 高さ A (m) | 波長 λ (m) | とがり | |---|---|---|---|---|---| | 1 | 1.00 | 0.10 | 1.144 | 31.7 | 0.42 | | 2 | 0.90 | −0.42 | 0.638 | 16.7 | 0.46 | | 3 | 1.00 | 0.52 | 0.361 | 9.2 | 0.51 | | 4 | 0.78 | −0.62 | 0.194 | 5.1 | 0.56 | | 5 | 1.00 | 0.20 | 0.106 | 2.7 | 0.72 | | 6 | 0.58 | 0.82 | 0.055 | 1.5 | 0.74 | | 7 | 1.00 | −0.30 | 0.026 | 0.8 | 0.74 | | 8 | 0.40 | 0.92 | 0.012 | 0.4 | 0.75 | 高さも波長も、**地球の嵐の海を 1/5 に縮めた値**です(体が 300m なので、そのままだと波が何十メートルにもなってしまう)。重力は縮めず 9.81 m/s² のまま使っています。 ### 1-2. 波の速さ 深い海の波の位相速度は波長だけで決まります。 ``` ω = √(g k), k = 2π / λ, c = ω / k = √(g / k) ``` 31.7m の波は約 7.0 m/秒、0.4m の波は約 0.8 m/秒。**長い波ほど速い**ので、時間が経つと 8 本の山の位置関係が変わり続け、同じ形が戻ってきません。 ### 1-3. とがった山:ガーストナー波 sin の足し算だけでは、山も谷も同じなだらかさになります。本物の海は**山がとがり、谷がなだらか**です。そこで水の粒を、上下だけでなく**進む向きにも**動かします(ガーストナー波/トロコイド波)。 ```glsl // 1 本ぶん(Q はとがりの強さ、表の「とがり」) float phase = k * dot(dir, xz) - omega * t; y += A * cos(phase); xz += dir * (Q * A * sin(phase)); // ← これで山がとがる ``` ### 1-4. 白波は「面積がつぶれた所」 ガーストナー波では、山の頂上で水面の面積がつぶれます。つぶれ具合はヤコビアン(座標を変えたときの面積の伸び縮み)で測れます。 ``` J = 1 − Σ Qᵢ Aᵢ kᵢ cos(phaseᵢ) J が 0 に近い = 面積がつぶれている = そこが白波 ``` 作品では J が小さいほど白い泡を濃く乗せています。**白波の位置を手で描いてはいません。** --- ## 2. 空・雨・かすみ ### 2-1. 雲 同じノイズを**大きさ半分・濃さ 1/1.35 倍**にして 8 回重ねます(`glsl.js` の fbm)。 ``` value = Σ (1/1.35)ⁿ · noise(2ⁿ · p) ``` 濃さの減り方 g と大きさの比 2 から、ハースト指数 H = log(1/g) / log 2 ≒ 0.43 になります。自然の雲で測られているフラクタル次元(Lovejoy 1982、約 1.35)に合わせた値です。 ### 2-2. 雨 雨粒は毎秒 7m で落ち、風は毎秒 17m。**足し算(ベクトルの合成)**で斜めに降ります。 ``` tanθ = 17 / 7 → θ ≒ 68°(鉛直から) ``` ### 2-3. かすみ(遠くほど暗い) 光は濁った水・空気の中を進むと、進んだ長さに対して**指数関数的に**弱まります(ランベルト・ベールの法則)。 ``` T = exp( −(0.012 d + 0.0069 D) ) d:横に進んだ距離(m) D:水の深さ(m) ``` 水では約 100m ごとに半分になります(0.0069 × 100 ≒ 0.69 = ln 2)。 --- ## 3. 体 ### 3-1. 寸法 ```js const SEG = 240; // 節の数 const STEP = 1.25; // 節の間隔 (m) // 全長 = 240 × 1.25 = 300 m ``` 太さは頭から尾へ、次の式で決めています(`creature.js`)。 ```js const radiusAt = i => { const s = i / SEG; return 5.2 * Math.pow(Math.sin(Math.PI * Math.pow(Math.min(s + 0.030, 1), 0.42)), 0.62); }; ``` いちばん太い所で半径 5.2m(直径 10.4m)。`0.42` 乗にしてあるので**太い所が前寄り**になり、尾へ向かってゆっくり細くなります。 ### 3-2. 体の向き(背中がどちらを向くか) 節は「前の節の向きを受け取って、道が曲がったぶんだけ回す」形で向きを決めます(平行移動の枠=Bishop frame に近い考え方)。角度は範囲のない数として持ち続け、毎回いちばん近い向きを選ぶので、360° をまたぐ瞬間に裏返りません。 --- ## 4. 泳ぎ ### 4-1. うねりと速さの関係 体をうねらせて進む生き物(ウナギ型)の速さは、次の関係で決まります。 ``` U = f · λ · slip f:ひとふりの回数(1/秒) λ:体を伝わる波の波長(m) slip:すべり(0〜1) ``` 解説の最後のページで泳いでいる個体は、`λ = 195m`、`slip = 0.75`、`U = 24 m/秒` とし、そこから**ひとふり 6.1 秒**を計算しています(数字を決め打ちしていません)。 ```js const om = 2 * Math.PI * (U / SLIP) / LAM; // 1.03 rad/s → 周期 6.1 秒 const off = AMP * Math.sin(2 * Math.PI * back / LAM - om * t); ``` 振れ幅 `AMP = 14m`(左右に ±14m = 体長の約 9%)。本物のウナギ類の尾の振れ幅もおよそ体長の 1 割です。 ### 4-2. 24 m/秒 という見積もりの根拠 体が大きくなるほど、ひとふりは遅くなります。出せる力は筋肉の体積(体長の 3 乗)に、水の抵抗で失う力は(断面積 × 速さの 3 乗)=(体長の 2 乗 × v³)に比例するので、 ``` L³ ∝ L² v³ → v ∝ L^(1/3) ``` 体長 30m・全力 10 m/秒 のシロナガスクジラを基準にすると、300m の体では 10 × 10^(1/3) ≒ **21〜24 m/秒**。作品の見せ場で出る最大 150 m/秒 は、この見積もりをはるかに超えた「演出」です(下の「9. 物理から外したところ」を参照)。 --- ## 5. 水との作用 ### 5-1. 浮く・沈む ``` F = ρ V g (アルキメデスの原理) a = ( s − 1 ) g s:その節が水につかっている割合(0〜1) ``` 体の密度は水と同じ(ρ体 = 1)としています。**全部沈めば浮きも沈みもしない**、水から出た分だけ重さが勝って落ちる、という形です。 ### 5-2. 水の抵抗 速さを「体に沿う向き」と「横切る向き」に分け、それぞれ 2 乗で効かせます。**横向きは 25 倍**効きます。細長い体が水を横に押すのは、とても大変だからです。 ``` v = v∥ + v⊥ a∥ = −k v∥² a⊥ = −25 k v⊥² k ≒ 0.010(太さの半径+1m から決めている) ``` この差があるおかげで、体は「頭が通った跡」をなぞるように進みます。 ### 5-3. 顔を水に戻す 道の山が水面を越えると頭が空中に出ます。本物の生き物なら首が疲れるので、**ただ落ちるより速く**下げます。 ``` Δy = −½ A u² A = 5g ≒ 49 m/s² u:鼻先が水から出てからの秒数 ``` 戻すのは鼻先が水面下 10m に届くまで。そこから元の道へ 3 次曲線でなめらかにつなぎます。 ### 5-4. 水面を割る 体が水に入るとき、穴を開けるのは**先頭だけ**。後ろの節は開いた穴を通ります。 ``` 入るとき:先頭 π r²、続く体 π ( r² − r穴² ) 出るとき:持ち上がった水の膜がちぎれて y = v₀ t − ½ g t² ``` ### 5-5. 押しのけられた水(マリンスノーの流れ) 1m 進むごとに押しのける水は π r²。その水は体の横へ回り込みます。広がりの輪の半径は、 ``` r(d) = √(g d) × (後ろへの距離) ÷ v ``` --- ## 6. 泡(キャビテーション) 速く動く物のまわりでは圧力が下がり、常温のまま水が沸きます。起きるかどうかは**キャビテーション数 σ** で決まります。 ```js const RHO = 1025, P_ATM = 101325, P_VAP = 2300; // 海水の密度 / 大気圧 / 20℃の飽和蒸気圧 const cavSigma = (depth, v) => (P_ATM + RHO * 9.81 * depth - P_VAP) / Math.max(0.5 * RHO * v * v, 1); ``` σ がその場所ごとのしきい値 σᵢ より小さいと沸きます。作品では、**首のすぐ後ろ(頭から 0〜12m)で σᵢ = 0.6、それより後ろで 0.3、とげの先で 1.0**。とげの先がいちばん沸きやすい設定です。 泡は 3 つの入れ物に分けて持っています。 ```js const CAV_P = 40000; // 圧力で沸く泡(短命・毎コマ数千個) const CAV_AIR = 20000; // 水面から巻き込んだ空気(5〜15 秒残る) const CAV_BRK = 30000; // 砕けた波の泡 ``` --- ## 7. 光 ### 7-1. 水面の反射(フレネル) 正確なフレネルの式ではなく、**シュリック近似**を使っています。 ```glsl float fres = 0.022 + 0.978 * pow(1.0 - clamp(dot(N, V), 0.0, 1.0), 5.0); ``` 0.022 は水の垂直入射での反射率(屈折率 1.33 から求まる値)。浅い角度ほど 1 に近づき、遠くの水面が空を映します。 ### 7-2. 濡れた皮膚 水から出た所は、しばらく濡れて見えます。 ``` 戻る光 = 入った光 × 0.78 (濡れると暗くなる) 滴る水の反射 = 1 − (出てからの秒) ÷ 3 (3 秒で乾く) ``` --- ## 8. 解説の 16 項目 ↔ 実装 | # | 解説での言い方 | 実際に使っている式 | 場所 | |---|---|---|---| | 01 | 波は足し算 | sin 8 本の重ね合わせ+ガーストナー | `sea.js WAVES` | | 02 | 遠くほど暗い | T = exp(−(0.012d + 0.0069D)) | `sea.js` かすみ | | 03 | 道も波の足し算 | 周期の違う sin の合成で道を作る | `route.js` | | 04 | コークスクリュー | 進む向きの回りに回す(回転行列) | `route.js MOVE_HELIX` | | 05 | 高さに天井をつける | y′ = B + K(1 − e^(−(y−B)/K)) | `route.js` | | 06 | 曲がりの急さ | R = h / (2 sin(θ/2))(正弦定理) | `swim.js` | | 07 | 顔を水に戻す | Δy = −½ A u²、A = 5g | `route.js` | | 08 | 浮く・沈む | a = (s − 1) g | `swim.js` | | 09 | 水の抵抗 | a∥ = −k v∥²、a⊥ = −25k v⊥² | `swim.js` | | 10 | 体の向き | a = v²/R、cos τ = 2g ÷ 0.6a | `swim.js` | | 11 | 速さが水を沸かす | σ = (p₀ + ρgh − pᵥ) ÷ ½ρv² | `sea.js cavSigma` | | 12 | 水面を割る | π r² と π(r² − r穴²)、放物運動 | `sea.js emitAirSheet` | | 13 | 体が濡れる | 0.78 倍+3 秒で乾く | `rig.js` | | 14 | 押しのけられた水 | π r²、√(g d) の広がり | `sea.js updateSnow` | | 15 | 空の表現 | fbm(半分の大きさ・1/1.35 の濃さ) | `glsl.js NOISE` | | 16 | 大学の知識 | ヤコビアン・ポテンシャル流・フレネル・乱流 | 解説ページ内 | --- ## 9. 物理から外したところ(正直な一覧) 作品は「高校の式で動く」ことを大事にしていますが、**そのままでは成り立たない所**もあります。隠さずに書きます。 1. **海の大きさを 1/5 に縮めた。** 重力は 9.81 のまま。300m の体に本物の嵐の波を合わせると、波高が数十メートルになり、地球では起こりえない海になるため 2. **体は力学で解いていない。** 先に道を決め、その上に体を乗せています(`?knobs=1` で見られる「本物の物理だけで泳ぐ」は実験用)。抵抗・浮力・向きは、その道の上での見え方を決めるために使っています 3. **抗力はレイノルズ数を使っていない。** 係数 k と「横は 25 倍」で近似しています 4. **向きは力のつり合いではなく角度で決めている**(cos τ = 2g ÷ 0.6a)。重力を強め、遠心力を弱めた値です 5. **泡の数と寿命は見た目で決めた。** 沸く条件(σ)は物理ですが、1 コマに出す個数や消えるまでの秒数は、見え方から選んだ値です 6. **光は近似。** フレネルはシュリック近似、体の中の散乱(SSS)も簡単な近似です 7. **最大 秒速 150m は演出。** 体長からの見積もり(4-2)は 21〜24 m/秒。水中の乗り物で最速の部類(泡に包まれて進む魚雷)でも約 100 m/秒です --- ## 10. 動く見本(1 ファイルで動きます) **→ [動く見本を開く](demo/index.html)** **解説の 01〜16 を全部入れた、1 つのファイル**です。ブラウザで開けば動きます。コードは**そのファイルの中に全部**あり、`【01】`〜`【16】` の番号で、どこがどの項目か分かるようにしてあります。 | 番号 | 見本の中でやっていること | |---|---| | 01 | 8 本のガーストナー波(値は 1-1 の表と同じ) | | 02 | 深さで暗くなる(e^(−0.0069 D))と、遠くのかすみ | | 03 | **道は作品のものを 1 本そのまま**(`page/route.js` の見せ場)。深さ 68m の闇から上がって、窓の前を横切り、また潜る | | 04 | 体を進む向きのまわりにねじる(つまみ「ねじり」) | | 05 | 道が水面より上へ行かないように、指数で頭打ちにする | | 06 | 3 点を通る円(外接円)から、曲がりの半径を出す | | 07 | 頭が水から出たら 5g で引き戻す | | 08 | 節ごとの沈み具合 s から a = (s − 1) g | | 09 | 抵抗 −k v²(上下は 25 倍)。進む速さも抵抗とつり合う所に落ち着く | | 10 | 曲がりの半径から傾きを出して、体を倒す | | 11 | キャビテーション数 σ から泡を出す | | 12 | 水面をまたいだ所から、しぶきが放物運動で飛ぶ | | 13 | 水から出た所は 0.78 倍に暗くなり、3 秒で戻る | | 14 | 体のまわりの粒が、押しのけられて外へ流れる | | 15 | 空の雲(半分の大きさ・1/1.35 の濃さで 6 回重ねる) | | 16 | ヤコビアンがつぶれた所を白波にする | **動かし方**:ドラッグで回す/ホイールで寄る/**R** でもとの追いかけに戻す。つまみは 4 つ(進む速さ・波の高さ・うねりの振れ幅・ねじり)。 画面の左上に、いま計算している数字(ひとふりの秒数・沈み具合・σ・曲がりの半径・傾き)が出ます。 **自分のパソコンで動かすとき**:ファイルを保存して、先頭にある読み込み先の 1 行をこれに替えてください。 ```html ``` **入っていないもの**:体の見た目(うろこ・とげ・目・口の中)、書き出したメッシュ(約 20MB)、雨、マリンスノーの細かい作り。体は**ただの筒**です。この見本は**仕組みを読むため**のものです。 ## 11. 使ってよい条件 **この技術ノートの文章と、動く見本のコードは CC0 1.0(パブリックドメイン)です。** 誰でも、何にでも、**名前を書かずに**使えます。商売に使っても構いません。連絡も要りません。 作品「リヴァイアサン」の**形・見た目・書き出したデータ(.glb)・写真**についても、**権利を主張しません**。使いたい人は使ってください。 ひとつだけ、はっきり書いておきます。**この作品と、ここに書いた方法を、作った本人(アトリエファクトリー)が今後も自由に使えること**を前提としています。誰かが同じ内容で権利を主張して、こちらの利用を止めることはできません。この文書は日付とともに公開されており、それ自体が「先に世に出ていた」という記録になります。 なお、見本が使っている **three.js は MIT ライセンス**(Copyright 2010–2024 Three.js Authors)です。three.js を一緒に配るときは、その表示を残してください。 --- ## 12. 出どころ - ガーストナー波:F. J. Gerstner (1802)。海面表現としては J. Tessendorf の一連の資料が有名 - シュリック近似:C. Schlick (1994) - 雲のフラクタル次元 1.35:S. Lovejoy (1982) - 乱れの −5/3 則:A. N. Kolmogorov (1941) - 浮力:アルキメデスの原理 - リヴァイアサン:旧約聖書「ヨブ記」41 章。寸法の記述はない。「300 パラサング」はタルムードの記述 --- 制作:アトリエファクトリー(aTELiER FACTORY)