Compartir a través de


Adición de contenido visual al ejemplo marble Maze

En este documento se describe cómo el juego Marble Maze usa Direct3D y Direct2D en el entorno de la aplicación de la Plataforma universal de Windows (UWP) para que puedas aprender los patrones y adaptarlos cuando trabajes con tu propio contenido de juego. Para obtener información sobre cómo encajan los componentes visuales del juego en la estructura general de aplicaciones de Marble Maze, consulte estructura de aplicaciones de Marble Maze.

Seguimos estos pasos básicos a medida que desarrollamos los aspectos visuales de Marble Maze:

  1. Cree un marco básico que inicialice los entornos direct3D y Direct2D.
  2. Usa programas de edición de imágenes y modelos para diseñar los recursos 2D y 3D que aparecen en el juego.
  3. Asegúrate de que los recursos 2D y 3D se carguen correctamente y aparezcan en el juego.
  4. Integre sombreadores de vértices y píxeles que mejoran la calidad visual de los recursos del juego.
  5. Integre la lógica del juego, como la animación y la entrada del usuario.

También nos centramos primero en agregar recursos 3D y, después, en recursos 2D. Por ejemplo, nos centramos en la lógica del juego principal antes de agregar el sistema de menús y el temporizador.

También es necesario recorrer en iteración algunos de estos pasos varias veces durante el proceso de desarrollo. Por ejemplo, al realizar cambios en los modelos de malla y mármol, también tuvimos que cambiar parte del código del sombreador que admite esos modelos.

Nota:

El código de ejemplo que corresponde a este documento se encuentra en el ejemplo de juego DirectX Marble Maze .

  Estos son algunos de los puntos clave que se describen en este documento para cuando se trabaja con directX y el contenido del juego visual, es decir, al inicializar las bibliotecas de gráficos de DirectX, cargar recursos de escena y actualizar y representar la escena:

  • La adición de contenido del juego suele implicar muchos pasos. Estos pasos también suelen requerir iteración. Los desarrolladores de juegos suelen centrarse primero en agregar contenido del juego 3D y luego en agregar contenido 2D.
  • Llegar a más clientes y darles toda una gran experiencia al admitir la mayor gama de hardware gráficos posible.
  • Separe claramente los formatos de diseño y de ejecución. Estructura tus recursos en tiempo de diseño para maximizar la flexibilidad y permitir iteraciones rápidas en el contenido. Formatee y comprima los recursos para cargar y representar lo más eficazmente posible en tiempo de ejecución.
  • Creas los dispositivos Direct3D y Direct2D en una aplicación para UWP como haces en una aplicación de escritorio clásica de Windows. Una diferencia importante es la forma en que la cadena de intercambio está asociada a la ventana de salida.
  • Al diseñar el juego, asegúrate de que el formato de malla que elijas admita tus escenarios clave. Por ejemplo, si tu juego requiere colisión, asegúrate de que puedes obtener datos de colisión de tus mallas.
  • Separa la lógica del juego de la lógica de representación actualizando primero todos los objetos de escena antes de representarlos.
  • Normalmente, dibuja los objetos de escena 3D y, a continuación, los objetos 2D que aparecen delante de la escena.
  • Sincronice el dibujo con el espacio en blanco vertical para asegurarse de que el juego no dedica tiempo a dibujar fotogramas que nunca se mostrarán realmente en la pantalla. Un vertical en blanco es el tiempo entre el momento en que un fotograma termina de dibujar en el monitor y el siguiente fotograma comienza.

Introducción a los gráficos directX

Cuando planeamos el juego Marble Maze Universal Windows Platform (UWP), elegimos C++ y Direct3D 11.1 porque son excelentes opciones para crear juegos 3D que requieren un control máximo sobre la representación y un alto rendimiento. DirectX 11.1 admite hardware de DirectX 9 a DirectX 11 y, por tanto, puede ayudarle a llegar a más clientes de forma más eficaz porque no tiene que volver a escribir código para cada una de las versiones anteriores de DirectX.

Marble Maze usa Direct3D 11.1 para representar los activos del juego 3D, es decir, la canica y el laberinto. Marble Maze también usa Direct2D, DirectWrite y Windows Imaging Component (WIC) para dibujar los recursos del juego 2D, como los menús y el temporizador.

El desarrollo de juegos requiere planificación. Si no estás familiarizado con los gráficos de DirectX, te recomendamos que leas DirectX: Introducción familiarizarte con los conceptos básicos de creación de un juego DirectX para UWP. A medida que lea este documento y trabaje con el código fuente de Marble Maze, puede consultar los siguientes recursos para obtener información más detallada sobre los gráficos de DirectX:

  • Direct3D 11 Graphics: describe Direct3D 11, una API de gráficos 3D potente y acelerada por hardware para representar geometría 3D en la plataforma Windows.
  • direct2D: describe Direct2D, una API de gráficos 2D acelerada por hardware que proporciona un alto rendimiento y una representación de alta calidad para geometría 2D, mapas de bits y texto.
  • DirectWrite: describe DirectWrite, que admite la representación de texto de alta calidad.
  • Componente de Imágenes de Windows: describe WIC, una plataforma extensible que proporciona una API de nivel básico para imágenes digitales.

Niveles de características

Direct3D 11 introduce un paradigma llamado niveles de características . Un nivel de característica es un conjunto bien definido de funcionalidades de GPU. Usa los niveles de características para optimizar tu juego y ejecutarlo en versiones anteriores del hardware compatible con Direct3D. Marble Maze admite el nivel de característica 9.1 porque no requiere características avanzadas de los niveles superiores. Le recomendamos que admita la mayor gama de hardware posible y escale el contenido del juego para que los clientes que tengan equipos de gama alta o baja tengan una gran experiencia. Para obtener más información sobre los niveles de funcionalidad, consulte Direct3D 11 en hardware de nivel inferior.

Inicialización de Direct3D y Direct2D

Un dispositivo representa el adaptador de pantalla. Creas los dispositivos Direct3D y Direct2D en una aplicación para UWP como haces en una aplicación de escritorio clásica de Windows. La principal diferencia es cómo conectar la cadena de intercambio de Direct3D al sistema de ventanas.

