domingo, 7 de julio de 2013

DirectX desde 0. Capitulo 2. Pintando con DirectX11 (Triángulos y Shaders)

Artículo perteneciente al tutorial de DirectX desde 0

Hola a todos,

El capítulo de hoy se basa en parte en el capitulo número 4 de rastertek que podréis ver aquí.

Cuando se empieza a aprender una API gráfica lo primero que hay que saber es como se pinta un triangulo. En OpenGL es bastante sencillo (siempre y cuando no nos importe demasiado el rendimiento) mientras que en DirectX es un proceso más tedioso y que generalmente va cambiando su manera de implementarse entre versiones. De hecho, uno de los grandes problemas que hay con DirectX (sobretodo el 11)  es que encontrar un buen tutorial en castellano que explique de forma sencilla como pintar un triangulo casi casi es imposible.

La gracia está en que aunque la curva de aprendizaje de DirectX sea más dura que la de OpenGL nos obliga ya desde el principio a usar la tarjeta gráfica correctamente . Con OpenGL podíamos pintar triángulos mediante primitivas directas aunque fuera ineficiente (ver aquí para más información) mientras que con DirectX hemos de situar toda la información de nuestro pintado en vectores y luego pasarle esos vectores a la tarjeta gráfica ( aunque ahora mismo no os lo pueda parecer es una de las maneras más eficientes de computar gráficos ya que se trabaja directamente con memoria de video.)

A nivel de código el pintado de un cuadrado quedaría tal que así,:

Por un lado la inicialización de la información de pintado
bool ModelClass::InitializeBuffers(ID3D11Device* device)
{
VertexType* vertices;
unsigned long* indices;
D3D11_BUFFER_DESC vertexBufferDesc, indexBufferDesc;
    D3D11_SUBRESOURCE_DATA vertexData, indexData;
HRESULT result;


NOTA: DirectX 11 nos obliga a pasarle a la tarjeta gráfica un vector con todos los vertices y un vector con todos los indices a esos vértices que usaremos para conformar triángulos  Esto es debido a que normalmente un vértice acostumbra a formar parte de más de un triangulo.

//Seteamos el número de vértices que vamos a pintar (4, por que es un cuadrado)
m_vertexCount = 4;

//Seteamos el número de indices que usaremos (6, por que describimos dos triángulos)
m_indexCount = 6;

// Create the vertex array.
vertices = new VertexType[m_vertexCount];
if(!vertices)
{
return false;
}

// Create the index array.
indices = new unsigned long[m_indexCount];
if(!indices)
{
return false;
}

NOTA: En la lista de vértices no solo pondremos su posición espacial sino que incluiremos todo aquello que puede modificar su pintado, como por ejemplo, el color.
vertices[0].position = D3DXVECTOR3(-1.0f, -1.0f, 0.0f);
vertices[0].color = D3DXVECTOR4(1.0f, 0.0f, 0.0f, 1.0f);

vertices[1].position = D3DXVECTOR3(-1.0f, 1.0f, 0.0f);
vertices[1].color = D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f);

vertices[2].position = D3DXVECTOR3(1.0f, 1.0f, 0.0f);
vertices[2].color = D3DXVECTOR4(0.5f, 0.5f, 0.5f, 0.5f);

vertices[3].position = D3DXVECTOR3(1.0f, -1.0f, 0.0f);
vertices[3].color = D3DXVECTOR4(0.0f, 0.0f, 1.0f, 1.0f);

// Load the index array with data.
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
indices[3] = 2;
indices[4] = 3;
indices[5] = 0;

// Set up the description of the static vertex buffer.
    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof(VertexType) * m_vertexCount;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;

// Give the subresource structure a pointer to the vertex data.
    vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;

// Now create the vertex buffer.
    result = device->CreateBuffer(&vertexBufferDesc, &vertexData, &m_vertexBuffer);
if(FAILED(result))
{
return false;
}

NOTA: Le pasamos la información de vertices e indices a la tarjeta gráfica
// Set up the description of the static index buffer.
    indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    indexBufferDesc.ByteWidth = sizeof(unsigned long) * m_indexCount;
    indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    indexBufferDesc.CPUAccessFlags = 0;
    indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;

// Give the subresource structure a pointer to the index data.
    indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;

// Create the index buffer.
result = device->CreateBuffer(&indexBufferDesc, &indexData, &m_indexBuffer);
if(FAILED(result))
{
return false;
}

// Release the arrays now that the vertex and index buffers have been created and loaded.
delete [] vertices;
vertices = 0;

delete [] indices;
indices = 0;

return true;
}


Por otro lado la petición de que esa información que hay en tarjeta gráfica se pinte de una manera u otra se hace mediante el Render. Al igual que OpenGL, DirectX usa matrices para realizar los cálculos de transformación del espacio, así que el siguiente código os debería resultar minimamente familiar.

bool GraphicsClass::Render()
{
D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix;
bool result;


// Clear the buffers to begin the scene.
m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

// Generate the view matrix based on the camera's position.
m_Camera->Render();

NOTA: Obtenemos las matrices de transformación del espacio y las almacenamos en memoria
// Get the world, view, and projection matrices from the camera and d3d objects.
m_Camera->GetViewMatrix(viewMatrix);
m_D3D->GetWorldMatrix(worldMatrix);
m_D3D->GetProjectionMatrix(projectionMatrix);

// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
m_Model->Render(m_D3D->GetDeviceContext());

NOTA: Usamos un shader para realizar el render. Al final de este artículo explicaremos lo que es un shader de forma rápida.
result = m_ColorShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix);

if(!result)
{
return false;
}

// Present the rendered scene to the screen.
m_D3D->EndScene();

return true;
}

Un shader, concepto nuevo en estos tutoriales, es un pequeño programa usable desde DirectX o desde OpenGL  que se encarga de ejecutarse directamente en la tarjeta gráfica. Hay diferentes tipos de shaders, los más usados: pixel shader y vertex shader. El shader usado en este tutorial es del tipo vertex shader y se encarga de aplicar las matrices de transformación de nuestra escena a nuestro modelo para poderlo renderizar correctamente por pantalla. Este proceso se podría realizar mediante CPU, pero es obvio que va más rápido si se ejecuta en la GPU.

El código del shader usado es el siguiente:
////////////////////////////////////////////////////////////////////////////////
// Filename: color.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
matrix worldMatrix;
matrix viewMatrix;
matrix projectionMatrix;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float4 color : COLOR;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float4 color : COLOR;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType ColorVertexShader(VertexInputType input)
{
    PixelInputType output;
 

// Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

// Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
 
// Store the input color for the pixel shader to use.
    output.color = input.color;
 
    return output;
}

En breve haré un capítulo enfocado solamente a shaders para que veais toda la potencia que tienen.

Ya para finalizar, si os bajáis el código del svn, compilad y ejecutad, debería quedaros algo tal que así:


Espero que os haya gustado

Nos vemos


LordPakusBlog

0 comentarios :

Publicar un comentario

Entradas populares