Compartir a través de


Marco de representación II: representación de juegos

Nota:

Este tema forma parte de la serie de tutoriales Crear un juego sencillo para la Plataforma universal de Windows (UWP) con DirectX. El tema de ese vínculo establece el contexto de la serie.

En el marco de representación I, hemos tratado cómo tomamos la información de la escena y la presentamos a la pantalla de presentación. Ahora, realizaremos un paso atrás y aprenderemos a preparar los datos para la representación.

Nota:

Si no has descargado el código de juego más reciente para este ejemplo, ve al juego de ejemplo de Direct3D. Este ejemplo forma parte de una gran colección de ejemplos de características de UWP. Para obtener instrucciones sobre cómo descargar el ejemplo, vea Aplicaciones de ejemplo para el desarrollo de Windows.

Objetivo

Resumen rápido del objetivo. Es entender cómo configurar un marco de representación básico para mostrar la salida de gráficos para un juego DirectX para UWP. Podemos agruparlos de forma flexible en estos tres pasos.

  1. Establecer una conexión a nuestra interfaz gráfica
  2. Preparación: cree los recursos que necesitamos para dibujar los gráficos.
  3. Mostrar los gráficos: Representar el marco

Marco de representación I: Introducción a la representación explica cómo se representan los gráficos, que abarcan los pasos 1 y 3.

En este artículo se explica cómo configurar otras partes de este marco y preparar los datos necesarios antes de que se pueda producir la representación, que es el paso 2 del proceso.

Diseño del representador

El representador es responsable de crear y mantener todos los objetos D3D11 y D2D usados para generar los objetos visuales del juego. La clase GameRenderer es el representador de este juego de ejemplo y está diseñado para satisfacer las necesidades de representación del juego.

Estos son algunos conceptos que puedes usar para ayudar a diseñar el representador para tu juego:

  • Dado que las API de Direct3D 11 se definen como API COM , debe proporcionar referencias de ComPtr a los objetos definidos por estas API. Estos objetos se liberan automáticamente cuando su última referencia sale del ámbito cuando finaliza la aplicación. Para obtener más información, consulte ComPtr. Ejemplo de estos objetos: búferes de constantes, objetos de sombreador de sombreador de vértices, sombreador de píxeles y objetos de recursos de sombreador.
  • Los búferes de constantes se definen en esta clase para contener varios datos necesarios para la representación.
    • Use varios búferes de constantes con diferentes frecuencias para reducir la cantidad de datos que se deben enviar a la GPU por fotograma. En este ejemplo se separan las constantes en diferentes búferes en función de la frecuencia con la que se deben actualizar. Este es un procedimiento recomendado para la programación de Direct3D.
    • En este juego de ejemplo, se definen 4 búferes de constantes.
      1. m_constantBufferNeverChanges contiene los parámetros de iluminación. Se establece una vez en el método FinalizeCreateGameDeviceResources y nunca cambia de nuevo.
      2. m_constantBufferChangeOnResize contiene la matriz de proyección. La matriz de proyección depende del tamaño y la relación de aspecto de la ventana. Se establece en CreateWindowSizeDependentResources y, a continuación, se actualiza una vez cargados los recursos en el método FinalizeCreateGameDeviceResources. Si se representa en 3D, también se cambia dos veces por fotograma.
      3. m_constantBufferChangesEveryFrame contiene la matriz de vistas. Esta matriz depende de la posición y la dirección de la vista de la cámara (la normal a la proyección) y cambia una vez por fotograma en el método Render . Esto se explicó anteriormente en el marco de representación I: Introducción a la representación, en el método GameRenderer::Render.
      4. m_constantBufferChangesEveryPrim contiene la matriz del modelo y las propiedades de material de cada primitivo. La matriz del modelo transforma los vértices de las coordenadas locales en coordenadas del mundo. Estas constantes son específicas de cada primitivo y se actualizan para cada llamada de dibujo. Esto se explicó anteriormente en el marco de representación I: Introducción a la representación, en la representación primitiva.
  • Los objetos de recursos del sombreador que contienen texturas para los primitivos también se definen en esta clase.
    • Algunas texturas están predefinidas (DDS es un formato de archivo que se puede usar para almacenar texturas comprimidas y sin comprimir. Las texturas DDS se usan para las paredes y el suelo del mundo, así como para las esferas de munición).
    • En este juego de ejemplo, los objetos de recursos de sombreador son: m_sphereTexture, m_cylinderTexture, m_ceilingTexture, m_floorTexture, m_wallsTexture.
  • Los objetos sombreador se definen en esta clase para calcular nuestras primitivas y texturas.
    • En este juego de ejemplo, los objetos de sombreador se m_vertexShader, m_vertexShaderFlat y m_pixelShader, m_pixelShaderFlat.
    • El sombreador de vértices procesa los primitivos y la iluminación básica, y el sombreador de píxeles (a veces denominado sombreador de fragmentos) procesa las texturas y los efectos por píxel.
    • Hay dos versiones de estos sombreadores (normales y planos) para representar diferentes primitivos. La razón por la que tenemos diferentes versiones es que las versiones planas son mucho más sencillas y no hacen resaltados especulares ni ningún efecto de iluminación por píxel. Estos se usan para las paredes y hacen que la representación sea más rápida en dispositivos con menos energía.

