worldlabs について
worldlabsは、World Labs Marble APIで3D環境を生成するスキルです。ゲーム開発者向けに、フォトリアルなGaussian Splatと衝突判定用メッシュなどを出力します。
主な機能
- 01テキストや画像から3D環境を生成
- 02複数解像度のSPZシーンを出力
- 03GLB衝突メッシュとパノラマを生成
- 04SparkJSでThree.jsに統合
- 05実行可能なアーケード例を提供
活用シーン
- 01コンセプトアートからゲーム環境を作成
- 02生成シーン用の衝突メッシュを作成
- 03Three.jsゲームでGaussian Splatを読み込み
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ playableintelligence/ game-creator --skill worldlabsインストール後、Claude Code で/worldlabsを入力して読み込まれたか確認します。
詳細
World Labs — 3D ワールド/環境の生成
World Labs Marble API を使用して、テキストプロンプトや画像からフォトリアルな 3D 環境を生成します。出力は、Three.js で SparkJS を介してレンダリングされる Gaussian Splat シーン(SPZ)と、物理演算用のコライダーメッシュ(GLB)です。
使用する場面
- 環境/レベルの生成 — 参照画像やテキストから、部屋、風景、建物などの 3D ワールド全体を作成します
- Meshy AI を補完 — Meshy は個々のモデルやキャラクターを生成し、World Labs はそれらが存在する環境を生成します
- フォトリアルなシーン — Gaussian Splat は、メッシュベースの環境よりもフォトリアルな品質を実現します
入力の優先順位
画像を優先 — テキストよりも常に画像入力を優先してください。
- 画像モード(デフォルト) — ユーザーが参照画像、コンセプトアート、スクリーンショット、または写真を用意している場合は、
--mode imageを使用します。AI がビジュアルスタイル、レイアウト、ライティング、雰囲気を正確に合わせられるため、最も忠実な結果が得られます。 - テキストモード(代替手段) — 参照画像がない場合に限り、
--mode textを使用します。API は短いプロンプトを詳細なシーンの説明に自動的に拡張しますが、画像を使った生成よりも結果の予測が困難です。
ゲームクリエーターのパイプラインを実行する際は、まずユーザーに参照画像を尋ねてください。
World Labs を使って、ゲーム用のフォトリアルな 3D 環境を生成できます。 環境の参照画像(写真、コンセプトアート、スクリーンショット)はありますか?
- はい → ファイルパスまたは URL を指定してください
- いいえ → 代わりにテキストの説明から生成します
技術スタック
| コンポーネント | 技術 |
|---|---|
| API | World Labs Marble API(https://api.worldlabs.ai/marble/v1) |
| 認証 | WLT-Api-Key ヘッダー |
| 出力:ビジュアル | Gaussian Splat(.spz)— 100k、500k、フル解像度の各ティア |
| 出力:物理演算 | コライダーメッシュ(.glb)— 衝突検出用 |
| 出力:スカイボックス | パノラマ画像(.jpg/.png) |
| ブラウザレンダラー | SparkJS 2.x(@sparkjsdev/spark)— SparkRenderer + SplatMesh、Three.js と互換 |
| CLI スクリプト | scripts/worldlabs-generate.mjs(依存関係なし) |
環境変数
ユーザーに尋ねる前に、キーがすでに存在するか確認してください。
test -f .env && grep -q '^WORLDLABS_API_KEY=.' .env && echo "found"
見つかった場合は、set -a; . .env; set +a でエクスポートし、ユーザーへの確認をスキップしてください。
設定されていない場合は、ユーザーに尋ねてください。
World Labs を使って、フォトリアルな 3D 環境を生成します。無料の API キーを取得できます。
- https://platform.worldlabs.ai でサインアップします
- API Keys に移動します
- 新しいキーを作成します
次のようにキーを貼り付けてください:
WORLDLABS_API_KEY=your-key-here(キーは .env に保存され、この会話から自動的に伏せ字になります。)または、「skip」と入力すると、基本的なジオメトリを使用します。
CLI スクリプトの使用方法
# Text to 3D world
WORLDLABS_API_KEY=<key> node scripts/worldlabs-generate.mjs \
--mode text --prompt "a medieval tavern with wooden beams and a roaring fireplace" \
--output public/assets/worlds/ --slug tavern
# Image to 3D world (local file or URL)
WORLDLABS_API_KEY=<key> node scripts/worldlabs-generate.mjs \
--mode image --image ./reference-photo.jpg \
--output public/assets/worlds/ --slug my-world
# Check generation status
WORLDLABS_API_KEY=<key> node scripts/worldlabs-generate.mjs \
--mode status --operation-id <op-id>
# Download assets from existing world
WORLDLABS_API_KEY=<key> node scripts/worldlabs-generate.mjs \
--mode get --world-id <id> --output public/assets/worlds/ --slug my-world
# List your worlds
WORLDLABS_API_KEY=<key> node scripts/worldlabs-generate.mjs --mode list
出力ファイル
public/assets/worlds/
tavern.spz # Gaussian Splat (full resolution)
tavern-500k.spz # Gaussian Splat (500k, medium quality)
tavern-100k.spz # Gaussian Splat (100k, lightweight/mobile)
tavern-collider.glb # Collider mesh for physics (GLB)
tavern-pano.jpg # Panorama image (skybox)
tavern.meta.json # Metadata: world ID, prompt, timestamps, asset URLs
Three.js ゲームとの統合
テスト済みで動作確認済み — 実行可能なデモ一式は examples/worldlabs-arcade/ を参照してください。
SparkJS 2.0 のインストール
npm install @sparkjsdev/spark@^2.0.0
パッケージ:@sparkjsdev/spark — Three.js 向けの高性能 Gaussian Splat レンダラーです。^2.0.0 に固定してください。0.x 系は API が異なり(SparkRenderer がありません)、以下のスニペットとは互換性がありません。SPZ、PLY、SOGS、KSPLAT、SPLAT 形式に対応し、拡張子から形式を自動判別します。
Three.js のピアバージョン:Spark 2.0 は、ピア依存関係として three@^0.180.0 を指定しています。Three.js では 0.x の各マイナーバージョンが破壊的変更を含む可能性があるものとして扱われるため、three@^0.181 以降を指定すると、npm install で ERESOLVE のピア依存関係エラーが発生します。プロジェクトの package.json で three を ^0.180.0 に固定してください。threejs-3d テンプレートと worldlabs-arcade の例でも、同じバージョンが使用されています。
バンドラーを使わない場合は、npm の代わりに CDN の importmap を使用してください。
<script type="importmap">{
"imports": {
"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.180.0/three.module.js",
"@sparkjsdev/spark": "https://sparkjs.dev/releases/spark/2.0.0/spark.module.js"
}
}</script>
WebGLRenderer の設定:antialias: false を指定して作成してください。Spark のスプラットシェーダーは独自のアンチエイリアシングを提供するため、さらに MSAA を有効にしても視覚的な効果はなく、GPU を無駄に消費します。
const renderer = new THREE.WebGLRenderer({ antialias: false });
Constants.js — ワールド設定
export const WORLD = {
splatPath: 'assets/worlds/tavern-500k.spz', // 500k is a good desktop default
colliderPath: 'assets/worlds/tavern-collider.glb',
panoPath: 'assets/worlds/tavern-pano.png',
scale: 1,
position: { x: 0, y: 0, z: 0 },
};
WorldLoader.js — Gaussian Splat とコライダーの読み込み
Spark 2.0 では、シーンレベルのオブジェクトである SparkRenderer が導入されています。これは Three.js のレンダーパイプラインに接続し、スプラットの品質、ソート、LOD を制御します。インスタンスを一度作成してシーンに追加し、通常の Three.js オブジェクトと同じように SplatMesh インスタンスを追加します。WebGLRenderer の render() 呼び出しが、すべてを透過的に処理します。
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { SparkRenderer, SplatMesh } from '@sparkjsdev/spark';
import { WORLD } from '../core/Constants.js';
let _colliderMesh = null;
let _splatMesh = null;
export async function loadWorld(scene, renderer, camera) {
// 0. SparkRenderer — quality + LOD controls for splat rendering.
// Must be added to the scene BEFORE any SplatMesh. One per scene.
const spark = new SparkRenderer({
renderer,
maxPixelRadius: 512, // bigger max → sharper close-up splats
sortRadial: true, // eliminates black-bar artifacts on camera rotation
enableLod: true, // auto-reduce splat count at distance
lodSplatScale: 1.0, // >1.0 = more splats (higher quality), <1.0 = fewer
});
scene.add(spark);
const promises = [];
// 1. Gaussian Splat via SparkJS — SplatMesh works like any Three.js object.
// `splat.initialized` is a Promise that resolves once the file is parsed
// and GPU buffers are uploaded.
if (WORLD.splatPath) {
promises.push((async () => {
const splat = new SplatMesh({ url: WORLD.splatPath });
splat.scale.setScalar(WORLD.scale);
splat.position.set(WORLD.position.x, WORLD.position.y, WORLD.position.z);
scene.add(splat);
if (splat.initialized) await splat.initialized;
_splatMesh = splat;
})());
}
// 2. Collider mesh (GLB) — invisible, for physics raycasting only
if (WORLD.colliderPath) {
promises.push((async () => {
const loader = new GLTFLoader();
const gltf = await loader.loadAsync(WORLD.colliderPath);
_colliderMesh = gltf.scene;
_colliderMesh.visible = false;
_colliderMesh.scale.setScalar(WORLD.scale);
_colliderMesh.position.set(WORLD.position.x, WORLD.position.y, WORLD.position.z);
_colliderMesh.traverse(c => { if (c.isMesh) c.material.side = THREE.DoubleSide; });
_colliderMesh.updateMatrixWorld(true); // needed before the first raycast
scene.add(_colliderMesh);
})());
}
// 3. Panorama as equirectangular skybox + environment lighting.
// NOTE: leave this OFF when using a World Labs splat scene — the panorama
// depicts the same environment as the splat and you get a "world inside a
// world" doubling effect. For IBL on Meshy characters, prefer
// `spark.renderEnvMap({ scene, worldCenter })` to light from the splat itself.
if (WORLD.panoPath) {
promises.push((async () => {
const texLoader = new THREE.TextureLoader();
const panoTex = await texLoader.loadAsync(WORLD.panoPath);
panoTex.mapping = THREE.EquirectangularReflectionMapping;
panoTex.colorSpace = THREE.SRGBColorSpace;
scene.environment = panoTex; // lighting only — NOT scene.background
})());
}
await Promise.all(promises);
return { splat: _splatMesh, collider: _colliderMesh };
}
const _raycaster = new THREE.Raycaster();
const _downDir = new THREE.Vector3(0, -1, 0);
const _rayOrigin = new THREE.Vector3();
export function getGroundHeight(x, z, fallback = 0) {
if (!_colliderMesh) return fallback;
_rayOrigin.set(x, 50, z);
_raycaster.set(_rayOrigin, _downDir);
const hits = _raycaster.intersectObject(_colliderMesh, true);
return hits.length > 0 ? hits[0].point.y : fallback;
}
export function getCollider() { return _colliderMesh; }
Game.js — レンダーループへの統合
シーンに SparkRenderer を追加すれば、スプラットのレンダリングは透過的に行われます。SparkRenderer.autoUpdate はデフォルトで true なので、renderer.render(scene, camera) がシーン内のすべての SplatMesh のソートと描画を処理します。追加の .render() や .update() 呼び出しは不要です。renderer.setAnimationLoop を使用してください(Spark 2.0 で推奨されるループ形式で、WebXR にも対応しています)。
import { loadWorld, getGroundHeight } from '../level/WorldLoader.js';
// In init():
await loadWorld(scene, renderer, camera);
// Render loop — standard Three.js, no extra splat pass:
renderer.setAnimationLoop((time) => {
const delta = clock.getDelta();
player.update(delta, input, azimuth);
// Snap player Y to collider ground
const groundY = getGroundHeight(player.mesh.position.x, player.mesh.position.z, 0);
player.mesh.position.y = groundY;
// Single render call handles both meshes AND splats
renderer.render(scene, camera);
});
解像度の段階
| 段階 | ファイル | 品質 | 用途 |
|---|---|---|---|
100k | {slug}-100k.spz | 低 | モバイル、高速読み込み、プレビュー |
500k | {slug}-500k.spz | 中 | デスクトップゲーム、バランス重視 |
full_res | {slug}.spz | 高 | ハイエンド、主役となる環境 |
対象プラットフォームに基づいて選択してください。コライダーメッシュ(GLB)は、スプラットの解像度にかかわらず同じです。
Spark 2.0 の品質調整
デフォルトの SparkRenderer({ renderer }) は、ほとんどのシーンで良好に見えます。そうでない場合、効果に直結する調整項目は次の4つです。SparkRenderer コンストラクターに渡してください。
| 症状 | 調整項目 | 方向 |
|---|---|---|
| クローズアップしたスプラットが粗い/低解像度に見える | lodSplatScale | 2.0 に近づけて上げます(描画されるスプラットが2倍になります) |
| 遠距離でスプラットが目に見えて出現/消失する | lodRenderScale | 2.0 に近づけて上げ、遠くのスプラットを画面上で大きく保ちます |
| カメラの回転時に黒い帯やティアリングが発生する | sortRadial | true(デフォルト)のままにします。コンポジットで厳密な Z 深度が必要な場合に限り、false に設定します。 |
| 低性能 GPU/モバイルでパフォーマンスが低い | lodSplatScale + maxPixelRadius | lodSplatScale を 0.5 に、maxPixelRadius を 256 に下げます |
スプラットシーンからの IBL — Spark 2.0 では、スプラットシーンを scene.environment または MeshStandardMaterial.envMap として使用できる THREE.Texture にレンダリングする spark.renderEnvMap({ scene, worldCenter }) が追加されました。Meshy のキャラクターに、配置された World Labs 環境のライティングを反映させる適切な方法です。すべてのオプションについては、SparkRenderer のドキュメントを参照してください。
パイプライン:World Labs + Meshy AI
完全な 3D ゲームを作るには、両方を組み合わせます。
- World Labs → 環境(部屋、風景、アリーナ)を生成します
- Meshy AI → キャラクターと小道具(プレイヤー、敵、アイテム)を生成します
- 統合 → キャラクターは World Labs のコライダーメッシュ上を歩き、Gaussian Splat シーン内にレンダリングされます
┌─────────────────────────────────────────────────┐
│ Complete 3D Scene │
├─────────────────────────────────────────────────┤
│ World Labs (environment) │
│ └─ Gaussian Splat (visual) │
│ └─ Collider mesh (physics) │
│ └─ Panorama (skybox) │
│ │
│ Meshy AI (entities) │
│ └─ Player character (rigged, animated GLB) │
│ └─ Enemies (rigged, animated GLB) │
│ └─ Props/items (static GLB) │
│ │
│ Three.js (engine) │
│ └─ SparkJS renders splats │
│ └─ GLTFLoader renders characters/props │
│ └─ Raycaster uses collider for ground/walls │
└─────────────────────────────────────────────────┘
参考実装
examples/worldlabs-arcade/ を参照してください。以下を備えた、完全かつテスト済みのデモです。
- World Labs の Gaussian Splat 環境(レトロなアーケード)
- アニメーションする兵士キャラクター(歩行/走行/待機)
- OrbitControls による三人称カメラ
- コライダーメッシュの地面へのレイキャスト
- パノラマのスカイボックス
トラブルシューティング
シーンが上下逆さまに表示される(Y 軸反転)
原因: World Labs の SPZ ファイルは、Three.js の規約と比べて Y 軸が反転した座標を使用しています。
修正方法: スプラットメッシュとコライダーメッシュの両方に rotation.x = Math.PI を適用します。次に position.z += (minZ + maxZ) を使って position.z を調整し、補正します。親グループに scale.y = -1 を使用しないでください。SparkJS は負の親スケールで動作しません。
レイキャストが床ではなく天井に当たる
原因: Y 軸反転後、座標系が反転しています。下向きのレイキャストは、もともと床だった場所(反転後の空間では天井)に当たります。
修正方法: Y=-50 から (0, 1, 0) の方向へ上向きにレイキャストし、見た目上の床に最初に当てます。反転後、床は最も低い位置にある面です。
コライダーメッシュへのレイキャストでヒットが返らない
原因: 特に最初のレンダーフレームより前に、回転や位置を設定した後でコライダーメッシュのワールド行列が更新されていません。
修正方法: 回転と位置を設定した直後、レイキャスト処理を行う前に _colliderMesh.updateMatrixWorld(true) を呼び出します。
シーンが二重に見える/ワールドの中にワールドがある
原因: World Labs のパノラマを scene.background として使うと、3D シーンを囲む巨大な球体として同じ環境が表示されます。
修正方法: パノラマをシーンの背景として使わないでください。単色(scene.background = new THREE.Color(0x87CEEB))またはカスタムスカイボックスを使います。
生成がタイムアウトする、または時間がかかりすぎる
原因: World Labs の生成には通常 3~8 分かかります。複雑なシーンやサーバーの高負荷により、さらに時間がかかることがあります。
修正方法: 10~15 秒ごとに操作ステータスエンドポイントをポーリングします。progress.status を確認し、"IN_PROGRESS" か "COMPLETE" かを確認します。15 分を超えて停止したままの場合は、新しい生成リクエストを作成してください。同じ操作を再試行しないでください。
スプラットファイルは読み込まれるが何も描画されない(シーンが真っ黒)
原因: Spark 2.0 では、スプラットを実際に描画するのは SparkRenderer です。SparkRenderer もシーンに追加せずに SplatMesh をシーンに追加しても、何も出力されません。
修正方法: SplatMesh を追加する前に、new SparkRenderer({ renderer }) をインスタンス化し、scene.add(spark) を実行します。シーンごとに SparkRenderer は 1 つあれば十分です。
Spark の API が違う/メソッドが存在しない
原因: 2.x ではなく 0.x がインストールされています。0.x 系は SparkRenderer、LOD、sortRadial より前のバージョンです。
修正方法: 2.0 以降に固定します:npm install @sparkjsdev/spark@^2.0.0。node_modules/@sparkjsdev/spark/package.json を確認してください。
カメラの移動に伴いスプラットが表示・非表示になる
原因: LOD システムが階層を切り替えています。広大なワールドでは問題ありませんが、小さなワールドでは目障りです。
修正方法: lodRenderScale を大きくして遠くのスプラットを視覚的に大きく保つか、全体をフルディテールで描画できる小さなシーンでは enableLod: false を設定します。
キャラクターが照明されていないように見える/スプラット環境に馴染まない
原因: Meshy(または他の GLB)のキャラクターは、環境マップのない MeshStandardMaterial を使うため、フォトリアリスティックなスプラットの背景に対して平坦に描画されます。
修正方法: spark.renderEnvMap({ scene, worldCenter }) を使ってスプラットシーンを THREE.Texture にベイクし、scene.environment = envMap を設定します。または spark.recurseSetEnvMap(character, envMap) を使ってマテリアルごとに割り当てます。
メディアのアップロードが 404 で失敗する
原因: メディアアップロードの準備に間違ったエンドポイントを使っています。
修正方法: POST /media-assets:prepare_upload(/media-assets ではありません)を使います。これは PUT アップロード用の署名付き URL を返します。コロン構文は意図的なもので、リソースに対するカスタムアクションです。
API が 401 Unauthorized を返す
原因: 認証ヘッダーの形式が間違っています。
修正方法: Authorization: Bearer <your-key> ではなく、WLT-Api-Key: <your-key> ヘッダーを使います。World Labs API はカスタムヘッダー形式を使います。
チェックリスト
-
WORLDLABS_API_KEY環境変数が設定されている - まずユーザーに参照画像を尋ねる(画像からのワールド生成が推奨されます)
-
scripts/worldlabs-generate.mjsを実行してワールドを生成する(約 3~8 分) - SPZ、コライダー GLB、パノラマが
public/assets/worlds/にダウンロードされている -
@sparkjsdev/spark@^2.0.0がインストールされている(0.x ではない。API が異なります) -
WebGLRendererがantialias: falseで作成されている -
src/level/にWorldLoader.jsが作成されている(SparkRenderer + SplatMesh + GLTFLoader) -
SplatMeshを追加する前にSparkRendererをインスタンス化してシーンに追加している - スプラットの読み込み完了として扱う前に
await splat.initializedを使っている -
Constants.jsがWORLD設定(splatPath、colliderPath、panoPath)で更新されている -
Game.jsが初期化時にloadWorld()を呼び出し、プレイヤーの Y 座標にgetGroundHeight()を使っている - 単一の
renderer.render(scene, camera)でスプラットとメッシュの両方を描画している(追加パスは不要) - 物理演算で、地面/壁のレイキャストに不可視のコライダーメッシュを使っている
- テスト:キャラクターがコライダーの表面を歩き、その周囲にスプラットが描画される
- パフォーマンス:デスクトップでは 500k SPZ、モバイルでは 100k を使い、必要に応じて
lodSplatScaleを調整する