La clase DeviceResources es una base para administrar Direct3D y Direct2D. Esta clase controla la infraestructura general, no los recursos específicos del juego. Marble Maze define la clase MarbleMazeMain para controlar los recursos específicos del juego, que tiene una referencia a un objeto DeviceResources para concederle acceso a Direct3D y Direct2D.

Durante la inicialización, el constructor DeviceResources crea recursos independientes del dispositivo y los dispositivos Direct3D y Direct2D.

// Initialize the Direct3D resources required to run. 
DX::DeviceResources::DeviceResources() :
    m_screenViewport(),
    m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
    m_d3dRenderTargetSize(),
    m_outputSize(),
    m_logicalSize(),
    m_nativeOrientation(DisplayOrientations::None),
    m_currentOrientation(DisplayOrientations::None),
    m_dpi(-1.0f),
    m_deviceNotify(nullptr)
{
    CreateDeviceIndependentResources();
    CreateDeviceResources();
}

La clase DeviceResources separa esta funcionalidad para que pueda responder más fácilmente cuando cambie el entorno. Por ejemplo, llama al método CreateWindowSizeDependentResources cuando cambia el tamaño de la ventana.

Inicialización de los generadores de Direct2D, DirectWrite y WIC

El método DeviceResources::CreateDeviceIndependentResources crea las factorías para Direct2D, DirectWrite y WIC. En los gráficos directX, las fábricas son los puntos de partida para crear recursos gráficos. Marble Maze especifica D2D1_FACTORY_TYPE_SINGLE_THREADED porque realiza todo el dibujo en el hilo principal.

// These are the resources required independent of hardware. 
void DX::DeviceResources::CreateDeviceIndependentResources()
{
    // Initialize Direct2D resources.
    D2D1_FACTORY_OPTIONS options;
    ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
    // If the project is in a debug build, enable Direct2D debugging via SDK Layers.
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

    // Initialize the Direct2D Factory.
    DX::ThrowIfFailed(
        D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            __uuidof(ID2D1Factory2),
            &options,
            &m_d2dFactory
            )
        );

    // Initialize the DirectWrite Factory.
    DX::ThrowIfFailed(
        DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory2),
            &m_dwriteFactory
            )
        );

    // Initialize the Windows Imaging Component (WIC) Factory.
    DX::ThrowIfFailed(
        CoCreateInstance(
            CLSID_WICImagingFactory2,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_wicFactory)
            )
        );
}

Creación de dispositivos Direct3D y Direct2D

El método DeviceResources::CreateDeviceResources llama a D3D11CreateDevice para crear el objeto de dispositivo que representa el adaptador de pantalla de Direct3D. Dado que Marble Maze admite el nivel de característica 9.1 y versiones posteriores, el método DeviceResources::CreateDeviceResources especifica los niveles 9.1 a 11.1 en la matriz featureLevels. Direct3D recorre la lista en orden y proporciona a la aplicación el primer nivel de característica que está disponible. Por lo tanto, las entradas de matriz de D3D_FEATURE_LEVEL se muestran de mayor a menor para que la aplicación obtenga el nivel de característica más alto disponible. El método DeviceResources::CreateDeviceResources obtiene el dispositivo Direct3D 11.1 consultando el dispositivo Direct3D 11 que se devuelve de D3D11CreateDevice.

// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
    if (DX::SdkLayersAvailable())
    {
        // If the project is in a debug build, enable debugging via SDK Layers 
        // with this flag.
        creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
    }
#endif

// This array defines the set of DirectX hardware feature levels this app will support.
// Note the ordering should be preserved.
// Don't forget to declare your application's minimum required feature level in its
// description.  All applications are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_1
};

// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;

HRESULT hr = D3D11CreateDevice(
    nullptr,                    // Specify nullptr to use the default adapter.
    D3D_DRIVER_TYPE_HARDWARE,   // Create a device using the hardware graphics driver.
    0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    creationFlags,              // Set debug and Direct2D compatibility flags.
    featureLevels,              // List of feature levels this app can support.
    ARRAYSIZE(featureLevels),   // Size of the list above.
    D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for UWP apps.
    &device,                    // Returns the Direct3D device created.
    &m_d3dFeatureLevel,         // Returns feature level of device created.
    &context                    // Returns the device immediate context.
    );

if (FAILED(hr))
{
    // If the initialization fails, fall back to the WARP device.
    // For more information on WARP, see:
    // https://go.microsoft.com/fwlink/?LinkId=286690
    DX::ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device.
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            &device,
            &m_d3dFeatureLevel,
            &context
            )
        );
}

// Store pointers to the Direct3D 11.1 API device and immediate context.
DX::ThrowIfFailed(
    device.As(&m_d3dDevice)
    );

DX::ThrowIfFailed(
    context.As(&m_d3dContext)
    );

A continuación, el método DeviceResources::CreateDeviceResources crea el dispositivo Direct2D. Direct2D usa microsoft DirectX Graphics Infrastructure (DXGI) para interoperar con Direct3D. DXGI permite compartir superficies de memoria de vídeo entre entornos de ejecución de gráficos. Marble Maze usa el dispositivo DXGI subyacente del dispositivo Direct3D para crear el dispositivo Direct2D desde la fábrica de Direct2D.

// Create the Direct2D device object and a corresponding context.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );

Para obtener más información sobre DXGI e interoperabilidad entre Direct2D y Direct3D, consulte dxGI Overview and Direct2D and Direct3D Interoperability Overview.

Asociar Direct3D con la vista

