天天看點

Rendering a triangle

Elements of a triangle

一個三角形由它的三個頂點來定義。三個不同位置的頂點能定義一個獨一無二的三角形。為了告訴GPU渲染一個三角形,我們必須告訴它這三個點的坐标。下圖是一個2D的例子。我們要傳遞三個坐标分别為(0,0), (0, 1), (1, 0)的點給GPU,然後GPU就有足夠的資訊來渲染我們想要的三角形。

Rendering a triangle

現在我們必須要告訴GPU三個坐标。我們要如何做呢?在D3D10中,頂點資訊都存儲在一個buffer資源中。一個用來存儲頂點資訊的buffer就稱為頂點緩沖。我們要建立一個足夠大的頂點緩沖來存儲三個頂點。在D3D10中,應用程式在建立緩沖資源時必須說明頂點緩沖的大小(以位元組為機關)。我們知道這個緩沖必須要足夠大來裝三個頂點,但我們怎麼知道每個頂點需要占幾個位元組空間呢?這就需要了解vertex layout(頂點布局)。

Input Layout

一個頂點有一個坐标,大部分時候還會有一些其他屬性,比如法線,一個或多個顔色值,紋理坐标(用作紋理映射),等等。頂點布局定義了這些屬性在記憶體中如何排列。每種屬性使用什麼資料類型,每個屬性占多大空間,以及在記憶體中的順序。頂點通常使用一個結構體來表達,它的大小也就是結構體的大小。

在本例中,我們隻需要用到頂點坐标,是以我們用一個單獨的D3DXVECTOR3類型定義我們的頂點結構體。這個類型是包含3個浮點元素的向量,通常在3D程式中都用它來作為坐标。

    struct SimpleVertex      
    {      
        D3DXVECTOR3 Pos;  // Position      
    };      

現在我們有了一個表示我們的頂點的結構,它可以用來在我們的應用程式中向系統記憶體中存儲頂點資訊。盡管如此,當我們向GPU提供頂點緩沖時,我們僅僅向它提供了一塊未加工的記憶體。GPU必須知道頂點布局才能從頂點緩沖中擷取到正确的屬性。為此要使用輸入布局(input layout)。

在d3d10中,輸入布局是一種用GPU可以了解的方式來描述頂點結構的一種d3d對象。每個頂點屬性都可以用D3D10_INPUT_ELEMENT_DESC結構描述。一個應用程式定義一個或多個D3D10_INPUT_ELEMENT_DESC,然後使用這個數組去建立input layout對象,以便描述整個頂點。下面我們看看D3D10_INPUT_ELEMENT_DESC的各個具體屬性。

1.       SemanticName:它是一個字元串或一個單詞,用來描述這個元素的種類或目的或語義。這個詞可以是任意形式。比如,對于一個頂點的坐标,一個好的語義應該是POSITION,語義不區分大小寫。

2.       SemanticIndex: 語義索引用來補充語義名。一個頂點可能有多個屬性都是相同種類的,比如,可以有兩組紋理坐标或2組顔色。我們可以讓這兩個屬性使用相同的語義名“COLOR”,但使用不同的語義索引0和1,而不使用”COLOR0”和”COLOR1“。

3.       Format:Format定義了這個元素使用的資料類型。比如DXGI_FORMAT_R32G32B32_FLOAT有3個32位浮點數,這個元素主肖12位元組長。

4.       InputSlot: 如前所述,d3d10應用程式使用頂點緩沖向GPU傳遞頂點資料。在d3d10中,多個頂點緩沖可以同時向GPU輸送資料,确切的說是最多同時16個。每個頂點緩沖都綁定到0~15中間的某個輸入槽,InputSlot就是告訴GPU,對于這個元素,它應該使用哪個頂點緩沖。

5.       AlignedByteOffset:一個頂點在頂點緩沖中隻是簡單的一塊記憶體。AlignedByteOffset就告訴GPU在那塊記憶體中從哪裡開始擷取這個元素。

6.       InputSlotClass:它通常取值為D3D10_INPUT_PER_INSTANCE_DATA。當應用程式使用執行個體化時,它可以把輸入布局的InputSlotClass設定為D3D10_INPUT_PER_INSTANCE_DATA來使用包含着執行個體資料的頂點緩沖。執行個體化是d3d中的一個進階話題,在這裡不會讨論,對示例程式來說,我們隻使用D3D10_INPUT_PER_INSTANCE_DATA。

7.       InstanceDataStepRate:它也是為執行個體化服務的。因為我們不使用執行個體化,是以通常設定為0。

