TL

座標変換(モデル・ビュー・射影)

3D の頂点が画面に映るまでの変換を、なぜ 4x4 行列と同次座標なのか、透視除算で遠近が付く理由、法線が壊れない掛け方まで一本の筋で追えます。

応用座標変換同次座標射影行列レンダリングパイプラインGPU線形代数最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.モデル・ビュー・射影の各行列を掛け合わせ、頂点をワールド→カメラ→クリップ空間へ順に写す。回転と平行移動を1つの行列で扱うために4次元の同次座標を使う。
  • 2.射影行列は w に深度(-z 等)を仕込み、その後 GPU が xyz を w で割る透視除算で遠近を付け、-1〜1 の NDC に正規化する。
  • 3.法線はモデル行列の逆転置で変換しないと、非等方スケールで面に対し傾く。右手系と左手系は Z 軸の向きと NDC の深度方向の約束の違い。

頂点が画面に届くまでは「空間の乗り換え」

3D モデルの頂点は、そのまま画面に描けません。まずワールド(世界)に配置し、カメラから見た座標に直し、遠近を付けてスクリーンの正規化座標へ写す——この一連の乗り換えを担うのが モデル・ビュー・射影(Model-View-Projection, MVP)行列 です。各段の変換を1つずつ 4x4 行列で表し、M_proj * M_view * M_model右から順に掛ける ことで、頂点 p を一気に写せます(列ベクトル規約)。GPU の頂点シェーダはこの合成行列を頂点ごとに掛けるのが本質的な仕事です。

p_clip = M_proj · M_view · M_model · p_model

  M_model : モデル座標 → ワールド座標(配置・回転・拡縮)
  M_view  : ワールド座標 → カメラ座標(視点を原点へ)
  M_proj  : カメラ座標 → クリップ座標(遠近と正規化の準備)

  掛ける順は「後で適用したい変換ほど左」。上式は
  まず M_model、次に M_view、最後に M_proj が効く。

なぜ 3 次元なのに 4x4 行列なのか(同次座標)

回転・拡大縮小は 3x3 行列で書けますが、平行移動は行列の掛け算では表せません。原点を動かす操作は線形写像(原点を保つ)ではなくアフィン変換だからです。そこで座標を1次元増やし、点 (x,y,z)(x,y,z,1) として扱う 同次座標(homogeneous coordinates) を導入します。4x4 にすると、平行移動が最右列に収まり、回転・拡縮・平行移動を1つの行列で合成できます。

平行移動 T(tx,ty,tz):        任意の点 (x,y,z,1) に掛けると
  [ 1 0 0 tx ]                (x+tx, y+ty, z+tz, 1)
  [ 0 1 0 ty ]              ← 4行目の 1 が tx,ty,tz を
  [ 0 0 1 tz ]                座標へ「持ち込む」役割
  [ 0 0 0  1 ]

同次座標の要は末尾成分 w の意味です。w ≠ 0(x,y,z,w) は 3D 点 (x/w, y/w, z/w) を表し、w = 0 は「無限遠の方向(ベクトル)」を表します。だから 点は w=1、方向ベクトルは w=0 で持つのが定石で、方向ベクトルに平行移動を掛けても動かない(w=0 が tx,ty,tz を無効化する)性質が自動的に得られます。この w を後段で射影に流用するのが、次のクリップ空間の肝になります。

モデル行列内の掛け算順序

1つのモデルに拡大 S・回転 R・平行移動 T をまとめるとき、通常は M_model = T · R · S とします。頂点にはまず S が効いて原点まわりで拡縮し、次に R で回転し、最後に T で目的地へ運ばれる。順序を変えると「回転してから移動」と「移動してから回転(=公転)」のように結果が別物になります。行列積が非可換であることの実務的な現れです。

ビュー行列 ── カメラを原点へ引き戻す逆変換