El método DeviceResources::CreateWindowSizeDependentResources crea los recursos gráficos que dependen de un tamaño de ventana determinado, como la cadena de intercambio y los destinos de representación de Direct3D y Direct2D. Una manera importante de que una aplicación para UWP de DirectX difiere de una aplicación de escritorio es cómo se asocia la cadena de intercambio con la ventana de salida. Una cadena de intercambio es responsable de mostrar el búfer en el que se representa el dispositivo en el monitor. estructura de aplicaciones de Marble Maze describe cómo difiere el sistema de ventanas de una aplicación para UWP de una aplicación de escritorio. Dado que una aplicación para UWP no funciona con objetos HWND, Marble Maze debe usar el método IDXGIFactory2::CreateSwapChainForCoreWindow para asociar la salida del dispositivo a la vista. En el ejemplo siguiente se muestra la parte del método DeviceResources::CreateWindowSizeDependentResources que crea la cadena de intercambio.

// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
    dxgiFactory->CreateSwapChainForCoreWindow(
        m_d3dDevice.Get(),
        reinterpret_cast<IUnknown*>(m_window.Get()),
        &swapChainDesc,
        nullptr,
        &m_swapChain
        )
    );

Para minimizar el consumo de energía, que es importante hacer en dispositivos con batería, como portátiles y tabletas, el método DeviceResources::CreateWindowSizeDependentResources llama al método IDXGIDevice1::SetMaximumFrameLatency para asegurarse de que el juego se representa solo después del espacio en blanco vertical. La sincronización con el espacio en blanco vertical se describe con más detalle en la sección Presentación de la escena en este documento.

// Ensure that DXGI does not queue more than one frame at a time. This both 
// reduces latency and ensures that the application will only render after each
// VSync, minimizing power consumption.
DX::ThrowIfFailed(
    dxgiDevice->SetMaximumFrameLatency(1)
    );

El método DeviceResources::CreateWindowSizeDependentResources inicializa los recursos gráficos de una manera que funciona para la mayoría de los juegos.

Nota:

El término vista tiene un significado diferente en Windows Runtime que en Direct3D. En Windows Runtime, una vista hace referencia a la colección de configuraciones de interfaz de usuario para una aplicación, incluido el área de visualización y los comportamientos de entrada, además del subproceso que usa para su procesamiento. Especifique la configuración y las opciones que necesita al crear una vista. El proceso de configuración de la vista de aplicaciones se describe en estructura de aplicaciones de Marble Maze. En Direct3D, el término "vista" tiene múltiples significados. Una vista de recursos define los subrecursos a los que puede acceder un recurso. Por ejemplo, cuando un objeto de textura está asociado a una vista de recursos del sombreador, ese sombreador puede acceder posteriormente a la textura. Una ventaja de una vista de recursos es que puede interpretar los datos de diferentes maneras en distintas fases de la canalización de representación. Para obtener más información sobre las vistas de recursos, consulte Vistas de recursos. Cuando se usan en el contexto de una transformación de vista o una matriz de transformación de vista, la vista hace referencia a la ubicación y la orientación de la cámara. Una transformación de vista reubica los objetos en el mundo alrededor de la posición y la orientación de la cámara. Para obtener más información sobre las transformaciones de vista, consulte Transformación de vista (Direct3D 9). Cómo Marble Maze usa vistas de recursos y matrices se describe con más detalle en este tema.

 

Carga de recursos de escena

Marble Maze usa la clase BasicLoader de , que se declara en BasicLoader.h, para cargar texturas y sombreadores. Marble Maze utiliza la clase SDKMesh de para cargar las mallas 3D del laberinto y la canica.

Para garantizar una aplicación con capacidad de respuesta, Marble Maze carga los recursos de escena de forma asincrónica o en segundo plano. A medida que se cargan los recursos en segundo plano, el juego puede responder a eventos de ventana. Este proceso se explica con más detalle en Carga de recursos del juego en segundo plano en esta guía.

Carga de la superposición 2D y la interfaz de usuario

En Marble Maze, la superposición es la imagen que aparece en la parte superior de la pantalla. La superposición siempre aparece delante de la escena. En Marble Maze, la superposición contiene el logotipo de Windows y la cadena de texto ejemplo de juego de DirectX Marble Maze. La administración de la superposición se realiza mediante la clase SampleOverlay, que se define en SampleOverlay.h. Aunque usamos la superposición como parte de los ejemplos de Direct3D, puedes adaptar este código para mostrar cualquier imagen que aparezca delante de la escena.

Un aspecto importante de la superposición es que, dado que su contenido no cambia, la clase SampleOverlay dibuja, o almacena en caché, su contenido en un objeto ID2D1Bitmap1 durante la inicialización. En el momento de dibujo, la clase SampleOverlay solo tiene que dibujar el mapa de bits en la pantalla. De este modo, no es necesario realizar rutinas costosas como el dibujo de texto para cada fotograma.

La interfaz de usuario (UI) consta de componentes 2D, como menús y pantallas de encabezado (HUD), que aparecen delante de la escena. Marble Maze define los siguientes elementos de la interfaz de usuario:

  • Elementos de menú que permiten al usuario iniciar el juego o ver puntuaciones altas.
  • Un temporizador que cuenta hacia atrás durante tres segundos antes de que comience el juego.
  • Temporizador que realiza un seguimiento del tiempo de reproducción transcurrido.
  • Tabla que muestra los tiempos de finalización más rápidos.
  • El texto muestra Pausado cuando el juego está en pausa.

Marble Maze define elementos de interfaz de usuario específicos del juego en UserInterface.h. Marble Maze define la clase ElementBase como un tipo base para todos los elementos de la interfaz de usuario. La clase ElementBase define atributos como el tamaño, la posición, la alineación y la visibilidad de un elemento de interfaz de usuario. También controla cómo se actualizan y representan los elementos.

class ElementBase
{
public:
    virtual void Initialize() { }
    virtual void Update(float timeTotal, float timeDelta) { }
    virtual void Render() { }

    void SetAlignment(AlignType horizontal, AlignType vertical);
    virtual void SetContainer(const D2D1_RECT_F& container);
    void SetVisible(bool visible);

    D2D1_RECT_F GetBounds();

    bool IsVisible() const { return m_visible; }

protected:
    ElementBase();

    virtual void CalculateSize() { }

    Alignment       m_alignment;
    D2D1_RECT_F     m_container;
    D2D1_SIZE_F     m_size;
    bool            m_visible;
};

