En este tema, aprenderemos cómo manejar el bucle de renderizado en una aplicación DirectX. El bucle de renderizado es el corazón de cualquier aplicación gráfica en tiempo real, ya que es responsable de actualizar y renderizar la escena continuamente.

Conceptos Clave

  1. Bucle de Renderizado: Es un ciclo continuo que actualiza y dibuja la escena en la pantalla.
  2. Mensajería de Windows: Mecanismo para manejar eventos del sistema operativo.
  3. Temporización: Control del tiempo para asegurar que la aplicación se ejecute a una velocidad constante.

Estructura del Bucle de Renderizado

El bucle de renderizado generalmente sigue estos pasos:

  1. Procesar Mensajes de Windows: Manejar eventos como entrada del teclado y ratón.
  2. Actualizar la Lógica del Juego: Actualizar la posición de los objetos, manejar colisiones, etc.
  3. Renderizar la Escena: Dibujar los objetos en la pantalla.

Ejemplo de Código

A continuación, se muestra un ejemplo básico de un bucle de renderizado en una aplicación DirectX:

#include <windows.h>
#include <d3d11.h>

// Variables globales
HWND hwnd = nullptr;
ID3D11Device* device = nullptr;
ID3D11DeviceContext* context = nullptr;
IDXGISwapChain* swapChain = nullptr;
ID3D11RenderTargetView* renderTargetView = nullptr;

// Prototipos de funciones
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void InitD3D(HWND hwnd);
void CleanD3D();
void RenderFrame();

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // Crear ventana
    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WindowProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("DirectXExample"), NULL };
    RegisterClassEx(&wc);
    hwnd = CreateWindow(wc.lpszClassName, _T("DirectX Example"), WS_OVERLAPPEDWINDOW, 100, 100, 800, 600, NULL, NULL, wc.hInstance, NULL);

    // Inicializar Direct3D
    InitD3D(hwnd);

    // Mostrar ventana
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Bucle de mensajes y renderizado
    MSG msg = { 0 };
    while (msg.message != WM_QUIT) {
        if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        } else {
            RenderFrame();
        }
    }

    // Limpiar Direct3D
    CleanD3D();

    UnregisterClass(wc.lpszClassName, wc.hInstance);
    return 0;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void InitD3D(HWND hwnd) {
    // Crear dispositivo y contexto
    D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, NULL, 0, D3D11_SDK_VERSION, &swapChainDesc, &swapChain, &device, NULL, &context);

    // Crear render target view
    ID3D11Texture2D* backBuffer;
    swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBuffer);
    device->CreateRenderTargetView(backBuffer, NULL, &renderTargetView);
    backBuffer->Release();

    context->OMSetRenderTargets(1, &renderTargetView, NULL);

    // Configurar viewport
    D3D11_VIEWPORT viewport = { 0.0f, 0.0f, 800.0f, 600.0f, 0.0f, 1.0f };
    context->RSSetViewports(1, &viewport);
}

void CleanD3D() {
    swapChain->Release();
    renderTargetView->Release();
    context->Release();
    device->Release();
}

void RenderFrame() {
    // Limpiar el back buffer
    float color[4] = { 0.0f, 0.2f, 0.4f, 1.0f };
    context->ClearRenderTargetView(renderTargetView, color);

    // Presentar el back buffer
    swapChain->Present(0, 0);
}

Explicación del Código

  1. Inicialización de Direct3D:

    • InitD3D(HWND hwnd): Configura el dispositivo Direct3D, el contexto y el swap chain.
    • D3D11CreateDeviceAndSwapChain: Crea el dispositivo y el swap chain.
    • CreateRenderTargetView: Crea la vista del render target.
  2. Bucle de Mensajes y Renderizado:

    • PeekMessage: Verifica si hay mensajes en la cola de mensajes.
    • TranslateMessage y DispatchMessage: Procesan los mensajes de Windows.
    • RenderFrame: Llama a la función de renderizado si no hay mensajes.
  3. Renderizado:

    • ClearRenderTargetView: Limpia el back buffer con un color específico.
    • Present: Presenta el back buffer en la pantalla.

Ejercicio Práctico

Ejercicio: Modifica el código anterior para que el color de fondo cambie gradualmente con el tiempo.

Solución:

void RenderFrame() {
    // Calcular el color basado en el tiempo
    static float t = 0.0f;
    t += 0.01f;
    float color[4] = { (sin(t) + 1.0f) / 2.0f, (cos(t) + 1.0f) / 2.0f, 0.4f, 1.0f };

    // Limpiar el back buffer
    context->ClearRenderTargetView(renderTargetView, color);

    // Presentar el back buffer
    swapChain->Present(0, 0);
}

Errores Comunes y Consejos

  1. No Procesar Mensajes de Windows: Asegúrate de que PeekMessage y DispatchMessage se llamen correctamente para evitar que la aplicación se congele.
  2. No Liberar Recursos: Siempre libera los recursos de Direct3D en CleanD3D para evitar fugas de memoria.
  3. Sincronización de Fotogramas: Usa swapChain->Present(1, 0) para habilitar V-Sync y evitar el tearing de la pantalla.

Conclusión

En esta sección, hemos aprendido cómo manejar el bucle de renderizado en una aplicación DirectX. Hemos cubierto la estructura básica del bucle, cómo inicializar Direct3D, y cómo renderizar un frame. Además, hemos visto un ejercicio práctico para reforzar los conceptos aprendidos. En el próximo módulo, profundizaremos en el uso de shaders para mejorar la calidad visual de nuestras aplicaciones.

© Copyright 2024. Todos los derechos reservados