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空間中。 這意味著要將對象放置在世界中的特定位置,我們需要使用坐標系并定義與位置對應的三個坐標。 在計算機圖形學中,3D空間最常用于笛卡爾坐標系。 在該坐標系中,三個軸X,Y和Z彼此垂直,決定了空間中每個點的坐標。 該坐標系進一步分為左手系統和右手系統。 在左手系統中,當X軸指向右側,Y軸指向上方時,Z軸指向前方。 在右手系統中,具有相同的X和Y軸,Z軸指向后方。
圖1.左手坐標系與右手坐標系
現在我們已經討論過坐標系,考慮3D空間。 點在不同的空間中具有不同的坐標。 作為一維中的一個例子,假設我們有一個標尺,我們注意到標尺的5英寸標記處的點P. 現在,如果我們將標尺向右移動1英寸,則相同的點位于4英寸標記處。 通過移動標尺,參考框架已經改變。 因此,當點沒有移動時,它有一個新的坐標。
圖2. 1D中的空間圖示
在3D中,空間通常由原點和來自原點的三個唯一軸定義:X,Y和Z.計算機圖形中通常使用多個空間:對象空間,世界空間,視圖空間,投影空間和屏幕空間。
圖3.在對象空間中定義的立方體
?
對象空間
請注意,多維數據集以原點為中心。?對象空間,也稱為模型空間,是指藝術家在創建3D模型時使用的空間。?通常,藝術家創建以原點為中心的模型,以便更容易執行轉換,例如旋轉模型,我們將在討論轉換時看到。?八個頂點具有以下坐標:? ? (-1, 1, -1)( 1, 1, -1)(-1, -1, -1)( 1, -1, -1)(-1, 1, 1)( 1, 1, 1)(-1, -1, 1)( 1, -1, 1) 因為對象空間是藝術家在設計和創建模型時通常使用的對象空間,所以存儲在磁盤上的模型也在對象空間中。 應用程序可以創建頂點緩沖區來表示此類模型,并使用模型數據初始化緩沖區。 因此,頂點緩沖區中的頂點通常也位于對象空間中。 這也意味著頂點著色器接收對象空間中的輸入頂點數據。世界空間
世界空間是場景中每個對象共享的空間。 它用于定義我們希望渲染的對象之間的空間關系。 為了想象世界空間,我們可以想象我們正站在朝北的長方形房間的西南角。 我們將我們的腳站立的角落定義為原點,(0,0,0)。 X軸向我們的右邊; Y軸上升; 并且Z軸向前,與我們面對的方向相同。 當我們這樣做時,房間中的每個位置都可以用一組XYZ坐標來識別。 例如,可能有一把椅子在前方5英尺處,在我們右側2英尺處。 在椅子頂部的8英尺高的天花板上可能有一盞燈。 然后我們可以將椅子的位置稱為(2,0,5),將燈的位置稱為(2,8,5)。 正如我們所看到的,世界空間就是所謂的在世界上相互聯系的物體所組成的。視圖空間
視圖空間(有時稱為相機空間)類似于世界空間,因為它通常用于整個場景。 但是,在視圖空間中,原點位于查看器或攝像機。 視圖方向(觀察者正在看的位置)定義正Z軸。 應用程序定義的“向上”方向變為正Y軸,如下所示。 圖4.世界空間(左)和視圖空間(右)中的相同對象左圖顯示了一個場景,該場景由類似人的物體和觀察物體的觀察者(相機)組成。 世界空間使用的原點和軸以紅色顯示。 右圖顯示了與世界空間相關的視圖空間。 視圖空間軸顯示為藍色。 為了更清楚地說明,視圖空間與左圖像中的世界空間的方向與讀者不同。 請注意,在視圖空間中,查看器正在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.投影
在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.查看平截頭體
假設兩棵樹的尖端恰好位于頂視圖平截頭體邊緣。進一步假設d = 2h。沿投影空間中頂邊的Y坐標將為0.5(因為h / d = 0.5)。因此,任何大于0.5的Y投影后Y值都將被裁剪。這里的問題是0.5由程序選擇的垂直視場確定,并且不同的FOV值導致GPU必須剪切的不同值。為了使這個過程更加方便,3D程序通??s放頂點的投影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空間和變換,我們將修改程序,以便在對象空間中定義頂點緩沖區,就像它應該的那樣。 然后,我們將修改頂點著色器以將頂點從對象空間轉換為投影空間。
?
修改頂點緩沖區
由于我們開始以三維方式表示事物,因此我們將前一個教程中的平面三角形更改為多維數據集。 這將使我們能夠更清楚地展示這些概念。
SimpleVertex vertices[] ={{ XMFLOAT3( -1.0f, 1.0f, -1.0f ), XMFLOAT4( 0.0f, 0.0f, 1.0f, 1.0f ) },{ XMFLOAT3( 1.0f, 1.0f, -1.0f ), XMFLOAT4( 0.0f, 1.0f, 0.0f, 1.0f ) },{ XMFLOAT3( 1.0f, 1.0f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 1.0f, 1.0f ) },{ XMFLOAT3( -1.0f, 1.0f, 1.0f ), XMFLOAT4( 1.0f, 0.0f, 0.0f, 1.0f ) },{ XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT4( 1.0f, 0.0f, 1.0f, 1.0f ) },{ XMFLOAT3( 1.0f, -1.0f, -1.0f ), XMFLOAT4( 1.0f, 1.0f, 0.0f, 1.0f ) },{ XMFLOAT3( 1.0f, -1.0f, 1.0f ), XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ) },{ XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT4( 0.0f, 0.0f, 0.0f, 1.0f ) },};
如果你注意到我們所做的只是指定立方體上的八個點,但我們實際上沒有描述各個三角形。 如果我們按原樣傳遞,輸出將不是我們所期望的。 我們需要通過這八個點指定形成立方體的三角形。
在立方體上,許多三角形將共享相同的頂點,并且一次又一次地重新定義相同的點將浪費空間。 因此,有一種方法只指定八個點,然后讓Direct3D知道要為三角形選擇哪些點。 這是通過索引緩沖區完成的。 索引緩沖區將包含一個列表,該列表將引用緩沖區中的頂點索引,以指定在每個三角形中使用哪些點。 下面的代碼顯示了構成每個三角形的點。
// Create index bufferWORD indices[] ={3,1,0,2,1,3,0,5,4,1,5,0,3,4,7,0,4,3,1,6,5,2,6,1,2,7,6,3,7,2,6,4,5,7,4,6,};
如您所見,第一個三角形由點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)。
D3D11_BUFFER_DESC bd;ZeroMemory( &bd, sizeof(bd) );bd.Usage = D3D11_USAGE_DEFAULT;bd.ByteWidth = sizeof( WORD ) * 36; // 36 vertices needed for 12 triangles in a triangle listbd.BindFlags = D3D11_BIND_INDEX_BUFFER;bd.CPUAccessFlags = 0;bd.MiscFlags = 0;InitData.pSysMem = indices;if( FAILED( g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pIndexBuffer ) ) )return FALSE;
一旦我們創建了這個緩沖區,我們就需要設置它,以便Direct3D知道在生成三角形時引用這個索引緩沖區。 我們指定緩沖區的指針,格式和緩沖區中的偏移量以開始引用。
// Set index bufferg_pImmediateContext->IASetIndexBuffer( g_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0 );
修改頂點著色器
在上一個教程的頂點著色器中,我們采用輸入頂點位置并輸出相同的位置而不進行任何修改。我們可以這樣做,因為輸入頂點位置已經在投影空間中定義?,F在,因為輸入頂點位置是在對象空間中定義的,所以我們必須在從頂點著色器輸出之前對其進行變換。我們通過三個步驟完成此任務:從對象轉換到世界空間,從世界轉換到視圖空間,以及從視圖轉換到投影空間。我們需要做的第一件事是聲明三個常量緩沖區變量。常量緩沖區用于存儲應用程序需要傳遞給著色器的數據。在渲染之前,應用程序通常會將重要數據寫入常量緩沖區,然后在渲染過程中可以從著色器中讀取數據。在FX文件中,常量緩沖區變量在C ++結構中聲明為全局變量。我們將使用的三個變量是HLSL類型“矩陣”的世界,視圖和投影變換矩陣。
一旦我們聲明了我們需要的矩陣,我們就會更新頂點著色器以使用矩陣變換輸入位置。 通過將矢量乘以矩陣來變換矢量。 在HLSL中,這是使用mul()內部函數完成的。 我們的變量聲明和新的頂點著色器如下所示:
cbuffer ConstantBuffer : register( b0 ){matrix World;matrix View;matrix Projection;}//// Vertex Shader//VS_OUTPUT VS( float4 Pos : POSITION, float4 Color : COLOR ){VS_OUTPUT output = (VS_OUTPUT)0;output.Pos = mul( Pos, World );output.Pos = mul( output.Pos, View );output.Pos = mul( output.Pos, Projection );output.Color = Color;return output;}
在頂點著色器中,每個 mul()將一個變換應用于輸入位置。 世界,視圖和投影變換按順序依次應用。 這是必要的,因為向量和矩陣乘法不是可交換的。
?
設置矩陣
我們更新了頂點著色器以使用矩陣進行變換,但我們還需要在程序中定義三個矩陣。 這三個矩陣將存儲渲染時要使用的變換。 在渲染之前,我們將這些矩陣的值復制到著色器常量緩沖區。 然后,當我們通過調用Draw()啟動渲染時,我們的頂點著色器讀取存儲在常量緩沖區中的矩陣。 除了矩陣之外,我們還需要一個代表常量緩沖區的ID3D11Buffer對象。 因此,我們的全局變量將添加以下內容:
ID3D11Buffer* g_pConstantBuffer = NULL;XMMATRIX g_World;XMMATRIX g_View;XMMATRIX g_Projection;
要創建ID3D11Buffer對象,我們使用 ID3D11Device :: CreateBuffer()并指定D3D11_BIND_CONSTANT_BUFFER
D3D11_BUFFER_DESC bd;ZeroMemory( &bd, sizeof(bd) );bd.Usage = D3D11_USAGE_DEFAULT;bd.ByteWidth = sizeof(ConstantBuffer);bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;bd.CPUAccessFlags = 0;if( FAILED(g_pd3dDevice->CreateBuffer( &bd, NULL, &g_pConstantBuffer ) ) )return hr;
我們需要做的下一件事是提出三個矩陣,我們將用它來進行轉換。我們希望三角形位于原點上,與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中的內存排列方式不同,我們必須在更新之前轉置矩陣。
//// Update variables//ConstantBuffer cb;cb.mWorld = XMMatrixTranspose( g_World );cb.mView = XMMatrixTranspose( g_View );cb.mProjection = XMMatrixTranspose( g_Projection );g_pImmediateContext->UpdateSubresource( g_pConstantBuffer, 0, NULL, &cb, 0, 0 );
?
?
轉載于:https://www.cnblogs.com/OctoptusLian/p/9748161.html
總結
以上是生活随笔為你收集整理的Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空间的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么让手机有u盘启动 让手机实现U盘启动
- 下一篇: BugKu 变量1