轉譯架構II:遊戲轉譯

注意

本主題屬於<使用 DirectX 建立簡單的通用 Windows 平台 (UWP) 遊戲>教學課程系列的一部分。 該連結主題是提供這系列教學的基本背景介紹。

在<轉譯架構 I>中,我們已討論如何取得場景資訊並將其呈現至顯示畫面。 現在,我們要往回一步,解釋如何準備資料以進行轉譯。

注意

如果您尚未下載此範例的最新遊戲程式碼,請至 Direct3D 範例遊戲頁面下載。 此範例屬於大型 UWP 功能範例集。 如需範例下載的相關指示,請參閱<適用 Windows 開發的範例應用程式>。

目標

首先簡要概述一下我們的目標。 我們的目標是瞭解如何設定基本的轉譯架構,以顯示 UWP DirectX 遊戲的圖形輸出。 大致上可分成以下三個步驟。

  1. 建立與圖形介面的連接
  2. 準備:建立繪製圖形所需的資源
  3. 顯示圖形:轉譯畫面格

轉譯架構 I:轉譯簡介>說明如何轉譯圖形的步驟 1 和 3。

本文說明整個程序的步驟 2,也就是如何設定此架構的其他部分,並準備必要的資料以進行轉譯。

設計轉譯器

轉譯器負責建立和維護所有用於產生遊戲視覺效果的 D3D11 和 D2D 物件。 此範例遊戲的轉譯器是 GameRenderer 類別,其專為符合遊戲的轉譯需求而設計。

下列這些概念有助於您設計遊戲的轉譯器:

  • 由於 Direct3D 11 API 定義為 COM API,因此您必須針對這些 API 定義的物件提供 ComPtr 參照。 如果應用程式終止,這些物件會在最後一個參照超出範圍時自動釋放。 如需詳細資訊,請參閱 ComPtr 頁面。 這類物件範例包括:常數緩衝區、著色器物件 - 頂點著色器像素著色器和著色器資源物件。
  • 常數緩衝區在此類別中進行定義,用以保存轉譯所需的各種資料。
    • 使用多個頻率不同的常數緩衝區,以減少每畫面格須傳送至 GPU 的資料量。 此範例會按規定的更新頻率,將常數分成不同的緩衝區。 這是 Direct3D 程式設計的最佳做法。
    • 在此範例遊戲中,共會定義 4 個常數緩衝區。
      1. m_constantBufferNeverChanges 包含光源參數。 在 FinalizeCreateGameDeviceResources 方法中設定一次之後就永遠不會再變更。
      2. m_constantBufferChangeOnResize 包含投影矩陣。 投影矩陣取決於視窗的大小和外觀比例。 該矩陣是在 CreateWindowSizeDependentResources 中設定,然後等資源載入後再在 FinalizeCreateGameDeviceResources 方法更新。 如果以 3D 轉譯,每畫面格也會變更兩次。
      3. m_constantBufferChangesEveryFrame 包含檢視矩陣。 此矩陣取決於相機位置和視角方向 (投影法線),並在 Render 方法中每畫面格變更一次。 這之前在<Rendering framework I: Intro to rendering>中的<GameRenderer::Render 方法>一節已有說明
      4. m_constantBufferChangesEveryPrim 包含每個基本類型的模型矩陣和材質屬性。 模型矩陣會將頂點從本機座標轉換成世界座標。 這些常數是每個基本類型特有的,且會針對每個繪製呼叫更新。 這稍早在<轉譯架構 I:轉譯簡介>中的<基本轉譯>一節已有說明。
  • 在此類別中定義的還有著色器資源物件,用於保存基本類型紋理。
    • 有些紋理已預先定義 (DDS 是檔案格式,可用來儲存壓縮和未壓縮的紋理。DDS 紋理用於世界牆壁和地板,以及彈藥球體)。
    • 在此範例遊戲中,著色器資源物件包括:m_sphereTexturem_cylinderTexturem_ceilingTexturem_floorTexturem_wallsTexture
  • 著色器物件在此類別中定義,用於計算基本類型和紋理。
    • 在此範例遊戲中,著色器物件是:m_vertexShaderm_vertexShaderFlatm_pixelShaderm_pixelShaderFlat
    • 頂點著色器處理基本類型和基本光源,像素著色器 (有時稱為片段著色器) 則處理紋理和任何每像素效果。
    • 這些著色器有兩個版本 (標準和簡單),用於轉譯不同的基本類型。 提供兩個版本是因為「簡單」版本比較單純,沒有反射高光或任何每像素光線效果。 這些會用於牆面,且在低電源裝置上可更快完成轉譯。

