Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Uwaga / Notatka
Ten temat jest częścią serii samouczków pt. Tworzenie prostej gry na uniwersalną platformę Windows (UWP) z użyciem DirectX. Ten temat pod tym linkiem ustawia kontekst serii.
W platformie renderowaniaomówiliśmy sposób uzyskiwania informacji o scenie i prezentowania jej na ekranie wyświetlania. Teraz wykonamy krok wstecz i dowiesz się, jak przygotować dane do renderowania.
Uwaga / Notatka
Jeśli nie pobrałeś najnowszego kodu gry tego przykładu, przejdź na przykład gry Direct3D. Ten przykład jest częścią dużej kolekcji przykładów funkcji platformy UWP. Aby uzyskać instrukcje dotyczące pobierania przykładu, zobacz Przykładowe aplikacje do programowania systemu Windows.
Cel
Szybkie podsumowanie celu. Ważne jest zrozumienie, jak skonfigurować podstawową strukturę renderowania w celu wyświetlenia danych wyjściowych grafiki dla gry DirectX platformy UWP. Możemy luźno pogrupować je w te trzy kroki.
- Nawiązywanie połączenia z interfejsem graficznym
- Przygotowanie: Utwórz zasoby potrzebne do narysowania grafiki
- Wyświetl grafikę: Renderowanie ramki
Platformy renderowania I: Wprowadzenie do renderowania wyjaśniono, w jaki sposób grafiki są renderowane, obejmujące kroki 1 i 3.
W tym artykule wyjaśniono, jak skonfigurować inne elementy tej struktury i przygotować wymagane dane przed rozpoczęciem renderowania, czyli krok 2 procesu.
Projektowanie modułu renderowania
Moduł renderowany jest odpowiedzialny za tworzenie i obsługę wszystkich obiektów D3D11 i D2D używanych do generowania wizualizacji gier. Klasa GameRenderer jest rendererem dla tej przykładowej gry i jest przeznaczony do zaspokojenia potrzeb związanych z renderowaniem gry.
Oto kilka pojęć, których można użyć, aby ułatwić projektowanie modułu renderatora dla twojej gry:
- Ponieważ interfejsy API Direct3D 11 są zdefiniowane jako interfejsy API COM, należy podać ComPtr odwołania do obiektów zdefiniowanych przez te interfejsy API. Te obiekty są automatycznie zwalniane, gdy ostatnie odniesienie wykracza poza zakres, a aplikacja się kończy. Aby uzyskać więcej informacji, zobacz ComPtr. Przykład tych obiektów: stałe bufory, obiekty shaderów - shader vertex shader, shader pixel shader, i obiekty zasobów shaderów.
- Bufory stałe są definiowane w tej klasie w celu przechowywania różnych danych potrzebnych do renderowania.
- Użyj wielu buforów stałych z różnymi częstotliwościami, aby zmniejszyć ilość danych, które muszą być wysyłane do GPU na klatkę. Ten przykład oddziela stałe do różnych buforów na podstawie częstotliwości, z jaką muszą być aktualizowane. Jest to najlepsze rozwiązanie w przypadku programowania Direct3D.
- W tej przykładowej grze zdefiniowano 4 stałe bufory.
- m_constantBufferNeverChanges zawiera parametry oświetlenia. Ustawiana jest jednorazowo w metodzie FinalizeCreateGameDeviceResources i nigdy nie zmienia się ponownie.
- m_constantBufferChangeOnResize zawiera macierz projekcji. Macierz projekcji zależy od rozmiaru i współczynnika proporcji okna. Jest on ustawiany w CreateWindowSizeDependentResources, a następnie aktualizowany po załadowaniu zasobów w metodzie FinalizeCreateGameDeviceResources. Jeśli renderowanie w 3D, jest ono również zmieniane dwa razy na klatkę.
- m_constantBufferChangesEveryFrame zawiera macierz widoków. Ta macierz jest zależna od położenia kamery i kierunku patrzenia (normalnej do projekcji) i zmienia się raz na klatkę w metodzie Render. Omówiono to wcześniej w sekcji Środowisko renderowania I: Wprowadzenie do renderowania, w ramach metody GameRenderer::Render.
- m_constantBufferChangesEveryPrim zawiera macierz modelu i właściwość materiału każdego prymitywu. Macierz modelu przekształca wierzchołki z współrzędnych lokalnych na współrzędne świata. Te stałe są specyficzne dla każdego prymitywu i są aktualizowane dla każdej operacji rysowania. Omówiono to wcześniej w Framework renderowania I: Wprowadzenie do renderowania, w sekcji Renderowanie prymitywne.
- Obiekty zasobów cieniowania, które przechowują tekstury dla elementów pierwotnych, są również zdefiniowane w tej klasie.
- Niektóre tekstury są wstępnie zdefiniowane (DDS to format pliku, który może służyć do przechowywania skompresowanych i nieskompresowanych tekstur. Tekstury DDS są używane do ścian i podłogi świata, a także kul ammo.)
- W tej przykładowej grze obiekty zasobów cieniowania to: m_sphereTexture, m_cylinderTexture, m_ceilingTexture, m_floorTexture, m_wallsTexture.
- Obiekty cieniowania są definiowane w tej klasie w celu obliczenia naszych elementów pierwotnych i tekstur.
- W tej przykładowej grze obiekty cieniowania są m_vertexShader, m_vertexShaderFlati m_pixelShader, m_pixelShaderFlat.
- Cieniowanie wierzchołków przetwarza pierwotne i podstawowe oświetlenie, a cieniowanie pikseli (nazywane czasem cieniowanie fragmentów) przetwarza tekstury i wszelkie efekty na piksel.
- Istnieją dwie wersje shaderów (zwykła i płaska) do renderowania różnych prymitywów. Powodem, dla którego mamy różne wersje, jest to, że płaskie wersje są znacznie prostsze i nie wykonują odbicia światła ani żadnych efektów oświetlenia per piksel. Są one używane na ścianach i sprawiają, że renderowanie jest szybsze na urządzeniach o niższej mocy.
GameRenderer.h
Teraz przyjrzyjmy się kodowi w obiekcie klasy renderera przykładowej gry.
// 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;
};
Konstruktor
Następnie przyjrzyjmy się konstruktorowi GameRenderer przykładowej gry i porównajmy go z konstruktorem Sample3DSceneRenderer podanym w szablonie aplikacji 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();
}
Tworzenie i ładowanie zasobów graficznych DirectX
W przykładowej grze (i w szablonie DirectX 11 App (Universal Windows) programu Visual Studio) tworzenie oraz ładowanie zasobów gry jest zrealizowane za pomocą dwóch metod, które są wywoływane z konstruktora GameRenderer.
metoda CreateDeviceDependentResources
W szablonie aplikacji DirectX 11 ta metoda służy do asynchronicznego ładowania wierzchołków i cieniowania pikseli, tworzenia cieniowania i buforu stałego, tworzenia siatki z wierzchołkami zawierającymi informacje o pozycji i kolorze.
W przykładowej grze te operacje obiektów sceny są podzielone między metody CreateGameDeviceResourcesAsync oraz FinalizeCreateGameDeviceResources.
W tej przykładowej grze, co wchodzi w tę metodę?
- Utworzone zmienne (m_gameResourcesLoaded = false, m_levelResourcesLoaded = false), które wskazują, czy zasoby zostały załadowane przed przejściem do renderowania, ponieważ ładujemy je asynchronicznie.
- Ponieważ HUD i renderowanie nakładki są w oddzielnych obiektach klasowych, wywołaj tutaj metody GameHud::CreateDeviceDependentResources oraz GameInfoOverlay::CreateDeviceDependentResources.
Oto kod 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();
}
Poniżej znajduje się lista metod używanych do tworzenia i ładowania zasobów.
- UtwórzZasobyZależneOdUrządzenia
- CreateGameDeviceResourcesAsync (dodano)
- FinalizeCreateGameDeviceResources (dodano)
- Utwórz zasoby zależne od rozmiaru okna
Przed rozpoczęciem pracy z innymi metodami, które są używane do tworzenia i ładowania zasobów, najpierw utwórzmy moduł renderujący i zobaczmy, jak pasuje do pętli gry.
Tworzenie modułu renderowania
GameRenderer jest tworzony w konstruktorze GameMain. Wywołuje również dwie inne metody, CreateGameDeviceResourcesAsync i FinalizeCreateGameDeviceResources, które zostały dodane, aby ułatwić tworzenie i ładowanie zasobów.
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();
...
}
Metoda CreateGameDeviceResourcesAsync
Metoda CreateGameDeviceResourcesAsync jest wywoływana z konstruktora GameMain w pętli create_task, ponieważ ładujemy zasoby gry asynchronicznie.
CreateDeviceResourcesAsync to metoda uruchamiana jako oddzielny zestaw zadań asynchronicznych do ładowania zasobów gry. Ponieważ oczekuje się, że będzie działać w osobnym wątku, ma dostęp tylko do metod urządzeń Direct3D 11 (zdefiniowanych w ID3D11Device), a nie metod kontekstu urządzenia (metod zdefiniowanych na ID3D11DeviceContext), więc nie wykonuje żadnego renderowania.
FinalizeCreateGameDeviceResources metoda jest uruchamiana w wątku głównym i ma dostęp do metod kontekstu urządzenia Direct3D 11.
Zasadniczo:
- Używaj tylko metod ID3D11Device w CreateGameDeviceResourcesAsync, ponieważ są one niezależne od wątków, co oznacza, że mogą działać w dowolnym wątku. Oczekuje się również, że nie będą uruchamiane w tym samym wątku, na którym GameRenderer został utworzony.
- Nie należy używać metod w ID3D11DeviceContext tutaj, ponieważ muszą być uruchamiane w jednym wątku i w tym samym wątku co GameRenderer.
- Użyj tej metody, aby utworzyć bufory stałe.
- Użyj tej metody, aby załadować tekstury (takie jak pliki .dds) i informacje o shaderach (takie jak pliki .cso) do shaderów .
Ta metoda służy do:
- Utwórz stałe 4 : m_constantBufferNeverChanges, m_constantBufferChangeOnResize, m_constantBufferChangesEveryFrame, m_constantBufferChangesEveryPrim
- Utwórz obiekt stanu próbkowania , który zawiera informacje dotyczące próbkowania dla tekstury
- Utwórz grupę zadań zawierającą wszystkie zadania asynchroniczne utworzone przez metodę . Oczekuje na ukończenie wszystkich tych zadań asynchronicznych, a następnie wywołuje FinalizeCreateGameDeviceResources.
- Utwórz moduł ładujący przy użyciu Basic Loader. Dodaj operacje ładowania asynchronicznego modułu ładującego jako zadania do utworzonej wcześniej grupy zadań.
- Metody takie jak BasicLoader::LoadShaderAsync i BasicLoader::LoadTextureAsync służą do ładowania:
- skompilowane obiekty cieniowania (VertextShader.cso, VertexShaderFlat.cso, PixelShader.cso i PixelShaderFlat.cso). Aby uzyskać więcej informacji, przejdź do Różne formaty plików cieniowania.
- tekstury specyficzne dla gry (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;
}
}
Metoda FinalizeCreateGameDeviceResources
FinalizeCreateGameDeviceResources metoda jest wywoływana po zakończeniu wszystkich zadań ładowania zasobów, które znajdują się w metodzie CreateGameDeviceResourcesAsync.
- Zainicjuj constantBufferNeverChanges z pozycjami światła i kolorem. Ładuje początkowe dane do stałych buforów za pomocą wywołania metody kontekstu urządzenia ID3D11DeviceContext::UpdateSubresource.
- Ponieważ zasoby ładowane asynchronicznie zostały załadowane, nadszedł czas, aby powiązać je z odpowiednimi obiektami gry.
- Dla każdego obiektu gry utwórz siatkę i materiał przy użyciu tekstur, które zostały załadowane. Następnie skojarz siatkę i materiał z obiektem gry.
- W przypadku obiektu gry docelowej tekstura składająca się z koncentrycznych kolorowych pierścieni, z wartością liczbową na górze, nie jest ładowana z pliku tekstury. Zamiast tego jest generowany proceduralnie przy użyciu kodu w TargetTexture.cpp. Klasa TargetTexture tworzy zasoby niezbędne do rysowania tekstury do zasobu poza ekranem w czasie inicjowania. Wynikowa tekstura jest następnie skojarzona z odpowiednimi obiektami gry docelowej.
FinalizeCreateGameDeviceResources i CreateWindowSizeDependentResources mają wspólne części kodu dla tych elementów:
- Użyj SetProjParams, aby upewnić się, że aparat ma właściwą macierz projekcji. Aby uzyskać więcej informacji, przejdź do kamery i przestrzeni współrzędnych.
- Obsługa rotacji ekranu poprzez przemnożenie macierzy obrotu 3D z macierzą projekcji aparatu. Następnie zaktualizuj ConstantBufferChangeOnResize bufor stały za pomocą wynikowej macierzy projekcji.
- Ustaw globalną zmienną logiczną
m_gameResourcesLoaded , aby wskazać, że zasoby zostały teraz załadowane do buforów, gotowe do następnego kroku. Pamiętaj, że najpierw zainicjowaliśmy tę zmienną jako FALSE w metodzie konstruktora GameRendererza pośrednictwem metody GameRenderer::CreateDeviceDependentResources. - Gdy ten m_gameResourcesLoaded jest TRUE, można renderować obiekty sceny. Opisano to w artykule
Rendering Framework I: Intro to rendering article (Wprowadzenie do renderowania) w .GameRenderer::Render, metoda
// 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;
}
Metoda CreateWindowSizeDependentResource
Metody CreateWindowSizeDependentResources są wywoływane za każdym razem, gdy zmieniają się rozmiar okna, orientacja, renderowanie z włączoną obsługą stereo lub rozdzielczość. W przykładowej grze aktualizuje macierz projekcji w ConstantBufferChangeOnResize.
Zasoby dotyczące rozmiaru okna są aktualizowane w następujący sposób:
- Struktura aplikacji pobiera jedno z kilku możliwych zdarzeń wskazujących zmianę stanu okna.
- Twoja główna pętla gry jest następnie informowana o zdarzeniu i wywołuje metodę CreateWindowSizeDependentResources w instancji głównej klasy (GameMain), która następnie wywołuje implementację CreateWindowSizeDependentResources w klasie renderera gry (GameRenderer).
- Podstawowym zadaniem tej metody jest upewnienie się, że wizualizacje nie stają się mylone lub nieprawidłowe z powodu zmiany właściwości okna.
W przypadku tej przykładowej gry wiele wywołań metod jest takie same jak metody FinalizeCreateGameDeviceResources. Aby zapoznać się z przewodnikiem po kodzie, przejdź do poprzedniej sekcji.
W rozdziale Dodaj interfejs użytkownikaomówiono dostosowanie renderowania rozmiaru okna gry HUD oraz nakładki.
// 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
);
}
}
Dalsze kroki
Jest to podstawowy proces implementowania struktury renderowania grafiki w grze. Tym większa gra, tym więcej abstrakcji trzeba by umieścić w celu obsługi hierarchii typów obiektów i zachowań animacji. Należy zaimplementować bardziej złożone metody ładowania zasobów, takich jak siatki i tekstury, oraz zarządzanie nimi. Następnie dowiesz się, jak dodać interfejs użytkownika.