GameRenderer.h

Ahora echemos un vistazo al código en el objeto de clase de representador del juego de ejemplo.

// Class handling the rendering of the game
class GameRenderer : public std::enable_shared_from_this<GameRenderer>
{
public:
    GameRenderer(std::shared_ptr<DX::DeviceResources> const& deviceResources);

    void CreateDeviceDependentResources();
    void CreateWindowSizeDependentResources();
    void ReleaseDeviceDependentResources();
    void Render();
    // --- end of async related methods section

    winrt::Windows::Foundation::IAsyncAction CreateGameDeviceResourcesAsync(_In_ std::shared_ptr<Simple3DGame> game);
    void FinalizeCreateGameDeviceResources();
    winrt::Windows::Foundation::IAsyncAction LoadLevelResourcesAsync();
    void FinalizeLoadLevelResources();

    Simple3DGameDX::IGameUIControl* GameUIControl() { return &m_gameInfoOverlay; };

    DirectX::XMFLOAT2 GameInfoOverlayUpperLeft()
    {
        return DirectX::XMFLOAT2(m_gameInfoOverlayRect.left, m_gameInfoOverlayRect.top);
    };
    DirectX::XMFLOAT2 GameInfoOverlayLowerRight()
    {
        return DirectX::XMFLOAT2(m_gameInfoOverlayRect.right, m_gameInfoOverlayRect.bottom);
    };
    bool GameInfoOverlayVisible() { return m_gameInfoOverlay.Visible(); }
    // --- end of rendering overlay section
...
private:
    // Cached pointer to device resources.
    std::shared_ptr<DX::DeviceResources>        m_deviceResources;

    ...

    // Shader resource objects
    winrt::com_ptr<ID3D11ShaderResourceView>    m_sphereTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_cylinderTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_ceilingTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_floorTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_wallsTexture;

    // Constant buffers
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferNeverChanges;
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferChangeOnResize;
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferChangesEveryFrame;
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferChangesEveryPrim;

    // Texture sampler
    winrt::com_ptr<ID3D11SamplerState>          m_samplerLinear;

    // Shader objects: Vertex shaders and pixel shaders
    winrt::com_ptr<ID3D11VertexShader>          m_vertexShader;
    winrt::com_ptr<ID3D11VertexShader>          m_vertexShaderFlat;
    winrt::com_ptr<ID3D11PixelShader>           m_pixelShader;
    winrt::com_ptr<ID3D11PixelShader>           m_pixelShaderFlat;
    winrt::com_ptr<ID3D11InputLayout>           m_vertexLayout;
};

Constructor

A continuación, vamos a examinar el constructor GameRenderer del juego de ejemplo y compararlo con el constructor Sample3DSceneRenderer proporcionado en la plantilla Aplicación directX 11.

