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

概述

在上一個教程中,咱們在應用程序窗口的中心成功渲染了一個三角形。 咱們沒有太注意咱們在頂點緩衝區中拾取的頂點位置。 在本教程中,咱們將深刻研究3D位置和轉換的細節。git

本教程的結果將是渲染到屏幕的3D對象。 雖然以前的教程側重於將2D對象渲染到3D世界,但在這裏咱們展現了一個3D對象。github

 

資源目錄

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

Github倉庫框架

 

3D空間

在上一個教程中,三角形的頂點被有策略地放置,以在屏幕上完美地對齊。 可是,狀況並不是老是如此。 所以,咱們須要一個系統來表示3D空間中的對象和一個顯示它們的系統。函數

在現實世界中,物體存在於3D空間中。 這意味着要將對象放置在世界中的特定位置,咱們須要使用座標系並定義與位置對應的三個座標。 在計算機圖形學中,3D空間最經常使用於笛卡爾座標系。 在該座標系中,三個軸X,Y和Z彼此垂直,決定了空間中每一個點的座標。 該座標系進一步分爲左手系統和右手系統。 在左手系統中,當X軸指向右側,Y軸指向上方時,Z軸指向前方。 在右手系統中,具備相同的X和Y軸,Z軸指向後方。佈局

圖1.左手座標系與右手座標系spa

如今咱們已經討論過座標系,考慮3D空間。 點在不一樣的空間中具備不一樣的座標。 做爲一維中的一個例子,假設咱們有一個標尺,咱們注意到標尺的5英寸標記處的點P. 如今,若是咱們將標尺向右移動1英寸,則相同的點位於4英寸標記處。 經過移動標尺,參考框架已經改變。 所以,當點沒有移動時,它有一個新的座標。設計

圖2. 1D中的空間圖示3d

在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程序一般縮放頂點的投影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 buffer
    WORD 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 list
    bd.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 buffer
    g_pImmediateContext->IASetIndexBuffer( g_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0 );

  

修改頂點着色器

在上一個教程的頂點着色器中,咱們採用輸入頂點位置並輸出相同的位置而不進行任何修改。咱們能夠這樣作,由於輸入頂點位置已經在投影空間中定義。如今,由於輸入頂點位置是在對象空間中定義的,因此咱們必須在從頂點着色器輸出以前對其進行變換。咱們經過三個步驟完成此任務:從對象轉換到世界空間,從世界轉換到視圖空間,以及從視圖轉換到投影空間。咱們須要作的第一件事是聲明三個常量緩衝區變量。常量緩衝區用於存儲應用程序須要傳遞給着色器的數據。在渲染以前,應用程序一般會將重要數據寫入常量緩衝區,而後在渲染過程當中能夠從着色器中讀取數據。在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 );
相關文章
相關標籤/搜索