はじめに
今回は、今朝公開した [Noise 入門 #33] Procedural Biome と大気散乱 の内容をもとに、Next.js プロジェクト内で動かす実験デモとして、ノイズ惑星に海・砂浜・森・雪・岩肌・大気を与える実装をまとめます。
理論面では Height / Slope / Fresnel を使って惑星らしい見た目を作る話でしたが、今回の Next.js 側ではそれをそのまま記事で終わらせず、実際に触って調整できるデモとして public 配下に落とし込みました。
位置づけとしては、こんな流れです。
- 理論面: Noise 入門 #33 の Procedural Biome と大気散乱
- 実装面: Next.js プロジェクト内での Three.js + GLSL デモ化
- 今回の着地: GUI で色・地形・大気・地形の流れまで調整できるノイズ惑星
なお、今回も実行環境は Next.js プロジェクト内ですが、構成としては React コンポーネント主体ではなく、public 配下で動かすほぼバニラ Three.js + GLSL です。 そのため、Next.js のページ設計そのものよりも、Next.js を足場にした shader 実験の記録に近い内容になります。
動画(Youtube):
Procedural Biome Planet | Three.js × GLSL でノイズ惑星に海と森を与える #short
Three.js と GLSL を使って、ノイズで生成した惑星に海・砂浜・森・雪・岩肌・大気を与えるデモを実装しました。Height による biome 分岐、Slope による岩肌ブレンド、Fresnel による atmospheric scattering を組み合わせています。記事:[Next.js #34...
https://www.youtube.com/shorts/xv6tIX2VSvA動画(PC):
元になった記事
- [Noise 入門 #33] Procedural Biome と大気散乱 — ノイズ惑星に海と森、そして「青い空」を纏わせる
- [Next.js #33] Interactive Noise Sphere — Three.js × GLSLで球体ノイズにクリック波紋を加える
[Noise 入門 #33] Procedural Biome と大気散乱 — ノイズ惑星に海と森、そして「青い空」を纏わせる
Three.jsとGLSLを用いて、ノイズで生成した惑星に海や森などのバイオームを動的に割り当て、フレネル効果で青い大気を纏わせる実装手法を図解とコードで解説。無機質な星が美しい惑星へと進化する過程を体験しましょう。
https://humanxai.info/posts/noise-intro-33-procedural-biome-atmosphere/
[Next.js #33] Interactive Noise Sphere — Three.js × GLSLで球体ノイズにクリック波紋を加える
Next.js プロジェクト内で運用しているほぼバニラ Three.js + GLSL 実装として、球体ノイズ地形とクリック波紋エフェクトを組み合わせたインタラクティブデモを作成。Next.js #31 の平面版からの発展と、Noise 入門 #32 の球体ノ …
https://humanxai.info/posts/nextjs-33-interactive-noise-sphere-click-ripple/今回作ったもの
今回のデモでは、次の要素を組み合わせました。
- SphereGeometry を使った球体メッシュ
- FBM ベースの 3D ノイズによる地形生成
- 高さ(Height)による海・砂浜・森・雪の塗り分け
- 傾斜(Slope)による岩肌ブレンド
- ランバート拡散反射による陰影
- Fresnel による大気表現
- lil-gui による各種パラメータ調整
- 地形ノイズの流れ方向 / 速度の GUI 制御
見た目としては、無機質なノイズ球から、海や森を持つ惑星へ進化した球体という構成です。 単にノイズで凹凸を作るだけではなく、色のルール・傾斜のルール・視線依存の大気を重ねることで、見た目の説得力を上げています。
前回との違い
[Next.js #33] では、球体ノイズ地形に対して クリック波紋 を加えるインタラクティブデモを作りました。 あちらは「球体に触れる」「リングが広がる」というイベント駆動の表現が主役でした。
一方、今回はインタラクションの中心がクリックではなく、見た目そのものの構造化 にあります。
- 前回: 球体ノイズ + クリック波紋 + 反応
- 今回: 球体ノイズ + バイオーム + 岩肌 + 大気 + GUI 調整
つまり今回は、 “触る惑星” から “見た目を設計する惑星” へ進んだ回 と言えます。
実装の流れ
1. 球体ノイズ地形をベースにする
まずは SphereGeometry を用意し、Vertex Shader 側で 3D ノイズを使って球体を変形します。
float noiseVal = fbm(pos * 1.2);
vec3 displacedPos = position + normal * noiseVal * uDisplacement;
ここで重要なのは、平面ノイズではなく 球体の 3D 座標ベース でノイズを評価していることです。 これにより、UV の継ぎ目に依存しない、シームレスな球体地形を作れます。
また、uDisplacement を uniform として切り出しているので、GUI から隆起量を直接調整できます。
2. Height ベースでバイオームを塗り分ける
Fragment Shader では、まず vHeight を使ってベースカラーを決めています。
float waterMask = smoothstep(-0.25, -0.02, vHeight);
vec3 color = mix(uWaterDeep, uWaterShallow, waterMask);
color = mix(color, uSand, smoothstep(-0.02, 0.06, vHeight));
color = mix(color, uForest, smoothstep(0.08, 0.32, vHeight));
color = mix(color, uSnow, smoothstep(0.42, 0.62, vHeight));
流れとしては、
- まず深海色で初期化
- 高さに応じて浅瀬を混ぜる
- さらに砂浜を上塗りする
- その上に森をのせる
- 最後に高所へ雪をのせる
という順です。
この「下から上に色を積み上げる」構造にすると、境界が比較的わかりやすく、しかも smoothstep によって自然な遷移が作れます。
3. 傾斜で岩肌を出す
高さだけで色を決めると、急斜面にも森や雪がべったり張りついてしまいます。 そこで、法線と上方向ベクトルの内積を使って Slope を計算し、崖っぽい場所に岩肌を出しています。
vec3 upVector = normalize(vPosition);
float slope = max(dot(normal, upVector), 0.0);
float flatMask = smoothstep(0.4, 0.6, slope);
color = mix(uRock, color, flatMask);
平坦な面ほど元のバイオーム色を残し、 急斜面ほど岩肌に寄せることで、地形全体がかなり引き締まります。
この段階で、 ただ色分けしただけの球体 から 地形のルールを持った惑星 に一歩進みます。
4. ライティングで立体感を加える
次に、簡易的な拡散反射で地形に陰影をつけます。
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float diff = max(dot(normal, lightDir), 0.0);
vec3 lighting = vec3(0.18) + diff * vec3(0.95);
color *= lighting;
ここでは複雑な PBR をやっているわけではなく、まずは 見た目が破綻しにくい最低限の陰影 を与えることを優先しています。
暗部を完全に潰さずに少し持ち上げているので、 海・森・砂・岩の違いが見えやすくなります。
さらに今回は夜側の落ち込みも少し補正しています。
float night = smoothstep(0.25, 0.0, diff);
color = mix(color, color * vec3(0.2, 0.25, 0.35), night * 0.6);
この処理を入れることで、ただ暗くなるだけでなく、 夜側が少し青黒く沈む惑星らしい見え方 になります。
5. Fresnel で大気を重ねる
最後に、大気の薄い縁取りを加えています。
vec3 cameraPos = vec3(0.0, 0.0, 8.0);
vec3 viewDir = normalize(cameraPos - vPosition);
float fresnel = max(1.0 - dot(normal, viewDir), 0.0);
float atmosphereIntensity = pow(fresnel, uAtmospherePower);
color = mix(color, color + uAtmosphereColor * 0.35, atmosphereIntensity);
ここでは、視線と法線の関係から フチに行くほど強くなる値 を作り、 そこへ青系の色を重ねています。
初期段階では単純加算にしていましたが、青が強く出すぎたため、最終的には mix ベースにして少し抑えました。
この調整で、惑星本体の色を殺さずに大気だけを残す バランスになっています。
GUI で触れるようにした項目
今回のデモでは、lil-gui で以下を調整できるようにしました。
Terrain Generation
DisplacementTerrain SpeedFlow XFlow YFlow Z
Biome Colors
- Water Deep
- Water Shallow
- Sand
- Forest
- Snow
- Rock
Atmosphere
- Color
- Power (Thinness)
Render Settings
- wireframe
- Rotation Speed
今回特に面白かったのは、地形ノイズの流れ方向を vec3 で持たせたことです。
vec3 pos = position + uTerrainFlowDir * uTime * uTerrainSpeed;
これにより、
- 静的な惑星
- 少しだけうごめく惑星
- X 方向へ流れる地形
- 斜め方向に流れる地形
といった差を、同じ shader のまま比較できるようになりました。
もっとも、これは「惑星の自転」そのものではなく、 ノイズ入力空間のドリフト に近い表現です。 そのため、常用するというよりは、実験用パラメータとして公開しておく くらいがちょうどよいと感じています。
実装中に調整した点
今回、見た目をかなり触り直しました。
1. 青が強すぎた
最初は海色と大気色が前に出すぎて、 ほぼ「青いノイズ球」みたいな見え方になっていました。
そこで、
- 水色の彩度を少し落とす
- 大気色を少し鈍くする
- 大気の合成を加算から
mixに寄せる
といった方向で調整しています。
2. 雪が出なかった
最初の雪帯は上側に寄りすぎていて、ほとんど見えていませんでした。
color = mix(color, uSnow, smoothstep(0.42, 0.62, vHeight));
のように閾値を少し下げたことで、 高所に雪が見えるようになり、地形の読みやすさが上がりました。
3. Flow Direction の uniform が噛み合っていなかった
途中で uTerrainFlowDir を触れるようにした際、
JavaScript 側では Vector3、shader 側では float になっていて、完全に不整合でした。
最終的には、
- JS 側:
THREE.Vector3 - GLSL 側:
uniform vec3 - GUI 側:
Flow X / Y / Z
で統一し直しています。
このあたりは、GUI を増やす時ほど uniform の型をちゃんと揃える必要がある と再確認した部分でした。
今回の構成
今回も Next.js のページコンポーネントではなく、
public/ProceduralBiomeAndAtmosphericScattering/ 配下に
index.htmlapp.jsstyle.css
を置く、ほぼバニラ構成です。
このやり方だと、
- React 側の都合に引っ張られにくい
- shader 実験をそのまま置ける
- 記事とデモをすばやく接続しやすい
という利点があります。
個人的には、ノイズ記事を書いた直後にその内容を public デモへ落とし込む という流れと相性が良いです。
まとめ
今回は、[Noise 入門 #33] Procedural Biome と大気散乱 の内容を、Next.js プロジェクト内の実デモとして接続しました。
実装した要素は次の通りです。
- FBM による球体地形
- Height による海・砂浜・森・雪のバイオーム分岐
- Slope による岩肌ブレンド
- ライティングによる陰影
- Fresnel による大気表現
- GUI による地形・色・大気・流れ方向の調整
前回の [Next.js #33] が「球体ノイズへ触れる回」だったとすれば、 今回は 「球体ノイズを惑星として設計する回」 でした。
単にノイズを表示するだけでなく、
- どう色を割り当てるか
- どこを岩にするか
- どこに大気を乗せるか
- どこまで GUI で触らせるか
まで含めて考えることで、 Three.js + GLSL の球体ノイズ表現が一段階広がった感触があります。
次はこの惑星に対して、
- 雲レイヤーを追加する
- 夜側の表現をもう少し強化する
- 月や背景星を追加する
- 複数惑星へ拡張する
といった方向へ進めても面白そうです。
💬 コメント