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@!

4 de julio de 2007

Crear una aplicación Direct3D con Visual C++ 2005 - Parte 3: Transformaciones

Continuando con la serie de posts sobre cómo crear una aplicación Direct3D con Visual C++ 2005 y habiendo aprendido a dibujar vértices transformados, es hora de abordar el dibujado de vértices propiamente en 3D para lo cual es necesario conocer y manejar el concepto de transformaciones, mediante las cuales se indica al sistema de Direct3D el lugar en el espacio con respecto a un origen en el que se encuentran los vértices, el punto y orientación desde el cual se están mirando y finalmente como se va a realizar la transformación de esa vista en 3D para proyectarla en una pantalla 2D.

La primera transformación, denominada Transformación de Mundo, nos permite Trasladar, Escalar y Rotar la geometría (los vértices) en el espacio 3D. Cada transformación es matemáticamente definida mediante una matriz de 16x16 especificada en C++ usando el tipo D3DXMATRIXA16. Para inicializar una matriz con los valores adecuados, la API de Direct3D provee una serie de funciones que nos ayudan con esta tarea. En nuestro ejemplo vamos a crear una matriz que represente una rotación de los vértices en torno al eje Y según el tiempo actual usando la función D3DXMatrixRotationY.

/* Declara la matriz para la transformacion de mundo */
D3DXMATRIXA16 matWorld;

/* Calcula el ángulo basado en el tiempo transcurrido para realizar un giro de 180° cada segundo */
UINT iTime = timeGetTime() % 1000;
FLOAT fAngle = iTime * (2.0f * D3DX_PI) / 1000.0f;

/* Inicializa la matriz con una transformacion de rotacion alrededor del eje Y */
D3DXMatrixRotationY (&matWorld, fAngle);

/* Fija la transformación de mundo para el dispositivo */
m_pD3DDevice->SetTransform (D3DTS_WORLD, &matWorld);

Otras funciones para trabajar con la matriz de transformación de mundo son: D3DXMatrixRotationY, D3DXMatrixRotationAxis, D3DXMatrixTranslation, D3DXMatrixScaling, entre otras.

La siguiente transformación, la Transformación de Vista, define la posición y rotación de la vista. Esta transformación puede ser vista como la cámara de la escena y generalmente es calculada a partir de 3 vectores: el punto de vista, el punto a mirar, y la dirección “arriba”. Cómo su nombre lo indica el punto de vista es la ubicación de la cámara o del ojo que está mirando la escena, el punto a mirar representa el punto hacia el cual está apuntando la cámara desde su ubicación, y finalmente la dirección arriba es un vector que define cómo esta rotada la cámara, por ejemplo, la escena puede estar siendo observada con la cámara boca abajo o girada hacia un costado en un ángulo dado.

El siguiente fragmento de código define una matriz de Transformación de Vista usando como punto de vista la posición (0.0, 3.0, -5.0) del espacio (X, Y, Z) respectivamente, como punto a mirar la posición (0.0, 0.0, 0.0) y como vector arriba el vector (0.0, 1.0, 0.0) que define el eje Y positivo como el lado arriba de la cámara.

/* Vector punto de vista */
D3DXVECTOR3 vEyePt (0.0f, 3.0f, -5.0f);

/* Vector punto a mirar */
D3DXVECTOR3 vLookatPt (0.0f, 0.0f, 0.0f);

/* Vector lado arriba */
D3DXVECTOR3 vUpVec (0.0f, 1.0f, 0.0f);

/* Declara la matriz de vista */
D3DXMATRIXA16 matView;
D3DXMatrixLookAtLH (&matView, &vEyePt, &vLookatPt, &vUpVec);

/* Fija la transformacion de vista para el dispositivo */
m_pD3DDevice->SetTransform (D3DTS_VIEW, &matView);

Cuando dibujamos una escena 3D en DirectX, en realidad lo que hacemos es una proyección del espacio de vista 3D a un espacio o puerto de vista 2D, en ultimas, nuestra pantalla del PC. Para definir cómo se debe hacer esa proyección usamos la Transformación de Proyección. Típicamente la matriz para aplicar esta transformación se calcula usando la función D3DXMatrixPerspectiveFovLH como se muestra en siguiente ejemplo.

/* Declara la matriz para la transformación de proyección */
D3DXMATRIX matProj;

/* Inicializa la matriz con un campo de vista de PI/4, relacion de aspecto de 1.0, plano cercano de 1.0 y plano lejano de 100.0 */
D3DXMatrixPerspectiveFovLH (&matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f);

/* Fija la transformación de proyección para el dispositivo */
m_pD3DDevice->SetTransform (D3DTS_PROJECTION, &matProj);

Los parámetros pasados a la función D3DXMatrixPerspectiveFovLH son: la referencia a la matriz a inicializar, el campo de vista, la relación de aspecto y los planos cercano y lejano respectivamente. El campo de vista es el ángulo de visualización de la escena en el eje Y (o vector arriba) definido en la transformación de vista. La relación de aspecto es definida como el campo de vista divido entre la altura (típicamente 1.0). El plano cercano es la distancia mínima necesaria a la que un objeto se debe encontrar de la cámara para poder ser “dibujado” y el plano lejano es la distancia máxima en que los objetos se dibujan en la escena.

Este es el proceso necesario para fijar las transformaciones de Mundo, Vista y Proyección y en nuestro ejemplo este código se encuentra definido en la función SetupMatrices de la clase MyD3DApplication y es llamada dentro del método Render, luego de la llamada a BeginEscene. Cuando se hace la llamada a DrawPrimitive, el dispositivo usa la información fijada para las transformaciones y las aplica a las primitivas dibujadas para calcular el resultado final de la operación y guardarlo en el buffer actual (* Las operaciones DrawPrimitive no se dibujan inmediatamente en la pantalla sino en un buffer trasero y es cuando se llama al método Present que se toma la información del siguiente buffer trasero y se muestra en el dispositivo de visualización).

Adicionalmente para que nuestro ejemplo funcione correctamente vamos a cambiar la definición de nuestro CUSTOMVERTEX y D3DFVF_CUSTOMVERTEX como se muestra a continuación usando la posición no transformada y el color.

/* Estructura para almacenar la informacion de los vértices */
struct CUSTOMVERTEX
{
  FLOAT x, y, z; // Posición 3D NO transformada para el vértice
  DWORD color; // Color del vértice
};

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

Además vamos a cambiar los valores usados para los vértices del triángulo a los siguientes con el fin de que se ubiquen dentro del campo de vista fijado en las transformaciones.

/* Define los vertices para el triangulo */
CUSTOMVERTEX vertices[] =
{
  { -1.0f, -1.0f, 0.0f, 0xffff0000, },
  { 1.0f, -1.0f, 0.0f, 0xff0000ff, },
  { 0.0f, 1.0f, 0.0f, 0xffffffff, },
};

Al ejecutar el código para este ejemplo (que puedes descargar desde aquí) podemos ver el siguiente triángulo girando en el espacio 3D.


Este triángulo se está dibujando con coordenadas no transformadas, lo que da más realismo y sensación de 3D al resultado final, sin embargo el color producido por cada vértice es el mismo sin importar su ubicación, esto es porque se está usando el color propio de cada vértice y no se está usando la característica de luces de Direct3D. En una próxima entrada trataremos esta interesante característica que ayuda a realzar el realismo de la escena 3D.

Código del ejemplo: Descargar

Hasta la próxima.

Willy R.