// Constructor method of the main rendering class object
GameRenderer::GameRenderer(std::shared_ptr<DX::DeviceResources> const& deviceResources) : ...
    m_gameInfoOverlay(deviceResources),
    m_gameHud(deviceResources, L"Windows platform samples", L"DirectX first-person game sample")
{
    // m_gameInfoOverlay is a GameHud object to render text in the top left corner of the screen.
    // m_gameHud is Game info rendered as an overlay on the top-right corner of the screen,
    // for example hits, shots, and time.

    CreateDeviceDependentResources();
    CreateWindowSizeDependentResources();
}

Creación y carga de recursos gráficos de DirectX

En el juego de ejemplo (y en la plantilla aplicación DirectX 11 (Universal Windows) de Visual Studio), la creación y carga de recursos del juego se implementa mediante estos dos métodos a los que se llama desde el constructor GameRenderer:

Método CreateDeviceDependentResources

En la plantilla Aplicación directX 11, este método se usa para cargar asincrónicamente el sombreador de vértices y píxeles, crear el sombreador y el búfer de constantes, crear una malla con vértices que contengan información de posición y color.

En el juego de ejemplo, estas operaciones de los objetos de escena se dividen entre los métodos CreateGameDeviceResourcesAsync y FinalizeCreateGameDeviceResources.

Para este juego de ejemplo, ¿qué pasa a este método?

  • Variables con instancias (m_gameResourcesLoaded = false y m_levelResourcesLoaded = false) que indican si los recursos se han cargado antes de avanzar a la representación, ya que los cargamos de forma asincrónica.
  • Dado que la representación de HUD y overlay están en objetos de clase independientes, llame a los métodos GameHud::CreateDeviceDependentResources y GameInfoOverlay::CreateDeviceDependentResources aquí.

Este es el código de GameRenderer::CreateDeviceDependentResources.

// This method is called in GameRenderer constructor when it's created in GameMain constructor.
void GameRenderer::CreateDeviceDependentResources()
{
    // instantiate variables that indicate whether resources were loaded.
    m_gameResourcesLoaded = false;
    m_levelResourcesLoaded = false;

    // game HUD and overlay are design as separate class objects.
    m_gameHud.CreateDeviceDependentResources();
    m_gameInfoOverlay.CreateDeviceDependentResources();
}

A continuación se muestra una lista de los métodos que se usan para crear y cargar recursos.

  • CreateDeviceDependentResources
    • CreateGameDeviceResourcesAsync (agregado)
    • FinalizeCreateGameDeviceResources (agregado)
  • CreateWindowSizeDependentResources

Antes de profundizar en los otros métodos que se usan para crear y cargar recursos, primero vamos a crear el representador y ver cómo encaja en el bucle de juego.

Creación del representador

GameRenderer se crea en el constructor de GameMain. También llama a los otros dos métodos, CreateGameDeviceResourcesAsync y FinalizeCreateGameDeviceResources que se agregan para ayudar a crear y cargar recursos.

GameMain::GameMain(std::shared_ptr<DX::DeviceResources> const& deviceResources) : ...
{
    m_deviceResources->RegisterDeviceNotify(this);

    // Creation of GameRenderer
    m_renderer = std::make_shared<GameRenderer>(m_deviceResources);

    ...

    ConstructInBackground();
}

winrt::fire_and_forget GameMain::ConstructInBackground()
{
    ...

    // Asynchronously initialize the game class and load the renderer device resources.
    // By doing all this asynchronously, the game gets to its main loop more quickly
    // and in parallel all the necessary resources are loaded on other threads.
    m_game->Initialize(m_controller, m_renderer);

    co_await m_renderer->CreateGameDeviceResourcesAsync(m_game);

    // The finalize code needs to run in the same thread context
    // as the m_renderer object was created because the D3D device context
    // can ONLY be accessed on a single thread.
    // co_await of an IAsyncAction resumes in the same thread context.
    m_renderer->FinalizeCreateGameDeviceResources();

    InitializeGameState();

    ...
}

Método CreateGameDeviceResourcesAsync