GameRenderer.h

現在我們來看看範例遊戲中,轉譯器類別物件的程式碼。

// 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;
};

建構函式

接下來,我們來檢視範例遊戲的 GameRenderer 建構函式,並將其與 DirectX 11 應用程式範本中提供的 Sample3DSceneRenderer 建構函式進行比較。

// 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();
}

建立和載入 DirectX 圖形資源

在範例遊戲 (以及 Visual Studio 的DirectX 11 應用程式 (通用 Windows) 範本) 中,我們會從GameRenderer 呼叫以下兩種方法以建立和載入遊戲資源:

CreateDeviceDependentResources 方法

在 DirectX 11 應用程式範本中,此方法可用來以非同步方式載入頂點和像素著色器、建立著色器和常數緩衝區、建立包含位置和色彩資訊的頂點網格。

在範例遊戲中,場景物件這些作業會改成分到 CreateGameDeviceResourcesAsyncFinalizeCreateGameDeviceResources 兩個方法中。

針對此範例遊戲,此方法有什麼用途?

  • 具現化變數 (m_gameResourcesLoaded = false and m_levelResourcesLoaded = false),指出在接著進行轉譯之前是否要載入資源,因為其為非同步載入。
  • 由於 HUD 和重疊轉譯位於不同的類別物件中,因此請在這裡呼叫 GameHud::CreateDeviceDependentResourcesGameInfoOverlay::CreateDeviceDependentResources 方法。

下方是 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();
}

以下是用來建立和載入資源的方法清單。

  • CreateDeviceDependentResources
    • CreateGameDeviceResourcesAsync (已新增)
    • FinalizeCreateGameDeviceResources (已新增)
  • CreateWindowSizeDependentResources

在深入探索用來建立和載入資源的其他方法之前,我們先建立轉譯器,並瞭解其如何整合到遊戲迴圈。

建立轉譯器

GameRenderer 是在 GameMain 的建構函式中建立。 其也會呼叫另外兩個方法:CreateGameDeviceResourcesAsyncFinalizeCreateGameDeviceResources (新增這兩個方法是為協助建立和載入資源)。

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();

    ...
}

CreateGameDeviceResourcesAsync 方法

CreateGameDeviceResourcesAsync 是從 create_task 迴圈中的 GameMain 建構函式方法呼叫,因為我們是以非同步方式載入遊戲資源。

CreateDeviceResourcesAsync 方法能以獨立的非同步工作集形式載入遊戲資源。 由於該方法應在獨立的執行緒上執行,只能存取 Direct3D 11 裝置方法 (ID3D11Device 中定義的方法),而不能存取裝置內容方法 (在 ID3D11DeviceContext 定義的方法),因此不會執行任何轉譯。

FinalizeCreateGameDeviceResources 方法會在主執行緒上執行,且可存取 Direct3D 11 裝置內容方法。

原則上:

  • CreateGameDeviceResourcesAsync 中只使用 ID3D11Device 方法,因為這些是自由執行緒方法,代表可在任何執行緒上執行。 而且,這些方法也不預期會在建立 GameRenderer 時所在的執行緒上執行。
  • 請勿在這裡使用 ID3D11DeviceContext,因為其需要在單一執行緒及 GameRenderer 所在的同一執行緒上執行。
  • 使用此方法建立常數緩衝區。
  • 使用此方法將紋理 (例如 .dds 檔案) 和著色器資訊 (例如 .cso 檔案) 載入著色器

