2011-12-26 26 views
25

比方說,我有一個如下的數據結構:基本渲染3D透視投影到二維的屏幕,攝像頭(無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

回答

44

'完成的方式'是使用同質轉換和座標。你在太空中取點:

  • 使用模型矩陣將其相對於相機進行定位。
  • 使用投影矩陣對其進行正投影或透視投影。
  • 應用視口trnasformation將其放置在屏幕上。

這非常含糊,但我會嘗試覆蓋重要的部分,並將其中的一部分留給您。我假設你理解矩陣數學的基礎知識:)。

均質載體,積分,轉換

在3D中,均勻的點會是以下形式的列矩陣[X,Y,Z,1]。最後的組件是'w',一個比例因子,對於向量是0:這樣做的效果是不能轉換向量,這在數學上是正確的。我們不會去那裏,我們正在談論點。

均勻轉換是4x4矩陣,因爲它們允許翻譯表示爲矩陣乘法,而不是添加,這對您的視頻卡來說是很好和快速的。也很方便,因爲我們可以通過將它們相乘來表示連續的轉換。我們通過執行變換*點來將變換應用於點。

有3個初級均一轉變:

還有其他的,特別是 '看' 的轉型,這是值得探討的。不過,我只想給出一個簡短的清單和幾個鏈接。應用於點的移動,縮放和旋轉的連續應用統稱爲模型變換矩陣,並將它們放置在相對於相機的場景中。意識到我們正在做的事情與在相機周圍移動物體類似,而不是相反。

正交和透視

從世界座標轉換爲屏幕座標,你會首先使用的投影矩陣,其中常見的有兩種形式:

  • 正字,常用於2D和CAD。
  • 透視圖,適用於遊戲和3D環境。

正投影矩陣被構造如下:

An orthographic projection matrix, courtesy of Wikipedia.

其中參數包括:

  • :的可視空間的頂部邊緣的Y座標。
  • 底部:可見空間底邊的Y座標。
  • Left:可視空間左邊緣的X座標。
  • :可見空間右邊緣的X座標。

我覺得這很簡單。你所建立的是一個將出現在屏幕上的空間區域,你可以對其進行修剪。這很簡單,因爲可見空間的區域是一個矩形。從透視角度來看更加複雜,因爲出現在屏幕或觀看音量上的區域是frustrum

如果你有一個很難與透視投影維基百科,下面是建立一個合適的矩陣代碼,courtesy of 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:現場的觀點,π/ 4弧度是一個很好的價值。
  • 方面:高寬比。
  • znear,zfar:用於剪輯,我會忽略這些。

和生成的矩陣是列優先,索引爲在上面的代碼如下:

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

視口變換,屏幕座標

這兩個變換的需要另一個矩陣矩陣放在屏幕座標中稱爲視口轉換。 That's described here, I won't cover it (it's dead simple)

因此,點P,我們會:

  • 執行模型變換矩陣* P,導致PM。
  • 執行投影矩陣* pm,導致pp。
  • 將pp與剪切矩陣對齊。
  • 執行視口轉換矩陣* pp,結果是ps:點在屏幕上。

摘要

我希望覆蓋大部分。上面有漏洞,地方模糊不清,請在下面回答任何問題。這個主題通常值得一本教科書的整章,我已經盡力提煉這個過程,希望你的優勢!

我鏈接到這上面,但我強烈建議你閱讀本,並下載二進制文件。這是進一步您的論文變革的理解一個極好的工具,以及如何獲取屏幕上的點:

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

至於實際工作中,你需要實現均勻轉換的4x4矩陣類以及作爲一個同類點類,你可以乘它來應用轉換(記住,[x,y,z,1])。您需要按照上述和鏈接生成轉換。一旦你瞭解程序,這並不是那麼困難。祝你好運:)。

+0

長而偉大的答案,值得讚賞! – Gepsens 2012-01-06 16:06:22

2

您可能會感興趣的只是看到幕後如何GLUT does it。所有這些方法都有類似的文檔,顯示了進入它們的數學。

UCSD的三個第一講可能是非常有幫助的,並且包含了關於這個主題的幾個插圖,就我所能看到的而言,你真的是這樣。

4

通過使用matrix可簡單地將3D空間中的點轉換爲屏幕上的2D點。使用矩陣來計算你的點的屏幕位置,這爲你節省了很多工作。

使用相機時,應考慮使用look-at-matrix,並將矩陣的外觀與投影矩陣相乘。

+0

我喜歡這種方法。 – 2012-01-05 14:54:46

+0

@BerlinBrown很高興,當我可以幫忙。如果你對矩陣有問題,可以在這裏問一下,我會發布一些例子(我有一個矩陣工作庫)。 – 2012-01-05 17:07:54

+0

您可以在矩陣乘法之後添加或顯示我們將它們轉換爲2D座標的位置。 – 2012-01-05 17:25:37

0

您想用類似於OpenGL的gluLookAt的矩陣轉換場景,然後使用類似於OpenGL的gluPerspective的投影矩陣計算投影。

您可以嘗試只計算矩陣並在軟件中進行乘法運算。

+0

我沒有使用opengl。 – 2012-01-05 03:25:11

+0

不,但原則是一樣的。如果你看看這些函數的文檔,你會看到他們如何計算他們的矩陣。也許你可以直接在你的程序中使用它們? – Krumelur 2012-01-06 09:59:12

5

繼維基百科,首先計算 「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

現在我們ca lculate「B」,一個2D點:

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