Se llama a CreateGameDeviceResourcesAsync desde el método del constructor GameMain en el bucle create_task, ya que estamos cargando los recursos del juego de forma asincrónica.

CreateDeviceResourcesAsync es un método que se ejecuta como un conjunto independiente de tareas asincrónicas para cargar los recursos del juego. Dado que se espera que se ejecute en un subproceso independiente, solo tiene acceso a los métodos de dispositivo Direct3D 11 (los definidos en ID3D11Device) y no a los métodos de contexto del dispositivo (los métodos definidos en ID3D11DeviceContext), por lo que no realiza ninguna representación.

El método FinalizeCreateGameDeviceResources se ejecuta en el subproceso principal y tiene acceso a los métodos de contexto del dispositivo Direct3D 11.

En principio:

  • Use solo los métodos ID3D11Device en CreateGameDeviceResourcesAsync porque son subprocesos libres, lo que significa que pueden ejecutarse en cualquier subproceso. También se espera que no se ejecuten en el mismo subproceso en el que se creó GameRenderer .
  • No use métodos en ID3D11DeviceContext aquí porque necesitan ejecutarse en un único subproceso y en el mismo subproceso que GameRenderer.
  • Use este método para crear búferes de constantes.
  • Use este método para cargar texturas (como los archivos .dds) y la información del sombreador (como los archivos .cso) en los sombreadores.

Este método se usa para:

  • Cree los 4 búferes de constantes: m_constantBufferNeverChanges, m_constantBufferChangeOnResize, m_constantBufferChangesEveryFrame, m_constantBufferChangesEveryPrim
  • Crear un objeto sampler-state que encapsula la información de muestreo de una textura
  • Cree un grupo de tareas que contenga todas las tareas asincrónicas creadas por el método . Espera la finalización de todas estas tareas asincrónicas y, a continuación, llama a FinalizeCreateGameDeviceResources.
  • Cree un cargador mediante El cargador básico. Agregue las operaciones de carga asincrónica del cargador como tareas en el grupo de tareas creado anteriormente.
  • Los métodos como BasicLoader::LoadShaderAsync y BasicLoader::LoadTextureAsync se usan para cargar:
    • objetos de sombreador compilados (VertextShader.cso, VertexShaderFlat.cso, PixelShader.cso y PixelShaderFlat.cso). Para obtener más información, vaya a Varios formatos de archivo de sombreador.
    • texturas específicas del juego (Assets\seafloor.dds, metal_texture.dds, cellceiling.dds, cellfloor.dds, cellwall.dds).
