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.

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.

11 de junio de 2007

Crear una aplicación Direct3D con Visual C++ 2005 (Parte 2)

En el post anterior vimos como inicializar una ventana para una aplicación usando la API de Windows y luego como adquirir los objetos IDirect3D9 e IDirect3DDevice9, los cuales son necesarios para dibujar escenas en 3D usando DirectX 9. Sin embargo, no vimos como usar dichos objetos para mostrar nuestra escena en la pantalla.

En esta ocasión quiero mostrar algunos de los conceptos básicos usados para trabajar en 3D y realizar un demo simple para dibujar vértices en la pantalla.

En mi concepto, la finalidad de una aplicación Direct3D es mostrar en una pantalla plana (2D) “la imagen de cómo se vería una escena en 3 dimensiones vista desde cierto punto y bajo ciertas condiciones de luz y otras”. Para lograr esto las aplicaciones Direct3D usan muchos vértices, los cuales son “puntos” de un objeto que al agruparse, generalmente formando triángulos, pueden dar forma a un objeto en 3 dimensiones.

Para hacerlo práctico y por cuestiones de simplicidad vamos a comenzar por dibujar un triángulo usando vértices transformados. Un vértice transformado en realidad está en coordenadas 2D de la ventana, lo cual quiere decir que el punto (0,0) está en la esquina superior izquierda de la ventana, el eje X positivo esta a la derecha y el eje Y positivo es hacia abajo. Este tipo de vértices no usan luces de Direct3D porque ellos proporcionan su propio color.

Lo primero que hacemos es definir un tipo de estructura en el que podamos almacenar los datos de nuestros vértices (El código de este ejemplo usa la información del post anterior, aunque dicha funcionalidad se encapsulo en las clases WinApplication y D3DApplication; el código de la estructura CUSTOMVERTEX se encuentra en el archivo MyD3DApplication.h donde se define la clase para este ejemplo).

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

Posteriormente debemos definir el FVF (Flexible Vertex Format) para nuestro tipo CUSTOMVERTEX, esto es un valor que indique al motor de Direct3D que información vamos a pasar para cada vértice en las operaciones de dibujado.

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

Aquí D3DFVF_XYZRHW indica un punto transformado y D3DFVF_DIFFUSE indica el color del vértice.

Una vez que hemos definido el FVF podemos inicializar los valores para los tres (3) vértices del triángulo que queremos dibujar (Esto se hace en el método InitDirect3D en el archivo MyD3DApplication.cpp).

// Define los vertices para el triangulo
CUSTOMVERTEX vertices[] =
{
  { 320.0f, 50.0f, 0.5f, 1.0f, 0xffff0000, }, // x, y, z, rhw, color
  { 490.0f, 350.0f, 0.5f, 1.0f, 0xff00ff00, },
  { 150.0f, 350.0f, 0.5f, 1.0f, 0xffff00ff, },
};

Esto define tres (3) vértices para nuestro triangulo en las posiciones de pantalla (320, 50), (490, 350) y (150, 350) y de colores rojo, verde y rojo-azul respectivamente. Luego de esto debemos llamar al método IDirect3DDevice9::CreateVertexBuffer para crear el buffer de vértices en el dispositivo.

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

Los parámetros de CreateVertexBuffer indican el tamaño del buffer, el uso (0 o una combinación de constantes D3DUSAGE), el formato de los vértices, la clase de memoria que va a almacenar el buffer y la dirección del puntero al buffer (LPDIRECT3DVERTEXBUFFER9, equivalente a IDirect3DVertexBuffer9 *). El último parámetro es reservado y debe fijarse a NULL. De aquí cabe destacar que D3DPOOL_DEFAULT indica que el buffer se alojará en la memoria más conveniente, generalmente memoria de video.

Después de crear el buffer, es necesario copiar en él los datos de los vértices. Para hacer esto hay que bloquear el buffer usando el método IDirect3DVertexBuffer9::Lock. Si se puede bloquear, éste método fija en un puntero la dirección de memoria del inicio del buffer, con lo cual se pueden copiar los datos y posteriormente desbloquearlo.


VOID* pVertices;

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

// Copia los datos de los vértices a la memoria del buffer
memcpy (pVertices, vertices, sizeof (vertices));

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

