web-dev-qa-db-ja.com

基本的なレンダリング3D透視投影をカメラで2Dスクリーンに投影(openglなし)

次のようなデータ構造があるとします。

Camera {
   double x, y, z

   /** ideally the camera angle is positioned to aim at the 0,0,0 point */
   double angleX, angleY, angleZ;
}

SomePointIn3DSpace {
   double x, y, z
}

ScreenData {
   /** Convert from some point 3d space to 2d space, end up with x, y */
   int x_screenPositionOfPt, y_screenPositionOfPt

   double zFar = 100;

   int width=640, height=480
}

...

画面のクリッピングやその他の多くのものがなければ、空間内の3D点が与えられた場合、ある点の画面のX、Y位置をどのように計算しますか?その3Dポイントを2Dスクリーンに投影したい。

Camera.x = 0
Camera.y = 10;
Camera.z = -10;


/** ideally, I want the camera to point at the ground at 3d space 0,0,0 */
Camera.angleX = ???;
Camera.angleY = ????
Camera.angleZ = ????;

SomePointIn3DSpace.x = 5;
SomePointIn3DSpace.y = 5;
SomePointIn3DSpace.z = 5;

ScreenData.xおよびyは、空間内の3Dポイントの画面X位置です。これらの値を計算するにはどうすればよいですか?

ここにある方程式を使用することもできますが、画面の幅と高さがどのように機能するのかわかりません。また、wikiエントリでは、視聴者の位置とカメラの位置の関係がわかりません。

http://en.wikipedia.org/wiki/3D_projection

26
Berlin Brown

「行われる方法」は、同種の変換と座標を使用することです。あなたは空間でポイントをとり、そして:

  • モデルマトリックスを使用して、カメラに対して相対的に配置します。
  • 射影行列を使用して、正投影または遠近法で投影します。
  • ビューポート変換を適用して画面に配置します。

これはかなりあいまいになりますが、私は重要な部分をカバーして、その一部をあなたに任せます。マトリックス数学の基礎を理解していると思います:)。

同種のベクトル、ポイント、変換

3Dでは、均一な点は[x、y、z、1]という形式の列行列になります。最後の要素は 'w'で、これはスケーリング係数であり、ベクトルの場合は0です。これには、数学的に正しいベクトルを変換できないという影響があります。私たちはそこには行きません。

同種変換は4x4行列です。これは、変換を加算ではなく行列の乗算として表すことができるため使用されます。これは、ビデオカードにとって素晴らしく高速です。また、連続する変換を掛け合わせることで表現できるので便利です。変換*ポイントを実行することにより、ポイントに変換を適用します。

3つの主要な均一変換があります。

他にも、「見る」変換があり、探索する価値があります。しかし、私は簡単なリストといくつかのリンクを提供したかっただけです。ポイントに適用される移動、スケーリング、回転の連続的な適用は、まとめてモデル変換行列であり、カメラに対してシーンに配置します。私たちが行っていることは、オブジェクトをカメラの周りで動かすことと同じであり、逆ではないことを理解することが重要です。

正投影と遠近法

世界座標から画面座標に変換するには、最初に射影行列を使用します。これは通常、2つの種類があります。

  • 2DおよびCADで一般的に使用される正投影。
  • 視点、ゲームや3D環境に適しています。

正投影行列は次のように構成されます。

An orthographic projection matrix, courtesy of Wikipedia.

パラメータには次のものが含まれます。

  • Top:表示スペースの上端のY座標。
  • Bottom:表示スペースの下部エッジのY座標。
  • Left:表示スペースの左端のX座標。
  • Right:表示スペースの右端のX座標。

とても簡単だと思います。確立するのは、画面に表示されるスペースの領域であり、クリップすることができます。表示されるスペースの領域は長方形であるため、ここでは簡単です。画面または表示ボリュームに表示される領域は フラストラム であるため、遠近法によるクリッピングはより複雑です。

透視投影のウィキペディアで苦労している場合、適切なマトリックスを作成するためのコードは次のとおりです geeks3Dの礼儀

void BuildPerspProjMat(float *m, float fov, float aspect,
float znear, float zfar)
{
  float xymax = znear * tan(fov * PI_OVER_360);
  float ymin = -xymax;
  float xmin = -xymax;

  float width = xymax - xmin;
  float height = xymax - ymin;

  float depth = zfar - znear;
  float q = -(zfar + znear) / depth;
  float qn = -2 * (zfar * znear) / depth;

  float w = 2 * znear / width;
  w = w / aspect;
  float h = 2 * znear / height;

  m[0]  = w;
  m[1]  = 0;
  m[2]  = 0;
  m[3]  = 0;

  m[4]  = 0;
  m[5]  = h;
  m[6]  = 0;
  m[7]  = 0;

  m[8]  = 0;
  m[9]  = 0;
  m[10] = q;
  m[11] = -1;

  m[12] = 0;
  m[13] = 0;
  m[14] = qn;
  m[15] = 0;
}

