2.3. Dibuixant un triangle

Ara que ja tenim una finestra on treballar, procedirem a pintar el nostre primer triangle de Directx11. Per a poder dibuixar geometria necessitem crear i inicialitzar un buffer de vèrtexs (vertex buffer). En altres llibreries gràfiques, com les primeres versions d’OpenGL o de DirectX, si que era possible crear primitives amb funcions específiques (ex: glBegin(GL_TRIANGLES) en OpenGL o DrawPrimitive en DirectX). El fet de treballar amb buffers millora el rendiment considerablement, és per això que Directx aposta per ells.

Així que dissenyarem la nostra pròpia estructura per a guardar els vèrtexs dels polígons, en el nostre cas, del triangle. L’anomenarem MyVertex:

struct MyVertex
{
         float Position[2];
         unsigned Color;
};

Alhora creem una matriu de vèrtexs a la qual anomenem myVertices. Això ho fem per  poder crear el buffer de vèrtexs que ve a continuació.

MyVertex myVertices[] = {
         {  0.0f,  0.5f, 0xFF0000FF },
         {  0.3f, -0.5f, 0xFF00FF00 },
         { -0.3f, -0.5f, 0xFFFF0000 },
};

Per crear un buffer de vèrtex, s'omple l'estructura D3D11_BUFFER_DESC, proporcionant diversos paràmetres com la mida del buffer en bytes, i alguns flags, entre altres.
D3D11_BUFFER_DESC bufDesc; //Creem la instància de buffer;
ZeroMemory&bufDesc, sizeof(bufDesc)); //Inicialitzem el buffer amb zeros.
bufDesc.ByteWidth = sizeof(MyVertex) * _countof(myVertices); //Definim la mida.
bufDesc.Usage = D3D11_USAGE_IMMUTABLE;
bufDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;

Els paràmetres que poden rebre tant Usage com BindFlags són els següents:
 
typedef enum D3D11_USAGE {
      D3D11_USAGE_DEFAULT   = 0,
      D3D11_USAGE_IMMUTABLE = 1,
      D3D11_USAGE_DYNAMIC   = 2,
      D3D11_USAGE_STAGING   = 3,
} D3D11_USAGE;

D3D11_USAGE_DEFAULT
Un recurs que requereix escriptura i lectura per la GPU. Aquest acostuma a ser el més usat.
D3D11_USAGE_IMMUTABLE
Un recurs que només pot ser llegit per la GPU. No pot ser escrit i no és completament accessible per tota la CPU. És immutable, no pot ser canviat un cop inicialitzat.
D3D11_USAGE_DYNAMIC
Un recurs que és accessible tant per GPU (només lectura) com per la CPU (només escriptura). És idoni si és tracta d’un recurs que serà actualitzat per la CPU almenys un cop per frame.
D3D11_USAGE_STAGING
Un recurs que suporta còpies de la GPU a la CPU. 

typedef enum D3D10_BIND_FLAG {      
      D3D10_BIND_VERTEX_BUFFER   = 0x1L,
      D3D10_BIND_INDEX_BUFFER    = 0x2L,
      D3D10_BIND_CONSTANT_BUFFER = 0x4L,
      D3D10_BIND_SHADER_RESOURCE = 0x8L,
      D3D10_BIND_STREAM_OUTPUT   = 0x10L,
      D3D10_BIND_RENDER_TARGET   = 0x20L,
      D3D10_BIND_DEPTH_STENCIL   = 0x40L
} D3D10_BIND_FLAG;

D3D11_BIND_VERTEX_BUFFER
Uneix un buffer com a vertex buffer a l’input-assembler stage.
D3D11_BIND_INDEX_BUFFER
Uneix un buffer com a index buffer a l’input-assembler stage.
D3D11_BIND_CONSTANT_BUFFER
Uneix un buffer com a constant buffer a l’input-assembler stage.
Aquest flag no es pot combinar amb cap altre bind flag.
D3D11_BIND_SHADER_RESOURCE
Uneix un buffer o una texture a un shader stage.
Aquest flag no pot ser usat amb el flag D3D10_MAP_WRITE_NO_OVERWRITE flag.
D3D11_BIND_STREAM_OUTPUT
Uneix un buffer de sortida per l’stream-output stage.
D3D10_BIND_RENDER_TARGET
Uneix una textura com a render-target per l’output-merger stage.
D3D11_BIND_DEPTH_STENCIL
Uneix una textura com a depth-stencil per l’output-merger stage.

Cal també omplir l'estructura D3D11_SUBRESOURCE_DATA. És un mecanisme nou i molt útil introduït a DX10 que permet passar les dades inicials per al recurs creat. Un recurs amb D3D11_USAGE_IMMUTABLE ha de ser inicialitzat d'aquesta manera ja que les seves dades no es poden canviar després de la creació.

D3D11_SUBRESOURCE_DATA bufData;         // En creem una instància.
ZeroMemory(&bufData, sizeof(bufData));   // Inicialitzem amb zeros.
bufData.pSysMem = myVertices;            // Dades inicials.