Al proporcionar una clase base común para los elementos de la interfaz de usuario, la clase UserInterface, que administra la interfaz de usuario, solo necesita contener una colección de objetos ElementBase, lo que simplifica la administración de la interfaz de usuario y proporciona un administrador de interfaz de usuario que es reutilizable. Marble Maze define los tipos que derivan de ElementBase que implementan comportamientos específicos del juego. Por ejemplo, highScoreTable define el comportamiento de la tabla de puntuación alta. Para obtener más información sobre estos tipos, consulte el código fuente.

Nota:

Dado que XAML te permite crear interfaces de usuario complejas más fácilmente, como las que se encuentran en juegos de simulación y estrategia, considera si usar XAML para definir tu interfaz de usuario. Para obtener información sobre cómo desarrollar una interfaz de usuario en XAML en un juego DirectX para UWP, consulta Ampliar la muestra de juego, que hace referencia a la muestra de juego de disparos en 3D de DirectX.

 

Carga de sombreadores

Marble Maze usa el método BasicLoader::LoadShader para cargar un sombreador desde un archivo.

Los sombreadores son la unidad fundamental de programación de GPU en los juegos de hoy en día. Casi todo el procesamiento de gráficos 3D se controla a través de sombreadores, ya sea la transformación del modelo y la iluminación de la escena, o el procesamiento de geometría más complejo, desde la máscara de caracteres hasta la teselación. Para obtener más información sobre el modelo de programación de sombreador, vea HLSL.

Marble Maze usa sombreadores de vértices y píxeles. Un sombreador de vértices siempre funciona en un vértice de entrada y genera un vértice como salida. Un sombreador de píxeles toma valores numéricos, datos de textura, valores interpolados por vértice y otros datos para generar un color de píxel como salida. Dado que un sombreador transforma un elemento a la vez, el hardware gráfico que proporciona varias canalizaciones de sombreador puede procesar conjuntos de elementos en paralelo. El número de canalizaciones paralelas que están disponibles para la GPU puede ser mucho mayor que el número que está disponible para la CPU. Por lo tanto, incluso los sombreadores básicos pueden mejorar considerablemente el rendimiento.

El método MarbleMazeMain::LoadDeferredResources carga un sombreador de vértices y un sombreador de píxeles después de cargar la superposición. Las versiones en tiempo de diseño de estos sombreadores se definen en BasicVertexShader.hlsl y BasicPixelShader.hlsl, respectivamente. Marble Maze aplica estos sombreadores tanto a la bola como al laberinto durante la fase de representación.

El proyecto Marble Maze incluye las versiones .hlsl (el formato en tiempo de diseño) y .cso (el formato en tiempo de ejecución) de los archivos de sombreador. En tiempo de compilación, Visual Studio usa el compilador de efectos de fxc.exe para compilar el archivo de origen .hlsl en un sombreador binario .cso. Para obtener más información sobre la herramienta del compilador de efectos, vea Effect-Compiler Tool.

El sombreador de vértices usa las matrices de modelo, vista y proyección proporcionadas para transformar la geometría de entrada. Los datos de posición de la geometría de entrada se transforman y generan dos veces: una vez en el espacio de pantalla, que es necesario para la representación y, de nuevo, en el espacio mundial, para permitir que el sombreador de píxeles realice cálculos de iluminación. El vector normal de la superficie se transforma en el espacio mundial, que también usa el sombreador de píxeles para la iluminación. Las coordenadas de textura se pasan sin cambios al sombreador de píxeles.

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

El sombreador de píxeles recibe la salida del sombreador de vértices como entrada. Este sombreador realiza cálculos de iluminación para imitar un foco de luz de bordes suaves que flota sobre el laberinto y se alinea con la posición de la canica. La iluminación es más fuerte para las superficies que apuntan directamente hacia la luz. El componente difuso se apaga a cero, ya que la superficie normal se vuelve perpendicular a la luz, y el término ambiente disminuye a medida que los puntos normales se alejan de la luz. Los puntos más cercanos a la canica (y, por lo tanto, más cerca del centro del foco) se iluminan con más intensidad. Sin embargo, la iluminación se modula para los puntos situados debajo del mármol para simular una sombra suave. En un entorno real, un objeto como la canica blanca reflejaría difusamente el foco en otros objetos de la escena. Esto se calcula para las superficies que están expuestas a la mitad brillante del mármol. Los factores de iluminación adicionales están en relación con el ángulo y la distancia respecto al mármol. El color de píxel resultante es una composición de la textura muestreada con el resultado de los cálculos de iluminación.

float4 main(sPSInput input) : SV_TARGET
{
    float3 lightDirection = float3(0, 0, -1);
    float3 ambientColor = float3(0.43, 0.31, 0.24);
    float3 lightColor = 1 - ambientColor;
    float spotRadius = 50;

    // Basic ambient (Ka) and diffuse (Kd) lighting from above.
    float3 N = normalize(input.norm);
    float NdotL = dot(N, lightDirection);
    float Ka = saturate(NdotL + 1);
    float Kd = saturate(NdotL);

    // Spotlight.
    float3 vec = input.worldPos - marblePosition;
    float dist2D = sqrt(dot(vec.xy, vec.xy));
    Kd = Kd * saturate(spotRadius / dist2D);

    // Shadowing from ball.
    if (input.worldPos.z > marblePosition.z)
        Kd = Kd * saturate(dist2D / (marbleRadius * 1.5));

    // Diffuse reflection of light off ball.
    float dist3D = sqrt(dot(vec, vec));
    float3 V = normalize(vec);
    Kd += saturate(dot(-V, N)) * saturate(dot(V, lightDirection))
        * saturate(marbleRadius / dist3D);

    // Final composite.
    float4 diffuseTexture = Texture.Sample(Sampler, input.tex);
    float3 color = diffuseTexture.rgb * ((ambientColor * Ka) + (lightColor * Kd));
    return float4(color * lightStrength, diffuseTexture.a);
}

Advertencia

