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

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.