天天看點

Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空間

在上一個教程中,我們在應用程式視窗的中心成功渲染了一個三角形。 我們沒有太注意我們在頂點緩沖區中拾取的頂點位置。 在本教程中,我們将深入研究3D位置和轉換的細節。

本教程的結果将是渲染到螢幕的3D對象。 雖然之前的教程側重于将2D對象渲染到3D世界,但在這裡我們展示了一個3D對象。

(SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial04

Github倉庫

在上一個教程中,三角形的頂點被有政策地放置,以在螢幕上完美地對齊。 但是,情況并非總是如此。 是以,我們需要一個系統來表示3D空間中的對象和一個顯示它們的系統。

在現實世界中,物體存在于3D空間中。 這意味着要将對象放置在世界中的特定位置,我們需要使用坐标系并定義與位置對應的三個坐标。 在計算機圖形學中,3D空間最常用于笛卡爾坐标系。 在該坐标系中,三個軸X,Y和Z彼此垂直,決定了空間中每個點的坐标。 該坐标系進一步分為左手系統和右手系統。 在左手系統中,當X軸指向右側,Y軸指向上方時,Z軸指向前方。 在右手系統中,具有相同的X和Y軸,Z軸指向後方。

圖1.左手坐标系與右手坐标系

Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空間

現在我們已經讨論過坐标系,考慮3D空間。 點在不同的空間中具有不同的坐标。 作為一維中的一個例子,假設我們有一個标尺,我們注意到标尺的5英寸标記處的點P. 現在,如果我們将标尺向右移動1英寸,則相同的點位于4英寸标記處。 通過移動标尺,參考架構已經改變。 是以,當點沒有移動時,它有一個新的坐标。

圖2. 1D中的空間圖示

Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空間

在3D中,空間通常由原點和來自原點的三個唯一軸定義:X,Y和Z.計算機圖形中通常使用多個空間:對象空間,世界空間,視圖空間,投影空間和螢幕空間。

圖3.在對象空間中定義的立方體

Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空間

請注意,多元資料集以原點為中心。 對象空間,也稱為模型空間,是指藝術家在建立3D模型時使用的空間。 通常,藝術家建立以原點為中心的模型,以便更容易執行轉換,例如旋轉模型,我們将在讨論轉換時看到。 八個頂點具有以下坐标:   

因為對象空間是藝術家在設計和建立模型時通常使用的對象空間,是以存儲在磁盤上的模型也在對象空間中。 應用程式可以建立頂點緩沖區來表示此類模型,并使用模型資料初始化緩沖區。 是以,頂點緩沖區中的頂點通常也位于對象空間中。 這也意味着頂點着色器接收對象空間中的輸入頂點資料。

世界空間是場景中每個對象共享的空間。 它用于定義我們希望渲染的對象之間的空間關系。 為了想象世界空間,我們可以想象我們正站在朝北的長方形房間的西南角。 我們将我們的腳站立的角落定義為原點,(0,0,0)。 X軸向我們的右邊; Y軸上升; 并且Z軸向前,與我們面對的方向相同。 當我們這樣做時,房間中的每個位置都可以用一組XYZ坐标來識别。 例如,可能有一把椅子在前方5英尺處,在我們右側2英尺處。 在椅子頂部的8英尺高的天花闆上可能有一盞燈。 然後我們可以将椅子的位置稱為(2,0,5),将燈的位置稱為(2,8,5)。 正如我們所看到的,世界空間就是所謂的在世界上互相聯系的物體所組成的。

視圖空間(有時稱為相機空間)類似于世界空間,因為它通常用于整個場景。 但是,在視圖空間中,原點位于檢視器或錄影機。 視圖方向(觀察者正在看的位置)定義正Z軸。 應用程式定義的“向上”方向變為正Y軸,如下所示。

圖4.世界空間(左)和視圖空間(右)中的相同對象

Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空間

左圖顯示了一個場景,該場景由類似人的物體和觀察物體的觀察者(相機)組成。 世界空間使用的原點和軸以紅色顯示。 右圖顯示了與世界空間相關的視圖空間。 視圖空間軸顯示為藍色。 為了更清楚地說明,視圖空間與左圖像中的世界空間的方向與讀者不同。 請注意,在視圖空間中,檢視器正在Z方向上檢視。

投影空間是指從視圖空間應用投影變換後的空間。 在此空間中,可見内容的X和Y坐标範圍為-1到1,Z坐标範圍為0到1。

螢幕空間通常用于指代幀緩沖區中的位置。 因為幀緩沖區通常是2D紋理,是以螢幕空間是2D空間。 左上角是坐标為(0,0)的原點。 正X向右,正Y向下。 對于w像素寬且h像素高的緩沖區,最右下像素具有坐标(w-1,h-1)。

轉換最常用于将頂點從一個空間轉換為另一個空間。 在3D計算機圖形學中,管道中邏輯上有三種這樣的變換:世界,視圖和投影變換。 下一個教程将介紹單個轉換操作,如轉換,旋轉和縮放。

顧名思義,世界轉換将頂點從對象空間轉換為世界空間。 它通常由一個或多個縮放,旋轉和平移組成,基于我們想要給對象的大小,方向和位置。 場景中的每個對象都有自己的世界變換矩陣。 這是因為每個對象都有自己的大小,方向和位置。

頂點轉換為世界空間後,視圖轉換将這些頂點從世界空間轉換為視圖空間。 回想一下之前的讨論,觀看空間是世界從觀衆(或相機)的角度出現的。 在視圖空間中,觀察者位于沿正Z軸向外看的原點。

值得注意的是,盡管視圖空間是來自觀察者參照系的世界,但視圖變換矩陣應用于頂點,而不是觀察者。 是以,視圖矩陣必須執行我們應用于我們的檢視器或相機的相反轉換。 例如,如果我們想要将錄影機朝向-Z方向移動5個單元,我們需要計算一個視圖矩陣,它可以沿着+ Z方向将頂點平移5個機關。 雖然相機向後移動,但從相機的角度來看,頂點已向前移動。 在XNA Math中,一個友善的API調用XMMatrixLookAtLH()通常用于計算視圖矩陣。 我們隻需要告訴它觀察者在哪裡,在哪裡看,以及表示觀察者頂部的方向,也稱為向上矢量,以獲得相應的視圖矩陣。

投影變換将頂點從諸如世界和視圖空間的3D空間轉換為投影空間。 在投影空間中,頂點的X和Y坐标是從3D空間中該頂點的X / Z和Y / Z比獲得的。

圖5.投影

Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空間

在3D空間中,事物以透視的方式出現。 也就是說,物體越近,它出現的越大。 如圖所示,在遠離觀察者眼睛的d個機關處高h機關的樹的尖端将出現在與另一棵樹的尖端2h機關高和2d機關遠的相同點處。 是以,在2D螢幕上出現頂點的位置與其X / Z和Y / Z比率直接相關。

定義3D空間的參數之一稱為視場(FOV)。 FOV表示在特定方向上檢視哪些對象從特定位置可見。 人類有一個前瞻性的FOV(我們無法看到我們背後的東西),我們看不到太近或太遠的物體。 在計算機圖形學中,FOV包含在視錐體中。 視錐體由3D中的6個平面定義。 這些平面中的兩個平行于XY平面。 這些被稱為近Z和遠Z平面。 其他四個平面由觀察者的水準和垂直視野定義。 視場越寬,視錐體體積越寬,觀察者看到的物體越多。

GPU會過濾掉視錐體外的對象,這樣就不必花時間渲染無法顯示的内容。 此過程稱為裁剪。 視錐體是一個四面金字塔,頂部被切掉。 剪切此卷是很複雜的,因為要剪切一個視錐體平面,GPU必須将每個頂點與平面的等式進行比較。 相反,GPU通常首先執行投影變換,然後針對視錐體量進行剪輯。 投影變換對視錐體的影響是金字塔形視錐體成為投影空間中的盒子。 這是因為,如前所述,在投影空間中,X和Y坐标基于3D空間中的X / Z和Y / Z. 是以,點a和點b在投影空間中将具有相同的X和Y坐标,這就是視錐體成為盒子的原因。

圖6.檢視平截頭體

Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空間

假設兩棵樹的尖端恰好位于頂視圖平截頭體邊緣。進一步假設d = 2h。沿投影空間中頂邊的Y坐标将為0.5(因為h / d = 0.5)。是以,任何大于0.5的Y投影後Y值都将被裁剪。這裡的問題是0.5由程式選擇的垂直視場确定,并且不同的FOV值導緻GPU必須剪切的不同值。為了使這個過程更加友善,3D程式通常縮放頂點的投影X和Y值,以便可見的X和Y值的範圍從-1到1.換句話說,任何X或Y坐标都在[-1]之外1]範圍将被删除。為了使該剪切方案起作用,投影矩陣必須通過h / d或d / h的倒數來縮放投影頂點的X和Y坐标。 d / h也是FOV一半的餘切。通過縮放,視錐體的頂部變為h / d * d / h = 1.大于1的任何内容都将被GPU裁剪。這就是我們想要的。