El sombreador de píxeles compilado contiene 32 instrucciones aritméticas y 1 instrucción de textura. Este sombreador debe funcionar bien en equipos de escritorio o tabletas de mayor rendimiento. Sin embargo, es posible que algunos equipos no puedan procesar este sombreador y seguir proporcionando una velocidad de fotogramas interactiva. Tenga en cuenta el hardware típico de su público objetivo y diseñe los sombreadores para satisfacer las funcionalidades de ese hardware.

 

El método MarbleMazeMain::LoadDeferredResources usa el método BasicLoader::LoadShader para cargar los sombreadores. En el ejemplo siguiente se carga el sombreador de vértices. El formato en tiempo de ejecución de este sombreador es BasicVertexShader.cso. La variable miembro m_vertexShader es un ID3D11VertexShader objeto.

BasicLoader^ loader = ref new BasicLoader(m_deviceResources->GetD3DDevice());

D3D11_INPUT_ELEMENT_DESC layoutDesc [] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
m_vertexStride = 44; // must set this to match the size of layoutDesc above

Platform::String^ vertexShaderName = L"BasicVertexShader.cso";
loader->LoadShader(
    vertexShaderName,
    layoutDesc,
    ARRAYSIZE(layoutDesc),
    &m_vertexShader,
    &m_inputLayout
    );

La variable miembro m_inputLayout es un objeto ID3D11InputLayout . El objeto de diseño de entradas encapsula el estado de entrada de la etapa del ensamblador de entradas (IA). Un trabajo de la fase de IA es hacer que los sombreadores sean más eficaces mediante el uso de valores generados por el sistema, también conocidos como semántica, para procesar solo los primitivos o vértices que aún no se han procesado.

Use el método ID3D11Device::CreateInputLayout para crear un diseño de entrada a partir de una matriz de descripciones de elementos de entrada. La matriz contiene uno o varios elementos de entrada; cada elemento de entrada describe un elemento de datos de vértices de un búfer de vértices. El conjunto completo de descripciones de elementos de entrada detalla todos los elementos de datos de vértices de todos los búferes de vértices que se enlazarán a la etapa IA.

layoutDesc en el fragmento de código anterior muestra la descripción del diseño que usa Marble Maze. La descripción del diseño describe un búfer de vértices que contiene cuatro elementos de datos de vértices. Las partes importantes de cada entrada de la matriz son el nombre semántico, el formato de datos y el desplazamiento de bytes. Por ejemplo, el elemento POSITION especifica la posición del vértice en el espacio de objetos. Comienza en el desplazamiento de bytes 0 y contiene tres componentes de punto flotante (para un total de 12 bytes). El elemento NORMAL especifica el vector normal. Comienza en el desplazamiento de byte 12 porque aparece directamente después de POSITION en el diseño, lo que requiere 12 bytes. El elemento NORMAL contiene un entero sin signo de cuatro componentes de 32 bits.

Compare el diseño de entrada con la sVSInput estructura definida por el sombreador de vértices, como se muestra en el ejemplo siguiente. La estructura sVSInput define los elementos POSITION, NORMALy TEXCOORD0. El tiempo de ejecución de DirectX asigna cada elemento del diseño a la estructura de entrada definida por el sombreador.

struct sVSInput
{
    float3 pos : POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
};

struct sPSInput
{
    float4 pos : SV_POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
    float3 worldPos : TEXCOORD1;
};

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

El documento Semantics describe cada una de las semánticas disponibles de manera más detallada.

Nota:

En un diseño, puede especificar componentes adicionales que no se usan para permitir que varios sombreadores compartan el mismo diseño. Por ejemplo, el sombreador no usa el elemento TANGENT. Puede usar el elemento TANGENT si desea experimentar con técnicas como el normal mapping. Mediante la asignación normal, también conocida como asignación de golpes, puede crear el efecto de los golpes en las superficies de los objetos. Para obtener más información sobre el mapeo de relieve, consulte mapeo de relieve (Direct3D 9).

 

Para obtener más información sobre la etapa de ensamblado de entrada, vea Input-Assembler etapa y consulte Introducción a la etapa Input-Assembler.

El proceso de usar los sombreadores de vértices y píxeles para representar la escena se describe en la sección Representación de la escena más adelante en este documento.

Creación del búfer de constantes

El búfer de Direct3D agrupa una colección de datos. Un búfer de constantes es un tipo de búfer que puede usar para pasar datos a sombreadores. Marble Maze usa un búfer de constantes para contener la vista del modelo (o del mundo), así como las matrices de proyección para el objeto de escena activo.

En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::LoadDeferredResources crea un búfer de constantes que contendrá datos de matriz más adelante. En el ejemplo se crea una estructura D3D11_BUFFER_DESC que usa la bandera D3D11_BIND_CONSTANT_BUFFER para especificar su uso como un búfer de constantes. A continuación, en este ejemplo se pasa esa estructura al método ID3D11Device::CreateBuffer. La variable m_constantBuffer es un objeto ID3D11Buffer.

// Create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};

// Multiple of 16 bytes
constantBufferDesc.ByteWidth = ((sizeof(ConstantBuffer) + 15) / 16) * 16;

constantBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags           = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags      = 0;
constantBufferDesc.MiscFlags           = 0;

// This will not be used as a structured buffer, so this parameter is ignored.
constantBufferDesc.StructureByteStride = 0;

DX::ThrowIfFailed(
    m_deviceResources->GetD3DDevice()->CreateBuffer(
        &constantBufferDesc,
        nullptr,    // leave the buffer uninitialized
        &m_constantBuffer
        )
    );

El método MarbleMazeMain::Update actualiza después los objetos ConstantBuffer, uno para el laberinto y otro para la canica. A continuación, el método MarbleMazeMain::Render enlaza cada objeto ConstantBuffer al búfer de constantes antes de que se represente cada objeto. En el ejemplo siguiente se muestra la estructura ConstantBuffer, que se encuentra en MarbleMazeMain.h.

// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
    XMFLOAT4X4 model;
    XMFLOAT4X4 view;
    XMFLOAT4X4 projection;

    XMFLOAT3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