IAsyncAction GameRenderer::CreateGameDeviceResourcesAsync(_In_ std::shared_ptr<Simple3DGame> game)
{
    auto lifetime = shared_from_this();

    // Create the device dependent game resources.
    // Only the d3dDevice is used in this method. It is expected
    // to not run on the same thread as the GameRenderer was created.
    // Create methods on the d3dDevice are free-threaded and are safe while any methods
    // in the d3dContext should only be used on a single thread and handled
    // in the FinalizeCreateGameDeviceResources method.
    m_game = game;

    auto d3dDevice = m_deviceResources->GetD3DDevice();

    // Define D3D11_BUFFER_DESC. See
    // https://learn.microsoft.com/windows/win32/api/d3d11/ns-d3d11-d3d11_buffer_desc
    D3D11_BUFFER_DESC bd;
    ZeroMemory(&bd, sizeof(bd));

    // Create the constant buffers.
    bd.Usage = D3D11_USAGE_DEFAULT;
    ...

    // Create the constant buffers: m_constantBufferNeverChanges, m_constantBufferChangeOnResize,
    // m_constantBufferChangesEveryFrame, m_constantBufferChangesEveryPrim
    // CreateBuffer is used to create one of these buffers: vertex buffer, index buffer, or 
    // shader-constant buffer. For CreateBuffer API ref info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11device-createbuffer.
    winrt::check_hresult(
        d3dDevice->CreateBuffer(&bd, nullptr, m_constantBufferNeverChanges.put())
        );

    ...

    // Define D3D11_SAMPLER_DESC. For API ref, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/ns-d3d11-d3d11_sampler_desc.
    D3D11_SAMPLER_DESC sampDesc;

    // ZeroMemory fills a block of memory with zeros. For API ref, see
    // https://learn.microsoft.com/previous-versions/windows/desktop/legacy/aa366920(v=vs.85).
    ZeroMemory(&sampDesc, sizeof(sampDesc));

    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    ...

    // Create a sampler-state object that encapsulates sampling information for a texture.
    // The sampler-state interface holds a description for sampler state that you can bind to any 
    // shader stage of the pipeline for reference by texture sample operations.
    winrt::check_hresult(
        d3dDevice->CreateSamplerState(&sampDesc, m_samplerLinear.put())
        );

    // Start the async tasks to load the shaders and textures.

    // Load compiled shader objects (VertextShader.cso, VertexShaderFlat.cso, PixelShader.cso, and PixelShaderFlat.cso).
    // The BasicLoader class is used to convert and load common graphics resources, such as meshes, textures, 
    // and various shader objects into the constant buffers. For more info, see
    // https://learn.microsoft.com/windows/uwp/gaming/complete-code-for-basicloader.
    BasicLoader loader{ d3dDevice };

    std::vector<IAsyncAction> tasks;

    uint32_t numElements = ARRAYSIZE(PNTVertexLayout);

    // Load shaders asynchronously with the shader and pixel data using the
    // BasicLoader::LoadShaderAsync method. Push these method calls into a list of tasks.
    tasks.push_back(loader.LoadShaderAsync(L"VertexShader.cso", PNTVertexLayout, numElements, m_vertexShader.put(), m_vertexLayout.put()));
    tasks.push_back(loader.LoadShaderAsync(L"VertexShaderFlat.cso", nullptr, numElements, m_vertexShaderFlat.put(), nullptr));
    tasks.push_back(loader.LoadShaderAsync(L"PixelShader.cso", m_pixelShader.put()));
    tasks.push_back(loader.LoadShaderAsync(L"PixelShaderFlat.cso", m_pixelShaderFlat.put()));

    // Make sure the previous versions if any of the textures are released.
    m_sphereTexture = nullptr;
    ...

    // Load Game specific textures (Assets\\seafloor.dds, metal_texture.dds, cellceiling.dds,
    // cellfloor.dds, cellwall.dds).
    // Push these method calls also into a list of tasks.
    tasks.push_back(loader.LoadTextureAsync(L"Assets\\seafloor.dds", nullptr, m_sphereTexture.put()));
    ...

    // Simulate loading additional resources by introducing a delay.
    tasks.push_back([]() -> IAsyncAction { co_await winrt::resume_after(GameConstants::InitialLoadingDelay); }());

    // Returns when all the async tasks for loading the shader and texture assets have completed.
    for (auto&& task : tasks)
    {
        co_await task;
    }
}

Método FinalizeCreateGameDeviceResources

Se llama al método FinalizeCreateGameDeviceResources una vez completadas todas las tareas de carga de recursos que se encuentran en el método CreateGameDeviceResourcesAsync .

  • Inicialice constantBufferNeverChanges con las posiciones y el color de la luz. Carga los datos iniciales en los búferes de constantes con una llamada de método de contexto de dispositivo a ID3D11DeviceContext::UpdateSubresource.
  • Dado que los recursos cargados de forma asincrónica se han completado la carga, es el momento de asociarlos a los objetos de juego adecuados.
  • Para cada objeto de juego, cree la malla y el material mediante las texturas que se han cargado. A continuación, asocie la malla y el material al objeto del juego.
  • Para el objeto del juego de destinos, la textura que se compone de anillos de color concéntricos, con un valor numérico en la parte superior, no se carga desde un archivo de textura. En su lugar, se genera mediante procedimientos mediante el código en TargetTexture.cpp. La clase TargetTexture crea los recursos necesarios para dibujar la textura en un recurso fuera de pantalla en el momento de la inicialización. A continuación, la textura resultante se asocia a los objetos de juego de destino adecuados.