Finalmente es hora de renderizar lo hecho. Para esto vamos al método MyD3DApplication::Render en donde se llaman las operaciones IDirect3DDevice9:: BeginScene y IDirect3DDevice9::EndScene entre las cuales se deben realizar todas las operaciones de dibujado. Lo primero es fijar la fuente de datos para el dispositivo llamando al método IDirect3DDevice9::SetStreamSource pasando el puntero a nuestro buffer de vértices.

/* Fijar la fuente para la operacion de dibujado */
m_pD3DDevice->SetStreamSource (0, m_pVertexBuffer, 0, sizeof (CUSTOMVERTEX));

Luego fijamos el formato de vértice flexible que usa el buffer y llamamos al método IDirect3DDevice9::DrawPrimitive para hacer que el dispositivo “dibuje” el triangulo pasándole el valor constante del tipo de primitiva D3DPT_TRIANGLELIST, el cual indica al dispositivo que se va a dibujar una lista de triángulos desde el buffer (Aunque en nuestro caso es solo un triángulo como se indica en el último parámetro).

/* Fijar el formato de vértices de la fuente */
m_pD3DDevice->SetFVF (D3DFVF_CUSTOMVERTEX);

/* Dibuja el triangulo desde el buffer */
m_pD3DDevice->DrawPrimitive (D3DPT_TRIANGLELIST, 0, 1);

Con esto terminamos los pasos necesarios para dibujar el triangulo en la pantalla como vemos en la siguiente imagen donde se ve que cada vértice representa una esquina y que cada una tiene un color diferente que se mezcla suavemente en el centro con el color de los demás vértices.



Este ejemplo ya hace uso del objeto IDirect3DDevice9 para dibujar objetos en la pantalla aunque en un modo transformado. En un siguiente post veremos cómo usar vértices no transformados y usar transformaciones para modificar la escena 3D.

El código de este ejemplo para Visual Studio 2005 lo puedes descargar desde aquí

Willy R.

3 de mayo de 2007

Crear una aplicación Direct3D con Visual C++ 2005

En esta ocasión quiero mostrar un ejemplo de cómo usar la API (Application Programming Interface) de DirectX y la API de Windows para crear una aplicación Direct3D. Para esto necesitamos tener el siguiente software instalado:

Lo primero que hacemos es crear un proyecto de Aplicación de Consola de Win32 en lenguaje Visual C++ (o también puedes seleccionar proyecto de Aplicación de Windows Forms), como muestra la siguiente imagen.


Luego de esto nos muestra un asistente de configuración de la aplicación en el que seleccionamos las opciones Aplicación de Windows y Proyecto Vacio (es importante aquí seleccionar Aplicación de Windows ya que esto configura algunas directivas en el compilador para que nuestro código funcione).


Con esto hemos creado un proyecto vacio, así que lo siguiente que hacemos es agregar un archivo llamado main.cpp y en él vamos a incluir la librería y el método WinMain (que es el punto de entrada de la aplicación de Windows).

#include <windows.h>

int WINAPI WinMain (HINSTANCE hInst, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow)
{
  return 0;
}

Para poder mostrar una ventana debemos primero definir y registrar la clase de Windows que va a usar.

/* Inicializar la clase a registrar */
WNDCLASSEX wc =
{
  sizeof(WNDCLASSEX), // Tamaño de la estructura
  CS_CLASSDC, // Class style
  MsgProc, // Procedimiento de ventana
  0L, // Bytes extra. Fijar a 0L
  0L, // Bytes extra. Fijar a 0L
  hInst, // Instancia que contiene el proc. ventana para la clase
  NULL, // Icono
  NULL, // Cursor
  NULL, // Background brush
  NULL, // Menu class
  L"MyDirect3DApp", // Class name
  NULL // Small icon
};

/* Si falla el registro de la clase termina el programa */
if (!::RegisterClassEx (&wc))
  return 0;

El procedimiento de ventana (MsgProc) es el procedimiento encargado de manejar los mensajes que Windows pasa a la ventana cada que un evento ocurre. En nuestro caso es la siguiente función.