Para comprender mejor cómo se asignan los búferes de constantes al código de sombreador, compare la estructura ConstantBuffer en MarbleMazeMain.h con el búfer de constantes ConstantBuffer definido por el sombreador de vértices en BasicVertexShader.hlsl:

cbuffer ConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

El diseño de la estructura ConstantBuffer coincide con el objeto Cbuffer. La variable cbuffer especifica el registro b0, lo que significa que los datos del búfer de constantes se almacenan en el registro 0. El método MarbleMazeMain::Render especifica el registro 0 cuando activa el búfer de constantes. Este proceso se describe con más detalle más adelante en este documento.

Para obtener más información sobre los búferes de constantes en Direct3D 11, consulte Introducción a los búferes. Para obtener más información sobre la palabra clave register, vea register.

Carga de mallas

Marble Maze usa SDK-Mesh como formato en tiempo de ejecución, ya que este formato proporciona una manera básica de cargar datos de malla para aplicaciones de ejemplo. Para su uso en producción, debes usar un formato de malla que cumpla los requisitos específicos del juego.

El método MarbleMazeMain::LoadDeferredResources carga los datos de malla después de cargar los sombreadores de vértices y píxeles. Una malla es una colección de datos de vértices que a menudo incluye información como posiciones, datos normales, colores, materiales y coordenadas de textura. Normalmente, las mallas se crean en el software de creación 3D y se mantienen en archivos que son independientes del código de la aplicación. La canica y el laberinto son dos ejemplos de mallas que usa el juego.

Marble Maze usa la clase SDKMesh de para administrar mallas. Esta clase se declara en SDKMesh.h. SDKMesh proporciona métodos para cargar, representar y destruir datos de malla.

Importante

Marble Maze usa el formato SDK-Mesh y proporciona la clase SDKMesh solo para la ilustración. Aunque el formato SDK-Mesh es útil para el aprendizaje y para crear prototipos, es un formato muy básico que podría no cumplir los requisitos de la mayoría del desarrollo de juegos. Te recomendamos que uses un formato de malla que cumpla los requisitos específicos de tu juego.

 

En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::LoadDeferredResources usa el método SDKMesh::Create para cargar datos de malla para el laberinto y para la bola.

// Load the meshes.
DX::ThrowIfFailed(
    m_mazeMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\maze1.sdkmesh",
        false
        )
    );

DX::ThrowIfFailed(
    m_marbleMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\marble2.sdkmesh",
        false
        )
    );

Carga de datos de colisión

Aunque esta sección no se centra en cómo Marble Maze implementa la simulación física entre la canica y el laberinto, tenga en cuenta que la geometría de malla para el sistema físico se lee cuando se cargan las mallas.

// Extract mesh geometry for the physics system.
DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_walls",
        m_collision.m_wallTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_Floor",
        m_collision.m_groundTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_floorSides",
        m_collision.m_floorTriList
        )
    );

m_physics.SetCollision(&m_collision);
float radius = m_marbleMesh.GetMeshBoundingBoxExtents(0).x / 2;
m_physics.SetRadius(radius);

La forma en que se cargan los datos de colisión depende en gran medida del formato en tiempo de ejecución que use. Para obtener más información sobre cómo Marble Maze carga la geometría de colisión desde un archivo SDK-Mesh, consulte el método MarbleMazeMain::ExtractTrianglesFromMesh en el código fuente.

Actualización del estado del juego

Marble Maze separa la lógica del juego de la lógica de representación actualizando primero todos los objetos de escena antes de representarlos.

estructura de aplicaciones de Marble Maze describe el bucle principal del juego. La actualización de la escena, que forma parte del bucle del juego, se produce después de que los eventos de Windows y la entrada se procesen y antes de que se represente la escena. El método MarbleMazeMain::Update controla la actualización de la interfaz de usuario y el juego.

Actualización de la interfaz de usuario

El método MarbleMazeMain::Update llama al método UserInterface::Update para actualizar el estado de la interfaz de usuario.

UserInterface::GetInstance().Update(
    static_cast<float>(m_timer.GetTotalSeconds()), 
    static_cast<float>(m_timer.GetElapsedSeconds()));

El método UserInterface::Update actualiza cada elemento de la colección de interfaz de usuario.

void UserInterface::Update(float timeTotal, float timeDelta)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        (*iter)->Update(timeTotal, timeDelta);
    }
}

Las clases que derivan de ElementBase (definidas en UserInterface.h) implementan el método Update para realizar comportamientos específicos. Por ejemplo, el método StopwatchTimer::Update actualiza el tiempo transcurrido por la cantidad proporcionada y actualiza el texto que se muestra más adelante.

void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
    if (m_active)
    {
        m_elapsedTime += timeDelta;

        WCHAR buffer[16];
        GetFormattedTime(buffer);
        SetText(buffer);
    }

    TextElement::Update(timeTotal, timeDelta);
}

Actualización de la escena

El método MarbleMazeMain::Update actualiza el juego en función del estado actual de la máquina de estado (la GameState, almacenada en m_gameState). Cuando el juego está en estado activo (GameState::InGameActive), Marble Maze actualiza la cámara para seguir la canica, actualiza la parte de matriz de vista de los búferes de constantes y actualiza la simulación física.

En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Update actualiza la posición de la cámara. Marble Maze usa la variable m_resetCamera para marcar que la cámara debe restablecerse para ubicarse directamente encima de la canica. La cámara se restablece cuando se inicia el juego o la canica cae a través del laberinto. Cuando el menú principal o la pantalla de alta puntuación está activa, la cámara se establece en una posición fija. De lo contrario, Marble Maze usa el parámetro timeDelta para interpolar la posición de la cámara entre sus posiciones actuales y de destino. La posición objetivo está un poco por encima y frente a la canica. El uso del tiempo de fotograma transcurrido permite que la cámara siga gradualmente o persiga la canica.

static float eyeDistance = 200.0f;
static XMFLOAT3A eyePosition = XMFLOAT3A(0, 0, 0);

// Gradually move the camera above the marble.
XMFLOAT3A targetEyePosition;
XMStoreFloat3A(
    &targetEyePosition, 
    XMLoadFloat3A(&marblePosition) - (XMLoadFloat3A(&g) * eyeDistance));