ビュー行列の考え方はシンプルです。「カメラを動かす」のではなく、世界全体をカメラの逆だけ動かして、カメラを原点・標準の向きに固定 します。したがってビュー行列はカメラのワールド姿勢(位置と回転)の 逆行列 です。回転部分 R は直交行列なので逆は転置 Rᵀ で済み、平行移動と合わせて次の形になります。

M_view = [ Rᵀ   -Rᵀ·t ]     R : カメラの回転(各軸がワールドでの向き)
         [ 0ᵀ      1   ]     t : カメラのワールド位置

  典型的な lookAt(eye, center, up) は
    forward = normalize(center - eye)
    right   = normalize(cross(forward, up))
    trueUp  = cross(right, forward)
  の3軸で R を組み、-Rᵀ·eye を平行移動に入れて構築する。

射影行列と透視除算 ── 遠近はどこで生まれるか

透視投影は「遠いものほど小さい」を作ります。鍵は、射影行列が出力の w 成分にカメラ空間の深度(右手系なら -z)を書き込む ことです。行列積の直後の座標はまだ割り算されていない クリップ座標 で、その後 GPU が固定機能として xyzw で割る 透視除算(perspective divide) を行います。深度で割るからこそ、遠い(w が大きい)頂点ほど中心へ寄り、遠近が付きます。

透視射影後のクリップ座標(列ベクトル、右手系の例):
  x_clip =  x · (f/aspect)          f = 1/tan(fovy/2)
  y_clip =  y · f
  z_clip =  z·A + w·B               A,B は near/far で決まる係数
  w_clip = -z                       ← 深度が w に入る(ここが要)

透視除算(GPU が実行):
  NDC = (x_clip/w_clip, y_clip/w_clip, z_clip/w_clip)
      → 各成分が概ね -1〜1 に収まる(正規化デバイス座標)

正射影(orthographic)では w_clip = 1 のまま。
割っても遠近が付かず、平行が保たれる(CAD やUI向き)。

透視除算後の空間が NDC(Normalized Device Coordinates) で、可視領域は一辺 2 の立方体(各軸 -1〜1、深度の範囲は API の約束次第)に正規化されます。この立方体に対してクリッピングと、ビューポート変換(NDC→ピクセル座標)が続きます。クリッピングを 除算前のクリップ座標で行う のは、w の符号情報(カメラ背後の点は w が負)が割り算で失われ、背後の点が画面前面に化ける「反転」を避けるためです。

深度の非線形性と Z ファイティング

z_clip/w_clip は z の逆数に比例するため、深度バッファの精度は near 側に密・far 側に粗 く分布します。near を極端に小さく取ると遠方の精度が枯れ、同一平面上の面がちらつく Z ファイティングが起きます。対策は near をできるだけ大きく取る、深度を反転させる Reversed-Z(浮動小数点深度の指数分布と精度分布を噛み合わせる)を使うなどです。near/far の比が精度を支配する点を押さえておきます。

右手系と左手系 ── 何が違い、なぜ食い違うか

座標系には 右手系と左手系 があり、X と Y を画面の右・上に取ったとき、奥行き Z の正方向が「手前向き(右手系)」か「奥向き(左手系)」かで分かれます。数学や OpenGL は右手系(カメラは -Z を向く)、Direct3D は伝統的に左手系を好むなど、API で約束が異なります。加えて NDC の深度範囲 も割れており、OpenGL は -1〜1、Direct3D・Vulkan・Metal は 0〜1 です。

観点右手系(OpenGL 系)左手系(Direct3D 系)
カメラの視線-Z 方向を向く+Z 方向を向く
前方の深度手前が +Z / 奥が -Z手前が小 / 奥が +Z
NDC 深度範囲-1 〜 10 〜 1(Vulkan/Metal も 0〜1)
外積の向き右手の法則左手の法則
主な影響先射影行列の符号・面の表裏判定同左(符号が逆になる)

実務では、利き手系と深度範囲が食い違うライブラリ間でモデルを移すと、モデルが裏返る・面の表裏(フェイスカリングの向き)が反転する・深度が逆になる、といった不具合が出ます。原因の多くは 射影行列の符号と、三角形の頂点巻き順(CW/CCW) の不一致です。移植時はまず射影行列の系と NDC 深度範囲を合わせ、次にカリングの巻き順を確認するのが定石です。

