En este blog encontrarás información sobre tecnologías como XNA, DirectX, Gaming y otras cositas relacionadas con el mundo del desarrollo de video juegos. Bienvenid@!

26 de agosto de 2007

Crear una aplicación Direct3D con Visual C++ 2005 - Parte 4: Luces

Hola. En esta ocasión quiero continuar con la serie de post sobre como "Crear una aplicación Direct3D usando Visual C++ 2005". Ahora vamos a revisar los pasos necesarios para adicionar luces a nuestra escena 3D.

Las luces son una importante característica de las aplicaciones Direct3D, ya que ayudan a mejorar la apariencia y el realismo de las escenas. Para esto, es necesario revisar el concepto matemático de los vectores normales, quienes finalmente definen como inciden las luces de la escena en un punto o vértice de los objetos que se estén dibujando.Un vector normal es un vector unidad que describe la dirección hacia la que está mirando un vértice o polígono. En la siguiente gráfica se observa el vector normal N, que representa “el lado del frente” o “vector normal” del vértice representado por el punto P0.



Cuando el motor gráfico de Direct3D procesa un conjunto de vértices que contienen información de sus vectores normales, usa dichos datos para calcular como afecta la luz a cada vértice y de esta manera determinar la intensidad con que se dibuja finalmente cada uno de ellos.

En nuestra aplicación debemos cambiar el tipo CUSTOMVERTEX para que contenga las coordenadas (X, Y, Z) del vértice y la información del vector normal, así como también la definición del formato flexible de vértice:

/* Estructura para almacenar la informacion de los vértices */
struct CUSTOMVERTEX
{
  D3DXVECTOR3 position; // Posición 3D para el vértice
  D3DXVECTOR3 normal; // Vector normal del vértice
};

/* Formato de Vertice Flexible para CUSTOMVERTEX */
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_NORMAL)

En esta ocasión vamos a dibujar un cilindro, el cual es una figura que requiere muchos más vértices que un triangulo simple, así que cambiamos la creación e inicialización del buffer de vértices como se ve en el siguiente fragmento de código.

// Crea el buffer de vértices
if (FAILED (m_pD3DDevice->CreateVertexBuffer (50*2*sizeof (CUSTOMVERTEX),
  0 /*Usage*/, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &m_pVertexBuffer, NULL)))
  return 0;

CUSTOMVERTEX* pVertices;

// Bloquea el buffer y obtiene un puntero a su memoria
if (FAILED (m_pVertexBuffer->Lock (0, 0, (void**)&pVertices, 0)))
  return E_FAIL;

// Crea los puntos del cilindro
for (DWORD i=0; i<50; i++)
{
  FLOAT theta = (2*D3DX_PI*i)/(50-1);
  pVertices[2*i+0].position = D3DXVECTOR3 (sinf(theta),-1.0f, cosf(theta));
  pVertices[2*i+0].normal = D3DXVECTOR3 (sinf(theta), 0.0f, cosf(theta));
  pVertices[2*i+1].position = D3DXVECTOR3 (sinf(theta), 1.0f, cosf(theta));
  pVertices[2*i+1].normal = D3DXVECTOR3 (sinf(theta), 0.0f, cosf(theta));
}

// Desbloquea el buffer
m_pVertexBuffer->Unlock ();

Para iluminar la escena 3D podemos usar 1 o varias luces. Para determinar el color que refleja un objeto cuando una luz golpea en él, usamos un material declarándolo mediante el tipo estructurado D3DMATERIAL9 y fijamos el color difuso y de ambiente a amarillo. Luego lo asignamos al dispositivo mediante una llamada al método IDirect3DDevice9::SetMaterial.

/* Crea un material para la luz */
D3DMATERIAL9 mtrl;
ZeroMemory (&mtrl, sizeof(mtrl));
mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
mtrl.Diffuse.b = mtrl.Ambient.b = 0.0f;
mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
m_pD3DDevice->SetMaterial (&mtrl);

El siguiente paso es crear y asignar las luces que vamos a usar. En Direct3D podemos hacer uso de 3 tipos de luces:
  • Luz de punto: Este tipo de luz tiene un color y una posición en la escena, pero no tiene una dirección definida, es decir, emite luz igualmente en todas las direcciones. Este tipo de luz es similar a la emitida por una bombilla en una habitación.

  • Luz direccional: La luz direccional únicamente tiene color y dirección, pero no posición. Esto es, la luz viaja en la misma dirección a través de toda la escena. Esta luz es comparable con la luz del sol en la tierra, ya que por la distancia a la que se encuentra el sol, todos sus rayos llegan casi paralelos a la tierra.

  • Luz de foco o SpotLight. Esta luz se define mediante un color, posición y dirección en la que se emite. Es similar a la luz emitida por una linterna, la cual disminuye su intensidad con respecto a la distancia al punto de luz y al centro del chorro de luz definido por el vector de dirección.

En nuestro ejemplo vamos a crear una luz direccional de color blanco y vamos a hacer que gire alrededor del eje Y modificando el vector de dirección con relación al tiempo actual.

/* Crea una luz direccional */
D3DLIGHT9 light;
ZeroMemory (&light, sizeof(light));
light.Type = D3DLIGHT_DIRECTIONAL;

/* Fija el color difuso para esta luz a blanco */
light.Diffuse.r = 1.0f;
light.Diffuse.g = 1.0f;
light.Diffuse.b = 1.0f;

/* Rota la luz alrededor del eje Y */
D3DXVECTOR3 vecDir;
vecDir = D3DXVECTOR3 (cosf(timeGetTime()/350.0f),
    0.0f,
    sinf(timeGetTime()/350.0f));
D3DXVec3Normalize ((D3DXVECTOR3*)&light.Direction, &vecDir);

/* Range de alcance de la luz */
light.Range = 1000.0f;

/* Fija la luz en el dispositivo */
m_pD3DDevice->SetLight (0, &light);

/* Habilita la luz anterior */
m_pD3DDevice->LightEnable (0, TRUE);

/* Indica al dispositivo que se van a usar luces */
m_pD3DDevice->SetRenderState (D3DRS_LIGHTING, TRUE);

/* Fija un color para la luz de ambiente */
m_pD3DDevice->SetRenderState (D3DRS_AMBIENT, 0x00202020);

Observe que el primer parámetro en la llamada a los métodos IDirect3DDevice9::SetLight y IDirect3DDevice9::LightEnable se pasa un cero (0), esto se debe a que podemos tener varias luces definidas en una escena y encenderlas o apagarlas a nuestro antojo usando dicho índice. Finalmente se habilita el uso de luces llamando al método IDirect3DDevice9::SetRenderState con la constante D3DRS_LIGHTING y el valor TRUE, y también se fija la luz de ambiente a un color gris llamando al mismo método usando la constante D3DRS_AMBIENT y el valor 0x00202020.

Al ejecutar el ejemplo podemos ver nuestro cilindro girando en torno al eje X y también que la luz que lo ilumina gira en torno al eje Y. Para modificar el movimiento del cilindro debes modificar el código para fijar la transformación de mundo en el método SetupMatrices y si deseas modificar el movimiento de la luz debes modificar el vector de dirección de la misma en el método SetupLights.



Como ya es costumbre, puedes descargar el código de este ejemplo desde aquí.

Espero que esta información haya sido útil y de tu agrado. Hasta la próxima.

Willy R.