if (m_resetCamera)
{
    eyePosition = targetEyePosition;
    m_resetCamera = false;
}
else
{
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&eyePosition) 
            + ((XMLoadFloat3A(&targetEyePosition) - XMLoadFloat3A(&eyePosition)) 
                * min(1, static_cast<float>(m_timer.GetElapsedSeconds()) * 8)
            )
    );
}

// Look at the marble. 
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
    // Override camera position for menus.
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&marblePosition) + XMVectorSet(75.0f, -150.0f, -75.0f, 0.0f));

    m_camera->SetViewParameters(
        eyePosition, 
        marblePosition, 
        XMFLOAT3(0.0f, 0.0f, -1.0f));
}
else
{
    m_camera->SetViewParameters(eyePosition, marblePosition, XMFLOAT3(0.0f, 1.0f, 0.0f));
}

En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Update actualiza los búferes de constantes para la canica y el laberinto. El modelo o el mundo del laberinto siempre permanecen en la matriz de identidades. Excepto para la diagonal principal, cuyos elementos son todos, la matriz de identidad es una matriz cuadrada compuesta de ceros. La matriz del modelo del mármol se basa en el producto de su matriz de posición y su matriz de rotación.

// Update the model matrices based on the simulation.
XMStoreFloat4x4(&m_mazeConstantBufferData.model, XMMatrixIdentity());

XMStoreFloat4x4(
    &m_marbleConstantBufferData.model, 
    XMMatrixTranspose(
        XMMatrixMultiply(
            marbleRotationMatrix, 
            XMMatrixTranslationFromVector(XMLoadFloat3A(&marblePosition))
        )
    )
);

// Update the view matrix based on the camera.
XMFLOAT4X4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;

Para obtener información sobre cómo el método MarbleMazeMain::Update lee la interacción del usuario y simula el movimiento de la canica, consulte Cómo agregar interacción e interactividad a la muestra de Marble Maze.

Representación de la escena

Cuando se representa una escena, normalmente se incluyen estos pasos.

  1. Establezca el buffer de profundidad-esténcil del objetivo de renderizado actual.
  2. Borrar las vistas de renderizado y plantilla.
  3. Prepare los sombreadores de vértices y píxeles para dibujar.
  4. Representar los objetos 3D en la escena.
  5. Represente cualquier objeto 2D que desee que aparezca delante de la escena.
  6. Presente la imagen representada en el monitor.

El método MarbleMazeMain::Render enlaza las vistas de destino de representación y las vistas de profundidad, borra esas vistas, dibuja la escena y, a continuación, dibuja la superposición gráfica.

Preparación de los objetivos de renderizado

Antes de renderizar la escena, debe establecer el búfer de profundidad-esténcil de destino de renderizado actual. Si no se garantiza que la escena dibuje sobre todos los píxeles de la pantalla, borre también las vistas de renderizado y de plantilla. Marble Maze borra las vistas de representación y stencil en cada fotograma para asegurarse de que no haya artefactos visibles del fotograma anterior.

En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Render llama al método ID3D11DeviceContext::OMSetRenderTargets para establecer el destino de representación y el búfer de profundidad-esténcil como los actuales.

auto context = m_deviceResources->GetD3DDeviceContext();

// Reset the viewport to target the whole screen.
auto viewport = m_deviceResources->GetScreenViewport();
context->RSSetViewports(1, &viewport);

// Reset render targets to the screen.
ID3D11RenderTargetView *const targets[1] = 
    { m_deviceResources->GetBackBufferRenderTargetView() };

context->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());

// Clear the back buffer and depth stencil view.
context->ClearRenderTargetView(
    m_deviceResources->GetBackBufferRenderTargetView(), 
    DirectX::Colors::Black);

context->ClearDepthStencilView(
    m_deviceResources->GetDepthStencilView(), 
    D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 
    1.0f, 
    0);

Las interfaces ID3D11RenderTargetView y ID3D11DepthStencilView admiten el mecanismo de vista de textura proporcionado por Direct3D 10 y posteriores. Para obtener más información sobre las vistas de textura, consulte Vistas de textura (Direct3D 10). El método OMSetRenderTargets prepara la etapa de mezcla de salida del pipeline de Direct3D. Para obtener más información sobre la fase de fusión de resultado, vea Output-Merger Fase.

Preparando los sombreadores de vértices y píxeles

Antes de representar los objetos de escena, realice los pasos siguientes para preparar los sombreadores de vértices y píxeles para dibujar:

  1. Configure el diseño de entrada del sombreador como el diseño actual.
  2. Establezca los sombreadores de vértices y píxeles como sombreadores actuales.
  3. Actualice los búferes de constantes con los datos que necesita pasar a los sombreadores.

Importante

Marble Maze emplea un par de sombreadores de vértices y píxeles para todos los objetos 3D. Si tu juego usa más de un par de sombreadores, debes realizar estos pasos cada vez que dibujas objetos que usan diferentes sombreadores. Para reducir la sobrecarga asociada al cambio del estado del sombreador, se recomienda agrupar llamadas de representación para todos los objetos que usan los mismos sombreadores.

 

En la sección titulada Cargar sombreadores de este documento, se describe cómo se crea el diseño de entrada cuando se crea el sombreador de vértices. En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Render usa el método ID3D11DeviceContext::IASetInputLayout para establecer este diseño como el diseño actual.

m_deviceResources->GetD3DDeviceContext()->IASetInputLayout(m_inputLayout.Get());

En el ejemplo siguiente se muestra cómo el método MarbleMazeMain::Render usa los métodos ID3D11DeviceContext::VSSetShader y ID3D11DeviceContext::P SSetShader para establecer los sombreadores de vértices y píxeles como sombreadores actuales, respectivamente.

// Set the vertex shader stage state.
m_deviceResources->GetD3DDeviceContext()->VSSetShader(
    m_vertexShader.Get(),   // use this vertex shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetShader(
    m_pixelShader.Get(),    // use this pixel shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetSamplers(
    0,                          // starting at the first sampler slot
    1,                          // set one sampler binding
    m_sampler.GetAddressOf());  // to use this sampler