法線に逆転置行列が要る理由

頂点を M で変換するとき、法線ベクトルを同じ M で変換すると、非等方スケール(軸ごとに倍率が違う変形)で法線が面に垂直でなくなります。ライティングは法線の向きで決まるため、これは陰影の破綻に直結します。正しくは法線を モデル行列(の左上 3x3 部分 A)の逆転置 (A⁻¹)ᵀ で変換します。

理由は「法線は面の接ベクトルと直交し続けねばならない」という制約から導けます。接ベクトル tA·t へ変換される。変換後も法線 n'A·t と直交する(内積 0)ためには、次が任意の t で成り立てばよい。

要件:  (n')ᵀ · (A·t) = 0   (変換後も法線 ⟂ 接線)
仮に   n' = B·n とおくと
       (B·n)ᵀ · (A·t) = nᵀ·Bᵀ·A·t

  元の直交 nᵀ·t = 0 を保つには  Bᵀ·A = I  が必要
  ⇒  Bᵀ = A⁻¹  ⇒  B = (A⁻¹)ᵀ    (=逆転置)
回転だけなら逆転置は「ただの回転」に戻る

A が回転のみ(直交行列)なら A⁻¹ = Aᵀ なので (A⁻¹)ᵀ = A となり、逆転置は元の行列と一致します。つまり 回転・一様スケールしか使わないなら法線をモデル行列でそのまま変換してよい(一様スケールは長さが変わるので変換後に再正規化する)。逆転置が本当に効いてくるのは非一様スケールやせん断を含むときです。GPU 側では法線行列をあらかじめ CPU で計算してユニフォームとして渡すのが一般的です。

面接・試験で問われる勘所

「なぜ 4x4 か」「w は何を運ぶか」「透視除算はどの段で誰が行うか」「法線の変換行列は」の4点はほぼ定番です。要点は、平行移動を線形化するための同次座標、深度を仕込む w、除算はラスタライズ前の固定機能、法線は逆転置——と即答できること。加えて、クリッピングを除算前に行う理由(負の w で前後が反転する)を説明できると強いです。

まとめ

  • MVP は モデル→ビュー→射影 の3変換を 4x4 行列で表し M_proj·M_view·M_model と合成する。列ベクトル規約では「後で効かせたい変換ほど左」。
  • 同次座標 は座標を1次元増やして平行移動を行列積に載せる仕組み。w=1 が点、w=0 が方向ベクトルで、この w を射影が深度に流用する。
  • 射影行列は w に深度を書き込み、GPU の 透視除算xyz/w を行って遠近を付け、可視域を NDC(-1〜1 の立方体)へ正規化する。クリッピングは符号情報を保つため除算前に行う。
  • 右手系・左手系 の違いは Z の向きと NDC 深度範囲の約束差で、移植時は射影行列の符号とカリング巻き順の不一致に注意する。
  • 法線はモデル行列の逆転置 (A⁻¹)ᵀ で変換する。非一様スケールで法線が面から傾くのを防ぐためで、回転のみなら元の行列と一致する。

グラフィックス Article

座標変換(モデル・ビュー・射影)を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

座標変換

比較で見る軸

難易度: advanced / カテゴリ: グラフィックス / タグ数: 6

導入後に効く点

射影行列は w に深度(-z 等)を仕込み、その後 GPU が xyz を w で割る透視除算で遠近を付け、-1〜1 の NDC に正規化する。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
グラフィックス
タグ数
6

判断チェックリスト

  • 自社の用途が「座標変換 / 同次座標」に近いか確認する。
  • 強みである「モデル・ビュー・射影の各行列を掛け合わせ、頂点をワールド→カメラ→クリップ空間へ順に写す。回転と平行移動を1つの行列で扱うために4次元の同次座標を使う。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

座標変換同次座標射影行列レンダリングパイプラインGPU座標変換同次座標射影行列