変数は次のとおりです。

  • fov:視野、pi/4ラジアンが適切な値です。
  • aspect:幅に対する高さの比率。
  • znear、zfar:クリッピングに使用されます。これらは無視します。

生成された行列は列優先で、上記のコードで次のようにインデックスが付けられます。

0   4   8  12
1   5   9  13
2   6  10  14
3   7  11  15

ビューポート変換、画面座標

これらの変換はどちらも、ビューポート変換と呼ばれる、画面座標に物事を配置する別の行列行列を必要とします。 ここで説明します。カバーしません(非常に簡単です)

したがって、点pの場合、次のようになります。

  • モデル変換行列* pを実行すると、結果はpmになります。
  • 射影行列*午後を実行すると、結果はppになります。
  • 表示ボリュームに対するppのクリッピング。
  • ビューポート変換行列* ppを実行すると、結果はps:画面上の点になります。

まとめ

それでほとんどカバーできるといいのですが。上記には穴があり、場所が曖昧です。質問があれば下に投稿してください。この主題は、通常、教科書の章全体に値します。うまくいけば、あなたの利益になるように、プロセスを蒸留するために最善を尽くしました!

上記にリンクしましたが、これを読んでバイナリをダウンロードすることを強くお勧めします。これらの変換と画面上でポイントを取得する方法について理解を深めるための優れたツールです。

http://www.songho.ca/opengl/gl_transform.html

実際の作業に関しては、同次変換用の4x4行列クラスと、それを乗算して変換を適用できる同次点クラスを実装する必要があります([x、y、z、1]を思い出してください)。上記およびリンクで説明されているように、変換を生成する必要があります。手順を理解すれば、それほど難しいことではありません。幸運を祈ります:).

49
Liam M

@BerlinBrownは一般的なコメントと同様に、カメラの回転をX、Y、Z角度として保存しないでください。これはあいまいになる可能性があるためです。

たとえば、x = 60degreesは-300度と同じです。 x、y、zを使用すると、あいまいな可能性の数が非常に多くなります。

代わりに、3D空間で2つのポイントを使用してみてください。カメラの場所にはx1、y1、z1、カメラの「ターゲット」にはx2、y2、z2です。角度は場所/ターゲットとの間で逆方向に計算できますが、私の意見ではこれはお勧めできません。カメラの位置/ターゲットを使用すると、カメラの方向(v ')の単位ベクトルである「LookAt」ベクトルを作成できます。これから、3D空間のオブジェクトを2D空間のピクセルに投影するために使用される4x4行列であるLookAt行列を構築することもできます。

この関連する質問 を参照してください。ここでは、カメラに直交する平面にあるベクトルRの計算方法について説明します。

ターゲットとするカメラのベクトルを指定すると、v = xi、yj、zk
ベクトルを正規化します、v '= xi、yj、zk/sqrt(xi ^ 2 + yj ^ 2 + zk ^ 2)
U =グローバルワールドアップベクトルu = 0、0、1とします
次に、R =カメラのビュー方向に平行な水平ベクトルを計算できますR = v '^ U、
ここで、^は次式で与えられる外積です
a ^ b =(a2b3-a3b2)i +(a3b1-a1b3)j +(a1b2-a2b1)k

これにより、次のようなベクターが得られます。

Computing a vector orthogonal to the camera

LookAt Vector v 'を取得すると、直交ベクトルRを3D空間の点からカメラの平面に投影し始めることができるため、これは質問に役立つ可能性があります。

基本的に、これらすべての3D操作の問題は、ワールド空間内のポイントをローカル空間に変換することになります。この場合、ローカルのX、Y、Z軸はカメラの方向にあります。それは理にかなっていますか?したがって、ポイントQ = x、y、zがあり、Rとv '(カメラ軸)がわかっている場合は、単純なベクトル操作を使用してそれを「画面」に投影できます。関与する角度は、Vectorsの内積演算子を使用して見つけることができます。

Projecting Q onto the screen

10
Dr. ABT

ウィキペディアに従って、最初に「d」を計算します。

http://upload.wikimedia.org/wikipedia/en/math/6/0/b/60b64ec331ba2493a2b93e8829e864b6.png

これを行うには、コードでこれらの行列を作成します。例から変数へのマッピング:

θ= Camera.angle*

a = SomePointIn3DSpace

c = Camera.x | y | z

または、行列を使用せずに方程式を個別に実行することもできます。

http://upload.wikimedia.org/wikipedia/en/math/1/c/8/1c89722619b756d05adb4ea38ee6f62b.png

次に、2Dポイントである「b」を計算します。

http://upload.wikimedia.org/wikipedia/en/math/2/5/6/256a0e12b8e6cc7cd71fa9495c0c3668.png

この場合、exとeyはビューアの位置です。ほとんどのグラフィックスシステムでは、デフォルトで画面サイズの半分(0.5)を使用して(0、0)を画面の中心にしていますが、任意の値を使用できます(遊び回ってください) )。 ezは、視野が機能する場所です。それはあなたが欠けていた一つのことです。 fov角度を選択し、ezを次のように計算します。