通常也對投影空間中的Z坐标進行類似的調整。 我們希望近和遠Z平面分别在投影空間中為0和1。 當Z = 3D空間中的近Z值時,Z在投影空間中應為0; 當Z = 3D空間中的遠Z時,Z在投影空間中應為1。 完成此操作後,GPU [0 1]以外的任何Z值都将被裁剪掉。

在Direct3D 11中,擷取投影矩陣的最簡單方法是調用XMMatrixPerspectiveFovLH()方法。 我們隻提供4個參數-FOVy,Aspect,Zn和Zf-并傳回一個矩陣,它可以完成上面提到的所有必要操作。 FOVy是Y方向的視野。 Aspect是寬高比,即視圖寬度與高度的比率。 從FOVy和Aspect,可以計算FOVx。 該縱橫比通常從渲染目标寬度與高度的比率獲得。 Zn和Zf分别是視圖空間中的近和遠Z值。

在上一個教程中,我們編寫了一個程式,用于渲染單個三角形。 當我們建立頂點緩沖區時,我們使用的頂點位置直接在投影空間中,這樣我們就不必執行任何變換。 現在我們已經了解了3D空間和變換,我們将修改程式,以便在對象空間中定義頂點緩沖區,就像它應該的那樣。 然後,我們将修改頂點着色器以将頂點從對象空間轉換為投影空間。

