はじめに
前回の Storm Planet では、球体地形・雲球・雷の発光演出を組み合わせることで、「荒れた気候を持つ惑星」の表現を作りました。 雲が流れ、雷が走り、昼側だけでも十分に表情はありましたが、惑星として見た時にまだ足りないものがありました。夜の顔です。
[Next.js #39] Procedural Lightning on Storm Planet — 球体惑星に雷を落とし、濃い雲でランダム発火させる
Next.js連載第39回。前回の Interactive Storm を拡張し、球体惑星の雲レイヤーに Procedural Lightning を追加します。高周波ノイズと閾値処理で稲妻の形状を作り、時間ノイズで不規則なフラッシュを制御。さらに、マウスで発 …
https://humanxai.info/posts/nextjs-39-procedural-lightning-storm-planet-threejs-glsl/現実の地球を宇宙から見ると、昼の地形だけでなく、夜の都市灯が文明の存在そのものを語ります。海岸線や大陸の輪郭とは別に、人間が作った光の網が浮かび上がることで、単なる球体ではなく「誰かが暮らしている惑星」へと印象が変わります。 そこで今回は、今朝の Noise 入門 #37 で実装した Procedural Night Lights の考え方をベースに、前回の Storm Planet へ 夜景表現(Night Lights) を移植しました。
[Noise 入門 #37] Procedural Night Lights — ノイズが描く「文明の灯火」と夜の境界線
Three.jsとGLSLを用いて、惑星の昼夜の境界線(Terminator)を計算し、Voronoi Noiseで作る都市網の幾何学パターンを陸地にだけ適用する方法を図解とコードで直感的に解説。荒ぶる自然と静かに瞬く文明の境界線を描き出します。
https://humanxai.info/posts/noise-intro-37-procedural-night-lights/今回の主役は、単なる発光ノイズではありません。
昼と夜を分ける境界線(Terminator) をシェーダー内で計算し、その夜側にだけ都市灯を浮かび上がらせます。さらに、固定値だった lightDir を uLightDir として uniform 化し、GUI や時間によって太陽方向を動かせるようにしました。これにより、惑星が回っているだけではなく、昼夜境界そのものが球面上を移動し、文明の灯りが押し寄せたり引いたりする、より“生きた惑星”らしい表現へ発展しました。
動画(YouTube):
Three.js + GLSL | Moving Terminator & Night Lights on a Procedural Planet
Three.js + GLSL で Procedural Night Lights を実装。昼夜境界(Terminator)を動かしながら、夜側にだけ文明の灯りが浮かぶ惑星を描いています。Voronoi Noise / smoothstep / moving light direction.Article:htt...
https://youtube.com/shorts/xOIpLkep2mU?feature=share動画(PC):
Storm Planet に Night Lights を追加する
今回の実装は、前回の記事で構築した惑星シーンを土台にしています。 地表用の Sphere Geometry、外側に重ねた Cloud Sphere、そして大気や雷の演出といった構成はそのまま維持しつつ、地表フラグメントシェーダの最終色合成段階に「夜景レイヤ」を追加する方針です。
このアプローチの利点は、既存の地形ノイズや雲影、昼側のライティングを壊さずに拡張できることです。 昼の世界観はそのままに、夜側にだけ別の情報層を追加する。つまり今回は「地形を作り直す」のではなく、惑星にもう一つの顔を与える実装になります。
また、夜景を最初から画像テクスチャで貼るのではなく、あくまで プロシージャル に組み立てることで、これまでの Noise 入門シリーズや自作シェーダー群と思想を揃えています。 見た目だけでなく、実装そのものが「ノイズで世界を育てる」流れの延長線上にあります。
昼夜境界(Terminator)を dot(normal, lightDir) で求める
昼夜を分ける基本はシンプルです。 各ピクセルの法線 normal と、太陽方向を表す lightDir の内積を取れば、その地点がどれだけ太陽に向いているかが分かります。
float daylight = dot(normalize(vNormal), normalize(uLightDir));
この daylight は、値が大きいほど昼、0 付近が境界、マイナス側が夜になります。 球体における太陽光の当たり方はこの一行で概ね表現できるため、惑星表現では非常に強力です。 しかもこれは単なる明暗処理に留まりません。後段で 夜景をどこに出すか、雲影をどちらへ伸ばすか、薄明(twilight)をどこまで広げるか といった判断の基準にもなります。
これまで lightDir を固定ベクトルにしていた場合、惑星が自転していても「太陽の演出そのもの」は止まったままでした。 それでもデモとしては成立しますが、今回はここを一段押し進めて、昼夜境界自体を動かすことで、作品の印象を大きく変えています。
smoothstep で twilight を作る
現実の惑星には、昼と夜がカチッと切り替わる境界線は存在しません。 夕方・朝方のような、薄く光が滲む帯があります。これをシェーダーで作る時に便利なのが smoothstep です。
たとえば、daylight の値をそのまま二値化すると、昼夜境界が硬く不自然になります。 そこで smoothstep を使い、境界付近だけをなめらかに補間することで、薄明の幅 をデザインできます。
float twilight = smoothstep(-0.15, 0.15, daylight);
このようにして得られる値は、昼側へ完全に寄る前の「中間地帯」を表現できます。 ここで重要なのは、smoothstep の閾値を少し広げたり狭めたりするだけで、惑星の見え方が大きく変わることです。
- 幅を狭くすると、宇宙空間らしいシャープな境界になる
- 幅を広くすると、大気の厚みや夕焼け感が強くなる
- 少し負側へずらすと、夜景が早めに立ち上がる
- 正側へ寄せると、夜景がより深い夜だけに限定される
今回のような「文明の灯り」を見せたい回では、昼夜をきっぱり切るよりも、境界の揺らぎや滲み を残した方が、夜景の出現にドラマが生まれます。
Voronoi Noise で都市網を作る
夜景表現の核になるのが、都市の灯りをどう配置するか です。 単純なランダムノイズをそのまま発光させると、どうしても「砂粒が光っている」ような見え方になりがちです。 そこで今回は、Voronoi Noise を使って、都市が集積し、道路やネットワークのような繋がりを感じるパターンを作ります。
Voronoi 系のパターンは、セル境界や点の分布から「人工物っぽさ」を出しやすいのが特徴です。 山や雲のような自然地形ではなく、区画・街区・網目 の印象を与えられるため、文明の気配を乗せるには非常に相性が良いです。
実装としては、球体上の UV や座標を元に Voronoi パターンを生成し、その結果をそのまま使うのではなく、
- 大きい都市コア
- 周囲へ伸びる細い道路状の線
- ランダムなノイズによる明滅差
- 海や山を想像させる“灯りのない余白”
といった複数の要素を混ぜることで、単調さを避けています。
つまり、重要なのは「Voronoi を使った」ことではなく、Voronoi を文明の痕跡に読み替える ことです。 自然ノイズで地形を作るのと同じように、人工ノイズで都市圏を作る。ここが今回の面白いところです。
夜側だけに文明の灯りを浮かび上がらせる
都市灯パターンができても、それを惑星全体に均一に載せてしまうと台無しです。 夜景はあくまで 夜側でだけ見えるもの でなければなりません。 そこで daylight から夜側のマスク、つまり nightMask を作ります。
float nightMask = 1.0 - smoothstep(-0.05, 0.2, daylight);
この nightMask に都市灯の強度を掛けることで、昼側では消え、夜側へ行くほど浮かび上がる 挙動になります。 ここでも smoothstep を使うことで、昼夜境界付近でいきなり消えるのではなく、薄明帯ではうっすら、深夜側ではしっかり、という自然な立ち上がりが作れます。
この処理の良いところは、都市灯が単なるエフェクトではなく、惑星の光環境に従属する存在 になることです。 つまり、夜景を足したのではなく、「太陽に照らされる惑星の片側に、文明の光が宿っている」という関係性を作れます。
この段階まで来ると、昼側の地形・雲・雷と、夜側の都市灯が一つの惑星の中に共存し始めます。 嵐の惑星だったものが、ただ荒れているだけではなく、その闇の中に文明が息づいている惑星 へ変わっていきます。
uLightDir を uniform 化して太陽方向を動かす
今回の実装で一番大きい変化はここです。 これまで lightDir = vec3(1.0, 1.0, 1.0) のように固定していた太陽方向を、uLightDir として shader に渡すようにしました。
uniform vec3 uLightDir;
JS 側では、方位角(Azimuth)と高度(Elevation)から 3D ベクトルを作り、毎フレーム uniforms.uLightDir.value を更新します。
const x = Math.cos(el) * Math.cos(az);
const y = Math.sin(el);
const z = Math.cos(el) * Math.sin(az);
uniforms.uLightDir.value.set(x, y, z).normalize();
これによって何が変わるか。 単に“光源が動く”だけではありません。
- 昼夜境界が球面上を移動する
- 夜景が点灯する領域そのものが変わる
- 雲影や明暗の向きが太陽方向に追従する
- 「この惑星はいま何時なのか」を感じられる
つまり、太陽方向を動かせるようにした時点で、惑星は静的なデモから、時間を持つ存在 に変わります。
今回ここまで入れたことで、今後は GUI で手動に動かすだけでなく、時刻や公転を模した自動制御にも繋げやすくなりました。 昼夜境界が動くというのは、見た目以上に大きい意味を持ちます。球体が回るのではなく、世界の光そのものが巡る ようになるからです。
GUI と自動周回で “生きた惑星” にする
uLightDir を可動化したことで、GUI 側にも太陽制御パラメータを追加できるようになりました。
- sunAzimuth
- sunElevation
- sunAutoOrbit
- sunOrbitSpeed
これにより、デモとしての触り心地も大きく変わります。 以前は完成した見た目を眺めるだけだったものが、今回は 太陽の位置を変えると惑星の表情そのものが変わる ため、インタラクティブな観察対象になります。
手動で sunAzimuth を回すと、夜景帯が地表を滑るように移動し、薄明のリングが球面上を這っていきます。 sunElevation を変えると、極地方への光の入り方や夜側の面積が変わり、同じ惑星でもまるで別の時間帯や季節のような見え方になります。 さらに sunAutoOrbit を有効にすると、境界線そのものが少しずつ巡回し、止まっていた惑星に時間の流れが宿る のが分かります。
ここで面白いのは、回っているのは planet の rotation.y だけではないという点です。 本当に動いているのは、惑星に当たる光の論理 です。 この差は大きく、見ている側の印象も「ただの回転デモ」から「宇宙に浮かぶ環境シミュレーション」へ近づきます。
雷・雲・夜景が共存することで惑星の物語性が増す
前回の Storm Planet は、雲と雷によって「自然の暴力」を持つ惑星でした。 今回そこへ夜景が入ったことで、ただの嵐の球体ではなくなりました。 暗い側に人工の灯りがあることで、見る側は無意識にこう考えます。
この惑星には街がある。 文明がある。 嵐の中でも誰かが生きている。
これは単なるビジュアル追加以上の効果です。 ノイズで地形を作ると、世界は生まれます。 しかし、夜景を載せると、そこに生活圏の気配が生まれます。 自然景観だったものが、一気に SF 的な物語を帯び始めます。
しかも今回は、雷・雲・昼夜境界・夜景がそれぞれバラバラのレイヤではなく、同じ lightDir と球面法線を基準に結び付いています。 この「共通の論理で複数の演出が支えられている」ことが、全体の説得力を底上げしています。
今回の実装で見えてきた次の拡張
今回の段階で、昼夜境界を動かしながら夜景を見せる、という目的は十分達成できました。 ただ、ここまで来ると次にやりたくなることもはっきりしてきます。
一つは、時刻ベースの制御 です。 今は Azimuth / Elevation を直接いじっていますが、次は timeOfDay のような値から太陽方向を求めれば、UI もより直感的になります。 朝・昼・夕・夜という概念で惑星を操作できるようになれば、表現としてさらに強くなります。
もう一つは、季節や自転軸傾斜 の導入です。 axialTilt や seasonPhase を持たせれば、極地方の光量差や昼の長さも変えられるようになります。 そこまで行けば、単なる発光球ではなく、かなり本格的な「惑星の時空間表現」へ近づいていきます。
そして視覚面では、夜景の分布をもっと地形と関連づける余地もあります。 たとえば高地や海洋部では灯りを減らし、平野や沿岸部で密度を高めれば、より現実らしい都市圏が作れます。 今はまず「文明の灯りが生える」段階ですが、その次には「どこに文明が育つか」という地理的文脈まで入れられます。
まとめ
今回は、前回の Storm Planet を土台にして、Procedural Night Lights を Three.js / GLSL の惑星表現へ移植しました。 dot(normal, lightDir) による昼夜判定、smoothstep による twilight 制御、Voronoi Noise による都市網、そして nightMask による夜側限定の発光。この組み合わせにより、単なるノイズ球ではなく、昼と夜、自然と文明が共存する惑星 を描けるようになりました。
さらに、固定だった lightDir を uLightDir として uniform 化したことで、太陽方向を GUI や時間で動かせるようになり、昼夜境界(Terminator)そのものが作品の主役になりました。 これによって惑星は、回転するだけの球体から、時間と環境が巡る存在 へ一歩進んだと感じています。
ノイズで山を作る。 雲を流す。 雷を走らせる。 そして夜にだけ文明の灯りを浮かべる。
こうして少しずつ層を重ねていくと、球体一つでも「世界」になっていくのが面白いところです。 次は、この太陽方向の可動化をさらに進めて、時刻・季節・軸傾斜まで含めた、より本格的な惑星の時間表現にも踏み込んでみたいと思います。
Storm Planet は、雷を得て、夜景を得て、ようやく「そこに誰かが住んでいそうな惑星」になってきました。次はこの世界に、さらに時間そのものを流し込んでいきます。
💬 コメント