Després de completar les estructures D3D11_SUBRESOURCE_DATA i D3D11_BUFFER_DESC opcionals, cridem a la funció CreateBuffer:

HRESULT CreateBuffer(
  [in]             const D3D11_BUFFER_DESC *pDesc,
  [in, optional]   const D3D11_SUBRESOURCE_DATA *pInitialData,
  [out, optional]  ID3D11Buffer **ppBuffer
);

On:

pDesc [in]
Tipus const D3D11_BUFFER_DESC*. Punter a l’estructura de dades D3D11_BUFFER_DESC que descriu el buffer. (Creada anteriorment).

pInitialData [in, optional]
Tipus const D3D11_SUBRESOURCE_DATA*. Punter a l’estructura de dades D3D11_SUBRESOURCE_DATA que descriu la inicialització de les dades. Usa NULL per assignar l’espai només (no és possible usar NULL si el flag d’Usage, comentat anteriorment, és D3D11_USAGE_IMMUTABLE).

ppBuffer [out, optional]
Tipus ID3D11Buffer**. Adreça d’un punter a la interfície ID311Buffer per l’objecte buffer crear. Estableix aquest paràmetre a NULL per validar els altres paràmetres d’entrada.

Un cop cridada la funció hem creat el buffer de vèrtex i al mateix temps l’hem omplert amb les dades transmeses a partir del punter a D3D311_SUBRESOURCE_DATA.

El següent pas és crear shaders. DX11 ens obliga a escriure shaders, fins i tot si volem un simple triangle sense textura. Una vegada més, es fa molt difícil el començament per a principiants, però al mateix temps reforça els bons hàbits en la programació.

Afortunadament, no és necessari proporcionar tots els tipus de shaders  suportats per DX11. N’hi ha molts : Vertex Shader (VS), Shader Hull i Domain Shader (nous a DX11, relacionats amb la tessel·lació), Geometry Shader (existent des de DX10, pot generar una nova geometria), Pixel Shader (PS) i Compute Shader (per al còmput de propòsit general). Per al nostre triangle n’hi ha prou amb un VS i un PS.
De moment no entrarem en la programació de shaders, que està explicada al seu apartat. Podeu utilitzar el codi de l’apartat de creació del vèrtex shader com a vèrtex shader per defecte i el de la pàgina de creació del píxel shader com a pixel shader per defecte.

Un cop tenim el codi dels shaders, aquests s’han de compilar. Per fer-ho cridem a:

On:

pSrcFile [in]
Tipus LPCTSTR. Nom de l’arxiu que conté el codi del shader.

pDefines [in]
Tipus const D3D10_SHADER_MACRO*. Opcional. Punter a un array de definicions macro. Si no s’usa, cal definir pDefines a NULL.

pInclude [in]
Tipus LPD3D10INCLUDE. Opcional. Punter a una interfície per al maneig d’arxius include. No s’ha de col·locar a NULL si el fitxer de shaders conté un #include.

pFunctionName [in]
Tipus LPCSTR. Nom de la funció de dins de l’arxiu on s’escriu el shader que comença l’execució del shader. En un mateix arxiu de shader hi pot haver una funció diferent pel píxel shader i una altra pel vèrtex shader. Amb aquest paràmetre es descriu quin és el nom de la funció que correspon al shader en qüestió. En el cas que només hi hagués funcions per un tipus de shader, aquest paràmetre perd el sentit i s’ha de col·locar a NULL.

pProfile [in]
Tipus LPCSTR. Cadena que específica el model de shader, pot ser shader model 2, 3, 4 o 5.

Flags1 [in]
Tipus UINT. Tipus de compilació de shader. Si s’està compilant un efecte i no un shader, cal col·locar aquest flag a 0.

Flags2 [in]
Tipus UINT. Tipus de compilació d’ efecte. Si s’està compilant un shader i no un efecte, cal col·locar aquest flag a 0.

pPump [in]
Tipus ID3DX11ThreadPump*. Un punter a una interfície thread pump.

ppShader [out]
Tipus ID3D10Bloob**. Punter a la memòria que conté el shader compilat.

ppErrorMsgs [out]
Tipus ID3D10Bloob**. Punter a la memòria que conté una llista d’errors i advertències que poden haver ocorregut durant la compilació.

pHResult [out]
Tipus HRESULT*. Punter del valor de retorn. Pot ser NULL. Si el paràmetre pPump no és NULL, pHResult ha de ser una localització de memòria vàlida fins que acabi l’execució.

Aquesta funció carrega l’arxiu on està escrit el shader i el compila. Si la compilació és satisfactòria, s’obté un objecte  ID3D10Blob amb el codi del shader compilat. És només un buffer amb algunes dades binaries existents a la memòria RAM. A continuació cal cridar, per separat, a la següent funció:

HRESULT CreateVertexShader(
  [in]   const void *pShaderBytecode,
  [in]   SIZE_T BytecodeLength,
  [in]   ID3D11ClassLinkage *pClassLinkage,
  [out]  ID3D11VertexShader **ppVertexShader
);