由于我們開始以三維方式表示事物,是以我們将前一個教程中的平面三角形更改為多元資料集。 這将使我們能夠更清楚地展示這些概念。

  

如果你注意到我們所做的隻是指定立方體上的八個點,但我們實際上沒有描述各個三角形。 如果我們按原樣傳遞,輸出将不是我們所期望的。 我們需要通過這八個點指定形成立方體的三角形。

在立方體上,許多三角形将共享相同的頂點,并且一次又一次地重新定義相同的點将浪費空間。 是以,有一種方法隻指定八個點,然後讓Direct3D知道要為三角形選擇哪些點。 這是通過索引緩沖區完成的。 索引緩沖區将包含一個清單,該清單将引用緩沖區中的頂點索引,以指定在每個三角形中使用哪些點。 下面的代碼顯示了構成每個三角形的點。

如您所見,第一個三角形由點3,1和0定義。這意味着第一個三角形的頂點位于:( - 1.0f,1.0f,1.0f),(1.0f,1.0f,-1.0) f),和(-1.0f,1.0f,-1.0f)。 立方體上有六個面,每個面由兩個三角形組成。 是以,您會看到此處定義的12個三角形。

由于每個頂點都是明确列出的,并且沒有兩個三角形共享邊(至少,它已經被定義),這被認為是一個三角形清單。 總的來說,對于三角形清單中的12個三角形,我們将需要總共36個頂點。