FinalizeCreateGameDeviceResources y CreateWindowSizeDependentResources comparten partes similares del código para estos:

  • Use SetProjParams para asegurarse de que la cámara tiene la matriz de proyección correcta. Para obtener más información, vaya a Cámara y espacio de coordenadas.
  • Controle la rotación de la pantalla mediante la multiplicación posterior de la matriz de rotación 3D a la matriz de proyección de la cámara. A continuación, actualice el búfer de constantes ConstantBufferChangeOnResize con la matriz de proyección resultante.
  • Establezca la m_gameResourcesLoaded variable global booleana para indicar que los recursos se cargan ahora en los búferes, listos para el paso siguiente. Recuerde que inicializamos esta variable como FALSE en el método constructor de GameRenderer, a través del método GameRenderer::CreateDeviceDependentResources.
  • Cuando este m_gameResourcesLoaded es TRUE, se puede realizar la representación de los objetos de escena. Esto se ha tratado en el artículo Marco de representación I: Introducción a la representación , en El método GameRenderer::Render.
// This method is called from the GameMain constructor.
// Make sure that 2D rendering is occurring on the same thread as the main rendering.
void GameRenderer::FinalizeCreateGameDeviceResources()
{
    // All asynchronously loaded resources have completed loading.
    // Now associate all the resources with the appropriate game objects.
    // This method is expected to run in the same thread as the GameRenderer
    // was created. All work will happen behind the "Loading ..." screen after the
    // main loop has been entered.

    // Initialize the Constant buffer with the light positions
    // These are handled here to ensure that the d3dContext is only
    // used in one thread.

    auto d3dDevice = m_deviceResources->GetD3DDevice();

    ConstantBufferNeverChanges constantBufferNeverChanges;
    constantBufferNeverChanges.lightPosition[0] = XMFLOAT4(3.5f, 2.5f, 5.5f, 1.0f);
    ...
    constantBufferNeverChanges.lightColor = XMFLOAT4(0.25f, 0.25f, 0.25f, 1.0f);

    // CPU copies data from memory (constantBufferNeverChanges) to a subresource 
    // created in non-mappable memory (m_constantBufferNeverChanges) which was created in the earlier 
    // CreateGameDeviceResourcesAsync method. For UpdateSubresource API ref info, 
    // go to: https://msdn.microsoft.com/library/windows/desktop/ff476486.aspx
    // To learn more about what a subresource is, go to:
    // https://msdn.microsoft.com/library/windows/desktop/ff476901.aspx

    m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
        m_constantBufferNeverChanges.get(),
        0,
        nullptr,
        &constantBufferNeverChanges,
        0,
        0
        );

    // For the objects that function as targets, they have two unique generated textures.
    // One version is used to show that they have never been hit and the other is 
    // used to show that they have been hit.
    // TargetTexture is a helper class to procedurally generate textures for game
    // targets. The class creates the necessary resources to draw the texture into 
    // an off screen resource at initialization time.

    TargetTexture textureGenerator(
        d3dDevice,
        m_deviceResources->GetD2DFactory(),
        m_deviceResources->GetDWriteFactory(),
        m_deviceResources->GetD2DDeviceContext()
        );

    // CylinderMesh is a class derived from MeshObject and creates a ID3D11Buffer of
    // vertices and indices to represent a canonical cylinder (capped at
    // both ends) that is positioned at the origin with a radius of 1.0,
    // a height of 1.0 and with its axis in the +Z direction.
    // In the game sample, there are various types of mesh types:
    // CylinderMesh (vertical rods), SphereMesh (balls that the player shoots), 
    // FaceMesh (target objects), and WorldMesh (Floors and ceilings that define the enclosed area)

    auto cylinderMesh = std::make_shared<CylinderMesh>(d3dDevice, (uint16_t)26);
    ...

    // The Material class maintains the properties that represent how an object will
    // look when it is rendered.  This includes the color of the object, the
    // texture used to render the object, and the vertex and pixel shader that
    // should be used for rendering.

    auto cylinderMaterial = std::make_shared<Material>(
        XMFLOAT4(0.8f, 0.8f, 0.8f, .5f),
        XMFLOAT4(0.8f, 0.8f, 0.8f, .5f),
        XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f),
        15.0f,
        m_cylinderTexture.get(),
        m_vertexShader.get(),
        m_pixelShader.get()
        );

    ...

    // Attach the textures to the appropriate game objects.
    // We'll loop through all the objects that need to be rendered.
    for (auto&& object : m_game->RenderObjects())
    {
        if (object->TargetId() == GameConstants::WorldFloorId)
        {
            // Assign a normal material for the floor object.
            // This normal material uses the floor texture (cellfloor.dds) that was loaded asynchronously from
            // the Assets folder using BasicLoader::LoadTextureAsync method in the earlier 
            // CreateGameDeviceResourcesAsync loop

            object->NormalMaterial(
                std::make_shared<Material>(
                    XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f),
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f),
                    XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f),
                    150.0f,
                    m_floorTexture.get(),
                    m_vertexShaderFlat.get(),
                    m_pixelShaderFlat.get()
                    )
                );
            // Creates a mesh object called WorldFloorMesh and assign it to the floor object.
            object->Mesh(std::make_shared<WorldFloorMesh>(d3dDevice));
        }
        ...
        else if (auto cylinder = dynamic_cast<Cylinder*>(object.get()))
        {
            cylinder->Mesh(cylinderMesh);
            cylinder->NormalMaterial(cylinderMaterial);
        }
        else if (auto target = dynamic_cast<Face*>(object.get()))
        {
            const int bufferLength = 16;
            wchar_t str[bufferLength];
            int len = swprintf_s(str, bufferLength, L"%d", target->TargetId());
            auto string{ winrt::hstring(str, len) };

            winrt::com_ptr<ID3D11ShaderResourceView> texture;
            textureGenerator.CreateTextureResourceView(string, texture.put());
            target->NormalMaterial(
                std::make_shared<Material>(
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f),
                    5.0f,
                    texture.get(),
                    m_vertexShader.get(),
                    m_pixelShader.get()
                    )
                );

            texture = nullptr;
            textureGenerator.CreateHitTextureResourceView(string, texture.put());
            target->HitMaterial(
                std::make_shared<Material>(
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f),
                    5.0f,
                    texture.get(),
                    m_vertexShader.get(),
                    m_pixelShader.get()
                    )
                );

            target->Mesh(targetMesh);
        }
        ...
    }

    // The SetProjParams method calculates the projection matrix based on input params and
    // ensures that the camera has been initialized with the right projection
    // matrix.  
    // The camera is not created at the time the first window resize event occurs.

    auto renderTargetSize = m_deviceResources->GetRenderTargetSize();
    m_game->GameCamera().SetProjParams(
        XM_PI / 2,
        renderTargetSize.Width / renderTargetSize.Height,
        0.01f,
        100.0f
        );

    // Make sure that the correct projection matrix is set in the ConstantBufferChangeOnResize buffer.

    // Get the 3D rotation transform matrix. We are handling screen rotations directly to eliminate an unaligned 
    // fullscreen copy. So it is necessary to post multiply the 3D rotation matrix to the camera's projection matrix
    // to get the projection matrix that we need.

    auto orientation = m_deviceResources->GetOrientationTransform3D();

    ConstantBufferChangeOnResize changesOnResize;

    // The matrices are transposed due to the shader code expecting the matrices in the opposite
    // row/column order from the DirectX math library.

    // XMStoreFloat4x4 takes a matrix and writes the components out to sixteen single-precision floating-point values at the given address. 
    // The most significant component of the first row vector is written to the first four bytes of the address, 
    // followed by the second most significant component of the first row, and so on. The second row is then written out in a 
    // like manner to memory beginning at byte 16, followed by the third row to memory beginning at byte 32, and finally 
    // the fourth row to memory beginning at byte 48. For more API ref info, go to: 
    // https://msdn.microsoft.com/library/windows/desktop/microsoft.directx_sdk.storing.xmstorefloat4x4.aspx

    XMStoreFloat4x4(
        &changesOnResize.projection,
        XMMatrixMultiply(
            XMMatrixTranspose(m_game->GameCamera().Projection()),
            XMMatrixTranspose(XMLoadFloat4x4(&orientation))
            )
        );

    // UpdateSubresource method instructs CPU to copy data from memory (changesOnResize) to a subresource 
    // created in non-mappable memory (m_constantBufferChangeOnResize ) which was created in the earlier 
    // CreateGameDeviceResourcesAsync method.

    m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
        m_constantBufferChangeOnResize.get(),
        0,
        nullptr,
        &changesOnResize,
        0,
        0
        );

    // Finally we set the m_gameResourcesLoaded as TRUE, so we can start rendering.
    m_gameResourcesLoaded = true;
}

