*)ワールド座標からNDCの算出方法
最終更新日時: 2025年08月25日 12:57
- nothing
3D座標変換処理: ワールド座標からNDCへの流れ
Section titled “3D座標変換処理: ワールド座標からNDCへの流れ”3D空間の点をスクリーン上に描画するためには、一連の座標変換が必要となる。本レポートでは、まず3D座標変換の理論と数式を説明し、次に具体例として点(1,1,1)がカメラ位置(5,0,0)からどのように変換されるかを示す。最後にこれらの変換を実装するサンプルコードを提示する。
座標変換の理論
Section titled “座標変換の理論”3Dグラフィックスでは、右手座標系を標準として使用することが多い。
- 右手座標系:
- X軸: 右方向が正
- Y軸: 上方向が正
- Z軸: 奥から手前へ向かう方向が正(観察者に向かう方向)
この右手座標系に基づいて議論をする
座標系の定義
Section titled “座標系の定義”- ワールド座標系
- 3D空間全体の基準となる右手座標系
- ビュー座標系
- カメラ座標系とも呼ばれることがある
- カメラを原点とし、カメラの向きに基づいた座標系。
- カメラのZ軸は一般的にカメラから見た奥行きの逆方向(つまりカメラに向かう方向)を表す
- クリップ空間
- 射影変換後の4次元座標空間
- 正規化デバイス座標(NDC)
- クリップ空間から透視除算後の3次元座標空間
3Dレンダリングパイプラインにおける座標変換の流れ
Section titled “3Dレンダリングパイプラインにおける座標変換の流れ”- ビュー行列による変換
- ワールド座標系 → ビュー座標系
- 射影行列による変換
- ビュー座標系 → クリップ空間
- 透視除算による変換
- クリップ空間 → 正規化デバイス座標(NDC)
ビュー座標系
Section titled “ビュー座標系”- ビュー座標系のZ軸
- カメラから対象を見る方向の逆(カメラに向かう方向、正規化)
- ビュー座標系のX軸
- 上方向ベクトルとZ軸の外積(正規化)
- ビュー座標系のY軸
- Z軸とX軸の外積
これらの軸と位置を元に、ビュー行列が構築される:
ここで、、、 はカメラ座標系の各軸ベクトル、はカメラの位置ベクトルを表す。
(参考) ビュー行列の導出
Section titled “(参考) ビュー行列の導出”- ビュー変換は同次座標系を用いたアフィン変換として定義できる。
- 具体的には「ワールド座標系からカメラ座標系への回転変換」と「カメラ位置を原点へ移動させる平行移動変換」の合成写像として表現される。
平行移動変換
Section titled “平行移動変換”カメラ位置 を原点に移動させる行列:
カメラ座標系からワールド座標系への回転行列:
ワールド座標系からカメラ座標系への回転行列(直交行列なので転置):
ビュー行列 は回転と平行移動の合成:
行列の積を計算:
積の結果:
内積表記で整理:
この行列によりワールド座標系の点をカメラ座標系へ変換できる。
射影変換の数学的定義
Section titled “射影変換の数学的定義”透視射影変換は、カメラの視野角、アスペクト比、近平面、遠平面に基づいて定義される:
ここで:
- は視野角
- はアスペクト比
- は近平面の距離
- は遠平面の距離
透視除算とNDC
Section titled “透視除算とNDC”クリップ空間の座標 から正規化デバイス座標(NDC)への変換は次式で行われる:
通常、NDC座標は の立方体内に収まるように設計される。
具体例: 点(1,1,1)の変換
Section titled “具体例: 点(1,1,1)の変換”- 右手座標系を使用
- X軸: 右方向が正
- Y軸: 上方向が正
- Z軸: 奥から手前へ向かう方向が正(観察者に向かう方向)
- カメラ設定
- 位置:
- 注視点: 原点
- 上方向:
- 変換対象の点:
- 射影パラメータ
- 視野角:
- アスペクト比:
- 近平面:
- 遠平面:
カメラ座標系の構築
Section titled “カメラ座標系の構築”-
Z軸の計算: カメラから注視点への方向の逆ベクトル: 正規化すると:
-
X軸の計算: 上方向とZ軸の外積:
-
Y軸の計算: Z軸とX軸の外積:
ビュー行列の値
Section titled “ビュー行列の値”上記の計算結果から、ビュー行列の値は次のようになる:
射影行列の値
Section titled “射影行列の値”パラメータを元に計算すると となり、射影行列は:
座標変換の過程
Section titled “座標変換の過程”-
ワールド座標系の点を同次座標で表現
-
ビュー変換:
-
射影変換:
-
透視除算によるNDC変換:
\begin{pmatrix} \frac{-2.414}{-0.200} \\ \frac{2.414}{-0.200} \\ \frac{-7.002}{-0.200} \end{pmatrix} = \begin{x} 12.059 \\ -12.059 \\ 34.975 \end{pmatrix}
- 得られたNDC座標はの範囲を大きく超えており、点はビューフラスタムの外部にある
- また成分が負値であることから、この点はカメラの背後に位置していることがわかる
サンプルコード
Section titled “サンプルコード”以下は上記の変換を実装するJavaScriptコードである:
// ベクトルと行列の計算に使用する関数function multiplyMatrixVector(matrix, vector) { const result = [0, 0, 0, 0]; for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { result[i] += matrix[i * 4 + j] * vector[j]; } } return result;}
function normalizeVector(vector) { const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]); return [vector[0] / length, vector[1] / length, vector[2] / length];}
function crossProduct(a, b) { return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ];}
// カメラの設定const cameraPosition = [5, 0, 0];const target = [0, 0, 0]; // 原点を見るconst up = [0, 1, 0]; // 上方向ベクトル
// カメラ座標系の計算// Z軸: カメラから対象への方向の逆(右手座標系ではカメラに向かう方向が正)const zAxis = normalizeVector([ cameraPosition[0] - target[0], cameraPosition[1] - target[1], cameraPosition[2] - target[2]]);
// X軸: 上方向ベクトルとZ軸の外積const xAxis = normalizeVector(crossProduct(up, zAxis));
// Y軸: Z軸とX軸の外積const yAxis = crossProduct(zAxis, xAxis);
// ビュー行列の作成const viewMatrix = [ xAxis[0], yAxis[0], zAxis[0], 0, xAxis[1], yAxis[1], zAxis[1], 0, xAxis[2], yAxis[2], zAxis[2], 0, -(xAxis[0] * cameraPosition[0] + xAxis[1] * cameraPosition[1] + xAxis[2] * cameraPosition[2]), -(yAxis[0] * cameraPosition[0] + yAxis[1] * cameraPosition[1] + yAxis[2] * cameraPosition[2]), -(zAxis[0] * cameraPosition[0] + zAxis[1] * cameraPosition[1] + zAxis[2] * cameraPosition[2]), 1];
// 射影行列の作成(透視投影)const fov = 45 * Math.PI / 180; // 視野角(ラジアン)const aspect = 1.0; // アスペクト比const near = 0.1; // 近平面const far = 100.0; // 遠平面
const f = 1.0 / Math.tan(fov / 2);const projectionMatrix = [ f / aspect, 0, 0, 0, 0, f, 0, 0, 0, 0, (far + near) / (near - far), -1, 0, 0, (2 * far * near) / (near - far), 0];
// 元の点の設定と変換const originalPoint = [1, 1, 1, 1]; // 同次座標系で表現
// ビュー変換const pointInViewSpace = multiplyMatrixVector(viewMatrix, originalPoint);
// 射影変換const pointInClipSpace = multiplyMatrixVector(projectionMatrix, pointInViewSpace);
// NDCへの変換const ndcPoint = [ pointInClipSpace[0] / pointInClipSpace[3], pointInClipSpace[1] / pointInClipSpace[3], pointInClipSpace[2] / pointInClipSpace[3]];
console.log("ビュー空間での点:", pointInViewSpace);console.log("クリップ空間での点:", pointInClipSpace);console.log("NDC座標:", ndcPoint);- ビュー行列(View Matrix): ワールド座標からカメラ座標への変換行列
- 射影行列(Projection Matrix): カメラ座標からクリップ空間への変換行列
- クリップ空間(Clip Space): 射影変換後の4次元空間で、透視除算前の座標空間
- 正規化デバイス座標(NDC): クリップ空間からw成分による除算後の座標で、通常は の立方体内に収まる
- 透視除算(Perspective Division): クリップ空間座標のxyz成分をw成分で割る操作
- ビューフラスタム(View Frustum): カメラから見える3D空間の領域を表す切頭四角錐