索引緩沖區的建立與頂點緩沖區非常相似,我們在結構中指定了諸如大小和類型之類的參數,并稱為CreateBuffer。 類型是D3D11_BIND_INDEX_BUFFER,因為我們使用DWORD聲明了我們的數組,是以我們将使用sizeof(DWORD)。

一旦我們建立了這個緩沖區,我們就需要設定它,以便Direct3D知道在生成三角形時引用這個索引緩沖區。 我們指定緩沖區的指針,格式和緩沖區中的偏移量以開始引用。

在上一個教程的頂點着色器中,我們采用輸入頂點位置并輸出相同的位置而不進行任何修改。我們可以這樣做,因為輸入頂點位置已經在投影空間中定義。現在,因為輸入頂點位置是在對象空間中定義的,是以我們必須在從頂點着色器輸出之前對其進行變換。我們通過三個步驟完成此任務:從對象轉換到世界空間,從世界轉換到視圖空間,以及從視圖轉換到投影空間。我們需要做的第一件事是聲明三個常量緩沖區變量。常量緩沖區用于存儲應用程式需要傳遞給着色器的資料。在渲染之前,應用程式通常會将重要資料寫入常量緩沖區,然後在渲染過程中可以從着色器中讀取資料。在FX檔案中,常量緩沖區變量在C ++結構中聲明為全局變量。我們将使用的三個變量是HLSL類型“矩陣”的世界,視圖和投影變換矩陣。

一旦我們聲明了我們需要的矩陣,我們就會更新頂點着色器以使用矩陣變換輸入位置。 通過将矢量乘以矩陣來變換矢量。 在HLSL中,這是使用mul()内部函數完成的。 我們的變量聲明和新的頂點着色器如下所示:

在頂點着色器中,每個 mul()将一個變換應用于輸入位置。 世界,視圖和投影變換按順序依次應用。 這是必要的,因為向量和矩陣乘法不是可交換的。

我們更新了頂點着色器以使用矩陣進行變換,但我們還需要在程式中定義三個矩陣。 這三個矩陣将存儲渲染時要使用的變換。 在渲染之前,我們将這些矩陣的值複制到着色器常量緩沖區。 然後,當我們通過調用Draw()啟動渲染時,我們的頂點着色器讀取存儲在常量緩沖區中的矩陣。 除了矩陣之外,我們還需要一個代表常量緩沖區的ID3D11Buffer對象。 是以,我們的全局變量将添加以下内容:

要建立ID3D11Buffer對象,我們使用 ID3D11Device :: CreateBuffer()并指定D3D11_BIND_CONSTANT_BUFFER

我們需要做的下一件事是提出三個矩陣,我們将用它來進行轉換。我們希望三角形位于原點上,與XY平面平行。這正是它如何存儲在對象空間中的頂點緩沖區中。是以,世界變換不需要做任何事情,我們将世界矩陣初始化為機關矩陣。我們想要設定我們的相機,使其位于[0 1 -5],檢視點[0 1 0]。我們可以使用向上矢量[0 1 0]調用 XMMatrixLookAtLH()來友善地為我們計算視圖矩陣,因為我們希望+ Y方向始終保持在頂部。最後,為了得到投影矩陣,我們稱之為XMMatrixPerspectiveFovLH(),具有90度垂直視場(pi / 2),寬高比為640/480,來自我們的後緩沖區大小,以及近和遠Z分别為0.1和110。這意味着螢幕上将看不到小于0.1或超過110的任何内容。這三個矩陣存儲在全局變量g_World,g_View和g_Projection中。

我們有矩陣,現在我們必須在渲染時将它們寫入常量緩沖區,以便GPU可以讀取它們。 要更新緩沖區,我們可以使用 ID3D11DeviceContext :: UpdateSubresource()API并将指針傳遞給以與着色器常量緩沖區相同的順序存儲的矩陣。 為了做到這一點,我們将建立一個與着色器中的常量緩沖區具有相同布局的結構。 另外,由于矩陣在C ++和HLSL中的記憶體排列方式不同,我們必須在更新之前轉置矩陣。

繼續閱讀