ライブで試す このプロジェクトについて
Foldscape Memories(リポジトリ内では Nature Memories · Duo とも題されている)は、旅の写真を三次元の記憶へ開いていくインタラクティブなサイトで、React、Three.js、GSAP、Vite で構築されている。端末の表現は、作者が以前ブラウザ上で再現した折りたたみ iPhone Duo(SkentSun / iPhone Duo Depth Lab)— 内側とカバーの両画面、七つの姿勢、二種類の仕上げがすべて動く完全なモックアップ — を土台にしており、このプロジェクトはその上に「記憶」の層を重ねている。
トップページは、閉じた端末と一枚の写真から始まる。スクロールすると候補の写真が右から円筒状の軌道で入ってきて、カバー画面の縁を通って溶け込む。Open memory を押すと、端末は 5 秒かけて開き、カメラを下げ、その間に写真の中の場所が画面から立ち上がる。現在の記憶は五つ — Round Island Light 灯台、グリーンランドの氷海の赤い帆船、アイスランド Kálfeyri の海辺の家、サントリーニ、大阪城。ドラッグで回り込み、スクロールでズーム、Rewind で同じタイムラインをたどって写真を折り戻す。右上には独立した壁紙スタジオがあり、アップロードした画像や動画で内側とカバー画面を着せ替え、ブラウザ内で深度を生成する。
五つの事例の写真と GLB モデルはリポジトリに同梱されているため、閲覧に API キーは不要。任意で使えるブラウザ内の深度生成は、必要時に推論モデルをダウンロードする。サイトは Vercel にデプロイされ(ビルド出力は dist/client)、MIT ライセンスで公開されている。Apple 公式モデルと壁紙アセットは THIRD_PARTY.md に別途記載され、このライセンスの対象外だ。
体験ポイント
- トップページは円形の写真ギャラリー。スクロール、ドラッグ、または矢印キーで操作すると、各写真は円筒状の軌道で端末の縁まで移動し、カバー画面に溶け込む。逆方向にスクロールすれば同じ経路を戻り、途中のどこでも止められる。
- Open memory は、まず選んだ写真を端末へ運び、そのシーンの読み込みを待ってから、5 秒かけて端末を開き、カメラを下げ、ズームを調整し、その後 4°/秒のゆっくりした自動回転に入る。ドラッグで回り込み、スクロールでズーム、Rewind でタイムラインを逆再生して閉じた写真へ戻す。
- 五つの記憶 — Round Island Light 灯台、グリーンランドの氷海に浮かぶ赤い帆船、アイスランド Kálfeyri の海辺の家、サントリーニ、大阪城 — それぞれ一枚の写真と一つの GLB シーンで構成され、リポジトリに同梱されているため、閲覧に API キーは不要。
- 壁紙スタジオ(?view=wallpaper)は、アップロードした画像や動画から内側とカバー画面の壁紙を差し替え、ブラウザ内で高速な深度マップを計算し、小型モデルで精緻化して、ポインターに追従する視差と被写界深度を作る。仕上げは Star White と Night Sky の二種類。サウンドは既定でオフ、オンにするとプロシージャルな海岸のノイズとオリジナルのアンビエントコードが流れる。「視覚効果を減らす」設定ではシーンの動き、自動回転、慣性を無効化する。
技術解説
3D アセット · 五つの記憶シーンの GLB と、Apple のモデルから作り直した折りたたみ端末
五つの記憶はそれぞれ一枚の写真と一つの GLB シーン(lighthouse-memory-v2.glb は約 25 MB)で、public/scenes 配下に保存されている。実行時に使うのは写真の上半分だけで、それをカバーにする。README には閲覧に Tripo の API キーは不要と明記されており、キーが必要なのはアセットを制作するときだけだ。
折りたたみ端末は Apple 公式の iPhone Duo USDZ が元になっている。scripts/prepare-model.mjs が Playwright を操作して開発ページで USDZ を読み込み、可動側と固定側を folding-half / stationary-half と命名し、内側とカバー画面のマテリアルを差し替え可能な白い面に置き換え、GLTFExporter でバイナリ GLB として書き出す(テクスチャは 512 上限)。色を変えてよいのは金属フレームだけで、画面・ガラス・レンズ・ヒンジの細部には手を触れない。THIRD_PARTY.md では Apple のアセットをリポジトリの MIT ライセンスの対象外として明記している。
写真の旅 · 一本の GSAP 進行度でギャラリー・実体の写真・カバー画面を同時に駆動
円形ギャラリーは React Bits の Circular Gallery を OGL から Three.js に移植したもの。100×50 分割のメッシュ、速度に応じた正弦波の頂点の揺れ、角丸の画像サンプリングを保ちつつ、弧を奥行きと向きを持つ円筒状の軌道へ拡張し、端末と一台の PerspectiveCamera を共有する。GSAP Observer がホイール・ポインター・タッチ入力とドラッグの閾値を受け持ち、Timeline が旅を approach → receive-and-melt → settle の三段に振り付ける。新しい入力は進行中のアニメーションを中断し、ドラッグで逆方向にサンプリングすることもできる。
写真キュー、実体の写真メッシュ、そして実際のカバー画面の画像は同じ進行度で駆動され、画面の縁でテクスチャ座標が一致する。外側のメッシュは画面に入る部分で切り取られ、画面側が対応する画像を補う。カバーの切り替えには Morph Slider の Melt 分岐(intensity 0.95 / scale 4.9 / aberration 0.35)を使い、uTime を実時間ではなく旅の進行度で駆動しているため、巻き戻しや静止フレームが可能になる。
画面のポータル · 見える範囲を決めるのは、実際に動くフタだけ
シーンは完全に開いた平面上に固定して作られ、フレームごとに再構築や再スケールはされない。どこまで見えるかは、可動の左画面と固定の右画面の間の角度で決まる。フタを開くとまず右画面の上の空間が現れ、フタが垂直を越えて水平に近づくにつれ左半分が見えてくる。フタを止めれば絵も止まり、巻き戻しは同じ空間を逆にたどる。フタがシーンと交わる箇所の一時的な断面は、実際のメッシュ交線から空間的な除外を行って三角形化し、変化のない平面ではキャッシュして GPU バッファを再利用し、淡い氷色か中間的な石の色で塗り、完全に閉じた・開いた両端では非表示にする。ステンシル方式は納品メッシュに開いた境界があるため採用しなかった。左側の水面反射は、傾いた二重のシルエットを避けるため水平に近づいてから初めてフェードインする。
2026-09-16 の起動最適化では、このヒンジ部のジオメトリ切断を module worker へ移した。以前は五つのシーンそれぞれに 1〜1.6 秒の同期的な準備が必要だったが、今はメインスレッドが transferable なバッファを復元する 8〜29 ミリ秒だけで済む。新たにマウントされたシーンは描画前に現在の折りたたみ状態を適用し、水面反射用カメラは端末やギャラリーではなく自然のシーンだけを映し、壁紙スタジオのプリセットと AI ワーカーはそのビューに入ったときだけ読み込まれる。
壁紙スタジオ · ブラウザ内の深度推定と、折りたたみの光学的なディテール
アップロードした画像はまず高速な深度マップを得て、次に小型モデルがそれを精緻化する(@huggingface/transformers が depth.worker.js 内で動作し、AI ワーカーは実際に生成ボタンを押したときだけ生成される)。前景と背景は深度差に応じてずらし・ぼかし、ポインターを動かすと画像に本物の奥行きが生まれる。五つのプリセット壁紙には 512×288 の事前生成した深度マップが同梱され、動画プリセットはポスターフレームのものを使う。内側とカバー画面のレイヤーは、JSON レイアウトと 20 個の個別 SVG アイコンから実行時に透明な GPU テクスチャへ合成されるため、アイコン一つを変えても画面全体を描き直す必要はない。
折りたたみそのものはパラメータの中にある。ぼかし半径の係数は内側 3.5、カバー 1.8 で、フタを閉じるとカバー画面が先にぼける。ヒンジは 96% で完全に折れ、残り 4% は画面がピントに「収まる」ために残してある。明るさは姿勢に合わせて正弦曲線に沿って落ち、90° 付近が最も暗い。横方向の視差は、サンプリング前に画面の頂点を「完全に開いた仮想平面」へ投影するため、ジオメトリは折れていても壁紙は目の前の平らなガラス板の上を滑るように横に動き、台形に歪むことはない。


