画素を3Dの点に戻す部屋を内側から撮って点群にする
Blender で作った地下通路を、内側の264か所から撮影し、写った画素をすべて3Dの点に戻して、ブラウザで粒子として動かしました。この記事では、その仕組みを分解します。後半のラボでは、わざと計算を取り違えて点群を壊してみます。
- 点群化は、レンダリング(3Dから2Dへの射影)を逆向きにたどる計算
- 重い光の計算は撮影時に1回だけ。表示側は色のついた点を描くだけ
- Blender では深度とカメラ姿勢の「正解」が分かるので、写真測量でいちばん難しい推定を飛ばせる
実例:部屋を内側から撮って点群にする
きっかけは、X に投稿された作品でした。作者の説明を要約すると、Blender の場面を Claude に264の角度からレンダリングさせ、すべての画素を3Dの点にし、three.js で粒子として動かした、というものです(@brice_deg、2026年10月20日の投稿)。この記事では、これを原作Cと呼びます。
原作Cの作り方は投稿の一文しか書かれていません。そこで、同じ手順を手元で組み直しました。これをC'(シー・プライム)と呼びます。C' のコードは Blender 用の Python(bake_room.py)と、表示用の HTML(room.html)の2本です。
C' の点群:地下通路を内側から撮って戻したもの
| C' の工程 | 中身(bake_room.py の設定) |
|---|---|
| 場面 | Python で組み立てた地下通路(長さ30m・幅8m・高さ3.2m)。濡れた床、コンクリート、柱と梁、蛍光灯、白線、配管 |
| 視点 | 通路の内側に264か所。高さ0.4〜2.6m、向きはランダム(上下は±40°まで)、柱の中は避ける |
| 撮影 | Blender 5.2.2 の Cycles。透視カメラ・画角80°・128×128画素・24サンプル |
| 画素→点 | 各画素の中心を通る光線を場面に当てて位置を求め、色はレンダー結果から取る |
| 間引き | 2.5cm の立方体ごとに束ね、位置と色を平均する |
| 結果 | 264 × 128 × 128 = 約433万画素 → 約171万点(25.6MB)。手元の Mac で約3分 |
射影の往復
レンダリングは、3Dの場面を2Dの画像に写す計算(射影)です。C' はこれを一度逆にたどって画素を3Dに戻し、ブラウザでもう一度、順向きに射影しています。
重い光の計算(どこが明るく、どこに何が映り込むか)は、撮影のときに1回だけ行い、結果を点の色として凍らせています。ブラウザの three.js は光を一切計算せず、色つきの点を画面に写すだけです。ゲームの「ライトマップ」と同じで、計算を実行時から前計算へ移す最適化です。この手法をベイクと呼びます。
画素を視線に戻す行列 K、部屋に戻す行列 T
1つの画素を3Dの点に戻すには、3つの材料がいります。
- その画素が、カメラから見てどの方向か:カメラの焦点距離と画像の中心から決まります。これをまとめた行列を K(内部パラメータ)と呼びます。
- その方向に、どれだけ進んだ所に面があったか:深度です。
- カメラが部屋のどこにあり、どちらを向いていたか:これをまとめた行列を T(外部パラメータ)と呼びます。
手順は「画素 → 視線の方向 → 深度の分だけ進む → 部屋の座標へ移す」の順です。下のラボの画像をクリックすると、選んだ画素でこの計算が実際の数値で表示され、3D表示にはカメラから点までの線が引かれます。
数式で見る
(u, v) は画素の位置(画素の中心は u + 0.5)、z は深度、K⁻¹ は K の逆行列です。K⁻¹ · (u, v, 1) は「奥行き1のところでの視線の位置」になり、それに z を掛けるとカメラから見た点の座標になります。最後に T(カメラの回転と位置)を掛けると部屋の座標です。
ラボでは画角90°・96×96画素なので fx = 48 / tan 45° = 48 です。
逆射影ラボ:撮って、戻して、壊して学ぶ
壊すと何が起きるか
| わざと壊す項目 | 起きること | 理由 |
|---|---|---|
| 深度の種類を取り違える | 平らな壁が、カメラに向かってふくらむ | 深度には「視線軸方向の z」と「光線に沿った距離」の2種類がある。z を距離だと思って単位ベクトルに掛けると、画面の端ほど手前に引き寄せられる |
| 輪郭の深度を平均する | 立方体や球の縁から、点が空中に尾を引く | 輪郭の画素では前景と背景の深度が混ざり、どちらの面にもない距離になる(flying pixels) |
| 画角を取り違える | 視点ごとの点群がずれて、二重に見える | K が違うと、視線の方向が視点ごとにずれる。1視点だけだと気づきにくい |
| 色空間を二重に変換する | 全体が白っぽくなる | 撮影時に表示用の sRGB にした色を、表示側がもう一度 sRGB に変換している |
| Z上→Y上の変換を外す | 部屋が横倒しになる | Blender は Z が上、three.js は Y が上。(x, y, z) を (x, z, −y) に写す必要がある |
「鏡面の床」を入れると、床の色が視点によって変わります。視点を増やすと、同じ床の点に違う色が重なり、まだらに見えます。「10cm のマスで間引く」を入れると、同じマスの点が1点にまとめられ、色も平均されてまだらがならされます。「点密度のヒートマップ」では、カメラに近い面ほど点が混んでいることが分かります。
深度の定義について:Blender の Cycles は、2020年5月の変更で、透視カメラの深度パスを視線軸方向の z(EEVEE や OpenGL と同じ)にしました。パノラマカメラは変更されず、カメラ中心からの距離のままです(コミット 18cda8be)。使うバージョンとカメラの種類で確かめてください。
何を捨てたか
点群は、場面そのものではなく「特定の視点から見えた光の記録」です。だから、次のものが抜け落ちます。
| 捨てたもの | 現れる症状 | 背景にある原理 |
|---|---|---|
| 視点による光の変化 | 反射やハイライトが面に貼りつき、視点を動かすと不自然。複数の角度の色がずれてちらつく | 光沢の見え方は視点によって変わる。3D Gaussian Splatting が点ごとの色を視点の関数(球面調和関数)にしているのは、この問題を解くため |
| 見えなかった面 | 柱の裏などに穴があく | 点になるのは見えた所だけ。多くの角度から撮るのは、隠れた所を埋めるため |
| 均一な密度 | 近い面は密、遠い面は疎。重なった視野では点が重複する | 遠くの1画素ほど広い面積を覆う(透視投影) |
| 面のつながり | 近づくと点の隙間が見える | 点は面ではなく、面から取った標本にすぎない |
C' では、2.5cm のマスで位置と色を平均しています。濡れた床の映り込みのように視点で変わる色は、ここでならされます。ちらつきは減りますが、映り込みは「床に描かれた模様」になります。
内側から撮る意味:答えが分かっている写真測量
現実のカメラでは、その場で向きを変えるだけでは奥行きが分かりません。視差が生じないからです。奥行きを測るには、カメラを動かして三角測量をする必要があります。
Blender では、深度とカメラ姿勢を正解として取り出せます。だから部屋の中心の一点から撮っても3Dに戻せます。つまり C' は「答え(深度とカメラ姿勢)が最初から分かっている写真測量」です。現実の写真測量や NeRF、3D Gaussian Splatting では、この答えを画像から推定する部分(SfM など)がいちばん難しい所です。C' はその難所を外したので、幾何だけを純粋に体験できます。
部屋の内側から撮ると、すべての画素がどこかの面に当たります。無限遠の背景がないので、点にできない画素がありません。これも、点群化しやすい理由です。
C' の実装で分かったこと
- 深度でなく、光線を当てて位置を出した。 当初は Cycles の Position パス(画素ごとの3D座標)を使う予定でした。しかし、この書き出し方(多層 EXR)では Combined(色)しか得られませんでした。この記事のために深度パスを有効にして試したときも、Blender 5.2.2 の多層 EXR には色しか入っていませんでした。そこで、画素の中心を通る光線を場面の形に当てて位置を求めています。これは「深度の正解」を別の道で得るのと同じです。
- 色と位置を同じ点に合わせた。 画素の色は、普通は画素の範囲を少しぼかして平均します。C' はこのぼかし(画素フィルタ)をほぼ0にし、色も位置も画素の中心を指すようにしました。輪郭で色と位置がずれるのを防ぐためです。
- 色は撮影時に表示用へ変換済み。 Cycles の色は物理量に近い値なので、撮影時に明るさを圧縮して sRGB にしてから保存しています。表示側で再び変換すると白っぽくなります。
- 座標を Y 上へ写した。 書き出しの直前に (x, y, z) → (x, z, −y) と並べ替えています。
原作Cとの違い
| 原作C(@brice_deg) | C' | |
|---|---|---|
| 作り方の情報 | 作者の説明は投稿の一文のみ | コード全体が手元にある |
| 視点の数 | 264(作者の説明) | 264(同じ数にそろえた) |
| 見せ方 | 元のレンダー映像と点群の映像を交互に切り替える編集がある | 点群だけ |
原作Cの映像そのものは、作者の投稿で見てください。この記事では、原作の画像や映像は使っていません。
用語集
- 射影
- 3Dの点を2Dの画像の上に写す計算。カメラで撮るのと同じこと
- 逆射影
- 画素と深度から、3Dの点を求める計算。射影の逆向き
- 深度
- カメラから面までの奥行き。視線軸方向の z と、光線に沿った距離の2種類がある
- 内部パラメータ K
- 焦点距離と画像の中心をまとめた行列。画素を視線の方向に変換する
- 外部パラメータ T
- カメラの位置と向きをまとめた行列。カメラから見た座標を部屋の座標へ移す
- 点群
- 位置と色を持つ点の集まり。面やつながりの情報は持たない
- ベイク
- 重い計算を前もって済ませ、結果を固定して保存すること
- 写真測量
- 複数の写真から、カメラの位置と物の形を推定する技術
- ボクセル
- 空間を区切った小さな立方体のマス。点を間引くのに使う
この記事の問い
シリーズの問いの地図(00 目次)のうち、この記事が答える問いです。
- II-1画素は「点」か?いいえ。小さな面に届いた光の平均。3D化した点は、その平均を1点に代表させたもの
- II-2深度は何を測った値か?視線軸方向の z か、光線に沿った距離か。取り違えると平らな壁が曲がる(ラボで確かめられる)
- II-3輪郭の画素の深度はどうなるか?前景と背景が混ざると、輪郭から空中に点が浮く
- II-4色は物理量か、見た目用の値か?C' は撮影時に sRGB にしている。表示側で二重に変換すると白っぽくなる
- II-5画素を視線に戻す行列は何か?内部パラメータ K。fx は画像の幅と画角から決まる。画素の中心は u + 0.5
- II-6カメラ空間から部屋の座標へどう移すか?カメラの回転と位置(外部パラメータ T)を掛ける
- II-7どの座標系の約束で書かれているか?世界は Blender が Z 上、three.js が Y 上。カメラの向きの約束も道具ごとに違う。バグの最大の発生源
- II-8なぜ一点から撮っても3Dになるのか?深度が正解として分かるから。現実のカメラは回転だけでは視差が生じない
- II-9点の密度は均一か?いいえ。遠い面や斜めから見た面ほど疎になる(ヒートマップで確かめられる)
- II-10同じ面を何度も撮るとどうなるか?点が重複する。マスごとに1点にまとめると密度がそろい、視点で変わる色もならされる
- II-18点群は「物体」を記述しているか?いいえ。特定の視点から見た光の記録。面も材質も持たない