Uwaga
Dostęp do tej strony wymaga autoryzacji. Może 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.
Do tej pory omówiliśmy, jak zbudować grę na Uniwersalnej Platformie Windows (UWP) oraz jak zdefiniować maszynę stanów do zarządzania przepływem gry. Teraz nadszedł czas, aby dowiedzieć się, jak opracować platformę renderowania. Przyjrzyjmy się, jak przykładowa gra renderuje scenę gry przy użyciu direct3D 11.
Direct3D 11 zawiera zestaw interfejsów API, które zapewniają dostęp do zaawansowanych funkcji sprzętu graficznego o wysokiej wydajności, który może służyć do tworzenia grafiki 3D dla aplikacji intensywnie korzystających z grafiki, takich jak gry.
Renderowanie grafiki gry na ekranie oznacza zasadniczo renderowanie sekwencji ramek na ekranie. W każdej klatce należy renderować obiekty widoczne w scenie, w oparciu o widok.
Aby przetworzyć klatkę, należy przekazać wymagane informacje o scenie do sprzętu, aby były wyświetlone na ekranie. Jeśli chcesz mieć cokolwiek wyświetlanego na ekranie, musisz rozpocząć renderowanie natychmiast po uruchomieniu gry.
Cele i zadania
Aby skonfigurować podstawową strukturę renderowania, aby wyświetlić wynik graficzny dla gry DirectX w platformie UWP. Możesz luźno podzielić te trzy kroki.
- Ustanów połączenie z interfejsem graficznym.
- Utwórz zasoby potrzebne do narysowania grafiki.
- Wyświetl grafikę, renderując ramkę.
W tym temacie opisano sposób renderowania grafiki obejmującej kroki 1 i 3.
Framework renderowania II: renderowanie gier obejmuje krok 2 — jak skonfigurować framework renderowania i jak dane są przygotowane przed rozpoczęciem renderowania.
Rozpocznij
Dobrym pomysłem jest zapoznanie się z podstawowymi pojęciami dotyczącymi grafiki i renderowania. Jeśli dopiero zaczynasz korzystać z funkcji Direct3D i renderowania, zobacz Terminy i pojęcia, aby zapoznać się z krótkim opisem grafiki i terminów renderowania używanych w tym temacie.
W tej grze GameRenderer klasa reprezentuje renderer dla tej przykładowej gry. Jest on odpowiedzialny za tworzenie i utrzymywanie wszystkich obiektów Direct3D 11 i Direct2D używanych do generowania wizualizacji gier. Utrzymuje również odwołanie do obiektu Simple3DGame używanego do pobierania listy obiektów do renderowania, a także stanu gry dla wyświetlacza HUD (heads-up display).
W tej części samouczka skupimy się na renderowaniu obiektów 3D w grze.
Nawiązywanie połączenia z interfejsem graficznym
Aby uzyskać informacje na temat uzyskiwania dostępu do sprzętu do renderowania, zobacz temat Definiowanie platformy aplikacji UWP gry.
Metoda App::Initialize
Funkcja std::make_shared, jak pokazano poniżej, służy do tworzenia shared_ptr do DX::DeviceResources, który zapewnia również dostęp do urządzenia.
W wersji Direct3D 11 urządzenie służy do przydzielania i niszczenia obiektów, renderowania elementów pierwotnych i komunikowania się z kartą graficzną za pośrednictwem sterownika graficznego.
void Initialize(CoreApplicationView const& applicationView)
{
...
// At this point we have access to the device.
// We can create the device-dependent resources.
m_deviceResources = std::make_shared<DX::DeviceResources>();
}
Wyświetlanie grafiki przez renderowanie ramki
Scena gry musi być renderowana po uruchomieniu gry. Instrukcje dotyczące renderowania rozpoczynają się w metodzie GameMain::Run, jak pokazano poniżej.
Prosty przepływ jest taki.
- Aktualizacja
- Renderowanie
- obecny
Metoda GameMain::Run
void GameMain::Run()
{
while (!m_windowClosed)
{
if (m_visible) // if the window is visible
{
switch (m_updateState)
{
...
default:
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
Update();
m_renderer->Render();
m_deviceResources->Present();
m_renderNeeded = false;
}
}
else
{
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
m_game->OnSuspending(); // Exiting due to window close, so save state.
}
Aktualizacja
Aby uzyskać więcej informacji na temat sposobu aktualizowania stanów gier w metodzie GameMain::Update, zobacz temat zarządzanie przepływem gry.
Renderowanie
Renderowanie jest implementowane przez wywołanie metody GameRenderer::Render z GameMain::Run.
Jeśli renderowanie stereo jest włączone, istnieją dwa przebiegi renderowania — jeden dla lewego oka i jeden dla prawego oka. W każdym przebiegu renderowania wiążemy element docelowy renderowania i widok wzornika głębokości z urządzeniem. Następnie wyczyścimy widok wzornika głębi.
Uwaga / Notatka
Renderowanie stereo można osiągnąć przy użyciu innych metod, takich jak stereo z jednym przebiegiem przy użyciu instancjonowania wierzchołków lub cieniowania geometrii. Metoda dwóch przebiegów renderowania jest wolniejsza, ale bardziej wygodna do osiągnięcia renderowania stereo.
Po uruchomieniu gry i załadowaniu zasobów zaktualizujemy macierz projekcji raz na każdą pętlę renderowania. Obiekty różnią się nieco w każdym ujęciu. Następnie skonfigurujemy potok renderowania grafiki .
Uwaga / Notatka
Zobacz Tworzenie i ładowanie zasobów graficznych DirectX, aby uzyskać więcej informacji na temat sposobu ładowania zasobów.
W tej przykładowej grze renderator jest przeznaczony do używania standardowego układu wierzchołków we wszystkich obiektach. Upraszcza to projektowanie cieniowania i umożliwia łatwe zmiany między cieniowaniami, niezależnie od geometrii obiektów.
Metoda GameRenderer::Render
Ustawiamy kontekst Direct3D tak, aby używał układu wierzchołka wejściowego. Obiekty układu wejściowego opisują sposób przesyłania danych strumieniowo z buforu wierzchołka do potoku renderowania .
Następnie ustawiliśmy kontekst Direct3D tak, aby używał zdefiniowanych wcześniej buforów stałych, które są używane w etapie cieniowania wierzchołków potoku oraz w etapie cieniowania pikseli potoku .
Uwaga / Notatka
Aby uzyskać więcej informacji na temat definicji buforów stałych, zobacz Renderowanie gier II: renderowanie gier.
Ponieważ ten sam układ wejściowy i zestaw stałych buforów są używane dla wszystkich shaderów, które znajdują się w linii przetwarzania, jest on konfigurowany raz na każdą klatkę.
void GameRenderer::Render()
{
bool stereoEnabled{ m_deviceResources->GetStereoState() };
auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };
int renderingPasses = 1;
if (stereoEnabled)
{
renderingPasses = 2;
}
for (int i = 0; i < renderingPasses; i++)
{
// Iterate through the number of rendering passes to be completed.
// 2 rendering passes if stereo is enabled.
if (i > 0)
{
// Doing the Right Eye View.
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetViewRight() };
// Resets render targets to the screen.
// OMSetRenderTargets binds 2 things to the device.
// 1. Binds one render target atomically to the device.
// 2. Binds the depth-stencil view, as returned by the GetDepthStencilView method, to the device.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargets
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
// Clears the depth stencil view.
// A depth stencil view contains the format and buffer to hold depth and stencil info.
// For more info about depth stencil view, go to:
// https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-stencil-view--dsv-
// A depth buffer is used to store depth information to control which areas of
// polygons are rendered rather than hidden from view. To learn more about a depth buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-buffers
// A stencil buffer is used to mask pixels in an image, to produce special effects.
// The mask determines whether a pixel is drawn or not,
// by setting the bit to a 1 or 0. To learn more about a stencil buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/stencil-buffers
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// Direct2D -- discussed later
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmapRight());
}
else
{
// Doing the Mono or Left Eye View.
// As compared to the right eye:
// m_deviceResources->GetBackBufferRenderTargetView instead of GetBackBufferRenderTargetViewRight
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };
// Same as the Right Eye View.
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// d2d -- Discussed later under Adding UI
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmap());
}
const float clearColor[4] = { 0.5f, 0.5f, 0.8f, 1.0f };
// Only need to clear the background when not rendering the full 3D scene since
// the 3D world is a fully enclosed box and the dynamics prevents the camera from
// moving outside this space.
if (i > 0)
{
// Doing the Right Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetViewRight(), clearColor);
}
else
{
// Doing the Mono or Left Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), clearColor);
}
// Render the scene objects
if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
{
// This section is only used after the game state has been initialized and all device
// resources needed for the game have been created and associated with the game objects.
if (stereoEnabled)
{
// When doing stereo, it is necessary to update the projection matrix once per rendering pass.
auto orientation = m_deviceResources->GetOrientationTransform3D();
ConstantBufferChangeOnResize changesOnResize;
// Apply either a left or right eye projection, which is an offset from the middle
XMStoreFloat4x4(
&changesOnResize.projection,
XMMatrixMultiply(
XMMatrixTranspose(
i == 0 ?
m_game->GameCamera().LeftEyeProjection() :
m_game->GameCamera().RightEyeProjection()
),
XMMatrixTranspose(XMLoadFloat4x4(&orientation))
)
);
d3dContext->UpdateSubresource(
m_constantBufferChangeOnResize.get(),
0,
nullptr,
&changesOnResize,
0,
0
);
}
// Update variables that change once per frame.
ConstantBufferChangesEveryFrame constantBufferChangesEveryFrameValue;
XMStoreFloat4x4(
&constantBufferChangesEveryFrameValue.view,
XMMatrixTranspose(m_game->GameCamera().View())
);
d3dContext->UpdateSubresource(
m_constantBufferChangesEveryFrame.get(),
0,
nullptr,
&constantBufferChangesEveryFrameValue,
0,
0
);
// Set up the graphics pipeline. This sample uses the same InputLayout and set of
// constant buffers for all shaders, so they only need to be set once per frame.
// For more info about the graphics or rendering pipeline, see
// https://learn.microsoft.com/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline
// IASetInputLayout binds an input-layout object to the input-assembler (IA) stage.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
// Set up the Direct3D context to use this vertex layout. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetinputlayout
d3dContext->IASetInputLayout(m_vertexLayout.get());
// VSSetConstantBuffers sets the constant buffers used by the vertex shader pipeline stage.
// Set up the Direct3D context to use these constant buffers. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-vssetconstantbuffers
ID3D11Buffer* constantBufferNeverChanges{ m_constantBufferNeverChanges.get() };
d3dContext->VSSetConstantBuffers(0, 1, &constantBufferNeverChanges);
ID3D11Buffer* constantBufferChangeOnResize{ m_constantBufferChangeOnResize.get() };
d3dContext->VSSetConstantBuffers(1, 1, &constantBufferChangeOnResize);
ID3D11Buffer* constantBufferChangesEveryFrame{ m_constantBufferChangesEveryFrame.get() };
d3dContext->VSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
ID3D11Buffer* constantBufferChangesEveryPrim{ m_constantBufferChangesEveryPrim.get() };
d3dContext->VSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
// Sets the constant buffers used by the pixel shader pipeline stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-pssetconstantbuffers
d3dContext->PSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
d3dContext->PSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
ID3D11SamplerState* samplerLinear{ m_samplerLinear.get() };
d3dContext->PSSetSamplers(0, 1, &samplerLinear);
for (auto&& object : m_game->RenderObjects())
{
// The 3D object render method handles the rendering.
// For more info, see Primitive rendering below.
object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
}
}
// Start of 2D rendering
...
}
}
Renderowanie pierwotne
Podczas renderowania sceny należy wykonać pętlę przez wszystkie obiekty, które muszą być renderowane. Poniższe kroki są powtarzane dla każdego obiektu (pierwotnego).
- Zaktualizuj bufor stały (m_constantBufferChangesEveryPrim) przy użyciu macierzy transformacji świata modelu i informacji materiałowych.
- m_constantBufferChangesEveryPrim zawiera parametry dla każdego obiektu. Zawiera macierz transformacji obiekt-świat, a także właściwości materiałowe, takie jak kolor i wykładnik widmowy na potrzeby obliczeń oświetlenia.
- Ustaw kontekst Direct3D, aby użyć układu wierzchołka wejściowego dla danych obiektu siatki, które mają być przesyłane strumieniowo do etapu IA (input-assembler) potoku renderowania .
- Ustaw kontekst Direct3D do używania bufora indeksów w etapie IA. Podaj informacje pierwotne: typ, kolejność danych.
- Złóż wywołanie rysowania, aby narysować indeksowany, nieinstancjonowany prymityw. Metoda GameObject::Render aktualizuje bufor stały prymitywy z danymi specyficznymi dla danej prymitywy. Spowoduje to wywołanie DrawIndexed kontekstu w celu narysowania geometrii każdego elementu pierwotnego. W szczególności to wywołanie rysowania umieszcza w kolejce polecenia i dane do jednostki przetwarzania grafiki (GPU), czyli sparametryzowane danymi z bufora stałego. Każde wywołanie rysowania wykonuje cieniowanie wierzchołków jeden raz na wierzchołek, a następnie cieniowanie pikseli jeden raz dla każdego piksela każdego trójkąta w typie pierwotnym. Tekstury są częścią stanu używanego przez pixel shader do wykonywania renderingu.
Oto przyczyny używania wielu stałych buforów.
- Gra używa wielu stałych buforów, ale musi je aktualizować tylko raz dla każdej prymitywy. Jak wspomniano wcześniej, bufory stałe są podobne do danych wejściowych shaderów uruchamianych dla każdego prymitywu. Niektóre dane są statyczne (m_constantBufferNeverChanges); niektóre dane są stałe na ramce (m_constantBufferChangesEveryFrame), takie jak położenie aparatu; a niektóre dane są specyficzne dla pierwotnych, takich jak jego kolor i tekstury (m_constantBufferChangesEveryPrim).
- Moduł renderujący gry oddziela te dane wejściowe do różnych stałych buforów, aby zoptymalizować przepustowość pamięci używanej przez CPU i GPU. Takie podejście pomaga również zminimalizować ilość danych, których procesor GPU potrzebuje do śledzenia. GPU ma dużą kolejkę poleceń i za każdym razem, gdy gra wywołuje Draw, to polecenie jest dodawane do kolejki wraz z danymi skojarzonymi z nim. Gdy gra aktualizuje pierwotny bufor konstant i wydaje następne polecenie Draw, sterownik graficzny dodaje to polecenie i skojarzone dane do kolejki. Jeśli gra rysuje 100 prymitywów, potencjalnie może mieć 100 kopii danych bufora stałego w kolejce. Aby zminimalizować ilość danych wysyłanych przez grę do procesora GPU, gra używa oddzielnego pierwotnego buforu stałego, który zawiera tylko aktualizacje dla każdego elementu pierwotnego.
Metoda GameObject::Render
void GameObject::Render(
_In_ ID3D11DeviceContext* context,
_In_ ID3D11Buffer* primitiveConstantBuffer
)
{
if (!m_active || (m_mesh == nullptr) || (m_normalMaterial == nullptr))
{
return;
}
ConstantBufferChangesEveryPrim constantBuffer;
// Put the model matrix info into a constant buffer, in world matrix.
XMStoreFloat4x4(
&constantBuffer.worldMatrix,
XMMatrixTranspose(ModelMatrix())
);
// Check to see which material to use on the object.
// If a collision (a hit) is detected, GameObject::Render checks the current context, which
// indicates whether the target has been hit by an ammo sphere. If the target has been hit,
// this method applies a hit material, which reverses the colors of the rings of the target to
// indicate a successful hit to the player. Otherwise, it applies the default material
// with the same method. In both cases, it sets the material by calling Material::RenderSetup,
// which sets the appropriate constants into the constant buffer. Then, it calls
// ID3D11DeviceContext::PSSetShaderResources to set the corresponding texture resource for the
// pixel shader, and ID3D11DeviceContext::VSSetShader and ID3D11DeviceContext::PSSetShader
// to set the vertex shader and pixel shader objects themselves, respectively.
if (m_hit && m_hitMaterial != nullptr)
{
m_hitMaterial->RenderSetup(context, &constantBuffer);
}
else
{
m_normalMaterial->RenderSetup(context, &constantBuffer);
}
// Update the primitive constant buffer with the object model's info.
context->UpdateSubresource(primitiveConstantBuffer, 0, nullptr, &constantBuffer, 0, 0);
// Render the mesh.
// See MeshObject::Render method below.
m_mesh->Render(context);
}
Metoda MeshObject::Render
void MeshObject::Render(_In_ ID3D11DeviceContext* context)
{
// PNTVertex is a struct. stride provides us the size required for all the mesh data
// struct PNTVertex
//{
// DirectX::XMFLOAT3 position;
// DirectX::XMFLOAT3 normal;
// DirectX::XMFLOAT2 textureCoordinate;
//};
uint32_t stride{ sizeof(PNTVertex) };
uint32_t offset{ 0 };
// Similar to the main render loop.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
ID3D11Buffer* vertexBuffer{ m_vertexBuffer.get() };
context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
// IASetIndexBuffer binds an index buffer to the input-assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetindexbuffer.
context->IASetIndexBuffer(m_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);
// Binds information about the primitive type, and data order that describes input data for the input assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetprimitivetopology.
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Draw indexed, non-instanced primitives. A draw API submits work to the rendering pipeline.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexed.
context->DrawIndexed(m_indexCount, 0, 0);
}
DeviceResources::Present metoda
Wywołujemy metodę DeviceResources::Present w celu wyświetlenia zawartości, którą umieściliśmy w buforach.
Używamy terminu łańcuch wymiany dla kolekcji buforów używanych do wyświetlania klatek użytkownikowi. Za każdym razem, gdy aplikacja prezentuje nową klatkę do wyświetlenia, pierwszy bufor w łańcuchu swapu zastępuje wyświetlany bufor. Ten proces jest nazywany zamianą lub odwracaniem. Aby uzyskać więcej informacji, zobacz Łańcuchy wymiany.
- Metoda IDXGISwapChain1 interfejsu Present instruuje DXGI, aby zablokować, dopóki nie nastąpi synchronizacja pionowa (VSync), wprowadzając aplikację w stan uśpienia do następnego VSync. Dzięki temu nie zmarnujesz żadnych procesorowych cykli na renderowanie klatek, które nigdy nie będą wyświetlane na ekranie.
- Metoda ID3D11DeviceContext3 interfejsu DiscardView odrzuca zawartość elementu docelowego renderowania . Jest to prawidłowa operacja tylko wtedy, gdy istniejąca zawartość zostanie całkowicie zastąpiona. Jeśli używane są brudne lub przewijane prostokąty, to wywołanie powinno zostać usunięte.
- Używając tej samej metody DiscardView, odrzuć zawartość bufora głębokości i szablonu .
- Metoda
HandleDeviceLost służy do zarządzania scenariuszem usuwania urządzenia. Jeśli urządzenie zostało usunięte przez rozłączenie lub uaktualnienie sterownika, należy ponownie utworzyć wszystkie zasoby urządzenia. Aby uzyskać więcej informacji, zobacz Radzenie sobie z usunięciem urządzenia w Direct3D 11.
Wskazówka
Aby zapewnić płynną szybkość klatek, należy upewnić się, że ilość pracy w celu renderowania ramki mieści się w czasie między narzędziami VSync.
// Present the contents of the swap chain to the screen.
void DX::DeviceResources::Present()
{
// 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);
// Discard the contents of the render target.
// This is a valid operation only when the existing contents will be entirely
// overwritten. If dirty or scroll rects are used, this call should be removed.
m_d3dContext->DiscardView(m_d3dRenderTargetView.get());
// Discard the contents of the depth stencil.
m_d3dContext->DiscardView(m_d3dDepthStencilView.get());
// 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 || hr == DXGI_ERROR_DEVICE_RESET)
{
HandleDeviceLost();
}
else
{
winrt::check_hresult(hr);
}
}
Dalsze kroki
W tym temacie wyjaśniono, jak grafika jest renderowana na wyświetlaczu i zawiera krótki opis niektórych użytych terminów renderowania (poniżej). Dowiedz się więcej o renderowaniu w temacie Rendering Framework II: Renderowanie gier i dowiedz się, jak przygotować potrzebne dane przed renderowaniem.
Terminy i pojęcia
Prosta scena gry
Prosta scena gry składa się z kilku obiektów z kilkoma źródłami światła.
Kształt obiektu jest definiowany przez zestaw współrzędnych X, Y i Z w przestrzeni. Rzeczywiste położenie renderowania w świecie gry można określić, stosując macierz transformacji do współrzędnych pozycyjnych X, Y i Z. Może również mieć zestaw współrzędnych tekstury — U i V — które określają, jak materiał jest nakładany na obiekt. Definiuje właściwości powierzchni obiektu i daje możliwość sprawdzenia, czy obiekt ma szorstką powierzchnię (na przykład piłkę tenisową), czy gładką błyszczącą powierzchnię (na przykład piłkę kręgle).
Informacje o scenie i obiektach są używane przez platformę renderowania do odtwarzania sceny klatka po klatce, dzięki czemu ożywa ona na monitorze.
Potok renderowania
Potok renderowania to proces, za pomocą którego informacje o scenie 3D są tłumaczone na obraz wyświetlany na ekranie. W Direct3D 11 ten potok graficzny jest programowalny. Etapy można dostosować do potrzeb związanych z renderowaniem. Etapy, które zawierają typowe rdzenie cieniowania, są programowalne przy użyciu języka programowania HLSL. Jest również znany jako potok renderowania grafiki lub po prostu potok .
Aby pomóc ci w tworzeniu tego pipeline'u, musisz zapoznać się z tymi szczegółami.
- HLSL. Zalecamy korzystanie z modelu cieniowania HLSL w wersji 5.1 lub nowszej dla gier DIRECTX platformy UWP.
- cieniowania.
- cieniowania wierzchołków i cieniowania pikseli.
- etapy cieniowania.
- różne formaty plików cieniowania.
Aby uzyskać więcej informacji, zobacz Omówienie potoku renderowania Direct3D 11 i potoku grafiki .
HLSL
HLSL to wysokopoziomowy język cieniowania dla DirectX. Za pomocą HLSL można utworzyć programowalne shadery w stylu języka C dla potoku Direct3D. Aby uzyskać więcej informacji, zobacz HLSL.
Shadery
Shader można traktować jako zestaw instrukcji określających, jak powierzchnia obiektu wygląda po renderowaniu. Te, które są zaprogramowane przy użyciu HLSL, są znane jako shadery HLSL. Pliki kodu źródłowego dla shaderów [HLSL](#hlsl) mają rozszerzenie pliku ..hlsl
. Te shadery można kompilować przy kompilacji lub w trakcie działania i ustawiać w odpowiednim etapie potoku w trakcie działania. Skompilowany obiekt cieniowania ma rozszerzenie pliku .cso
.
Cieniowania Direct3D 9 można zaprojektować przy użyciu modelu cieniowania 1, modelu cieniowania 2 i modelu cieniowania 3; Cieniowania Direct3D 10 można zaprojektować tylko w modelu cieniowania 4. Cieniowania Direct3D 11 można zaprojektować w modelu cieniowania 5. Modele direct3D 11.3 i Direct3D 12 można zaprojektować w modelu cieniowania 5.1, a model direct3D 12 można również zaprojektować w modelu cieniowania 6.
Cieniowania wierzchołków i cieniowania pikseli
Dane wchodzą do potoku grafiki jako strumień prymitywów i są przetwarzane przez różne shadery, takie jak shadery wierzchołków i shadery pikseli.
Shader wierzchołków przetwarzają wierzchołki, zazwyczaj wykonując operacje, takie jak przekształcenia, skinning i oświetlenie. Cieniowanie pikseli umożliwia zaawansowane techniki cieniowania, takie jak oświetlenie na piksel i przetwarzanie końcowe. Łączy zmienne stałe, dane tekstury, interpolowane wartości wierzchołków i inne dane w celu wygenerowania danych wyjściowych na piksel.
Etapy cieniowania
Sekwencja tych różnych cieniowania zdefiniowanych do przetwarzania tego strumienia elementów pierwotnych jest znana jako etapy cieniowania w potoku renderowania. Rzeczywiste etapy zależą od wersji Direct3D, ale zwykle obejmują wierzchołki, geometrię i etapy pikseli. Istnieją również inne etapy, takie jak cieniowanie kadłuba i domeny dla tessellacji, oraz cieniowanie obliczeniowe. Wszystkie te etapy są całkowicie programowalne przy użyciu HLSL. Aby uzyskać więcej informacji, zobacz Ścieżka przetwarzania grafiki.
Różne formaty plików cieniowania
Poniżej przedstawiono rozszerzenia pliku kodu cieniującego.
- Plik z rozszerzeniem
.hlsl
zawiera kod źródłowy [HLSL])(#hlsl). - Plik z rozszerzeniem
.cso
zawiera skompilowany obiekt cieniowania. - Plik z rozszerzeniem
.h
jest plikiem nagłówka, ale w kontekście kodu cieniowania ten plik nagłówka definiuje tablicę bajtów, która zawiera dane cieniowania. - Plik z rozszerzeniem
.hlsli
zawiera format buforów stałych. W przykładowej grze plik jest Shaders>ConstantBuffers.hlsli.
Uwaga / Notatka
Shadera można osadzić, ładując plik .cso
w czasie wykonywania lub dodając plik .h
w kodzie wykonywalnym. Nie należy jednak używać obu dla tego samego cieniowania.
Dokładniejsze zrozumienie języka DirectX
Direct3D 11 to zestaw interfejsów API, które mogą pomóc nam tworzyć grafiki dla aplikacji intensywnie korzystających z grafiki, takich jak gry, gdzie chcemy mieć dobrą kartę graficzną do przetwarzania intensywnych obliczeń. W tej sekcji krótko opisano koncepcje programowania grafiki Direct3D 11: zasoby, podźródło, urządzenie i kontekst urządzenia.
Zasób
Zasoby (nazywane również zasobami urządzeń) można traktować jako informacje o sposobie renderowania obiektu, takiego jak tekstura, pozycja lub kolor. Zasoby dostarczają dane do przepływu danych i definiują, co jest renderowane w trakcie Twojej sceny. Zasoby mogą być ładowane z mediów z gry lub tworzone dynamicznie w trakcie działania.
Zasób to w rzeczywistości obszar w pamięci, do którego pipeline Direct3Dmoże uzyskać dostęp. Aby rurociąg mógł efektywnie uzyskiwać dostęp do pamięci, dane dostarczane do rurociągu (takie jak geometria wejściowa, zasoby shaderów i tekstury) muszą być przechowywane w zasobie. Istnieją dwa typy zasobów, z których pochodzą wszystkie zasoby Direct3D: bufor lub tekstura. Maksymalnie 128 zasobów może być aktywnych na każdym etapie przetwarzania. Aby uzyskać więcej informacji, zobacz Resources.
zasób pomocniczy
Termin podźródło odnosi się do podzestawu zasobu. Funkcja Direct3D może odwoływać się do całego zasobu lub odwoływać się do podzbiorów zasobu. Aby uzyskać więcej informacji, zobacz Subresource.
Wzornik głębokości
Zasób wzornika głębokości zawiera format i bufor do przechowywania informacji o głębokości i wzorniku. Jest on tworzony przy użyciu zasobu tekstury. Aby uzyskać więcej informacji na temat tworzenia zasobu wzornika szczegółowego, zobacz Configuring Depth-Stencil Functionality. Uzyskujemy dostęp do zasobu bufora głębi za pośrednictwem widoku bufora głębi zaimplementowanego przy użyciu interfejsu ID3D11DepthStencilView.
Informacje o głębokości informują nas, które obszary wielokątów znajdują się za innymi, abyśmy mogli określić, które obszary są ukryte. Informacje o wzorniku informują nas, które piksele są maskowane. Może służyć do wytwarzania efektów specjalnych, ponieważ określa, czy piksel jest rysowany, czy nie; ustawia bit na wartość 1 lub 0.
Aby uzyskać więcej informacji, zobacz widok głębi i szablonu , bufor głębokości oraz bufor szablonu .
Renderuj obiekt docelowy
Element docelowy renderowania to zasób, do którego możemy zapisywać dane na końcu przebiegu renderowania. Jest często tworzony metodą ID3D11Device::CreateRenderTargetView przy użyciu bufora tylnego łańcucha wymiany (który jest również zasobem) jako parametru wejściowego.
Każdy element docelowy renderowania powinien również mieć odpowiedni widok wzornika głębokości, ponieważ w przypadku używania OMSetRenderTargets w celu ustawienia obiektu docelowego renderowania przed jego użyciem, wymaga również widoku wzornika głębokości. Mamy dostęp do zasobu docelowego renderowania za pośrednictwem widoku celu renderowania zaimplementowanego przy użyciu interfejsu ID3D11RenderTargetView.
Urządzenie
Możesz sobie wyobrazić urządzenie jako sposób przydzielania i niszczenia obiektów, renderowania elementów pierwotnych i komunikowania się z kartą graficzną za pośrednictwem sterownika graficznego.
Aby dokładniej wyjaśnić, urządzenie Direct3D jest składnikiem renderowania Direct3D. Urządzenie hermetyzuje i przechowuje stan renderowania, wykonuje przekształcenia i operacje oświetlenia oraz rasteryzuje obraz na powierzchnię. Aby uzyskać więcej informacji, zobacz Devices
Urządzenie jest reprezentowane przez interfejs ID3D11Device. Innymi słowy, interfejs ID3D11Device reprezentuje wirtualną kartę graficzną i służy do tworzenia zasobów należących do karty graficznej.
Istnieją różne wersje ID3D11Device. ID3D11Device5 jest najnowszą wersją i dodaje nowe metody do tych w ID3D11Device4. Aby uzyskać więcej informacji na temat sposobu komunikacji Direct3D z podstawowym sprzętem, odwołaj się do architektura modelu sterowników urządzeń w systemie Windows (WDDM).
Każda aplikacja musi mieć co najmniej jedno urządzenie; większość aplikacji tworzy tylko jedną. Utwórz urządzenie dla jednego ze sterowników sprzętu zainstalowanych na komputerze, wywołując D3D11CreateDevice lub D3D11CreateDeviceAndSwapChain i określając typ sterownika za pomocą flagi D3D_DRIVER_TYPE. Każde urządzenie może używać co najmniej jednego kontekstu urządzenia, w zależności od żądanej funkcjonalności. Aby uzyskać więcej informacji, zobacz funkcja D3D11CreateDevice.
Kontekst urządzenia
Kontekst urządzenia służy do ustawiania stanu potoku oraz generowania poleceń renderowania przy użyciu zasobów będących własnością urządzenia .
Direct3D 11 implementuje dwa typy kontekstów urządzenia, jeden do natychmiastowego renderowania, a drugi do renderowania odroczonego; Oba konteksty są reprezentowane za pomocą interfejsu ID3D11DeviceContext.
Interfejsy ID3D11DeviceContext mają różne wersje; ID3D11DeviceContext4 dodaje nowe metody do tych w ID3D11DeviceContext3.
ID3D11DeviceContext4 została wprowadzona w Aktualizacji Twórców dla systemu Windows 10 i to najnowsza wersja interfejsu ID3D11DeviceContext. Aplikacje przeznaczone dla systemu Windows 10 Creators Update i nowsze powinny używać tego interfejsu zamiast wcześniejszych wersji. Aby uzyskać więcej informacji, zobacz ID3D11DeviceContext4.
DX::D eviceResources
Klasa DX::DeviceResources znajduje się w plikach DeviceResources.cpp/.h i kontroluje wszystkie zasoby urządzeń DirectX.
Bufor
Zasób buforu to kolekcja w pełni typiowanych danych pogrupowanych w elementy. Możesz użyć buforów do przechowywania szerokiej gamy danych, w tym wektorów położenia, wektorów normalnych, współrzędnych tekstury w buforze wierzchołków, indeksów w buforze indeksów lub stanu urządzenia. Elementy buforu mogą zawierać spakowane wartości danych (takie jak R8G8B8A8 wartości powierzchni), pojedyncze 8-bitowe liczby całkowite lub cztery 32-bitowe wartości zmiennoprzecinkowe.
Dostępne są trzy typy: bufor wierzchołka, bufor indeksu i bufor stały.
Bufor wierzchołka
Zawiera dane wierzchołka używane do definiowania geometrii. Dane wierzchołka obejmują współrzędne położenia, dane kolorów, dane współrzędnych tekstury, dane normalne itd.
Bufor indeksu
Zawiera przesunięcia liczb całkowitych do buforów wierzchołków i są używane do wydajniejszego renderowania prymitywów. Bufor indeksowy zawiera sekwencyjny zestaw indeksów 16-bitowych lub 32-bitowych; każdy indeks służy do identyfikowania wierzchołka w buforze wierzchołków.
Bufor stały lub bufor stały cieniowania
Umożliwia wydajne dostarczanie danych cieniowania do potoku. Można używać buforów stałych jako danych wejściowych do shaderów uruchamianych dla każdego prymitywu i przechowywać wyniki etapu wyjściowego strumienia z potoku renderowania. Koncepcyjnie bufor stały wygląda jak bufor wierzchołka z jednym elementem.
Projektowanie i implementacja buforów
Możesz projektować bufory na podstawie typu danych, na przykład w naszej przykładowej grze, jeden bufor jest tworzony dla danych statycznych, inny dla danych zmiennych w ramce, a kolejny dla danych specyficznych dla prymitywu.
Wszystkie typy są hermetyzowane przez interfejs ID3D11Buffer i można utworzyć zasób buforu, wywołując ID3D11Device::CreateBuffer. Jednak bufor musi być powiązany z potokiem, zanim będzie można uzyskać do niego dostęp. Bufory mogą być powiązane z wieloma etapami potoku jednocześnie do odczytu. Bufor może być również przypisany do pojedynczego etapu potoku do zapisu; jednak ten sam bufor nie może być jednocześnie używany do odczytu i zapisu.
Można powiązać bufory w te sposoby.
- Do etapu input-assembler wywołując ID3D11DeviceContext metod, takich jak ID3D11DeviceContext::IASetVertexBuffers i ID3D11DeviceContext::IASetIndexBuffer.
- Aby wywołać etap strumienia wyjściowego, użyj funkcji ID3D11DeviceContext::SOSetTargets.
- W etapie cieniowania poprzez wywołanie metod cieniowania, takich jak ID3D11DeviceContext::VSSetConstantBuffers.
Aby uzyskać więcej informacji, zobacz Introduction to buffers in Direct3D 11.
DXGI
Microsoft DirectX Graphics Infrastructure (DXGI) jest podsystemem, który hermetyzuje niektóre zadania niskiego poziomu wykorzystywane przez Direct3D. Należy zachować szczególną ostrożność, w przypadku używania DXGI w aplikacji wielowątkowej, aby upewnić się, że zakleszczenia nie występują. Aby uzyskać więcej informacji, zobacz Multithreading i DXGI
Poziom funkcji
Poziom funkcji to koncepcja wprowadzona w wersji Direct3D 11 do obsługi różnorodności kart wideo na nowych i istniejących maszynach. Poziom funkcji to dobrze zdefiniowany zestaw funkcji procesora graficznego (GPU).
Każda karta wideo implementuje określony poziom funkcji DirectX w zależności od zainstalowanych procesorów GPU. W poprzednich wersjach usługi Microsoft Direct3D możesz dowiedzieć się, w której wersji direct3D zaimplementowano kartę wideo, a następnie odpowiednio zaprogramować aplikację.
Na poziomie funkcji podczas tworzenia urządzenia możesz spróbować utworzyć urządzenie dla poziomu funkcji, którego chcesz zażądać. Jeśli tworzenie urządzenia działa, ten poziom funkcji istnieje, jeśli nie, sprzęt nie obsługuje tego poziomu funkcji. Możesz spróbować ponownie utworzyć urządzenie na niższym poziomie funkcji lub zamknąć aplikację. Na przykład poziom funkcji 12_0 wymaga modelu direct3D 11.3 lub Direct3D 12 oraz modelu cieniowania 5.1. Aby uzyskać więcej informacji, zobacz Poziomy funkcji Direct3D: Omówienie dla każdego poziomu funkcji.
Za pomocą poziomów funkcji można utworzyć aplikację dla wersji Direct3D 9, Microsoft Direct3D 10 lub Direct3D 11, a następnie uruchomić ją na sprzęcie 9, 10 lub 11 (z pewnymi wyjątkami). Aby uzyskać więcej informacji, zobacz poziomy funkcji Direct3D.
Renderowanie stereo
Renderowanie stereo służy do wzmacniania iluzji głębokości. Używa dwóch obrazów, jeden od lewego oka, a drugi z prawej strony, aby wyświetlić scenę na ekranie wyświetlania.
Matematycznie stosujemy macierz projekcji stereo, która jest niewielkim przesunięciem poziomym w prawo i w lewo od regularnej macierzy projekcji mono, aby to osiągnąć.
Wykonaliśmy dwa etapy renderowania, aby osiągnąć renderowanie stereo w tej przykładowej grze.
- Powiąż z prawym elementem docelowym renderowania, zastosuj projekcję prawą, a następnie narysuj obiekt pierwotny.
- Powiąż z lewym obiektem renderowania, zastosuj projekcję lewą, a następnie narysuj obiekt pierwotny.
Kamera i przestrzeń współrzędna
Gra ma kod aktualizowania świata we własnym układzie współrzędnych (czasami nazywanym przestrzenią światową lub przestrzenią sceny). Wszystkie obiekty, w tym aparat, są rozmieszczone i zorientowane w tej przestrzeni. Aby uzyskać więcej informacji, zobacz Układy współrzędnych.
Wierzchołkowy shader wykonuje główną pracę konwersji ze współrzędnych modelu na współrzędne urządzenia za pomocą następującego algorytmu (gdzie V jest wektorem, a M jest macierzą).
V(device) = V(model) x M(model-to-world) x M(world-to-view) x M(view-to-device)
-
M(model-to-world)
jest macierzą transformacji ze współrzędnych modelu na współrzędne świata, znaną również jako macierz transformacji świata . Jest to dostarczane przez element pierwotny. -
M(world-to-view)
to macierz transformacji współrzędnych świata na współrzędne widoku, znana również jako macierz transformacji widoku .- Jest to zapewniane przez macierz widoku kamery. Jest on definiowany przez położenie kamery wraz z wektorami patrzenia (wektor obserwacji, który wskazuje bezpośrednio na scenę z kamery, oraz wektor wzroku uniesionego, który jest prostopadły do niego w górę).
- W przykładowej grze m_viewMatrix jest macierzą przekształcania widoku i jest obliczana przy użyciu Camera::SetViewParams.
-
M(view-to-device)
to macierz przekształcania współrzędnych widoku na współrzędne urządzenia, znana również jako macierz przekształcenia projekcji .- Jest to zapewniane przez projekcję kamery. Zawiera on informacje o tym, ile miejsca jest rzeczywiście widoczne w ostatniej scenie. Pole widoku (FoV), współczynnik proporcji i płaszczyzny wycinków definiują macierz przekształcania projekcji.
- W przykładowej grze m_projectionMatrix definiuje transformację do współrzędnych projekcji, obliczaną przy użyciu Camera::SetProjParams (w przypadku projekcji stereo należy użyć dwóch macierzy projekcji — jednej dla widoku każdego oka).
Kod cieniowania w VertexShader.hlsl
ładuje te wektory i macierze z buforów stałych i wykonuje tę transformację dla każdego wierzchołka.
Przekształcanie współrzędnych
Funkcja Direct3D używa trzech przekształceń w celu zmiany współrzędnych modelu 3D na współrzędne pikseli (miejsce na ekranie). Te przekształcenia to transformacje świata, przekształcanie widoku i transformacja projekcji. Aby uzyskać więcej informacji, zobacz omówienie Transform.
Macierz przekształceń w przestrzeni światowej
Transformacja świata zmienia współrzędne z przestrzeni modelu, gdzie wierzchołki są definiowane względem lokalnego źródła modelu, do przestrzeni światowej, gdzie wierzchołki są definiowane względem źródła wspólnego dla wszystkich obiektów w scenie. W istocie, transformacja świata umieszcza model w świecie; stąd wynika jego nazwa. Aby uzyskać więcej informacji, zobacz Transformacja świata.
Wyświetlanie macierzy przekształceń
Przekształcenie widoku lokalizuje obserwatora w przestrzeni świata, przekształcając wierzchołki w przestrzeń kamery. W przestrzeni kamery aparat lub widz znajduje się w początku układu współrzędnych, patrząc w kierunku dodatniej osi z. Aby uzyskać więcej informacji, przejdź do View transform.
Macierz przekształcania projekcji
Przekształcenie projekcji konwertuje frustum wyświetlania na kształt cuboid. Frustum wyświetlania jest woluminem 3D w scenie umieszczonej względem kamery viewport. Widok jest prostokątem 2D, na który rzutowana jest scena 3D. Aby uzyskać więcej informacji, zobacz Viewports and clipping
Ponieważ bliski koniec stożka widoku jest mniejszy niż daleki koniec, powoduje to rozszerzanie się obiektów znajdujących się w pobliżu kamery; w ten sposób perspektywa jest zastosowana w scenie. Tak więc obiekty, które są bliżej odtwarzacza, wydają się większe; obiekty, które są dalej, pojawiają się mniejsze.
Matematycznie transformacja projekcji jest macierzą, która zazwyczaj jest zarówno skalą, jak i projekcją perspektywy. Działa jak obiektyw aparatu. Aby uzyskać więcej informacji, zobacz Transformacja projekcji.
Stan próbkatora
Stan próbkowania określa sposób próbkowania danych tekstury przy użyciu trybów adresowania tekstur, filtrowania i poziomu szczegółów. Próbkowanie odbywa się za każdym razem, gdy piksel tekstury (lub texel) jest odczytywany z tekstury.
Tekstura zawiera tablicę texels. Położenie każdego texela jest oznaczone przez (u,v)
, gdzie u
jest szerokość, a v
jest wysokością i jest mapowany między 0 a 1 na podstawie szerokości tekstury i wysokości. Wynikowe współrzędne tekstury są używane do adresowania texel podczas próbkowania tekstury.
Gdy współrzędne tekstury znajdują się poniżej 0 lub powyżej 1, tryb adresu tekstury definiuje sposób, w jaki współrzędna tekstury odpowiada lokalizacji texel. Na przykład w przypadku używania TextureAddressMode.Clampkażda współrzędna poza zakresem 0-1 jest ograniczana do maksymalnej wartości 1 i minimalnej wartości 0 przed próbkowaniem.
Jeśli tekstura jest za duża lub za mała dla wielokąta, tekstura jest filtrowana, aby dopasować przestrzeń. Filtr powiększenia powiększa teksturę, filtr minyfikacji zmniejsza teksturę, aby zmieścić się w mniejszym obszarze. Powiększenie tekstury powoduje powtarzanie texeli próbki dla jednego lub więcej adresów, co skutkuje bardziej rozmytym obrazem. Minifikacja tekstury jest bardziej skomplikowana, ponieważ wiąże się z połączeniem więcej niż jednej wartości texela w jedną wartość. Może to prowadzić do aliasingu lub poszarpanych krawędzi w zależności od danych tekstury. Najpopularniejszym podejściem do zmniejszania rozmiaru jest użycie mapy mipmap. Mipmap to wielopoziomowa tekstura. Rozmiar każdego poziomu to potęga 2 mniejsza niż poprzedni poziom, aż do tekstury 1x1. Gdy jest używana minification, gra wybiera poziom mipmap najbliższy rozmiarowi potrzebnemu w czasie renderowania.
Klasa BasicLoader
BasicLoader to prosta klasa modułu ładującego, która zapewnia obsługę ładowania cieniowania, tekstur i siatk z plików na dysku. Zapewnia metody synchroniczne i asynchroniczne. W tej przykładowej grze pliki
Aby uzyskać więcej informacji, zobacz Basic Loader.