はじめに
大晦日、元旦も、休まず実装を続けてますが、流石に、疲れてきたので今日は半日休んでました。
毎朝、AM4時起きで炊事・洗濯・掃除して、一日分の家族の食事をまとめて作った後、24時間スーパーで買い物。
帰宅途中に、近所の神社で初詣。
帰宅後は、クルマの洗車して部屋の掃除をした後、コーヒーを飲みつつ昨日のコードを読み直してリファクタと、実装相談をAIとするという日課です。
以前は、毎年、出雲大社へ参拝して、時間があれば元旦に出雲で初詣してましたが、コロナ以降は出歩かなくなくなり、出不精になってます。
出かけても車が多くて疲れますし、特にほしい物もなく、コーディングしている方が楽しく、スキルアップになり出来る事が増えるので、結局休まずやってます。
去年の夏ごろからなんとなくブログをはじめて、半年以上経過しましたが、ほぼ毎日休まず継続していて、初期の頃に比べたら出来る事は格段に増えたと思います。
JavaScriptの基本から、カードゲームアプリ開発でHTML・CSS・JavaScriptでDOM操作、indexedDB、P2P。
ブロック崩しゲーム開発で、CANVASを駆使したアニメーションや、物理演算処理。
そして、Three.jsでWebGLを駆使した3Dゲーム開発。
この半年間の間に、かなりスキルアップした気がします。
このブログは、去年の夏、何となく立ち上げたもので、今見るとデザインが良くないので、折を見て、ゼロから作り直して、今まで学んだスキルを全部出す感じでWEBを構築しようと考えてます。
Three.jsの話に戻して、大分やりたい事をやった感があり、今日、何をやるか考えてたのですが、昨晩寝る前に、AIと対話・アイデア出しする中で、 3Dゲームでよくある、画面端にミニマップを表示するというのを実装してみたので、その備忘録メモです。

viewport/scissorで複数カメラ描画
Three.jsで「メイン視点+ミニマップ視点」を同じcanvasに同時表示する方法。
いわゆるゲームの右上ミニマップ方式は、viewport/scissor を使うのが一番シンプルで壊れにくい。
-
カメラを2台用意する
camera:通常のメイン視点cameraSub:俯瞰(ミニマップ)視点
-
1フレーム内で
renderer.render()を2回呼ぶ- その前に
setViewport()/setScissor()で「描画枠」を切り替える
- その前に
「どちらが描かれるか」は renderer ではなく camera の役割。renderer は「枠(どこに描くか)」だけ担当する。
カメラ準備(例)
// メインカメラ
config.camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
config.camera.position.set(0, 10.6, 3);
// サブカメラ(俯瞰)
config.cameraSub = new THREE.PerspectiveCamera(60, 1, 0.1, 2000);
config.cameraSub.position.set(0, 100, 0);
config.cameraSub.lookAt(0, 0, 0);
cameraSub の aspect は「ミニマップ枠は正方形」にするので 1 にしておくと扱いやすい。
描画ループ:viewport/scissorで2回描く
ポイントはこれだけ。
renderer.setScissorTest(true)を有効にする- メインを描く → ミニマップ枠に切り替える → サブカメラで描く
- 1フレームの中で
render()を2回呼んでもOK
function updateRender() {
const renderer = config.renderer;
const scene = config.scene;
const cameraMain = config.camera;
const cameraSub = config.cameraSub;
const w = window.innerWidth;
const h = window.innerHeight;
renderer.setScissorTest(true);
renderer.clear();
// ===== メインビュー(全画面)=====
renderer.setViewport(0, 0, w, h);
renderer.setScissor(0, 0, w, h);
renderer.render(scene, cameraMain);
// ===== ミニマップ(右上)=====
const size = Math.min(w, h) / 3;
const x = w - size - 10;
const y = h - size - 10;
renderer.setViewport(x, y, size, size);
renderer.setScissor(x, y, size, size);
renderer.render(scene, cameraSub);
}
setViewport / setScissor を2回呼んでいるけど「区別がつかない問題」はない。
区別は“枠”でつき、描画内容は“カメラ”で決まる。
window resizeの注意(必須)
ミニマップは正方形枠なので、メインカメラとサブカメラで更新が別。
window.addEventListener('resize', () => {
const w = window.innerWidth;
const h = window.innerHeight;
config.renderer.setSize(w, h);
// メインカメラ
config.camera.aspect = w / h;
config.camera.updateProjectionMatrix();
// サブカメラ(正方形前提)
config.cameraSub.aspect = 1;
config.cameraSub.updateProjectionMatrix();
});
よくある疑問:rendererの中でカメラが配列になる?
ならない。 renderer は「今から描く枠」を切り替えてるだけ。
renderer:描画する場所(枠)を操作するcamera:何をどう見るか(視点)を決めるscene:描かれる対象
同じ renderer に対して render(scene, camera) を2回呼んでいるだけ。
VRでは複数カメラが難しい?理由(具体)
PC(非XR)では上の方法で問題なく動く。 ただ、WebXR(VR)に入ると状況が変わる。
1) WebXRは描画フレームバッファ管理が特殊
VR中はブラウザ側(XR compositor)が表示用のフレームバッファを管理していて、通常の「1フレーム内でviewportを切り替えて2回描画」という発想がそのまま通りにくい。
2) VR中は「描画に使われるカメラ」がThree.js内部で差し替わる
renderer.render(scene, camera) を呼んでいても、VR中は内部的に XR用のカメラ(左右目) が使われる。
その結果、PCと同じ感覚で「サブカメラを描く」「HUDを貼る」などをやると、予想外に破綻しやすい。
3) RenderTargetやHUD表示は、フリーズ/黒画面になりやすい
VR中に setRenderTarget() を多用して「サブカメラをRTに描いて、それを右手の板に貼る」みたいな構成をすると、環境によっては極端に重くなったり、真っ黒になったり、最悪フリーズすることがある(エラーが出ずに起きるのが厄介)。
そのためVRでは、「PCのミニマップ」をそのまま持ち込むより、
- 方角だけを出す(コンパス)
- 静的マップ+現在地マーカー
- ワールド内の端末として表示
みたいに、VR向けに設計を変えた方が安定する。
💬 コメント