ブログ |

PROMPT → WEB
#011
ANAMORPHIC SKY
CLAUDE CODE × CANVAS 2D / PERSPECTIVE

そのWeb演出、Claude Codeで作れます。クリエイティブWeb表現のプロンプト集 #11 Anamorphic Sky

株式会社グルーヴサインの米沢です。

AIコーディングでクリエイティブなWeb表現を実際に作り、そのプロンプトごと公開していく「PROMPT → WEB」の第11回です。

PROMPT → WEB #011

Anamorphic Sky

INTRODUCTION

前回(#10)の最後に、次は「歪像(ある一点から見たときだけ像を結ぶ絵)」と書きました。今回がそれです。

歪像、アナモルフォーシスと聞くと、ホルバインの『大使たち』の床に描かれた、斜めから見たときだけ髑髏になる染みを思い出す方が多いと思います。正面からはでたらめな形で、ある一点に立ったときだけ像が結ぶ。

実は、私たちは毎晩それを見ています。星座です。

オリオン座の星々は、同じ平面に並んでいるわけではありません。地球からの距離は数百光年から千数百光年まで、ばらばらに散らばっています。たまたま地球という一点から眺めたときに、狩人の形に見えているだけです。何百光年か横から眺めれば、オリオンはどこにもありません。

つまり星座は、宇宙でいちばん大きな歪像です。

今回はそれを、そのまま画面にしました。架空の星座を4つ用意し、その星を視線上のばらばらな奥行きに置いています。地球から見ると白鯨の形をしている。視点を少し動かすと、星座はほどけ、それぞれの星の本当の距離が見えてくる。地球に戻ると、また結ぶ。

RESULT

まず現物です。下のボタンを押してみてください。

クリックすると、この画面の上で Anamorphic Sky が再生されます(何度でも再生可能)。ESC または CLOSE で終了します。

深い宇宙に天の川が斜めに流れ、視点が地球へ近づいていくところから始まります。地球に着いた瞬間、星と星のあいだに線が伸び、白鯨が浮かび上がって、星座名が現れます。操作は2つです。

視点を大きく動かすと、天の川の奥に銀河の中心が見えてきます。しばらく触らずにいると、視点がひとりでに漂い、地球を通り過ぎるたびに星座が結んでは、またほどけます。ときどき流れ星も走ります。ESC または CLOSE で終了します。

WHAT WE MADE

技術的に分解すると、次のようになります。

HOW IT WORKS

1. 逆投影 ── 視線の上なら、どこに置いても同じに見える

透視投影では、3次元の点 (x, y, z) は画面の (x/z, y/z) に写ります。ということは、ある画面上の位置に見える点は、目から伸びる1本の視線の上に無数にあります。手前にあっても、奥にあっても、同じ場所に見える。

今回はこれを逆に使います。星座の図形を2次元で描いておき、それぞれの星を、その位置へ伸びる視線の上の好きな奥行きへ置くのです。

function placeFigure(figure) {
  return figure.points.map(([u, v]) => {
    const ly = 170 + random() * 1450;            // 地球からの距離(光年)。星ごとにばらばら
    const dx = u * SPREAD, dy = v * SPREAD;      // 地球から見た方向
    const k = ly / Math.hypot(dx, dy, 1);        // 視線を、距離が ly になるまで伸ばす
    return { x: dx * k, y: dy * k, z: k, ly };
  });
}

地球(原点)から見ると、x/z = dx、y/z = dy なので、奥行き k は割り算で消えます。4つの星座の57個の星すべてで、地球から見た位置と元の図形とのずれを計算すると、最大で 5.55e-17。浮動小数点の誤差だけで、完全に一致しています。

それでいて、白鯨座の17個の星は、地球から 233〜1,542光年に散らばっています。同じ平面には1つもありません。

2. 視点は「地球を通る球面」の上を動く

視点は星座の中心(奥行き760光年の地点)を見つめたまま、そのまわりを球面上で回り込みます。この球の半径を中心までの距離と同じにしておくと、角度0のときに視点がちょうど原点=地球に立ちます。

function makeCamera(yaw, pitch) {
  const fwd = { x: sin(yaw) * cos(pitch), y: sin(pitch), z: cos(yaw) * cos(pitch) };
  const pos = { x: -fwd.x * PIVOT, y: -fwd.y * PIVOT, z: PIVOT - fwd.z * PIVOT };
  const right = { x: cos(yaw), y: 0, z: -sin(yaw) };
  const down = cross(fwd, right);
  return { pos, fwd, right, down };
}

あとは、星の位置から視点を引き、right・down・fwd との内積を取って、x/z と y/z を画面に写すだけです。Three.js を入れれば一行で済む処理ですが、今回描くのは点と線だけです。ライブラリを丸ごと読み込むより、この10行を書いたほうが軽く、何が起きているかも見えます。

3. 13光年ずれただけで、星座は崩れる

作ってみて一番驚いたのがここです。地球から視点をどれだけ動かすと、形が崩れるのか。白鯨座で測りました(画面上の星座の半幅を360pxとしたとき、各星が本来の位置から最大何pxずれるか)。

視点のずれ   地球からの距離   形の崩れ(最大)
   1°           13 光年          32.9 px
   1.4°         19 光年          46.0 px
   5°           66 光年         162.0 px
  30°          393 光年         708.9 px

たった1°、13光年ずれただけで、33pxも崩れます。 星が数百光年先にあるとはいえ、手前と奥の差が1,300光年もあるので、わずかな視差で前後の星が大きくずれるのです。

これは、マウスで地球の位置に「ぴたりと」合わせるのは、ほぼ不可能だということでもあります。中心付近で1ピクセル動かしただけで、星座はもう崩れている。

そこで、地球の近く(5°以内)では視点を吸い寄せるようにしました。カーソルが中心に近づくほど、目標の角度を2乗で小さくします。

const r = Math.hypot(yaw, pitch);
if (r < SNAP) { const k = (r / SNAP) ** 2; yaw *= k; pitch *= k; }

5°の境目では倍率がちょうど1なので、吸い寄せが始まる瞬間に視点が跳ねることはありません。中心へ寄せていくと、最後に「すっ」と合って星座が結ぶ。この「合った」という手応えが、歪像を体験として成立させています。 計算上は正しくても、一点に立てなければ、見る人にとって星座は存在しないのと同じです。

4. 結ぶ演出は「線を両端から伸ばす」

視点が地球に十分近い(1.4°以内)間だけ、bind という値を0から1へ上げていきます。離れると下がります。描画はすべてこの値で決めています。

逆に、ほどけている間は、星座線を破線で薄く残しています。 線が消えてしまうと、ただ星が散らばっているだけに見えますが、破線が残っていると「さっきの鯨がこう崩れた」と分かります。視点が4°以上離れると、各星に本当の距離(640 ly など)が添えられます。

5. 夜空にも奥行きを持たせる

背景の星を画面に貼った絵にしてしまうと、視点を動かしても背景が止まったままになり、とたんに嘘っぽくなります。そこで、遠景も同じ3D空間に置いています。

すべて同じ投影を通すので、視点を動かすと近い星座の星は大きく、遠い天の川はゆっくり動きます。視差が勝手に正しくなる。正面からは見えない銀河の中心核が、視点を大きく回り込んだときだけ現れるのも、同じ理屈です。

星の色は、恒星のスペクトル型(O・B型の青白からK・M型の橙赤まで)の7色から選んでいます。星座線や背景色に色を足さなくても、これだけで夜空が豊かになります。

6. 「本当の配置」を常に見せる

左下の小窓は、星座を真上から見た図です。左端の地球から、星の数だけ視線が扇状に伸び、その上に星がばらばらの奥行きで乗っています。いまの視点の位置も、菱形で表示されます。

歪像は、仕組みが分かった瞬間に一番面白くなる表現です。結んだ星座だけを見せても「きれいな星座」で終わってしまうので、種明かしを最初から画面に出しておくことにしました。

THE PROMPT

ここが本題です。上のデモを作るためにClaude Codeへ渡せる、汎用プロンプト全文を掲載します。右上の COPY ボタンでそのままコピーできます。

あなたは3D数学とCreative Codingに精通したフロントエンドエンジニアです。
「Anamorphic Sky」= 地球から見たときだけ形になる星座を、Canvas 2D で実装してください。
星座の星は本当は奥行きがばらばらで、視点を動かすと星座がほどけ、地球に戻ると結ぶ。
星座そのものを「宇宙でいちばん大きな歪像(アナモルフォーシス)」として見せる。
(Three.js などの3Dライブラリは使わない。透視投影は自前で書く)

# 置き換える変数
- SPACE   = "#03060d"  // 深宇宙(真っ黒ではなく、ごく暗い藍)
- 星座ごとの線の色(淡く):
    白鯨座 "#a9dcff" / 帆船座 "#f6e3bd" / 渡り鳥座 "#ffc4b0" / 鍵座 "#c9f2d6"
- 星雲の色: 青緑 "#1d6b73" "#2a8a7e" / 薔薇 "#7d2f57" "#a3586a" / 琥珀 "#9a6a35" / 藍 "#1e3a78"
- 恒星の色はスペクトル型の7色: "#9db4ff" "#b5c7ff" "#d8e2ff" "#fbf8ff" "#fff1df" "#ffd2a1" "#ffb877"
- LABEL = "ANAMORPHIC SKY"

# 最重要:逆投影
- 星座は2次元の図形として定義する(各星の u,v ∈ -1..1 と等級、星どうしを結ぶ辺のリスト)
- 各星を「地球(原点)から (u*SPREAD, v*SPREAD, 1) 方向へ伸びる視線」の上の、
  ランダムな距離(170〜1620光年)に置く。視線上ならどこに置いても地球からは同じ位置に見える
- 乱数は星座ごとに固定の種から作る(開くたびに奥行きが変わらないように)
- 検証: 地球から投影した位置が元の図形と浮動小数点誤差の範囲で一致すること

# 視点
- 視点は星座の中心 (0,0,PIVOT) を見つめたまま、半径 PIVOT の球面上を yaw/pitch で回り込む。
  【重要】半径を中心までの距離と同じにし、yaw=pitch=0 のときに視点がちょうど地球(原点)に立つようにする
- 投影は「点から視点を引き、right/down/fwd との内積を取り、x/z・y/z に焦点距離を掛ける」だけ。
  z が小さすぎる点は描かない
- ポインタの位置で目標角度を決める(画面の中心=地球。横±58°、縦±26°)。視点は指数的に追従させる
- 【罠】地球から1°(13光年)ずれただけで星座は30px以上崩れる。マウスで正確に合わせるのはほぼ不可能。
  地球から5°以内では、目標角度に (r/5°)^2 を掛けて吸い寄せること。境目で倍率が1になり、跳ねない

# 結ぶ/ほどける
- 視点が地球から1.4°以内にある間だけ bind を 0→1 へ上げ(約1.3秒)、離れたら速めに下げる
- bind に応じて: ①星座線を両端の星から中点へ伸ばす ②図形の面(胴・帆・翼など)を星座の色で淡く塗る
  ③bind が1に届いた瞬間、星を一度だけ大きく瞬かせる ④星座名(和名・ラテン名・英名)と
  「N個の星は、地球から ○〜○ 光年に散らばっている」をフェードインする
- ほどけている間も星座線は破線で薄く残す(どう崩れたかが見える)
- 地球から4°以上離れたら、明るい星に本当の距離("640 ly")を添える。画面端で切れるものは出さない

# 夜空(すべて同じ3D空間に置き、同じ投影を通す。視差が自然に正しくなる)
- 遠い星 3000個(モバイル1400個)を 7000〜12000光年に。6割は天の川の帯(ある大円のまわりの正規分布)に寄せる
- 明るさはべき分布(ほとんど暗く、ごく一部が明るい)。暗い星は色ごとにまとめて fillRect、
  明るい星だけ、起動時に焼いた放射グラデーションの素材を drawImage で置く。ゆっくり瞬かせる
- 星雲は柔らかい光の塊を140個ほど遠方に置き、'lighter' で淡く重ねる。帯の一か所に暖色の中心核
- 星座の星は放射グラデの素材+明るい星だけ十字の光芒。近づいた星は明るく、遠ざかった星は暗く
- 流れ星をたまに(同時2本まで)。周辺減光
- 左下に「真上から見た配置」の小窓: 地球から伸びる視線、星の本当の位置、いまの視点(菱形)

# 架空の星座を4つ(クリック/タップで切り替え。古い星座はフェードアウトさせながら入れ替える)
- 白鯨座 BALAENA(潮吹きのある鯨)/ 帆船座 NAVIS / 渡り鳥座 AVIS MIGRANS / 鍵座 CLAVIS

# 演出の流れ
- 起動時は地球から31°離れた位置から始め、3秒ほどかけて地球へ寄って星座が結ぶ。
  画面上部に「星座は、地球からしか見えない。」と1行
- 2.6秒操作が無ければ自動航行。地球を通り過ぎるときは速度を落とし、しばらく結像を見せる
- HUD: 「視点 地球から ○ 光年」。結んでいる間は「— 結像」。DOM更新は値が変わったときだけ

# 品質要件(production)
- 全画面オーバーレイ。CLOSE ボタンと ESC で終了。多重起動を防ぐ
- レスポンシブ: 文字組みと小窓の位置は「画面幅」で決める(狭い画面では小窓を左上へ逃がす)。
  devicePixelRatio は最大2でクランプ。canvas に touch-action:none
- タッチ: なぞると視点が動く。動かさずに短く離したらタップ扱いで次の星座
- prefers-reduced-motion: アニメーションせず、結んだ星座を1枚だけ描いて静止させる
- 終了時に requestAnimationFrame を停止し、全リスナを解除し、CanvasをDOMから除去する
- Canvas 2D が使えない環境では静かに何もしない。本文の閲覧を妨げない

CUSTOMIZE

PERFORMANCE

WHAT AI DID / WHAT HUMAN DIRECTION MATTERS

透視投影も、視線上に点を置き直す逆投影も、球面上を回り込むカメラも、AIにとっては教科書どおりの仕事です。行列の向きや符号で多少迷うことはあっても、聞けば正しいコードが返ってきます。夜空の星を3,000個置くことも、天の川の帯に寄せることも、指示すればすぐにできます。

ただ、今回いちばん効いた判断は、どれもコードの外にありました。

AIは、正しく投影された星座を一瞬で作ります。でも、その星座が「合った」と感じられるかどうかは、数式の外側の問題でした。今回は、見る人が一点を探し当てる手応えをどう作るかが、作品のほとんどだったように思います。

NEXT

粒子、文字、線、物理、計算、製版、音、分割、光と影、作図、そして今回の視点。

次回はいよいよスクロール連動か、歯車や平行リンクが噛み合って動く「リンク機構」あたりを考えています。

今回のプロンプトも丸ごとコピーして使えます。星座の形を自社のロゴに置き換えて、Claude Codeに渡してみてください。ばらばらだった星が、ある一点で急に意味を持つ瞬間は、何度見ても少しどきっとします。

レガシーPHPシステムの保守・引き継ぎのご相談を受け付けています。
初回のご相談・ヒアリングは無料です。

無料相談・お問い合わせはこちら

株式会社グルーヴサイン 代表取締役 米沢忠司。PHP開発歴約20年。他社開発システムの保守・引き継ぎを主力に、2007年の設立から19期目。