LRESULT WINAPI MsgProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch (msg)
  {
    case WM_DESTROY:
      Cleanup ();
      ::PostQuitMessage (0);
      return 0;

    case WM_PAINT:
      Render ();
      ::ValidateRect (hWnd, NULL);
      return 0;
  }

  return ::DefWindowProc (hWnd, msg, wParam, lParam);
}

Luego de registrar la clase de Windows debemos crear y mostrar la ventana donde Direct3D va a realizar el Render.

/* Crear la ventana de la aplicacion. */
g_hWnd = ::CreateWindow (
  L"MyDirect3DApp",
  L"Aplicacion Direct3D",
  WS_OVERLAPPEDWINDOW,
  0,
  0,
  800,
  600,
  ::GetDesktopWindow (),
  NULL,
  wc.hInstance,
  NULL);

/* Mostrar la ventana */
::ShowWindow (g_hWnd, nCmdShow);

La variable g_hWnd es de tipo HWND y es declarada por fuera del main para que sea global a todas las funciones. Con esto ya tenemos la ventana donde dibujar, pero para ello es necesario inicializar la interfaz Direct3D.

/* Creacion del objeto Direct3D */
g_pD3D = Direct3DCreate9 (D3D_SDK_VERSION);

Aquí g_pD3D también es declarada global y de tipo LPDIRECT3D9 el cual es un tipo de apuntador a una interfaz IDirect3D9. Esta interfaz nos permite entre otras cosas, verificar las capacidades del dispositivo de video e inicializar un dispositivo Direct3D para dibujar en una ventana.

Para que nuestro proyecto reconozca el tipo LPDIRECT3D9 y la función Direct3DCreate9 debemos incluir la librería <d3d9.h>, pero también debemos decirle a Visual Studio donde buscar las librerías de DirectX. Para hacerlo vamos a las propiedades del proyecto y en la opción Propiedades de Configuración / C++, adicionamos el directorio Include del directorio de instalación del SDK de DirectX a los Directorios de Include Adicionales, como lo muestra la siguiente figura.


Luego en la opción Linker adicionamos el directorio Lib/x86 a los Directorios de Library Adicionales así:


Finalmente en la opción Input de Linker agregamos d3d9.lib en las Dependencias Adicionales


Al inicializar el dispositivo es necesario indicar al motor de Direct3D los parámetros de presentación para nuestra aplicación tal como la ventana donde se va a dibujar, si se quiere mostrar en pantalla completa o en modo ventana y el formato que se va a utilizar y esto se hace mediante una variable del tipo estructura D3DPRESENT_PARAMETERS.

/* Parámetros de inicializacion */
D3DPRESENT_PARAMETERS pD3DParameters;

/* Parametros por defecto para crear el dispositivo */
ZeroMemory (&pD3DParameters, sizeof (pD3DParameters));

pD3DParameters.hDeviceWindow = g_hWnd;
pD3DParameters.EnableAutoDepthStencil = TRUE;
pD3DParameters.AutoDepthStencilFormat = D3DFMT_D16;
pD3DParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;

/* Cambie a true para pantalla completa */
bool bFullScreen = false;

/* Si el modo es pantalla completa */
if (bFullScreen)
{
  /* En fullscreen se debe indicar el tamaño y formato de los back buffers */
  pD3DParameters.BackBufferWidth = 800;
  pD3DParameters.BackBufferHeight = 600;
  pD3DParameters.BackBufferFormat = D3DFMT_R5G6B5;
  pD3DParameters.BackBufferCount = 1;
  pD3DParameters.MultiSampleType = D3DMULTISAMPLE_NONE;
  pD3DParameters.Windowed = FALSE;
}
else
{
  pD3DParameters.Windowed = TRUE;
  pD3DParameters.BackBufferFormat = D3DFMT_UNKNOWN;
}

Con la estructura D3DPRESENT_PARAMETERS y usando el objeto Direct3D podemos inicializar el dispositivo Direct3D en la variable global g_pD3DDevice la cual es del tipo LPDIRECT3DDEVICE9 que representa un apuntador a una interfaz del tipo IDirect3DDevice9. Esta interfaz es la que luego nos va a permitir invocar una serie de operaciones de dibujado sobre el dispositivo gráfico.