Después de que MarbleMazeMain::Render establece los sombreadores y su layout de entrada, usa el método ID3D11DeviceContext::UpdateSubresource para actualizar el búfer de constantes con las matrices de modelo, vista y proyección para el laberinto. El método UpdateSubresource copia los datos de matriz de la memoria de CPU a la memoria de GPU. Recuerde que los componentes de modelo y vista de la estructura ConstantBuffer se actualizan en el método MarbleMazeMain::Update. El método MarbleMazeMain::Render llama al método ID3D11DeviceContext::VSSetConstantBuffers y ID3D11DeviceContext::PSSetConstantBuffers para establecer este búfer constante como el actual.

// Update the constant buffer with the new data.
m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
    m_constantBuffer.Get(),
    0,
    nullptr,
    &m_mazeConstantBufferData,
    0,
    0);

m_deviceResources->GetD3DDeviceContext()->VSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

m_deviceResources->GetD3DDeviceContext()->PSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

El método MarbleMazeMain::Render realiza pasos similares para preparar la canica que será renderizada.

Representación del laberinto y la canica

Después de activar los sombreadores actuales, puede dibujar sus objetos de escena. El método MarbleMazeMain::Render llama al método SDKMesh::Render para representar la malla del laberinto.

m_mazeMesh.Render(
    m_deviceResources->GetD3DDeviceContext(), 
    0, 
    INVALID_SAMPLER_SLOT, 
    INVALID_SAMPLER_SLOT);

El método MarbleMazeMain::Render realiza pasos similares para renderizar la canica.

Como se mencionó anteriormente en este documento, la clase SDKMesh se proporciona con fines de demostración, pero no se recomienda su uso en un juego de calidad de producción. Sin embargo, observe que el método SDKMesh::RenderMesh, al que llama SDKMesh::Render, usa los métodos ID3D11DeviceContext::IASetVertexBuffers y ID3D11DeviceContext::IASetIndexBuffer para establecer los búferes de vértices e índices actuales que definen la malla, y el método ID3D11DeviceContext::DrawIndexed para dibujar los búferes. Para obtener más información sobre cómo trabajar con búferes de vértices e índices, consulte Introducción a los búferes en Direct3D 11.

Dibujo de la interfaz de usuario y superposición

Después de dibujar objetos de escena 3D, Marble Maze dibuja los elementos de la interfaz de usuario 2D que aparecen delante de la escena.

El método MarbleMazeMain::Render finaliza dibujando la interfaz de usuario y la superposición.

// Draw the user interface and the overlay.
UserInterface::GetInstance().Render(m_deviceResources->GetOrientationTransform2D());

m_deviceResources->GetD3DDeviceContext()->BeginEventInt(L"Render Overlay", 0);
m_sampleOverlay->Render();
m_deviceResources->GetD3DDeviceContext()->EndEvent();

El método UserInterface::Render usa un objeto ID2D1DeviceContext para dibujar los elementos de la interfaz de usuario. Este método establece el estado de dibujo, dibuja todos los elementos activos de la interfaz de usuario y, a continuación, restaura el estado de dibujo anterior.

void UserInterface::Render(D2D1::Matrix3x2F orientation2D)
{
    m_d2dContext->SaveDrawingState(m_stateBlock.Get());
    m_d2dContext->BeginDraw();
    m_d2dContext->SetTransform(orientation2D);

    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if ((*iter)->IsVisible())
            (*iter)->Render();
    }

    // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
    // is lost. It will be handled during the next call to Present.
    HRESULT hr = m_d2dContext->EndDraw();
    if (hr != D2DERR_RECREATE_TARGET)
    {
        DX::ThrowIfFailed(hr);
    }

    m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}

El método SampleOverlay::Render usa una técnica similar para dibujar el mapa de bits de superposición.

Presentación de la escena

Después de dibujar todos los objetos de escena 2D y 3D, Marble Maze presenta la imagen representada al monitor. Sincroniza el dibujo con el espacio en blanco vertical para asegurarse de que el tiempo no se dedica a dibujar fotogramas que nunca se mostrarán realmente en la pantalla. Marble Maze también gestiona los cambios de dispositivo al presentar la escena.

Una vez que el método MarbleMazeMain::Render ha devuelto el control, el bucle del juego llama al método DX::DeviceResources::Present para enviar la imagen renderizada al monitor o pantalla. El método DX::DeviceResources::Present llama a IDXGISwapChain::Present para realizar la operación de presentación, como se muestra en el siguiente ejemplo:

// The first argument instructs DXGI to block until VSync, putting the application
// to sleep until the next VSync. This ensures we don't waste any cycles rendering
// frames that will never be displayed to the screen.
HRESULT hr = m_swapChain->Present(1, 0);

En este ejemplo, m_swapChain es un objeto IDXGISwapChain1. La inicialización de este objeto se describe en la sección Inicializar Direct3D y Direct2D en este documento.

El primer parámetro para IDXGISwapChain::P resent, SyncInterval, especifica el número de espacios en blanco verticales que se deben esperar antes de presentar el marco. Marble Maze especifica 1 para que espere hasta el siguiente espacio en blanco vertical.

El método IDXGISwapChain::Present devuelve un código de error que indica que el dispositivo ha sido removido o que de otro modo ha fallado. En este caso, Marble Maze reinicializa el dispositivo.

// If the device was removed either by a disconnection or a driver upgrade, we
// must recreate all device resources.
if (hr == DXGI_ERROR_DEVICE_REMOVED)
{
    HandleDeviceLost();
}
else
{
    DX::ThrowIfFailed(hr);
}

Pasos siguientes

Lea Agregar entrada e interactividad al ejemplo de Marble Maze para obtener información sobre algunas de las prácticas clave que debe tener en cuenta al trabajar con dispositivos de entrada. En este documento se describe cómo Marble Maze admite la entrada táctil, acelerómetro, controladores de juego y mouse.