現在我人定義D3D10_INPUT_ELEMENT_DESC數組來建立輸入布局。

    // Define the input layout

    D3D10_INPUT_ELEMENT_DESC layout[] =

    {

        { L"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 }, 

    };

   UINT numElements

Vertex Layout

在下個示例中,我們将解釋technique對象和與之相關的着色器(shaders)。現在,我們要專注于為technique建立d3d10頂點布局對象。盡管如此,我們還是會認識到technique和shaders都是和頂點布局緊密關聯的。原因是建立頂點布局對象需要頂點着色器的輸入簽名(input signature)。我們首先調用technique的GetPassByIndex()方法來獲得一個特效pass對象(表示technique的第一個pass)。然後,我們調用pass的GetDesc()方法來擷取一個pass描述結構。在這個結構内,有一個pIAInputSignature指向一塊表示在這個pass中用到的頂點着色器的輸入簽名的二進制資料。一旦我們擷取到這個資料,我們可以調用ID3D10Device::CreateInputLayout()來建立一個頂點布局對象,調用ID3D10Device::IASetInputLayout()來把它設定為激活的頂點布局。代碼如下:

    // Create the input layout      
    D3D10_PASS_DESC PassDesc;      
    g_pTechnique->GetPassByIndex( 0 )->GetDesc( &PassDesc );      
    if( FAILED( g_pd3dDevice->CreateInputLayout( layout, numElements, PassDesc.pIAInputSignature, &g_pVertexLayout ) ) )      
        return FALSE;      
    // Set the input layout      
    g_pd3dDevice->IASetInputLayout( g_pVertexLayout );      

Createing Vertex Buffer

在初始化期間還有一件事要做就是建立頂點緩沖。在d3d10中要建立頂點緩沖,需要兩個結構體, D3D10_BUFFER_DESC and D3D10_SUBRESOURCE_UP。然後調用ID3D10Device::CreateBuffer()。D3D10_BUFFER_DESC描述了頂點緩沖對象,D3D10_USBRESOURCE_UP描述了将要被複制到頂點緩沖中的實際資料。建立和初始化頂點緩沖立即完成,這樣我們就不用在後面初始化緩沖了。将要複制到頂點緩沖中的資料是3個SimpleVertex類型的頂點。在這個頂點數組中的坐标是為了讓我們可以在程式視窗的中央看到三角形。在頂點緩沖建立之後,我們調用ID3D10Device::IASetVertexBuffers()來綁定它到裝置上。完整代碼如下:

    // Create vertex buffer      
    SimpleVertex vertices[] =      
    {      
        D3DXVECTOR3( 0.0f, 0.5f, 0.5f ),      
        D3DXVECTOR3( 0.5f, -0.5f, 0.5f ),      
        D3DXVECTOR3( -0.5f, -0.5f, 0.5f ),      
    };      
    D3D10_BUFFER_DESC bd;      
    bd.Usage = D3D10_USAGE_DEFAULT;      
    bd.ByteWidth = sizeof( SimpleVertex ) * 3;      
    bd.BindFlags = D3D10_BIND_VERTEX_BUFFER;      
    bd.CPUAccessFlags = 0;      
    bd.MiscFlags = 0;      
    D3D10_SUBRESOURCE_UP InitData;      
    InitData.pSysMem = vertices;      
    if( FAILED( g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pVertexBuffer ) ) )      
        return FALSE;      
    // Set vertex buffer      
    UINT stride = sizeof( SimpleVertex );      
    UINT offset = 0;      
    g_pd3dDevice->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset );      

Primitive Topology 基礎拓撲

primitive topology指明了GPU如果擷取這三個頂點來渲染三角形。我們讨論它以便渲染單個的三角形。應用程式需要傳送三個頂點給GPU。前三個表示第一個三角形,另三個表示第二個三角形。這個拓撲稱為三角形鍊。盡管如此,兩個相連的三角形通常共享一些頂點。通常,三角形都按順時針方向來定義頂點。如果我們把下圖中的兩個三角ABC和CBD發送給GPU,那麼頂點緩沖會像這樣:ABCCBD。

Rendering a triangle

如果我們在渲染三角形時從前一個三角形中取兩個點,而從頂點緩沖中取一個點,這種拓撲就稱為三角帶(triangle strip)。當渲染一條三角帶時,第一個三角形使用頂點緩沖中的前三個頂點來定義,後面的三角形則使用前一個三角形的最後兩個點加上頂點緩沖中的下一個點來組成。組成a圖的頂點緩沖會這樣:ABCD

對于b圖,三角鍊的緩沖如下:ABCCBDCDE

使用三角帶的緩沖如下ABCDE。你可能注意到第二個三角形的頂點順序是BCD,它們沒有構成順時針方向。這是使用三角帶時的一個必然現象。為了修正它,GPU會自動交換來自前一個三角形的兩個頂點的順序,并且隻交換第2,4,6...個三角形。這保證了每個三角形都是按順時針方向定義的。除了三角鍊和三角帶,d3d10還支援很多其他的基本拓撲,我們會在本例中讨論它們。

在代碼中,我們隻一個三角形,是以無論我們使用哪種拓撲都沒什麼關系。

    // Set primitive topology      
    g_pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );      

Rendering the Triangle

本例中最後要做的就是渲染三角形。如前面提到的那樣,本例會使用特效系統來進行渲染。我們用早先獲得的technique對象來調用ID3D10EffectTechnique::GetDesc()擷取D3D10FX_TECHNIQUE_DESC結構,該結構描述了technique。該結構中的成員之一Passes,訓示了該technique包含的pass數量。為了正确的使用這個technique來進行渲染。應用程式應該循環調用每一個pass。在這個循環中,首先調用technique的GetPassByIndex()函數來擷取pass對象,然後調用它的Apply()方法來把特效系統把關聯的頂點着色器和渲染狀态綁定到圖形渲染管線。接下來要做的就是調用ID3D10Device::Draw(),這個函數控制GPU使用目前的頂點緩沖、頂點布局還有基本拓撲類型來進行渲染。Draw()的第一個參數是發送給GPU的頂點數量,第二個參數是要發送的第一個頂點的索引号。由于我們隻渲染一個三角形,并且是從頂點緩沖的起點開始渲染,是以我們給這兩個參數設定為3和0.整個渲染過程如下:

    //      
    // Render a triangle      
    //      
    D3D10_TECHNIQUE_DESC techDesc;      
    g_pTechnique->GetDesc( &techDesc );      
    for( UINT p = 0; p < techDesc.Passes; ++p )      
    {      
        g_pTechnique->GetPassByIndex( p )->Apply(0);      
        g_pd3dDevice->Draw( 3, 0 );      
    }