デプスによる除算で即座に3Dを
Divide by Depth for Instant 3D (gabrieloc.com)
要約
この記事では、3Dグラフィックスにおけるカメラの基本的な数学的原理、特にデプス(奥行き)による除算を用いた2Dへの投影方法を解説しています。単純な数式 (x/z, y/z) を用いて3D座標を2D平面にマッピングする手法や、それを応用したシェーダーコードの例が示されています。さらに、より現実的な3Dレンダリングに必要な透視投影行列の概念と、その構築方法についても触れています。
全文翻訳
デプスによる除算で即座に3Dを生成
ゲーム開発を始めた頃、私はCameraがすぐに使える高レベルなフレームワークを使用していました。しかし、より深く学び、よりクリエイティブなテクニックを適用したいと思ったとき、自分がやりたいことを検索するための語彙すら持っていないことに苦労しました。そして、低レベルのグラフィックスコードを書くようになって初めて、カメラが非常に単純な数学に基づいていることを理解しました。
優れた3Dグラフィックスを分かりやすくする一つの公式(@tsodingより)では、以下の式が提示されています。
(x, y, z)
x' = x/z
y' = y/z
本質的に、(y)を上方向、(z)を前方とすると、3D座標((x, y, z))は、(x)と(y)を(z)で割ることで2D座標((x', y'))に投影できます。例えば、奥行きのみが変化する一連の3D点がある場合、奥行きが増加するにつれて、それらの投影位置は消失点((0,0))に近づきます。
(\[\begin{array}{c|c} (x,y,z) & (x',y') \ \hline (2,1,2) & (1,0.5) \ (2,1,4) & (0.5,0.25) \ (2,1,8) & (0.25,0.125) \end{array}\])
これは、カメラのアップ軸を中心に周回し、forwardによって(z)軸方向にオフセットされた、奥行きに応じて移動およびスケーリングするボールで実証できます。
[画像: カメラのアップ軸を中心に周回し、奥行きに応じて移動およびスケーリングするボール]
同じ原理を使用して、さらに洗練された「ジオメトリ」を描画することさえできます!
[画像: デプスによる除算で描画された複雑な形状]
明らかに、これらは非常に制約された単純な例であり、最も単純なシナリオ以外ではほとんど実用的ではありません。3D作業を行う場合、通常はカメラの向き、位置、視野角などの要素を考慮する必要があります。理論的にはこれらのシェーダーをハックしてこれらの機能に対応させることもできますが、より労力が少なく実用的な方法があります。それは透視投影行列と呼ばれ、強力なCameraの背後にある魔法です。
透視投影
透視投影行列を構築する方法はいくつかあり、使用ケースによって異なります。例えば、コンピュータビジョンとグラフィックスではレイアウトの規約がわずかに異なるため、単一のWikipedia記事を指して普遍的な形式を期待するのが難しい理由です。とはいえ、三角形ベースのグラフィックスでは、よく従われる一般的な規約には、視野角、アスペクト比、ニアおよびファー・クリッピング・プレーンのパラメータ化が含まれます。これらの値はすべて非常に重要です。なぜなら、それらはフレーム内にあるものとないものを簡単に知るための方法としても機能し、画面外のジオメトリをカリングしたり、表示されているものだけをレンダリングしたりするなど、パフォーマンスの高い方法でシーンをレンダリングできるからです。
これはさまざまな座標規約(どの軸が上、右、前方に対応するかについてはほとんどコンセンサスがありません)によって異なりますが、一般的な構造はほとんど一貫しています。
(\[P = \begin{bmatrix} \frac{f}{a} & 0 & 0 & 0 \ 0 & f & 0 & 0 \ 0 & 0 & A & B \ 0 & 0 & 1 & 0 \end{bmatrix}\])
これは、焦点スケール(f)(垂直視野角(\theta)から導出)とアスペクト比(a)を持つカメラを表します。
(\[f = \frac{1}{\tan(\theta/2)},\quad a = \frac{width}{height}\])
ここで、(A)と(B)は、それぞれニアおよびファー・クリッピング・プレーン(n)と(F)から導出される深度マッピングを表します。
(\[A = \frac{F+n}{F-n}, \quad B = -\frac{2Fn}{F-n}\])
では、これが前述のデプス除算トリックとどのように関連するのでしょうか? 結局のところ、それは透視投影行列の特殊な形式なのです。
3D空間内のどこかにある点を考えると、まずカメラに対する相対的な位置を記述します。これにより、シーン全体の座標が共有されるワールド空間から、カメラが原点に配置されるビュー空間に移行します。次に、透視投影行列を乗算して、透視除算の準備ができたクリップ空間座標を生成します。
(\[\begin{bmatrix} \frac{f}{a} & 0 & 0 & 0 \ 0 & f & 0 & 0 \ 0 & 0 & A & B \ 0 & 0 & 1 & 0 \end{bmatrix} \begin{bmatrix} x \ y \ z \ 1 \end{bmatrix} = \begin{bmatrix} \frac{f}{a}x + 0y + 0z + 0(1) \ 0x + fy + 0z + 0(1) \ 0x + 0y + Az + B(1) \ 0x + 0y + z + 0(1) \end{bmatrix} = \begin{bmatrix} \frac{f}{a}x \ fy \ Az + B \ z \end{bmatrix}\])
クリップ空間の最初の3つの成分を(w)で割った後、座標はシーン内の距離を表さなくなります。代わりに、それらはカメラの表示範囲内のどこに点が位置するかを表します。これらは正規化デバイス座標(通常はNDCと略されます)であり、後で点が画面の出力解像度に最終的にマッピングされることを可能にします。
(\[x ext{_ndc} = \frac{\frac{f}{a}x}{z}, \qquad y ext{_ndc} = \frac{fy}{z}\])
前の例では、焦点スケール(f)とアスペクト比(a)の両方が省略されています。両方を1に置き換えると、元のデプス除算トリックが正確に得られます。
(\[x ext{_ndc} = \frac{\frac{1}{1}x}{z} = \frac{x}{z}, \qquad y ext{_ndc} = \frac{1y}{z} = \frac{y}{z}\])
デプス除算トリックは単純なシナリオでカメラ相対座標に対して機能しますが、それは完全なグラフィックスパイプラインの1ステップにすぎません。今やすべての用語に名前がついたので、全体の旅は次のようになります。
(\[\mathrm{world} \rightarrow \mathrm{view} \rightarrow \mathrm{clip} \xrightarrow{,/w,} \mathrm{NDC} \rightarrow \mathrm{pixels} \rightarrow \mathrm{rasterization}\])
まとめ
この解説から takeaway として一つだけ挙げるとすれば、Cameraはいくつかの単純な変換を行っているだけであり、それぞれの変換が何のためにあるのかを理解すれば、自分のニーズや制約に基づいて実装する部分を選択できるということです。時には完全なパイプラインが必要になり、時にはそのデプス除算だけが必要になることもあります。