On:
pShaderBytecode [in]
Tipus const void*. Punter al shader compilat.

BytecodeLength [in]
Tipus SIZE_T. Mida del vèrtex shader compilat.

pClassLinkage [in]
Tipus ID3D11ClassLinkage*. Punter a la classe linkage interface. El valor pot ser NULL.

ppVertexShader [out]
Tipus ID3D11VertexShader**. Adreça d’un punter a la interfície ID3D11VertexShader. Si és NULL els altres paràmetres seran validats i comprovats si passen la validació amb S_FALSE enlloc d’S_OK.
per crear un objecte ID3D11VertexShader real d'aquest codi.

Pel cas del Pixel shader cal utilitzar altre cop les funcions esmentades pel vèrtex shader, però aquest cop amb petites diferències a l’hora de passar els paràmetres especificant les raons del píxel shader. Aquestes diferències poden ser apreciades al codi font del programa, descrit al final d’aquest capítol.
Ara només falta un objecte abans de poder renderitzar el tan esperat triangle. Es diu disseny d'entrada (InputLayout) i representa la descripció de la nostra estructura de vèrtex.

Hem de definir un array de D3D11_INPUT_ELEMENT_DESC dient que els nostres vèrtexs tenen posició del tipus float [2] i color de tipus unsigned, d’aquesta manera:

D3D11_INPUT_ELEMENT_DESC inputDesc[] = {
                 { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT,   0,
         D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
                 { "COLOR",    0, DXGI_FORMAT_R8G8B8A8_UNORM, 0,
         D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

Un cop omplerta la descripció, cal cridar a la següent funció:
HRESULT CreateInputLayout(
  [in]   const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
  [in]   UINT NumElements,
  [in]   const void *pShaderBytecodeWithInputSignature,
  [in]   SIZE_T BytecodeLength,
  [out]  ID3D11InputLayout **ppInputLayout
);

On:
pInputElementDescs [in]
Tipus const D3D11_INPUT_ELEMENT_DESC*. Array de l’input-assembler amb els diferents tipus de dades del programa. Cada tipus està descrit dins de la constant. Creat anteriorment.

NumElements [in]
Tipus UINT. El nombre d’elements dins l’array anterior.

pShaderBytecodeWithInputSignature [in]
Tipus const void*. Un punter al vèrtex shader compilat.

BytecodeLength [in]
Tipus SIZE_T. Mida del shader compilat.

ppInputLayout [out]
Tipus ID3D11InputLayout**. Un punter a l’objecte input-layout a crear.

per a crear un objecte ID3D11InputLayout.

Finalment configurem els següents estats de render a la variable tipus ID3D11DeviceContext. S’hi poden configurar més, però pel nostre triangle amb aquests n’hi ha prou. Més endavant en veurem més, com  el ID3D11DepthStencilState.
  • IASetVertexBuffers – per obligar a usar el vertex buffer.
  • IASetInputLayout – per informar de l’estructura dels nostres vèrtexs.
  • IASetPrimitiveTopology – per dir que volem dibuixar una D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
  • VSSetShader, PSSetShader – per fixar els shaders.
Val a dir que hi ha altres tipus de primitives com ara:
D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED
L'estadi IA no s'ha inicialitzat amb una topologia primitiva. L'estadi IA no funcionarà correctament si no una topologia primitiva es defineix.
D3D11_PRIMITIVE_TOPOLOGY_POINTLIST
Interpretar les dades dels vèrtexs d'una llista de punts.
D3D11_PRIMITIVE_TOPOLOGY_LINELIST
Interpretar les dades dels vèrtexs d'una llista de línies.
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP
Interpretar les dades dels vèrtexs d'una tira de línies.
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
Interpretar les dades dels vèrtexs d'una llista de triangles.
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
Interpretar les dades dels vèrtexs com una tira de triangles.
D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ
Interpretar les dades dels vèrtexs de la llista de les línies amb les dades d'adjacència.
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ
Interpretar les dades dels vèrtexs de la tira d'acord amb les dades d'adjacència.
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ
Interpretar les dades dels vèrtexs dels triangles de la llista amb les dades d'adjacència.
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ
Interpretar les dades dels vèrtexs que tira de triangles amb les dades d'adjacència.
D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST
Interpretar les dades dels vèrtexs de la llista d'actualitzacions.

I finalment fem la representació real, per la qual cosa per cada frame que cridem:

• ID3D11DeviceContext:: ClearRenderTargetView - per esborrar la finestra amb una mica de color.

• ID3D11DeviceContext:: Draw - per dibuixar el triangle.

• IDXGISwapChain:: Present - per visualitzar el fotograma.

Això s’escriu a la funció OnFrame, predefinida per l’API de Windows.

A continuació hi ha escrit el codi font sencer del programa que crea un triangle:


El resultat final és el següent triangle:


Per a descarregar-se un exemple fes click en aquest enllaç.

No hay comentarios:

Publicar un comentario