/* Si no se puede crear el dispositivo retorna 0 */
if (FAILED (g_pD3D->CreateDevice (
  D3DADAPTER_DEFAULT,
  D3DDEVTYPE_HAL,
  g_hWnd,
  D3DCREATE_SOFTWARE_VERTEXPROCESSING,
  &pD3DParameters,
  &g_pD3DDevice ) ) )
  return 0;

Si se puede crear el dispositivo, la aplicación esta lista para iniciar la ejecución, esto es, verificar constantemente la cola de mensajes y procesarlos hasta que se cierre la ventana.

MSG msg;

/* Recibir el mensaje y manejarlo */
ZeroMemory (&msg, sizeof(msg));
while (msg.message != WM_QUIT)
{
  if (::PeekMessage (&msg, NULL, 0U, 0U, PM_REMOVE))
  {
    ::TranslateMessage( &msg );
    ::DispatchMessage( &msg );
  }
}

Como vemos en el procedimiento MsgProc, cuando llega el mensaje WM_PAINT se llama a la función Render, en la cual realmente se realiza el dibujado de los objetos en la pantalla. Para realizar este proceso se debe llamar a la función Clear del dispositivo con el fin de limpiar la pantalla (en este caso a un color azul), luego invocar la función BeginScene, la cual indica el comienzo del proceso de dibujado. Al terminar de dibujar todos los objetos de la escena, es necesario llamar a los métodos EndScene y Present para que se copie toda la información desde el buffer trasero al dispositivo y realmente se pinte en pantalla nuestro contenido.

VOID Render ()
{
  /* Limpia el buffer secundario */
  g_pD3DDevice->Clear (0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0f, 0L);

  /* Inicia el rendering */
  g_pD3DDevice->BeginScene ();

  /* Aquí van las operaciones de dibujado */

  /* Finaliza el dibujado */
  g_pD3DDevice->EndScene ();
  g_pD3DDevice->Present (NULL, NULL, NULL, NULL);
}

En el procedimiento de mensajes también se controla el evento WM_DESTROY que es lanzado luego de cerrar nuestra ventana. Cuando llega este mensaje se llama a la función Cleanup, en donde se libera el dispositivo Direct3D, así como también el objeto Direct3D.

VOID Cleanup ()
{
  if (g_pD3DDevice != NULL)
    g_pD3DDevice->Release ();

  if (g_pD3D != NULL)
    g_pD3D->Release ();
}

Adicionalmente se debe des-registrar la clase de Windows cuando el programa salga del bucle de mensajes.

::UnregisterClass (L"MyDirect3DApp", wc.hInstance);

Con esto hemos creado una aplicación que inicializa el dispositivo Direct3D y hace que se pinte de azul la ventana como se observa en la siguiente figura.


A primera vista esto no debería llevar tanto trabajo, es más, si hubiéramos desarrollado una aplicación de Windows Forms únicamente hubiéramos tenido que fijar la propiedad BackColor al color Blue, pero la diferencia radica en que aquí llevamos a cabo los pasos iniciales necesarios para poder usar el dispositivo Direct3D en el “dibujado” de objetos en 3 dimensiones. Sin embargo esto lo mostraré en un siguiente post, por ahora si tienes dudas sobre como usar la API de Windows para crear aplicaciones puedes visitar http://winapi.conclase.net/ en donde explican de una forma sencilla este proceso.

El código de este ejemplo para Visual Studio 2005 lo puedes descargar desde aquí.

Willy R.

25 de marzo de 2007

Conociendo Windows Live

Hola. Parece que despues de mucho tiempo por fin he sacado el tiempo para sentarme a escribir esta corta presentación de algunos servicios de Windows Live que me parecen muy interesantes. Espero sea de tu agrado.

Para comenzar a acceder a los servicios de Windows Live necesitas contar con una cuenta de Windows Live, la cual puedes obtener registrandote gratis en: http://account.live.com/ o actualizando tu cuenta de hotmail a la version de Windows Live Mail.

Bueno, quiero comenzar por Windows Live Favorites, el cual es un servicio que permite exportar tus favoritos desde el Internet Explorer al sitio Web y luego administrarlos desde alla, de modo que ya no dependas del pc en el cual los tenías guardados. Es muy sencillo de usar, solo tienes que entrar a http://favorites.live.com/ e iniciar sesión en el sitio (la primera vez te pedira que aceptes los términos de uso del servicio). Una vez hayas ingresado te mostrará una página como la siguiente.



