その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
技術的に分解すると、次のようになります。
- 逆投影 ── 2次元の星座の図形を、地球から伸びる視線の上の「任意の奥行き」へ置きます。この1つの操作が、作品のすべてです。
- 自前の透視投影 ── 3Dライブラリは使っていません。点を画面に写すのは、割り算を1回するだけの10行ほどの関数です。
- 回り込む視点 ── 視点は星座の中心のまわりを球面上で動き、角度0のときにちょうど地球に立つようにしてあります。
- 結ぶ/ほどける演出 ── 地球に立っている間だけ星座線が両端から伸び、図形の面が淡く浮かびます。ほどけている間は、崩れた星座線が破線で残り、本当の距離が表示されます。
- 地球への吸い寄せ ── 地球の近くでは視点が吸い寄せられ、ぴたりと合います(理由は後述。これがないと、まず合いません)。
- 奥行きのある夜空 ── 3,000個の遠い星と、140個ほどの星雲の塊を同じ3D空間に置いています。視点を動かすと、星座の星より遠景のほうがゆっくり流れます。
- 真上から見た小窓 ── 地球から伸びる視線と、その上に散らばる星の本当の位置を、常に表示しています。
- 静止フォールバック ── アニメーションを好まない設定では、結んだ星座を1枚だけ描きます。
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空間に置いています。
- 遠い星(3,000個) ── 7,000〜12,000光年の距離に配置。うち6割は天の川の帯(ある大円のまわり)に寄せています。
- 星雲(約140個) ── 柔らかい光の塊を、同じく遠方に配置。青緑・薔薇色・琥珀を重ね、天の川の一か所には暖色の中心核を置いています。
- 星座の星 ── 170〜1,620光年。
すべて同じ投影を通すので、視点を動かすと近い星座の星は大きく、遠い天の川はゆっくり動きます。視差が勝手に正しくなる。正面からは見えない銀河の中心核が、視点を大きく回り込んだときだけ現れるのも、同じ理屈です。
星の色は、恒星のスペクトル型(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
- 星座を足す ── 2次元の点と辺のリストを書き足すだけです。奥行きは逆投影が勝手に決めます。自社のロゴを星座にすると、「ある一点からだけロゴが見える」演出になります。
- 奥行きの幅 ── 星を置く距離の範囲です。幅を狭くすると少し動かしても形が保たれ、広くすると少し動かしただけでほどけます。今回は「ほどけ方」を見せたいので、あえて広く取っています。
- 吸い寄せの強さ ── 吸い寄せる範囲(5°)と、効き方(2乗)。範囲を広げると子どもでも合わせられ、狭めると「探し当てる」遊びになります。
- 実在の星座にする ── 実在の星の赤経・赤緯・距離を入れれば、本物のオリオン座がどう崩れるかを見られます(距離の推定値は文献によって幅がある点に注意)。
- 夜空の色 ── 星雲の色の組み合わせで、印象が大きく変わります。青緑と薔薇色を外して琥珀だけにすると、古い天体写真のような落ち着いた空になります。
- 種明かしの量 ── 距離のラベルや小窓を消すと、純粋に「探すと見つかる星座」になります。逆に増やすと、天文の教材に寄ります。
PERFORMANCE
- 投影は軽い ── 遠い星3,000個・星雲・星座の星を合わせた約3,160点を、1フレームで全部投影し直して 32.6µs(Node実測)。60fpsの1フレーム予算の0.2%です。視点が動くたびに全点を写し直しても、まったく問題になりません。
- 重いのは描画のほう ── 特に、画面いっぱいに重なる星雲の塊です。グラデーションを毎フレーム作らず、起動時に色ごとに1枚ずつ焼いた素材を
drawImageで置いています。星雲の塊は画面上で数百pxまで引き伸ばされるので、素材は256pxで作っています。 - 暗い星は色ごとにまとめる ── 3,000個の星を1個ずつ色を切り替えて描くと遅いので、7色のスペクトル型ごとに
fillStyleを一度だけ設定し、1〜2pxの矩形で打っています。光の素材を使うのは明るい星だけです。 - 画面外は描かない ── 投影した結果が画面外なら、その時点で捨てています。
- DOMは値が変わったときだけ書き換える ── 視点の距離などの表示は、毎フレームではなく、文字列が変わったときだけ更新しています。
- 後始末 ── 終了時にRAFを停止し、全リスナを解除してCanvasを除去。開閉を繰り返してもリークしません。
WHAT AI DID / WHAT HUMAN DIRECTION MATTERS
透視投影も、視線上に点を置き直す逆投影も、球面上を回り込むカメラも、AIにとっては教科書どおりの仕事です。行列の向きや符号で多少迷うことはあっても、聞けば正しいコードが返ってきます。夜空の星を3,000個置くことも、天の川の帯に寄せることも、指示すればすぐにできます。
ただ、今回いちばん効いた判断は、どれもコードの外にありました。
- 星座を歪像として見ること。前回予告した「歪像」をそのまま作れば、斜めから見ると絵になる床の染みになります。それを「星座こそ宇宙でいちばん大きな歪像だ」と読み替えたところで、題材と技術が一つになりました
- 吸い寄せを入れると決めたこと。数式だけなら、吸い寄せは要りません。でも「1°で33px崩れる」と測ってみると、手で地球に合わせるのは無理だと分かる。正しさとは別に、見る人が一点に立てるかを設計する必要がありました
- ほどけた状態にも意味を持たせること。破線の星座線と、本当の距離のラベル。これが無いと、視点を動かしたときに「ただ星が散らかった」だけに見えます
- 種明かしの小窓を出すこと。歪像は仕組みが分かったときに一番面白い、という判断です
- そもそも、架空の星座を4つ描くこと。白鯨、帆船、渡り鳥、鍵。点と線の座標を一つずつ決めたのは人間です
AIは、正しく投影された星座を一瞬で作ります。でも、その星座が「合った」と感じられるかどうかは、数式の外側の問題でした。今回は、見る人が一点を探し当てる手応えをどう作るかが、作品のほとんどだったように思います。
NEXT
粒子、文字、線、物理、計算、製版、音、分割、光と影、作図、そして今回の視点。
次回はいよいよスクロール連動か、歯車や平行リンクが噛み合って動く「リンク機構」あたりを考えています。
今回のプロンプトも丸ごとコピーして使えます。星座の形を自社のロゴに置き換えて、Claude Codeに渡してみてください。ばらばらだった星が、ある一点で急に意味を持つ瞬間は、何度見ても少しどきっとします。
レガシーPHPシステムの保守・引き継ぎのご相談を受け付けています。
初回のご相談・ヒアリングは無料です。