threejs-scene について
threejs-scene は、実寸に基づく three.js の 3D シーン制作を支援するスキル兼スタータープロジェクトです。参照寸法やジオメトリの作成から、ライティング、性能確認、画面対応、フレーム単位の録画まで案内します。
主な機能
- 01レンダラーと照明設定を含むスターターシーン
- 02反復部品とスイープ形状の作成パターン
- 03空の色に合わせたライティングプリセット
- 04カメラ視点とアニメーション遷移
- 05性能確認とフレーム単位の録画
活用シーン
- 01寸法に基づく建築 3D シーンの制作
- 02インスタンシングによる反復ジオメトリの作成
- 03プリセットごとのシーン照明の調整
- 04three.js シーンの確認と録画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ moh4696/ open-3d-skills --skill threejs-sceneインストール後、Claude Code で/threejs-sceneを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlassets/starter/env.jsindex.htmlkit.jsmain.jsscene.jsreferences/geometry.mdlighting.mdperformance.mdshaders.md詳細
three.js シーン
説得力のある3Dページを作るのは、ポリゴン数ではなく、ほとんどが光とプロポーションです。ホワイトハウスがホワイトハウスらしく見えるのは、階数や柱の間隔、コーニスの奥行きが適切で、低い位置の太陽が建物に長い影を落とすからです。すべての窓に窓枠があるからではありません。このスキルでは、建築模型を作るように、実際の寸法をもとにコードでシーンを組み立て、適切にライティングします。
スターターから始める
assets/starter/ には、必要なものがすべて接続済みの、動作するシーン(芝生の上の円形パビリオン)が入っています。これをコピーし、env.js、kit.js、main.js はそのまま使い、scene.js を作りたい題材に置き換えてください。
cp -r assets/starter my-scene && cd my-scene
python3 -m http.server 8000 # any static server; ES modules need http://, not file://
| ファイル | 内容 |
|---|---|
index.html | three r169([email protected])に固定されたインポートマップ、キャンバス、タイトル、ライトボタン、ローダー |
main.js | レンダラー、カメラ、ポストエフェクトスタック、品質ティア、視点とフライト、録画フック、フレームループ |
env.js | 空、太陽、イメージベースライト、フォグ、雲、夜空、ブレンド可能な3つのプリセット、シャドウの調整 |
kit.js | シード付き乱数生成器、キャンバステクスチャ、インスタンシングキット、回転体、円弧、留め継ぎコーナー付きプロファイルスイープ |
scene.js | サンプルの題材と、参考にできるすべてのパターン |
URLパラメーターは最初から使えます。?view=front|corner|low|aerial、?light=morning|golden|night、?quality=high|balanced|fast、およびframe-captureでフレーム単位の正確な録画を行う?record=1を指定できます。
ワークフロー
- ジオメトリを書く前に、参考資料と寸法を集めます。 平面図、立面図、各側面から撮影した写真、実際の寸法(階高、ベイ間隔、柱径、ホイールベース)を用意します。シーンファイルの先頭に、メートル単位の定数として記述してください。推測したプロポーションで作った建物は、どれだけ細部を作り込んでもおもちゃのように見えます。
- 適切なサイズのボックスで大まかな形を作り、訪問者が見る視点から確認します。プロポーションはこの段階で修正してください。ディテールで問題は隠せません。
- 個々のパーツではなく、種類ごとにディテールを作ります。 柱、窓、手すり子をそれぞれ関数として1つ作り、その後
Kitを使って数百個配置します(パーツごとにドローコールは1回です)。モールディング、縁石、階段、手すりは、パスに沿ってスイープするプロファイル(sweep)で作り、丸いものには回転体を使います。 env.jsのプリセットでライティングを設定し、露出、太陽高度、フォグを調整して雰囲気を整えます。リアリティの大部分はこの段階で生まれます。- 動きを加えます。 木々に風を加え、水、旗、人、交通をインスタンス化されたスプライトとして配置し、夜にはランプを点灯させます。録画とライトの変更が常に同期されるよう、すべてを共有ユニフォーム(
U.uTime、U.uWind、U.uNight)で制御します。 site-checkで計測して確認します。 フレーム時間、ドローコール、すべての視点とライト、スマートフォンでの表示を確認します。その後、frame-captureで録画します。
重要なレンダリング設定
これらは main.js で設定されています。理由がわからない限り、そのままにしてください。
- トーンマッピング:プリセットごとに露出を設定した
ACESFilmicToneMapping(0.6~1.0)。トーンマッピングがないと、明るい空が白飛びし、すべてが2009年のゲームのように見えます。 - キャンバスではなくコンポーザーでアンチエイリアス処理:レンダラーでは
antialias: falseにし、エフェクトコンポーザー用のレンダーターゲットにはHalfFloatTypeとsamples: 4を使用します。ポストプロセッシングを有効にすると、キャンバスのMSAAは何の効果もありません。 - ポストプロセッシングの構成:
RenderPass→GTAOPass(アンビエントオクルージョン:コーニスの下、柱の間、壁と地面の接合部にできる接触影)→UnrealBloomPass(強度は昼間が約0.12、夜間が0.5)→OutputPass(最後にトーンマッピングとsRGB変換)。GTAOパスは、userData.noAOを持つもの(空のレイヤー、パーティクル、ガラス)を非表示にするサブクラスです。AOは対象に映るものすべてを暗くするためです。 - 空そのものからのイメージベース照明:
env.jsは空の専用コピーをPMREM環境マップにレンダリングするため、どのプリセットでも反射とアンビエントライトの色が空に一致します。プリセットのブレンド中に再生成してください(PMREMは負荷が高いため、6フレームごとで十分です)。 - 影を落とす太陽は1つ:4096のマップを使用し、視錐台はビューに合わせてサイズを調整してカメラのターゲットを中心に配置し、シャドウがカメラの移動に伴ってちらつかないよう、テクセル単位にスナップします。シーンに合わせて
normalBiasを調整すれば、ピーターパニングを起こさずにシャドウアクネを取り除けます。 - 望遠レンズ:視野角は38です。縦長画面では、スターターが垂直方向のFOVを広げて、横方向のビューが16:9画面と一致するようにします。スマートフォンでも被写体が切り取られず、全体が表示されます。
- ピクセル比は最大2に制限:3xのスマートフォンでは、2xと比べてピクセル数が2.25倍になりますが、見た目の違いはありません。
光
env.js の各プリセットでは、空、太陽、半球光、フォグ、露出、雲が一緒に変化します。調整する前に references/lighting.md を読んでください。要点は次のとおりです。
- 太陽が低いとき(5~10度)が、シーンを豪華に見せるポイントです。長い影、暖色の顔、グラデーションのかかった空が得られます。顔が見えるよう、光を描かれた太陽より数度高く設定してください。
- 夜は、ほとんど光がない状態です。太陽の強度は0.15前後に保ち、発光マテリアルとブルームで窓やランプにシーンを照らさせ、実際のライトはごく少数にしてください。ポイントライトとスポットライトでは、
decay: 2を指定して物理単位(カンデラ)を使います。減衰を1.4にして強度を推測で設定したところ、噴水のライト1つでシーンの半分が白飛びしました。 - 暗いライトでも、シャドウパスの処理コストは発生します。強度がゼロのときはライトを非表示にしてください(
visible = false)。
ジオメトリのパターン
詳細とコードについては references/geometry.md を参照してください。よく使うパターンは次のとおりです。
sweep(profile, path, { closed, y }):XZ平面上のパスに沿って、留め継ぎの角を持つ[外側へのオフセット, 高さ]のペアからなる2Dプロファイルを押し出します。コーニス、エンタブラチュア、縁石、道路の縁、階段、幅木、ピクチャーレールに使えます。閉じたパスでは巻き方向を判定するため、「外側」は常に外側になります。lathe([[r, y], ...]):エンタシスのある柱、手すり子、壺、ドーム、街灯、車輪、ボトルに使えます。Kit:パーツを一度定義し、行列をaddして、パーツごとに1つのInstancedMeshをbuildします。インスタンスごとの色で変化を付けられます(葉の緑、レンガの色調など)。コストはかかりません。canvasTex(w, h, draw):シード付きRNGを使い、コード内でテクスチャを描画します。石の斑点、草、砂利、標識、窓の格子などに使えます。ダウンロードもライセンスも不要で、読み込みのたびに同じ結果になります。- インスタンス化するほど繰り返されない場合は、同じマテリアルを共有する静的ジオメトリを結合してください(
BufferGeometryUtils.mergeGeometries)。
シェーダーパッチ
シェーダーをゼロから書くのではなく、onBeforeCompile を使って組み込みマテリアルを拡張します。ライティング、シャドウ、フォグをそのまま利用できます。references/shaders.md には、動作確認済みのパッチとして、風による揺れ、上向きの面に積もる雪、季節に応じた葉の色合い、夜間に明かりが灯る窓、水面のさざ波が記載されています。
見落としやすいルールがあります。頂点を動かすパッチでは、シャドウパスにも同じ変更を適用する必要があります。 scene.js の木々のように、同じ onBeforeCompile を設定した MeshDepthMaterial(RGBADepthPacking を使用)を customDepthMaterial としてメッシュに指定します。そうしないと、葉は揺れても影は動きません。
パフォーマンス
1440x900 でノート PC の GPU を使う場合の目安は、1 フレームあたり 10 ms 未満、ドローコール 300 未満、三角形 200 万個未満です。測定と改善方法については references/performance.md を参照してください。効果の高い順に示します。
- 繰り返し使うパーツ(
Kit)をインスタンス化します。通常、三角形よりもドローコール数が先に限界に達します。 - 品質ティアを設定します。
high(DPR 最大 2、GTAO、シャドウ 4096)、balanced(DPR 1.5)、fast(DPR 1、GTAO なし、シャドウ 2048)があります。スターターは遅いフレームが 3 秒続くと自動的に品質ティアを下げます。録画中に下げることはありません。 - 動いていないものは描画しないようにします。アニメーションがなく、カメラも静止している場合は、
composer.render()をスキップします。 - 小さく、遠くにあり、平らなものは
castShadowをオフにします。
カメラ
- 見る価値のあるすべての視点について、名前付きの視点(位置と注視点)を定義します。ボタン、ディープリンク、静止画、録画のすべてでこれを使います。
- 視点間の移動では、中間点を通るときに上昇し、加速と減速を滑らかにします(
main.jsのflyTo)。単純な線形補間では建物を突き抜け、動きもロボットのようになります。 OrbitControlsを制限します。maxPolarAngleは水平よりわずかに小さく設定し、最小距離と最大距離を適切にし、ダンピングを有効にします。prefers-reduced-motionが指定されている場合、移動は飛行せずに切り替わり、自動では何も動きません。
録画
?record=1 を指定すると UI が非表示になり、ページの時計に合わせて window.__recordFrame(t) からスクリプト化されたツアーが実行されます。遅延処理には setTimeout ではなく、main.js の later(secs, fn) を使ってください。frame-capture スキルで録画します(そのスキルがインストールされている場所は <frame-capture> です)。
python3 <frame-capture>/scripts/record.py "http://127.0.0.1:8000/?record=1" tour.mp4 --seconds 20 --ready "window.__ready === true"
完了前の確認事項
- ノート PC サイズとスマートフォンで、すべての照明プリセットにおけるすべての視点を確認します(
site-check)。 - シャドウのアクネがないこと、オービット中にシャドウのエッジがちらつかないこと、面が交わる箇所で Z ファイティングが起きないことを確認します(同一平面上のデカールには 1~2 cm のオフセットを付けます)。
- 物体が浮いたり沈んだりしていないことを確認します。低い視点から、すべての物体が地面と接する箇所を確認してください。
- フレーム時間とドローコール数が予算内であることを確認します。
- 対象が実在の場所、物体、ブランドの場合は、ページに非公式の検証目的であることを明記し、ロゴを使用しないでください。