En la parte superior puedes encontrar un cuadro de texto que te permite buscar con facilidad entre todos tus favoritos, al lado aparece tu nombre y la opcion para cerrar la sesión. Luego puedes ver los menus Opciones, Importar, Exportar y Ayuda. En Opciones puedes configurar si quieres que el sitio te muestre una confirmación siempre que quieras eliminar un favorito o una carpeta y tambien si deseas que tus nuevos favoritos sean Compartidos por defecto (mas adelante veras con mas detalle para que sirve esta opción). Ahora viene lo interesante... en Importar tienes 3 opciones: desde Internet Explorer, Nestcape Navigator o Mozilla, o una ubicacion diferente (Del.icio.us). Si eliges importar desde Internet Explorer te pide que instales un complemento en tu navegador, el cual lee todas las entradas de tus favoritos de Intenet Explorer y los sube al sitio organizados tal como los tengas en tu pc. Luego puedes administrar cada uno de tus favoritos y crear nuevos, cambiar el nombre, la url, asignar etiquetas (son como palabras clave, tambien sirven para crear un nivel de clasificacion diferente al de las carpetas), mover a una carpeta diferente o eliminar un favorito o carpeta.



La opción Exportar es bastante útil tambien ya que mediante esta puedes bajar tus favoritos al Internet Explorer si los quieres tener de modo permanente en el. Cuando eliges Exportar te muestra el dialogo de descargar un archivo con las opciones Abrir, Guardar o Cancelar. Hasta ahora no me ha funcionado la opción Guardar, asi que toca conformarse con la opción Abrir la cual te muestra una página sin estilos y con todos tus favoritos en forma de árbol como en la siguiente figura.



Ahora, para importar estos favoritos a tu Internet Explorer debes primero Guardar esta página en tu pc como un archivo htm o html, luego ir al menu Archivo, Importar y Exportar, seguir el asistente para importar favoritos y listo (el se encarga del resto).

Con esto vemos grandes utilidades de este servicio... desde mi punto de vista te puede servir como un repositorio para tus favoritos, para que nos los pierdas si se daña tu equipo, se piede la configuración o necesitas formatear y te olvidas de salvar esa info. Ademas si estas es un equipo público y necesitas tus favoritos solo vas a tener que ingresar al sitio para disponer de ellos.

Continuando con los servicios de Windows Live tengo que mencionar al cuasi-omni-presente Windows Live Messenger el cual es quizá el programa de mensajería instantánea mas popular de los últimos tiempos y que por su amplio uso no necesita mucha presentación. Sin embargo me gustaría destacar de este servicio que una vez hayas comenzado a usar Windows Live Favorites puedes aprovechar la integracion de estos dos servicios ya que el Messenger dedica una de sus Fichas (iconos que aparecen en el lado izquierdo de la ventana principal) al servicio Favorites. Si das clic en esa ficha, el Messenger descarga tus favoritos desde el sitio Web y te los muestra en el espacio donde generalmente aparece tu lista de contactos, de modo que puedas navegar por tus favoritos o ir al sitio para administrarlos, tal como lo muestra la siguiente figura.



Otro servicio con el que se integra el Messenger es Windows Live Spaces. Este servicio te permite expresar tus ideas, pensamientos, gustos, fotos y demas mediante un blog, album de fotos y otros contenidos que puedes agregar a él. Desde el Messenger puedes acceder este servicio mediante el botón que se muestra en la siguiente figura.



Cuando uses este servicio por primera vez te va a pedir que escojas un Título y un Nombre con el que se pueda identificar el espacio, asi:



Una vez que hayas creado tu espacio podras personalizar los módulos que quieres agregar o quitar, ademas de los temas, diseño y colores para mostrar. Algo que quiero destacar aquí es la posibilidad de agregar tus favoritos de Windows Live Favorites como un modulo del espacio. Solo debes ir a la opcion Personalizar, buscar la pestaña Modulos y dentro de la categoría Windows Live podrás encontrar la opción para agregar los Favoritos de Window Live. Hay que tener en cuenta que a los visitantes de tu espacio solo se les mostrarán los favoritos que marcaste como Compartidos (es aquí donde tiene utilidad la opción "Marcar este favorito como compartido"). Y como es mejor ver las cosas en acción, en la siguiente figura se puede apreciar como se ven mis favoritos compartidos como un módulo en mi espacio.