Método CreateWindowSizeDependentResource

Se llama a los métodos CreateWindowSizeDependentResources cada vez que cambia el tamaño de la ventana, la orientación, la representación habilitada para estéreo o la resolución. En el juego de ejemplo, actualiza la matriz de proyección en ConstantBufferChangeOnResize.

Los recursos de tamaño de ventana se actualizan de esta manera:

  • El marco de trabajo de la aplicación obtiene uno de varios eventos posibles que indican un cambio en el estado de la ventana.
  • A continuación, el bucle principal del juego se informa sobre el evento y llama a CreateWindowSizeDependentResources en la instancia de clase principal (GameMain), que luego llama a la implementación CreateWindowSizeDependentResources en la clase del representador de juegos (GameRenderer).
  • El trabajo principal de este método es asegurarse de que los objetos visuales no se confundan o no sean válidos debido a un cambio en las propiedades de la ventana.

Para este juego de ejemplo, una serie de llamadas de método son las mismas que el método FinalizeCreateGameDeviceResources. Para ver el tutorial de código, vaya a la sección anterior.

Los ajustes de representación de tamaño de ventana de superposición y HUD del juego se tratan en Agregar una interfaz de usuario.

// Initializes view parameters when the window size changes.
void GameRenderer::CreateWindowSizeDependentResources()
{
    // Game HUD and overlay window size rendering adjustments are done here
    // but they'll be covered in the UI section instead.

    m_gameHud.CreateWindowSizeDependentResources();

    ...

    auto d3dContext = m_deviceResources->GetD3DDeviceContext();
    // In Sample3DSceneRenderer::CreateWindowSizeDependentResources, we had:
    // Size outputSize = m_deviceResources->GetOutputSize();

    auto renderTargetSize = m_deviceResources->GetRenderTargetSize();

    ...

    m_gameInfoOverlay.CreateWindowSizeDependentResources(m_gameInfoOverlaySize);

    if (m_game != nullptr)
    {
        // Similar operations as the last section of FinalizeCreateGameDeviceResources method
        m_game->GameCamera().SetProjParams(
            XM_PI / 2, renderTargetSize.Width / renderTargetSize.Height,
            0.01f,
            100.0f
            );

        XMFLOAT4X4 orientation = m_deviceResources->GetOrientationTransform3D();

        ConstantBufferChangeOnResize changesOnResize;
        XMStoreFloat4x4(
            &changesOnResize.projection,
            XMMatrixMultiply(
                XMMatrixTranspose(m_game->GameCamera().Projection()),
                XMMatrixTranspose(XMLoadFloat4x4(&orientation))
                )
            );

        d3dContext->UpdateSubresource(
            m_constantBufferChangeOnResize.get(),
            0,
            nullptr,
            &changesOnResize,
            0,
            0
            );
    }
}

Pasos siguientes

Este es el proceso básico para implementar el marco de representación de gráficos de un juego. Cuanto mayor sea tu juego, más abstracciones tendrías que poner en marcha para controlar jerarquías de tipos de objetos y comportamientos de animación. Debe implementar métodos más complejos para cargar y administrar recursos como mallas y texturas. A continuación, vamos a aprender a agregar una interfaz de usuario.