此方法可用來:

  • 建立 4 個常數緩衝區m_constantBufferNeverChangesm_constantBufferChangeOnResizem_constantBufferChangesEveryFramem_constantBufferChangesEveryPrim
  • 建立 sampler-state 物件,以封裝紋理的取樣資訊
  • 建立工作群組,內含所有由方法建立的非同步工作。 該群組會等候所有非同步工作完成,然後呼叫 FinalizeCreateGameDeviceResources
  • 使用 Basic Loader 建立載入器。 將載入器的非同步載入作業以「工作」的形式新增至稍早建立的工作群組。
  • BasicLoader::LoadShaderAsyncBasicLoader::LoadTextureAsync 等方法用於載入:
    • 已編譯的著色器物件 (VertextShader.cso、VertexShaderFlat.cso、PixelShader.cso 和 PixelShaderFlat.cso)。 如需詳細資訊,請移至<各種著色器檔案格式>。
    • 遊戲特定紋理 (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;
    }
}

FinalizeCreateGameDeviceResources 方法

CreateGameDeviceResourcesAsync 方法中所有的載入資源工作完成之後,才會呼叫 FinalizeCreateGameDeviceResources 方法。

  • 使用光線位置和色彩初始化 constantBufferNeverChanges。 使用裝置內容方法的 ID3D11DeviceContext::UpdateSubresource 呼叫,將初始資料載入常數緩衝區。
  • 由於非同步載入的資源已完成載入,因此現在可將其與適當的遊戲物件建立關聯。
  • 針對每個遊戲物件,使用已載入的紋理建立網格和材質。 然後將網格和材質與遊戲物件建立關聯。
  • 針對目標遊戲物件,由同心彩色環形組成的紋理 (上面有數值) 不會從紋理檔案載入。 該紋理是透過 TargetTexture.cpp 中的程式碼以程序方式產生。 TargetTexture 類別會建立必要的資源,以在初始化時將紋理繪製到螢幕外資源。 接著,產生的紋理會與適當的目標遊戲物件建立關聯。

FinalizeCreateGameDeviceResourcesCreateWindowSizeDependentResources 會共用這些程式碼的類似部分並用於:

  • 使用 SetProjParams 以確保相機具有正確的投影矩陣。 如需詳細資訊,請移至<相機和座標空間>。
  • 將 3D 旋轉矩陣與相機投影矩陣進行後乘,以處理螢幕旋轉。 然後使用產生的投影矩陣,更新 ConstantBufferChangeOnResize 常數緩衝區。
  • 設定 m_gameResourcesLoadedBoolean 全域變數,指出資源現在已載入緩衝區,可供下一個步驟使用。 回想一下,我們首先將此變數初始化為 FALSE 時,是在 GameRenderer 的建構函式方法中 (透過 GameRenderer::CreateDeviceDependentResources 方法執行)。
  • 當此 m_gameResourcesLoadedTRUE,則會發生場景物件轉譯。 這在<轉譯架構 I:轉譯簡介>一文的<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;
}

CreateWindowSizeDependentResource 方法

每次視窗大小、方向、啟用立體轉譯或解析度變更時,都會呼叫 CreateWindowSizeDependentResources 方法。 在範例遊戲中,其會更新 ConstantBufferChangeOnResize 中的投影矩陣。

視窗大小資源會以下列方式更新:

  • 應用程式架構會接收到幾種可能的事件,其指出視窗狀態的變更。
  • 接著,系統會向主要遊戲迴圈通知事件相關資訊,並對主要類別 (GameMain) 執行個體呼叫 CreateWindowSizeDependentResources,然後在遊戲轉譯器 (GameRenderer) 類別中呼叫 CreateWindowSizeDependentResources 實作。
  • 此方法最主要是用來確定視覺效果不會因為視窗屬性中的變更,而變得混亂或無效。

在此範例遊戲中,有很多的方法呼叫都和 FinalizeCreateGameDeviceResources 方法相同。 如需程式碼逐步解說,請移至上一節。

如需遊戲 HUD 和重疊視窗大小轉譯調整的相關資訊,請參閱<新增使用者介面>。

// 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
            );
    }
}

下一步

這是實作遊戲圖形轉譯架構的基本程序。 遊戲規模越大,需要配置的抽象概念越多,才能處理物件類型和動畫行為階層。 因此,需要實作更複雜的方法來載入和管理資產,例如:網格和紋理。 接下來,我們來瞭解如何新增使用者介面