[JavaScript] 3D ROOM制作 8日目 Render・Deno Deploy配置 + スロープ実装
今日やったこと
- Render に WIRED ROOM をデプロイ(5サーバー目)
- Deno Deploy に WIRED ROOM をデプロイ(6サーバー目)
- スロープの上り下り実装(controls.js)
- HDRI環境マップ + トーンマッピング導入
- Render の CORS 罠を発見・解決
Render デプロイ(5サーバー目)
Render は Static Sites を選んで GitHub リポジトリを接続するだけ。Vercel と同じ流れ。
- Static Site を選択
- GitHub の
ROOM_RENDERリポジトリを接続 - Publish Directory を
.に設定 - Build Command は空欄
- Deploy
初回デプロイで全ファイルが 404 になる問題が発生。原因はキャッシュ。「Manual Deploy」→「Clear build cache & deploy」で解決。
Render の CORS 罠
_headers ファイルが効かない。
Netlify や Cloudflare Pages と同じ形式の _headers ファイルをルートに置いたが、Render の Static Sites では無視される。
/*
Access-Control-Allow-Origin: *
このファイルを置いても、レスポンスヘッダーに Access-Control-Allow-Origin が付かない。
$ curl -I https://room-render.onrender.com/aisle/banner.webp
HTTP/2 200
content-type: image/webp
# ← Access-Control-Allow-Origin がない
解決方法:ダッシュボードの GUI で設定する。
Render ダッシュボード → Static Site → Headers → Add Rule
- Request Path:
/* - Header Name:
Access-Control-Allow-Origin - Header Value:
*
これで CORS ヘッダーが付くようになった。
$ curl -I https://room-render.onrender.com/aisle/banner.webp
HTTP/2 200
access-control-allow-origin: *
他の5サービス(GitHub Pages, Netlify, Vercel, Cloudflare Pages, Deno Deploy)はすべてファイルベースかコードベースで CORS 設定できるのに、Render だけ管理画面が必須。要注意。
Deno Deploy デプロイ(6サーバー目)
Deno Deploy は他のサービスと根本的に思想が違う。
静的ファイルホスティングではない。 エッジでJavaScriptを動かすサーバーレスプラットフォーム。Cloudflare Workers と同じ系統。
つまり、ファイルを置くだけでは動かない。静的ファイルを配信するためのスクリプトが必要になる。
Deno CLI のインストール
curl -fsSL https://deno.land/install.sh | sh
パスを通して確認:
source ~/.bashrc
deno --version
# deno 2.9.2
配信スクリプト(serve.ts)
たった6行で静的ファイル配信 + CORS 設定が完結する:
import { serveDir } from "https://deno.land/std/http/file_server.ts";
Deno.serve((req) => serveDir(req, {
fsRoot: ".",
headers: ["Access-Control-Allow-Origin:*"],
}));
CORS ヘッダーをコード内で設定できるのが Deno Deploy の強み。_headers ファイルもダッシュボード設定も不要。
デプロイ
- GitHub にリポジトリ作成(
ROOM_DENO) serve.tsを含めて push- Deno Deploy ダッシュボード(dash.deno.com)で GitHub 連携
- Entrypoint を
serve.tsに設定 - Create App
7.3秒でデプロイ完了。URL は https://room-deno.lain-lab.deno.net/。
$ curl -I https://room-deno.lain-lab.deno.net/room01/banner.webp
HTTP/2 200
access-control-allow-origin: *
server: deno
CORS もコード通りに動作。
各サービスの CORS 設定比較
| サービス | 設定方法 | 備考 |
|---|---|---|
| GitHub Pages | 不要 | デフォルトで有効 |
| Netlify | _headers ファイル | ファイルベース |
| Cloudflare Pages | _headers ファイル | ファイルベース |
| Vercel | vercel.json | JSON設定ファイル |
| Render | ダッシュボード GUI | _headers は効かない |
| Deno Deploy | serve.ts 内のコード | コードで完全制御 |
スロープの上り下り実装
これまでの衝突判定では、段差=壁として扱っていた。スロープや緩い段差を歩けるように controls.js を改修。
上りスロープ:Floor Follow
移動後にレイキャストで床のY座標を取得し、MAX_STEP(0.5m)以内の高低差なら滑らかに追従する。
// Floor follow (upward slope only)
_rayOrigin.set(S.playerPos.x, S.playerPos.y + S.PLAYER_HEIGHT, S.playerPos.z);
_floorRaycaster.set(_rayOrigin, _rayDown);
const floorHits = _floorRaycaster.intersectObjects(S.colliderMeshes, false);
if (floorHits.length > 0) {
const groundY = floorHits[0].point.y;
const diff = groundY - S.playerPos.y;
const MAX_STEP = 0.5;
if (diff > 0 && diff < MAX_STEP) {
S.playerPos.y = THREE.MathUtils.lerp(S.playerPos.y, groundY, 0.3);
S.velocity.y = 0;
S.onGround = true;
} else if (diff <= -0.05) {
S.onGround = false; // 重力に任せる
}
}
下りスロープ:重力 + レイキャスト着地
下りは floor follow ではなく重力で自然に落下させ、レイキャストで着地判定する。
// Gravity + floor snap
_rayOrigin.set(S.playerPos.x, S.playerPos.y + S.PLAYER_HEIGHT, S.playerPos.z);
_floorRaycaster.far = S.PLAYER_HEIGHT + 3.0;
_floorRaycaster.set(_rayOrigin, _rayDown);
const gravFloorHits = _floorRaycaster.intersectObjects(S.colliderMeshes, false);
if (gravFloorHits.length > 0) {
const groundY = gravFloorHits[0].point.y;
if (S.playerPos.y <= groundY) {
S.playerPos.y = groundY;
S.velocity.y = 0;
S.onGround = true;
}
}
_floorRaycaster.far を PLAYER_HEIGHT + 3.0 に拡大したのがポイント。元の値(+ 1.0)だと、段差を降りようとした時に低い床が検出範囲外になり、移動自体がブロックされていた。
処理順序
移動(X/Z) → 重力 + 着地判定 → Floor Follow(上りのみ) → カメラ更新
この順序が重要。Floor Follow をカメラ計算の前に持ってくることで、スロープ追従が1フレーム遅れるのを防ぐ。
ビジュアル改善の試行
HDRI 環境マップ
Poly Haven から studio_small_09_1k.hdr(1.6MB)をダウンロードして scene.environment に設定。
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
const rgbeLoader = new RGBELoader();
rgbeLoader.load('assets/room/studio_small_09_1k.hdr', (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping;
S.scene.environment = texture;
});
scene.environment は背景(スカイボックス)ではなく、PBR マテリアルの反射・間接光に使われる。既存のプロシージャルスカイはそのまま。
トーンマッピング
S.renderer.toneMapping = THREE.ACESFilmicToneMapping;
S.renderer.toneMappingExposure = 1.0;
結果
ローポリモデル(468tri のバンカー)ではほぼ効果なし。マテリアルが単色でメタルネスやラフネスマップがないため、HDRI の反射が効く面がない。
高ポリ + PBR マテリアルのモデル(living room interior 等)では効果が出る。基盤として残しておく。
気づき
モデル選びの基準は tri 数でもファイルサイズでもなくテクスチャのベイク品質。ベイクドテクスチャ(ライティングが焼き込まれたテクスチャ)があれば、ローポリでもリッチに見える。リアルタイムの HDRI やシャドウに頼る必要がない。
6サーバー体制
| # | ホスト | URL |
|---|---|---|
| 1 | Netlify | https://lain-lab.com/room/ |
| 2 | GitHub Pages | https://fixtan.github.io/WIRED_ROOM/ |
| 3 | Vercel | https://room-vercel.vercel.app/ |
| 4 | Cloudflare Pages | https://room-cloudflare.fixjp.workers.dev/ |
| 5 | Render | https://room-render.onrender.com/ |
| 6 | Deno Deploy | https://room-deno.lain-lab.deno.net/ |
すべてポータルで接続済み。VR でサーバー間を歩いて移動できる。
リポジトリ
- 本体: https://github.com/fixtan/WIRED_ROOM
- Render: https://github.com/fixtan/ROOM_RENDER (private)
- Deno Deploy: https://github.com/fixtan/ROOM_DENO (private)