Si has llegado hasta aquí puede que estes algo fascinad@ con Windows Live y tambien algo cansad@ de leer, como yo de escribir... pero no quiero cerrar este post sin antes mencionar lo que consideron un punto de encuentro para la mayor parte de contenido e información que usualmente visito en Internet y es la Página personalizada de Windows Live. La idea detras de esta página es que tu puedas, como su nombre lo indica, personalizar el contenido que te gustaría revisar según tus gustos o necesidades y sin tener que visitar muchos sitios independientemente. Imagina que te gusta leer las noticas desde varios diarios cada día. ¿No sería interesante que pudieras hacerlo simplemente visitando un sitio y no tener que ir al sitio de cada diario independientemente? Pues bien, con Windows Live esto es posible.

Para empezar debes ingresar al sitio http://www.live.com en el cual por defecto se muestra la página de búsqueda de Windows Live.



Luego debes seleccionar la opción "Personalizar la página" que se encuentra en la parte superior derecha. Dicha opción te muestra una página como la siguiente.



Esta es ya la página personalizada pero hasta ahora solo tiene información agregada por defecto: un tab (o página) con varios módulos de contenido. A la página por defecto (Nueva página) puedes cambiarle el nombre y/o agregar más páginas de modo tal que puedas agrupar la información por tópicos de interes u otro criterio o simplemente distribuirla para no sobrecargar una página. En cuanto a los módulos de contenido por defecto, puedes simplemente dar clic en la x para cerrarlos si no te interesan.

Para agregar nuevos módulos puedes usar la opción "Agregar contenido" que se encuentra en la parte superior izquierda. Dicha opción muestra 3 posibilidades: Directorio, Opciones Avanzadas y Mis cosas. En Directorio puedes encontrar el contenido que podríamos llamar predefinido por Windows Live, dentro del que encontramos Gadgets, Noticias, Entretenimiento, Deportes, etc. Si seleccionas uno de los módulos puedes observarlo en parte derecha de esa área de la página. Para agregarlo a la página activa, solo tienes que dar clic en el botón Agregar o arrastrar el módulo hasta el lugar de la página donde quieras ubicarlo. Por ejemplo, si miras en la categoría Gadgets puedes encontrar el módulo Favoritos, el cual, como era de esperarse, permite ver tus Favoritos de Windows Live. En este momento debo advertir que si deseas que tu configuración personalizada perdure aún si vas a otro equipo es necesario que inicies sesión con tu cuenta de Windows Live antes de agregar contenidos, de modo que toda la información personalizada quede asociada con tu cuenta y pueda ser descargada a otros equipos. Si no lo haces corres el riesgo de perder todo el trabajo de personalización.

En Opciones Avanzadas es donde encuentro la mayor utilidad, ya que es donde puedes agregar contenidos diversos mediante la opción Suscribirse usando la URL de un recurso RSS (aprende mas sobre RSS aquí). Una vez te suscribes a un recurso RSS, éste se agrega automáticamente a la página que se encuentre activa y tambien queda como un item de la sección "Mis cosas", de modo que si accidentalmente lo quitas de la página, puedes volver a arrastrarlo desde allá sin tener que suscribirte nuevamente, y lo mejor, sin tener que volver a escribir la URL.

En la actualidad muchos sitios de internet (principalmente los de noticias) difunden sus contenidos por medio de RSS y tu puedes usar esa característica para revisar todo el contenido que desees desde un solo sitio. Por estandar de facto, el siguiente ícono te ayudará a ubicar dichos recursos.



Finalmente, si vas a otro equipo y quieres volver a ver tu página personalizada solo tienes que entrar a http://www.live.com, seguir el link de Página personalizada y luego de iniciar sesión tendrás de vuelta tu página tal y como la configuraste.

Los servicios de Windows Live que he mencionado no son los únicos que existen en el momento y seguramente seguiran surgiendo nuevos, pero creo que siempre se perseguirá la misma idea detras de toda la familia Windows Live y es la facilidad de uso y algo que considero impactante: la Integración.

Espero este post haya sido de utilidad y vuelvas por este blog.

Willy R.