プログラミング
カメラレイの計算
Computing Camera Rays (momentsingraphics.de)
要約
このブログ記事では、リアルタイムレンダリングパイプラインにおいて、ラスタライゼーションとレイトレーシングが共存する現状を踏まえ、レイトレーシングでカメラレイを計算する方法を解説しています。特に、ビュー・プロジェクション行列を用いて、パースペクティブ投影と正射投影の両方に対応するカメラレイの原点と方向を、数値的キャンセルを避けつつ信頼性高く導出する代替手法を提示しています。最終的には、シェーダーコードも公開されています。
全文翻訳
カメラレイの計算公開 2026-06-20レイトレーシングとラスタライゼーションが共存する移行期にいます。典型的なリアルタイムレンダリングパイプラインは、プライマリ可視性のためにラスタライゼーション(しばしば遅延シェーディングを使用)を使用し、その後、シャドウ、反射、またはグローバルイルミネーションのためにレイをトレースします。しかし、ラスタライゼーションを捨てて、少なくとも一部のプラットフォームではプライマリ可視性のためにもレイトレーシングを使用することを真剣に検討できる段階に近づいています。その場合、ラスタライゼーションのために通常行うことと一致する方法で、レイの原点、レイの方向、およびレイの長さで特徴付けられるカメラレイを計算する必要があります。ラスタライゼーションでは、一般的にワールドからクリップ空間への変換行列、別名ビュー・プロジェクション行列を使用してカメラを指定します。このブログ記事では、そのような行列に基づいてカメラレイを計算する方法を導出します。目標は、パースペクティブ投影と正射投影、およびそのような行列が表す可能性のあるその他のものすべてに対して同等にうまく機能するものを取得することです。明白なアプローチは数値的キャンセルを起こしやすいことが判明し、私はより信頼性の高く機能する代替案を提示します。全体として、これは難しい問題ではありません。任意のカメラモデル(例:既知の視野角を持つパースペクティブ投影)に対して、機能するアドホックなソリューションを考案することは非常に簡単です。しかし、私は、追加の調整をカメラモデルごとに必要としない、容易に入手可能な変換行列に基づいたソリューションを持つことは価値があると考えています。導出に関心がない場合は、リスト4(この記事のすべてのコードと同様に、私はこれをパブリックドメインにリリースします)のシェーダーコードをコピーしても構いません。クリップ空間でのカメラレイラスタライゼーションパイプラインとクリップ空間は同次座標に大きく依存するため、そこからレビューを始めましょう。3Dデカルト座標 ingen(x^
squo, y^
squo, z^
squo)^ extsf{T} を持つ点があるとすると、4番目の座標として1を単純に追加することで、その点の同次座標を得ることができます: ingen(x^
squo,y^
squo,z^
squo,1)^ extsf{T} 。これはまだ3D点を表しますが、その座標をゼロでない任意の係数 w でスケーリングする自由度が得られ、 ingen(x,y,z,w)^ extsf{T} = (wx^
squo, wy^
squo, wz^
squo, w)^ extsf{T} となります。w をどのように選択しても、これらの座標は同じ点を表します。4番目の成分 w で割ることによって、非同次座標(つまり、同次化解除)を回復できます: rac{1}w(x,y,z,w)^ extsf{T} = ingen(rac{x}{w}, rac{y}{w}, rac{z}{w}, 1)^ extsf{T} = ingen(x^
squo,y^
squo,z^
squo,1)^ extsf{T} 。同次座標は多くの公式を簡潔にします。例えば、後で3D空間の平面の同次座標を書き留めることができ、点が平面上にあるかどうかを確認するには、ドット積を取るだけで済みます。また、4x4行列を使用して平行移動を表現することもできます。さらに、パースペクティブ投影を伴うラスタライゼーションに役立ちます。パースペクティブ投影は本質的に、ある時点で除算を実行する必要があります。同次座標を使用すると、この除算はラスタライゼーションの直前に行われ、単に前述の同次化解除です。ラスタライザーのこの設計に伴い、クリップ空間という概念が生じます。スクリーン空間の場合、座標 x^
squo_c と y^
squo_c がカメラのファストラム(c はクリップ空間を表す)の範囲全体で -1 から 1 まで変化する座標系を使用します。同次座標では、これらの境界は -w_c ingen x_c ingen w_c および -w_c ingen y_c ingen w_c に変換されます。さらに、クリップ空間のz座標に基づいて、ニアクリッピング面とファークリッピング面を定義します。ファークリッピング面は z^
squo_c=1 にあり、これは z_c ingen w_c に変換されます。ニアクリッピング面はAPIによって異なります。Direct3Dの場合、不等式は 0 ingen z_c です。OpenGLの場合、デフォルトの動作はニアクリッピング面が -w_c ingen z_c であることですが、これは GL_ARB_clip_control 拡張機能を通じて構成可能になっており、 0 ingen z_c というDirect3Dの動作を選択できます。この拡張機能は、OpenGL 4.5 のコア機能に移行しました。Vulkanの場合も、動作は同様に構成可能です。これらの違いを考慮するために、Direct3Dの規約では0、古いOpenGLのデフォルトでは-1となる変数 z^
squo_n を使用します。つまり、どちらの場合もニアクリッピング面は z^
squo_n w_c ingen z_c です。典型的なレンダラーは、カメラ空間や各オブジェクトのオブジェクト空間など、他の多くの座標系を使用しますが、ここで気にするのはワールド空間だけです。なぜなら、レイをワールド空間で取得したいからです。レイトレーシングの文脈では、ワールド空間の意味を定義するのは非常に簡単です。それはトップレベルのアクセラレーション構造が使用するものです。ラスタライゼーションの場合、ワールド空間からクリップ空間への変換行列 M_{w,c} ingen ext{R}^{4 imes4} を準備して、ワールド空間の点をクリップ空間に変換します。 p_w = (x_w, y_w, z_w, w_w)^ extsf{T} が点のワールド空間座標を表す場合、対応するクリップ空間座標は p_c := (x_c, y_c, z_c, w_c)^ extsf{T} := M_{w,c} p_w となります。ここではOpenGLの規約を使用しており、一般的な線形代数の規約と一致しています。Direct3Dの規約では、行列とベクトルは転置され、乗算の順序は逆になります(誰がそれを良い考えだと思ったのか、なぜそうなのかはわかりません)。詳細は後述。さて、スクリーン空間の座標 x^
squo_c, y^
squo_c が -1 から 1 の範囲にある場合、クリップ空間の同次座標で、そのピクセルのニアクリッピング面とファークリッピング面上の点を簡単に決定できます。それらはそれぞれ n_c := (x^
squo_c, y^
squo_c, z^
squo_n, 1)^ extsf{T} および f_c := (x^
squo_c, y^
squo_c, 1, 1)^ extsf{T} です。私たちの目標は、これをワールド空間のレイの原点とレイの方向に向けることです。単純に聞こえますね?ワールド空間のレイの原点レイの原点に関しては、それは本当に単純です。ニアクリッピング面上の点を取り、それをワールド空間に変換し、同次化解除します。これを行うには、クリップからワールドへの変換行列 M_{c,w} := M^{-1}_{w,c} が必要です。すると、欲しい点は単純に n_w := M_{c,w} n_c となります。レイトレーシングAPIは同次座標を扱わないため、それを同次化解除したい、つまりw座標で割ることになります。リスト1はGLSLでの実装を提供します。リスト1:指定されたクリップからワールドへの変換を持つカメラのニアクリッピング面上の点を返します。clip_space_xy が (-1, -1) の場合、点はビューポートの左上隅にあり、(1, -1) の場合は右上にあり、(1, 1) の場合は右下にあります(少なくともデフォルトのOpenGL、Vulkan、およびDirect3Dの動作では)。vec3 get_camera_ray_origin( vec2 clip_space_xy, mat4 clip_to_world) { // 古いOpenGLの規約では、z_n=-1を使用します float z_n = 0.0; vec4 n_c = vec4(clip_space_xy, z_n, 1.0); vec4 n_w = clip_to_world * n_c; return n_w.xyz / n_w.w; } レイの方向のための悪いアプローチレイの方向を記述するには、レイの原点に加えてレイの方向が必要です。明白な戦略は次のとおりです。z_n=1 を使用して、ファープラン上の点を計算するために上記のコードスニペットを使用します。次に、ファー平面上の点とニア平面上の点の差を取り、このベクトルを正規化します。数学的な観点からは、このアプローチに問題はありません。正しい結果が得られ、計算も非常に効率的です。それにもかかわらず、私はそれを使用することを強くお勧めしません。実際、誤ってコピーする人がいないように、コードリストすら提供しません。代わりに、図1の結果を見てみましょう。私は誰よりもグラフィックスのグリッチが好きですが、それでも私たちが求めていた結果ではありません。ゆっくりとした連続的なカメラの動きが、ガタガタの混乱に変わります。何が起こっているのでしょうか?図1:カメラが移動するビデオ...