ez = 1/tan(fov/2)

最後に、bxとbyを実際のピクセルにするには、画面サイズに関連する係数でスケーリングする必要があります。たとえば、bが(0、0)から(1、1)にマッピングされている場合、1920 x 1080のディスプレイでは、xを1920、yを1080にスケーリングできます。そうすれば、どの画面サイズでも同じことが表示されます。もちろん、実際の3Dグラフィックスシステムには他にも多くの要素がありますが、これは基本的なバージョンです。

5
user155407

3Dスペース内のポイントを画面上の2Dポイントに変換するには、単純に matrix を使用します。マトリックスを使用して、ポイントの画面位置を計算します。これにより、多くの作業を節約できます。

カメラを操作するときは、 look-at-matrix の使用を検討し、look at行列に投影行列を乗算する必要があります。

4
Felix K.

カメラが(0、0、0)にあり、まっすぐ向いているとすると、方程式は次のようになります。

ScreenData.x = SomePointIn3DSpace.x / SomePointIn3DSpace.z * constant;
ScreenData.y = SomePointIn3DSpace.y / SomePointIn3DSpace.z * constant;

ここで、「定数」は正の値です。通常、ピクセル単位の画面幅に設定すると、良い結果が得られます。高く設定すると、シーンはより「ズームイン」し、逆もまた同様です。

カメラを別の位置または角度にしたい場合は、カメラが(0、0、0)になり、まっすぐ向くようにシーンを移動および回転する必要があります。その後、上記の方程式を使用できます。 。

基本的に、カメラを通過する線と3D点との交点と、カメラの少し前に浮いている垂直面を計算しています。

3
Thomas

GLUTがそれを行う方法 の舞台裏を見るだけでも興味があるかもしれません。これらのすべてのメソッドには、それらに入る数学を示す同様のドキュメントがあります。

[〜#〜] ucsd [〜#〜] からの最初の3つの講義は非常にやりがいがあり、このトピックに関するいくつかの図が含まれている可能性があります。

2

OpenGLの gluLookAt に類似した行列でシーンを変換し、OpenGLの gluPerspective に類似した投影行列を使用して投影を計算します。

行列を計算して、ソフトウェアで乗算を実行することもできます。

0
Krumelur

レイトレーサーを使用して実行します。

C#のレイトレーサー -彼が持っているオブジェクトのいくつかはあなたに見慣れているようです;-)

そしてキックのためだけに LINQバージョン

アプリの大きな目的が何であるかはわかりません(教えてください、sparkより良いアイデア)かもしれません)が、投影とレイトレーシングが異なる問題セットであることは明らかですが、彼らはたくさんのオーバーラップを持っています。

アプリがシーン全体を描画しようとしているだけなら、これは素晴らしいことです。

問題#1の解決隠されたポイントは投影されません。
解決策:ブログページに不透明度または透明度について何も表示されませんでしたが、これらのプロパティとコードを追加して、跳ね返った1つの光線を処理することができます(通常どおり)と(「透明性」のために)継続されたもの。

問題#2を解決する単一のピクセルを投影するには、すべてのピクセルの費用のかかるフルイメージトレースが必要です
明らかに、オブジェクトを描画したいだけの場合は、レイトレーサーを使用してオブジェクトを作成します。しかし、ランダムオブジェクトのランダムな部分から画像内の何千ものピクセルを検索する場合(なぜですか)、各リクエストに対して完全なレイトレースを実行すると、パフォーマンスが大幅に低下します。

幸いなことに、彼のコードをさらに調整することで、1つのレイトレーシングを(透過性を使用して)前もって実行し、オブジェクトが変更されるまで結果をキャッシュすることができます。

レイトレーシングに慣れていない場合は、ブログエントリを読んでください。これは、各2Dピクセルからオブジェクト、次にライトまで、ピクセル値を決定するライトが実際にどのように後方に機能するかを説明していると思います。

オブジェクトとの交差が行われるようにコードを追加して、オブジェクトの交差するポイントによってインデックスが付けられたリストを作成し、アイテムを現在の2Dピクセルで追跡することができます。

次に、点を投影する場合は、そのオブジェクトのリストに移動し、投影する点に最も近い点を見つけて、気になる2Dピクセルを調べます。数学はあなたの記事の方程式よりもはるかに最小限になります。 残念ながら、たとえば、オブジェクト+ポイント構造の辞書を2Dピクセルにマッピングして使用すると、マップされたポイントのリスト全体を実行せずに、オブジェクトの最も近いポイントを見つける方法がわかりません。それは世界で最も遅いものではなく、おそらくあなたはそれを理解できるでしょうが、私はそれについて考える時間がないのです。誰でも?

頑張ってください!

また、wikiエントリで、視聴者の位置とカメラの位置の関係を理解できません」...これは同じことだと99%確信しています。

0
FastAl