在這種情況下Ex和Ey是觀看者的位置,相信在大多數圖形系統的屏幕尺寸的一半(0.5)用於製備(0,0 )默認情況下,屏幕的中心,但你可以使用任何值(玩耍)。 ez是視野開始發揮作用的地方。這是你錯過的一件事。選擇一個視場角,並計算EZ爲:

EZ = 1/TAN(FOV/2)

最後,讓BX和實際像素,必須通過相關的屏幕尺寸的因子來縮放。例如,如果b從(0,0)到(1,1)映射,那麼1920 x 1080顯示器就可以縮放x 1920 x 1080。這樣任何屏幕尺寸都會顯示相同的內容。當然在實際的3D圖形系統中涉及許多其他因素,但這是基本版本。

0

運行它直通光線追蹤:

Ray Tracer in C# - 某些對象,他會很熟悉你;-)

而只是踢一個LINQ version

我不確定你的應用程序的更大目的是什麼(你應該告訴我們,它可能引發更好的想法),但雖然投影和光線追蹤顯然是不同的問題集合,但它們有很多交疊。

如果您的應用只是試圖繪製整個場景,這將是偉大的。

解決問題#1模糊點不會被預測。
解決方案:儘管我沒有在博客頁面上看到任何關於不透明度或透明度的內容,但您可能需要添加這些屬性和代碼來處理一個反射光線(正常情況)和一個持續光線'透明度')。

解決問題#2投射一個像素都需要所有像素的昂貴的全圖像跟蹤。
很明顯,如果你只是想繪製對象,使用光線追蹤器來做它的用途!但是如果你想在圖像中查找數千個像素,從隨機對象的隨機部分(爲什麼?),爲每個請求做一個完整的光線跟蹤將是一個巨大的性能狗。

幸運的是,通過對代碼進行更多調整,您可能能夠預先進行一次光線追蹤(具有透明度),並緩存結果直到對象發生變化。

如果您對光線追蹤不熟悉,請閱讀博客條目 - 我認爲它解釋了事情真正從每個2D像素向物體反射,然後是確定像素值的光線。

您可以添加代碼,以便與對象進行交集,您正在構建由對象的相交點索引的列表,該項目是當前的2d像素被跟蹤的。

然後,當您想投影一個點時,轉到該對象的列表,找到您想要投影的點的最近點,然後查找您關心的二維像素。數學將比你的文章中的方程式小得多。 不幸的是,使用例如你的對象的字典+點結構映射到2d像素,我不知道如何在沒有遍歷整個映射點列表的情況下找到對象上的最近點。雖然這不會是世界上最慢的事情,你也許可以想出來,但我沒有時間去思考。任何人?

祝你好運!

此外,我不明白在維基條目什麼是觀察者的位置相機位置」...我99%肯定這是同樣的事情。

10

@BerlinBrown就像一般性評論一樣,您不應該將相機旋轉存儲爲X,Y,Z角度,因爲這會導致模糊。

例如,x = 60degrees與-300度相同。當使用x,y和z時,含糊可能性的數量非常高。

相反,嘗試在三維空間中使用兩個點,x1,y1,z1用於相機位置,x2,y2,z2用於相機「目標」。角度可以向/從位置/目標向後計算,但在我看來,這是不推薦的。使用相機位置/目標可以構建一個「LookAt」矢量,它是相機方向上的單位矢量(v')。從這裏你還可以構建一個LookAt矩陣,它是一個4x4矩陣,用於將3D空間中的對象投影到2D空間中的像素。

請參閱this related question,我在這裏討論如何計算矢量R,它位於與攝像機正交的平面上。

鑑於相機的載體來靶向,V = XI,YJ,ZK
規範化矢量,V」 = XI,YJ,ZK/SQRT(ⅹⅰ^ 2 + YJ^2 + ZK^2 )
設U =全球向上向量u = 0,0,1
然後我們可以計算出R =水平向量,該向量平行於相機的視角方向R = v'^ U,其中^是叉積通過
一個^ b =給定(A2B3 - A3B2)1 +(A3B1 - A1B3)J +(A1B2 - A2B1)K

這會給你的向量看起來像這樣。

Computing a vector orthogonal to the camera

這可能是利用你的問題,因爲一旦你的注視矢量V」,正交向量R你就可以開始在3D空間中的點到相機的飛機項目。

基本上所有這些3D操作問題都歸結爲將世界空間中的一個點轉換爲局部空間,其中局部x,y,z軸與攝像機的方向一致。那有意義嗎?所以,如果你有一個點,Q = x,y,z,並且你知道R和v'(相機軸),那麼你可以使用簡單的矢量操作將它投影到「屏幕」。涉及的角度可以使用矢量上的點積算子找出。

Projecting Q onto the screen

+1

這是一個很好的答案和簡單的拼寫技巧。但是,有一點:如果攝像機和目標不在同一個XZ平面(相同高度),則不能使用全局「向上」向量投影點。相反,通過將V與U交叉來導出R,然後通過將R與V交叉得到實際的向上矢量以得到正交基。 – Phrogz 2014-05-13 03:44:54

2

假設攝影機在(0,0,0),並指出直走,方程將是:

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

其中 「恆定」 是一些正值。將其設置爲以像素爲單位的屏幕寬度通常會給出好的結果。如果將它設置得更高,則場景看起來會更「放大」,反之亦然。

如果您希望相機處於不同的位置或角度,那麼您需要移動並旋轉場景以使相機處於(0,0,0)並指向前方,然後您可以使用上面的公式。

您基本上正在計算穿過相機的線和3D點以及在相機前稍微浮動的垂直平面之間的交點。