Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Det här dokumentet beskriver hur Marble Maze-spelet använder Direct3D och Direct2D i UWP-appmiljön (Universal Windows Platform) så att du kan lära dig mönstren och anpassa dem när du arbetar med ditt eget spelinnehåll. Information om hur visuella spelkomponenter passar in i den övergripande programstrukturen i Marble Maze finns i Programstrukturen för Marble Maze.
Vi följde dessa grundläggande steg när vi utvecklade de visuella aspekterna av Marble Maze:
- Skapa ett grundläggande ramverk som initierar Direct3D- och Direct2D-miljöerna.
- Använd bild- och modellredigeringsprogram för att utforma de 2D- och 3D-tillgångar som visas i spelet.
- Se till att 2D- och 3D-resurser läses in korrekt och visas i spelet.
- Integrera hörn- och pixelskuggare som förbättrar speltillgångarnas visuella kvalitet.
- Integrera spellogik, till exempel animering och användarindata.
Vi fokuserade också först på att lägga till 3D-tillgångar och sedan på 2D-tillgångar. Vi fokuserade till exempel på kärnspellogik innan vi lade till menysystemet och timern.
Vi behövde också iterera genom några av dessa steg flera gånger under utvecklingsprocessen. När vi till exempel gjorde ändringar i mesh- och marmormodellerna var vi tvungna att ändra en del av skuggningskoden som stöder dessa modeller.
Anmärkning
Exempelkoden som motsvarar det här dokumentet finns i DirectX Marble Maze-spelexemplet.
Här är några av de viktigaste punkterna som beskrivs i det här dokumentet när du arbetar med DirectX- och visuellt spelinnehåll, nämligen när du initierar DirectX-grafikbiblioteken, läser in scenresurser och uppdaterar och återger scenen:
- Att lägga till spelinnehåll omfattar vanligtvis många steg. Dessa steg kräver också ofta iteration. Spelutvecklare fokuserar ofta först på att lägga till 3D-spelinnehåll och sedan på att lägga till 2D-innehåll.
- Nå fler kunder och ge dem alla en bra upplevelse genom att stödja det största utbudet av grafikmaskinvara som möjligt.
- Separera designtids- och körningsformaten tydligt. Strukturera dina designtidstillgångar för att maximera flexibiliteten och möjliggöra snabba iterationer av innehåll. Formatera och komprimera dina resurser för att ladda och rendera så effektivt som möjligt vid körtid.
- Du skapar Direct3D- och Direct2D-enheter i en UWP-app ungefär som du gör i en klassisk Windows-skrivbordsapp. En viktig skillnad är hur växlingskedjan är associerad med utdatafönstret.
- När du utformar ditt spel ska du se till att det mesh-format som du väljer stöder dina viktiga scenarier. Om ditt spel till exempel kräver kollision kontrollerar du att du kan hämta kollisionsdata från dina nät.
- Separera spellogik från renderingslogik genom att först uppdatera alla scenobjekt innan du renderar dem.
- Du ritar vanligtvis dina 3D-scenobjekt och sedan alla 2D-objekt som visas framför scenen.
- Synkronisera ritningen till det lodräta tomma objektet för att säkerställa att spelet inte ägnar tid åt att rita ramar som aldrig visas på skärmen. Ett vertikal tomperiod är tiden mellan det att en bildruta har ritats på bildskärmen och nästa bildruta börjar.
Komma igång med DirectX-grafik
När vi planerade spelet Marble Maze Universal Windows Platform (UWP) valde vi C++ och Direct3D 11.1 eftersom de är utmärkta val för att skapa 3D-spel som kräver maximal kontroll över rendering och höga prestanda. DirectX 11.1 stöder maskinvara från DirectX 9 till DirectX 11 och kan därför hjälpa dig att nå fler kunder mer effektivt eftersom du inte behöver skriva om kod för var och en av de tidigare DirectX-versionerna.
Marble Maze använder Direct3D 11.1 för att återge 3D-speltillgångarna, nämligen marmorn och labyrinten. Marble Maze använder också Direct2D, DirectWrite och Windows Imaging Component (WIC) för att rita 2D-speltillgångarna, till exempel menyerna och timern.
Spelutveckling kräver planering. Om du är nybörjare på DirectX-grafik rekommenderar vi att du läser DirectX: Komma igång med att bekanta dig med de grundläggande begreppen för att skapa ett UWP DirectX-spel. När du läser det här dokumentet och går igenom marble maze-källkoden kan du läsa följande resurser för mer detaljerad information om DirectX-grafik:
- Direct3D 11-grafik: Beskriver Direct3D 11, ett kraftfullt, maskinvaruaccelererat 3D-grafik-API för återgivning av 3D-geometri på Windows-plattformen.
- Direct2D: Beskriver Direct2D, ett maskinvaruaccelererat 2D-grafik-API som ger hög prestanda och högkvalitativ rendering för 2D-geometri, bitmappar och text.
- DirectWrite: Beskriver DirectWrite, som stöder textåtergivning av hög kvalitet.
- Windows Imaging-komponent: Beskriver WIC, en utökningsbar plattform som tillhandahåller lågnivå-API för digitala bilder.
Funktionsnivåer
Direct3D 11 introducerar ett paradigm med namnet funktionsnivåer. En funktionsnivå är en väldefinierad uppsättning GPU-funktioner. Använd funktionsnivåer för att rikta in dig på ditt spel för att köra på tidigare versioner av Direct3D-maskinvara. Marble Maze stöder funktionsnivå 9.1 eftersom det inte krävs några avancerade funktioner från de högre nivåerna. Vi rekommenderar att du stöder så många olika typer av maskinvara som möjligt och skalar ditt spelinnehåll så att dina kunder som har antingen hög- eller lågnivådatorer alla har en bra upplevelse. Mer information om funktionsnivåer finns i Direct3D 11 on Downlevel Hardware(Direct3D 11 on Downlevel Hardware).
Initiera Direct3D och Direct2D
En enhet representerar bildskärmskortet. Du skapar Direct3D- och Direct2D-enheter i en UWP-app ungefär som du gör i en klassisk Windows-skrivbordsapp. Den största skillnaden är hur du ansluter Direct3D-växlingskedjan till fönstersystemet.
Klassen DeviceResources är en grund för att hantera Direct3D och Direct2D. Den här klassen hanterar allmän infrastruktur, inte spelspecifika tillgångar. Marble Maze definierar klassen MarbleMazeMain för att hantera spelspecifika tillgångar, som har en referens till ett DeviceResources-objekt för att ge den åtkomst till Direct3D och Direct2D.
Under initieringen skapar konstruktorn DeviceResources enhetsoberoende resurser och Direct3D- och Direct2D-enheterna.
// Initialize the Direct3D resources required to run.
DX::DeviceResources::DeviceResources() :
m_screenViewport(),
m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
m_d3dRenderTargetSize(),
m_outputSize(),
m_logicalSize(),
m_nativeOrientation(DisplayOrientations::None),
m_currentOrientation(DisplayOrientations::None),
m_dpi(-1.0f),
m_deviceNotify(nullptr)
{
CreateDeviceIndependentResources();
CreateDeviceResources();
}
Klassen DeviceResources separerar den här funktionen så att den enklare kan svara när miljön ändras. Den anropar till exempel metoden CreateWindowSizeDependentResources när fönsterstorleken ändras.
Initiera Direct2D-, DirectWrite- och WIC-fabrikerna
Metoden DeviceResources::CreateDeviceIndependentResources skapar fabrikerna för Direct2D, DirectWrite och WIC. I DirectX-grafik är fabriker startpunkterna för att skapa grafikresurser. Marble Maze specificerar D2D1_FACTORY_TYPE_SINGLE_THREADED eftersom denna utför all ritning på huvudtråden.
// These are the resources required independent of hardware.
void DX::DeviceResources::CreateDeviceIndependentResources()
{
// Initialize Direct2D resources.
D2D1_FACTORY_OPTIONS options;
ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));
#if defined(_DEBUG)
// If the project is in a debug build, enable Direct2D debugging via SDK Layers.
options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif
// Initialize the Direct2D Factory.
DX::ThrowIfFailed(
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
__uuidof(ID2D1Factory2),
&options,
&m_d2dFactory
)
);
// Initialize the DirectWrite Factory.
DX::ThrowIfFailed(
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory2),
&m_dwriteFactory
)
);
// Initialize the Windows Imaging Component (WIC) Factory.
DX::ThrowIfFailed(
CoCreateInstance(
CLSID_WICImagingFactory2,
nullptr,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&m_wicFactory)
)
);
}
Skapa Direct3D- och Direct2D-enheter
Metoden DeviceResources::CreateDeviceResources anroparD3D11CreateDevice för att skapa enhetsobjektet som representerar Direct3D-bildskärmskortet. Eftersom Marble Maze stöder funktionsnivå 9.1 och senare, anger DeviceResources::CreateDeviceResources-metoden nivåerna 9.1 till 11.1 i featureLevels-matrisen. Direct3D går igenom listan i ordning och ger appen den första tillgängliga funktionsnivån. Därför visas de D3D_FEATURE_LEVEL matrisposterna från högsta till lägsta så att appen får den högsta tillgängliga funktionsnivån. Metoden DeviceResources::CreateDeviceResources hämtar Direct3D 11.1-enheten genom att begära Direct3D 11-enheten som returneras från D3D11CreateDevice.
// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(_DEBUG)
if (DX::SdkLayersAvailable())
{
// If the project is in a debug build, enable debugging via SDK Layers
// with this flag.
creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
}
#endif
// This array defines the set of DirectX hardware feature levels this app will support.
// Note the ordering should be preserved.
// Don't forget to declare your application's minimum required feature level in its
// description. All applications are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1
};
// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
HRESULT hr = D3D11CreateDevice(
nullptr, // Specify nullptr to use the default adapter.
D3D_DRIVER_TYPE_HARDWARE, // Create a device using the hardware graphics driver.
0, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
creationFlags, // Set debug and Direct2D compatibility flags.
featureLevels, // List of feature levels this app can support.
ARRAYSIZE(featureLevels), // Size of the list above.
D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for UWP apps.
&device, // Returns the Direct3D device created.
&m_d3dFeatureLevel, // Returns feature level of device created.
&context // Returns the device immediate context.
);
if (FAILED(hr))
{
// If the initialization fails, fall back to the WARP device.
// For more information on WARP, see:
// https://go.microsoft.com/fwlink/?LinkId=286690
DX::ThrowIfFailed(
D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device.
0,
creationFlags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
&device,
&m_d3dFeatureLevel,
&context
)
);
}
// Store pointers to the Direct3D 11.1 API device and immediate context.
DX::ThrowIfFailed(
device.As(&m_d3dDevice)
);
DX::ThrowIfFailed(
context.As(&m_d3dContext)
);
Metoden DeviceResources::CreateDeviceResources skapar sedan Direct2D-enheten. Direct2D använder Microsoft DirectX Graphics Infrastructure (DXGI) för att samverka med Direct3D. DXGI gör att videominnesytor kan delas mellan grafikkörningar. Marble Maze använder den underliggande DXGI-enheten från Direct3D-enheten för att skapa Direct2D-enheten från Direct2D-fabriken.
// Create the Direct2D device object and a corresponding context.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
m_d3dDevice.As(&dxgiDevice)
);
DX::ThrowIfFailed(
m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
);
DX::ThrowIfFailed(
m_d2dDevice->CreateDeviceContext(
D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
&m_d2dContext
)
);
Mer information om DXGI och samverkan mellan Direct2D och Direct3D finns i Översikt över DXGI och Direct2D och Direct3D Interoperability Overview.
Associera Direct3D med vyn
Metoden DeviceResources::CreateWindowSizeDependentResources skapar de grafikresurser som är beroende av en viss fönsterstorlek, till exempel växlingskedjan och Direct3D- och Direct2D-återgivningsmål. Ett viktigt sätt att en DirectX UWP-app skiljer sig från en skrivbordsapp är hur växlingskedjan är associerad med utdatafönstret. En växlingskedja ansvarar för att visa bufferten som enheten renderas till på bildskärmen. Marble Maze-programstrukturen beskriver hur fönstersystemet för en UWP-app skiljer sig från en skrivbordsapp. Eftersom en UWP-app inte fungerar med HWND-objekt måste Marble Maze använda metoden IDXGIFactory2::CreateSwapChainForCoreWindow för att associera enhetens utdata till vyn. I följande exempel visas den del av DeviceResources::CreateWindowSizeDependentResources metod som skapar växlingskedjan.
// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
dxgiFactory->CreateSwapChainForCoreWindow(
m_d3dDevice.Get(),
reinterpret_cast<IUnknown*>(m_window.Get()),
&swapChainDesc,
nullptr,
&m_swapChain
)
);
För att minimera strömförbrukningen, vilket är viktigt att göra på batteridrivna enheter som bärbara datorer och surfplattor, anropar metoden DeviceResources::CreateWindowSizeDependentResources metoden IDXGIDevice1::SetMaximumFrameLatency för att säkerställa att spelet renderas först efter det lodräta tomma objektet. Synkronisering med det lodräta tomma objektet beskrivs mer detaljerat i avsnittet Presentera scenen i det här dokumentet.
// Ensure that DXGI does not queue more than one frame at a time. This both
// reduces latency and ensures that the application will only render after each
// VSync, minimizing power consumption.
DX::ThrowIfFailed(
dxgiDevice->SetMaximumFrameLatency(1)
);
Metoden DeviceResources::CreateWindowSizeDependentResources initierar grafikresurser på ett sätt som fungerar för de flesta spel.
Anmärkning
Termen view har en annan betydelse i Windows Runtime än i Direct3D. I Windows Runtime refererar en vy till samlingen med användargränssnittsinställningar för en app, inklusive visningsområdet och indatabeteenden, plus den tråd som används för bearbetning. Du anger den konfiguration och de inställningar som du behöver när du skapar en vy. Processen för att konfigurera appvyn beskrivs i Marble Maze-programstrukturen. I Direct3D har termvyn flera betydelser. En resursvy definierar de underresurser som en resurs kan komma åt. När ett texturobjekt till exempel är associerat med en shaderresursvy kan shadern senare få tillgång till texturen. En fördel med en resursvy är att du kan tolka data på olika sätt i olika steg i återgivningspipelinen. Mer information om resursvyer finns i Resursvyer. När den används i kontexten för en visningstransformerings- eller visningstransformeringsmatris refererar vyn till kamerans plats och orientering. En vytransformering flyttar objekt i världen runt kamerans position och orientering. Mer information om visningstransformering finns i Visa transformering (Direct3D 9). Hur Marble Maze använder resurs- och matrisvyer beskrivs mer detaljerat i det här avsnittet.
Laddar scenresurser
Marble Maze använder klassen BasicLoader , som deklareras i BasicLoader.h, för att läsa in texturer och skuggor. Marble Maze använder SDKMesh klass för att ladda 3D-näten för labyrinten och marmorn.
För att säkerställa en dynamisk app läser Marble Maze in scenresurser asynkront eller i bakgrunden. När resurser läses in i bakgrunden kan ditt spel svara på fönsterhändelser. Den här processen beskrivs mer detaljerat i Läsa in speltillgångar i bakgrunden i den här guiden.
Läser in 2D-överlägget och användargränssnittet
I Marble Maze är överlägget den bild som visas överst på skärmen. Överlägget visas alltid framför scenen. I Marble Maze innehåller överlägget Windows-logotypen och textsträngen DirectX Marble Maze-spelexempel. Hanteringen av överlägget utförs av klassen SampleOverlay , som definieras i SampleOverlay.h. Även om vi använder överlägget som en del av Direct3D-exemplen kan du anpassa den här koden så att den visar alla bilder som visas framför scenen.
En viktig aspekt med överlägget är att SampleOverlay-klass ritar eller cachelagrar sitt innehåll till ett ID2D1Bitmap1-objekt under initialiseringen. Vid dragningen behöver klassen SampleOverlay bara rita bitmappen till skärmen. På så sätt behöver dyra rutiner som textritning inte utföras för varje bildruta.
Användargränssnittet (UI) består av 2D-komponenter, till exempel menyer och heads-up-skärmar (HUD) som visas framför scenen. Marble Maze definierar följande gränssnittselement:
- Menyalternativ som gör att användaren kan starta spelet eller visa höga poäng.
- En timer som räknas ned i tre sekunder innan uppspelningen börjar.
- En timer som spårar den förflutna speltiden.
- En tabell som visar de snabbaste sluttiderna.
- Texten visar Pausad när spelet pausas.
Marble Maze definierar spelspecifika gränssnittselement i UserInterface.h. Marble Maze definierar klassen ElementBase som en bastyp för alla gränssnittselement. Klassen ElementBase definierar attribut som storlek, position, justering och synlighet för ett gränssnittselement. Den styr också hur element uppdateras och återges.
class ElementBase
{
public:
virtual void Initialize() { }
virtual void Update(float timeTotal, float timeDelta) { }
virtual void Render() { }
void SetAlignment(AlignType horizontal, AlignType vertical);
virtual void SetContainer(const D2D1_RECT_F& container);
void SetVisible(bool visible);
D2D1_RECT_F GetBounds();
bool IsVisible() const { return m_visible; }
protected:
ElementBase();
virtual void CalculateSize() { }
Alignment m_alignment;
D2D1_RECT_F m_container;
D2D1_SIZE_F m_size;
bool m_visible;
};
Genom att tillhandahålla en gemensam basklass för gränssnittselement behöver klassen UserInterface , som hanterar användargränssnittet, endast innehålla en samling ElementBase-objekt , vilket förenklar UI-hanteringen och ger en användargränssnittshanterare som kan återanvändas. Marble Maze definierar typer som härleds från ElementBase som implementerar spelspecifika beteenden. HighScoreTable definierar till exempel beteendet för tabellen med höga poäng. Mer information om dessa typer finns i källkoden.
Anmärkning
Eftersom XAML gör det enklare att skapa komplexa användargränssnitt, som de som finns i simulerings- och strategispel, bör du överväga om du vill använda XAML för att definiera användargränssnittet. Information om hur du utvecklar ett användargränssnitt i XAML i ett DirectX UWP-spel finns i Utöka spelexemplet, som refererar till DirectX 3D-skjutspelsexemplet.
Läser in skuggningar
Marble Maze använder metoden BasicLoader::LoadShader för att läsa in en skuggning från en fil.
Shaders är den grundläggande enheten för GPU-programmering i spel idag. Nästan all 3D-grafikbearbetning drivs genom skuggor, oavsett om det är modellomvandling och scenbelysning, eller mer komplex geometribearbetning, från karaktärsskalning till tessellation. Mer information om programmeringsmodellen för skuggning finns i HLSL.
Marble Maze använder vertex- och pixel shaders. En hörnskuggning fungerar alltid på ett indatahörn och skapar ett hörn som utdata. En pixelshader tar numeriska värden, texturdata, interpolerade värden per vertix och andra data för att producera en pixelfärg som utdata. Eftersom en skuggning omvandlar ett element i taget kan grafikmaskinvara som tillhandahåller flera skuggningspipelines bearbeta uppsättningar med element parallellt. Antalet parallella pipelines som är tillgängliga för GPU:n kan vara mycket större än det antal som är tillgängligt för processorn. Därför kan även grundläggande skuggningar avsevärt förbättra dataflödet.
Metoden MarbleMazeMain::LoadDeferredResources läser in en hörnskuggare och en pixelskuggare efter att överlägget har lästs in. Designtidsversionerna av dessa shaders definieras i BasicVertexShader.hlsl och i BasicPixelShader.hlsl, respektive. Marble Maze tillämpar dessa skuggor på både bollen och labyrinten under renderingsfasen.
Marble Maze-projektet innehåller både .hlsl (i designläge) och .cso (i körtid) versioner av shaderfilerna. Vid kompilering använder Visual Studio fxc.exe effektkompilator för att kompilera din .hlsl-källa till en binär .cso-shader. Mer information om verktyget effect-compiler finns i Effect-Compiler Verktyg.
Vertexskuggan använder de tillhandahållna modell-, vy- och projektionsmatriserna för att transformera indatageometrin. Positionsdata från indatageometrin transformeras och matas ut två gånger: en gång i skärmutrymmet, vilket är nödvändigt för rendering, och igen i världskoordinater för att göra det möjligt för pixelskuggaren att utföra belysningsberäkningar. Ytans normala vektor omvandlas till världsrymden, som också används av pixelskuggaren för belysning. Texturkoordinaterna skickas oförändrade till pixelskuggningen.
sPSInput main(sVSInput input)
{
sPSInput output;
float4 temp = float4(input.pos, 1.0f);
temp = mul(temp, model);
output.worldPos = temp.xyz / temp.w;
temp = mul(temp, view);
temp = mul(temp, projection);
output.pos = temp;
output.tex = input.tex;
output.norm = mul(float4(input.norm, 0.0f), model).xyz;
return output;
}
Pixelskuggaren tar emot utdata från hörnskuggningen som indata. Denna skuggning utför belysningsberäkningar för att efterlikna en mjuk kantad spotlight som hovrar över labyrinten och är i linje med marmorns position. Belysning är starkast för ytor som pekar direkt mot ljuset. Den diffusa komponenten avsmalnar till noll när ytnormalen blir vinkelrät mot ljuset, och den omgivande komponenten minskar när normallen pekar bort från ljuset. Punkter närmare marmorn (och därför närmare mitten av strålkastarljuset) lyses starkare. Belysningen är dock modulerad för punkter under marmorn för att simulera en mjuk skugga. I en verklig miljö skulle ett objekt som den vita marmorn sprida strålkastarljuset till andra objekt i scenen. Detta är ungefärligt för de ytor som är synliga från marmorns ljusa sida. De extra belysningsfaktorerna är i relativ vinkel och avståndet till marmorn. Den resulterande pixelfärgen är en sammansättning av den samplade strukturen med resultatet av belysningsberäkningarna.
float4 main(sPSInput input) : SV_TARGET
{
float3 lightDirection = float3(0, 0, -1);
float3 ambientColor = float3(0.43, 0.31, 0.24);
float3 lightColor = 1 - ambientColor;
float spotRadius = 50;
// Basic ambient (Ka) and diffuse (Kd) lighting from above.
float3 N = normalize(input.norm);
float NdotL = dot(N, lightDirection);
float Ka = saturate(NdotL + 1);
float Kd = saturate(NdotL);
// Spotlight.
float3 vec = input.worldPos - marblePosition;
float dist2D = sqrt(dot(vec.xy, vec.xy));
Kd = Kd * saturate(spotRadius / dist2D);
// Shadowing from ball.
if (input.worldPos.z > marblePosition.z)
Kd = Kd * saturate(dist2D / (marbleRadius * 1.5));
// Diffuse reflection of light off ball.
float dist3D = sqrt(dot(vec, vec));
float3 V = normalize(vec);
Kd += saturate(dot(-V, N)) * saturate(dot(V, lightDirection))
* saturate(marbleRadius / dist3D);
// Final composite.
float4 diffuseTexture = Texture.Sample(Sampler, input.tex);
float3 color = diffuseTexture.rgb * ((ambientColor * Ka) + (lightColor * Kd));
return float4(color * lightStrength, diffuseTexture.a);
}
Varning
Den kompilerade pixelskuggningen innehåller 32 aritmetiska instruktioner och 1 texturinstruktion. Den här skuggningen bör fungera bra på stationära datorer eller surfplattor med högre prestanda. Men vissa datorer kanske inte kan bearbeta den här skuggningen och ändå tillhandahålla en interaktiv bildfrekvens. Överväg den typiska maskinvaran för målgruppen och utforma dina skuggor för att uppfylla funktionerna i maskinvaran.
Metoden MarbleMazeMain::LoadDeferredResources använder metoden BasicLoader::LoadShader för att läsa in skuggorna. I följande exempel läses vertex shader in. Körningsformatet för det här shaderprogrammet är BasicVertexShader.cso. Den m_vertexShader medlemsvariabeln är ett ID3D11VertexShader-objekt .
BasicLoader^ loader = ref new BasicLoader(m_deviceResources->GetD3DDevice());
D3D11_INPUT_ELEMENT_DESC layoutDesc [] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
m_vertexStride = 44; // must set this to match the size of layoutDesc above
Platform::String^ vertexShaderName = L"BasicVertexShader.cso";
loader->LoadShader(
vertexShaderName,
layoutDesc,
ARRAYSIZE(layoutDesc),
&m_vertexShader,
&m_inputLayout
);
Variabeln m_inputLayout medlem är ett ID3D11InputLayout-objekt . Indatalayoutobjektet kapslar in indatatillståndet för IA-fasen (input assembler). Ett jobb i IA-fasen är att göra skuggningar mer effektiva genom att använda systemgenererade värden, även kallade semantik, för att endast bearbeta de primitiver eller hörn som inte redan har bearbetats.
Använd metoden ID3D11Device::CreateInputLayout för att skapa en indatalayout från en matris med beskrivningar av indataelement. Matrisen innehåller ett eller flera indataelement. varje indataelement beskriver ett hörndataelement från en brytpunktsbuffert. Hela uppsättningen beskrivningar av indataelement beskriver alla hörndataelement från alla hörnbuffertar som ska bindas till IA-fasen.
layoutDesc i kodfragmentet ovan visar layoutbeskrivningen som Marble Maze använder. Layoutbeskrivningen beskriver en brytpunktsbuffert som innehåller fyra hörndataelement. De viktiga delarna i varje post i matrisen är det semantiska namnet, dataformatet och byteförskjutningen . Elementet POSITION anger till exempel hörnpositionen i objektutrymmet. Den börjar vid byteförskjutning 0 och innehåller tre flyttalskomponenter (totalt 12 byte). Elementet NORMAL anger den normala vektorn. Den börjar vid byteförskjutning 12 eftersom den visas direkt efter POSITION i layouten, vilket kräver 12 byte. NORMAL-elementet innehåller ett 32-bitars heltal med fyra komponenter.
Jämför indatalayouten med sVSInput--strukturen som definieras av vertexshadern, som visas i följande exempel. SVSInput-strukturen definierar elementen POSITION, NORMAL och TEXCOORD0. DirectX-runtime mappar varje element i layouten till den indatastruktur som definieras av shadren.
struct sVSInput
{
float3 pos : POSITION;
float3 norm : NORMAL;
float2 tex : TEXCOORD0;
};
struct sPSInput
{
float4 pos : SV_POSITION;
float3 norm : NORMAL;
float2 tex : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
sPSInput main(sVSInput input)
{
sPSInput output;
float4 temp = float4(input.pos, 1.0f);
temp = mul(temp, model);
output.worldPos = temp.xyz / temp.w;
temp = mul(temp, view);
temp = mul(temp, projection);
output.pos = temp;
output.tex = input.tex;
output.norm = mul(float4(input.norm, 0.0f), model).xyz;
return output;
}
Dokumentet Semantik beskriver var och en av de tillgängliga semantikerna i detalj.
Anmärkning
I en layout kan du ange ytterligare komponenter som inte används för att göra det möjligt för flera skuggningar att dela samma layout. Till exempel används inte TANGENT-elementet av skuggningen. Du kan använda TANGENT-elementet om du vill experimentera med tekniker som normal mappning. Genom att använda normal mappning, även kallad bumpmappning, kan du skapa effekten av stötar på objektens ytor. Mer information om bump mapping finns i Bump Mapping (Direct3D 9).
Mer information om indatasammansättningssteget, se Input-Assembler Stage och Getting Started with the Input-Assembler Stage.
Processen att använda hörn- och pixelskuggarna för att återge scenen beskrivs i avsnittet Återge scenen senare i det här dokumentet.
Skapa konstantbufferten
Direct3D-buffert grupperar en samling data. En konstant buffert är en slags buffert som du kan använda för att skicka data till skuggningar. Marble Maze använder en konstant buffert för att hålla modellvyn (eller världsvyn) och projektionsmatriserna för det aktiva scenobjektet.
I följande exempel visas hur metoden MarbleMazeMain::LoadDeferredResources skapar en konstant buffert som senare innehåller matrisdata. I exemplet skapas en D3D11_BUFFER_DESC struktur som använder flaggan D3D11_BIND_CONSTANT_BUFFER för att ange användning som en konstant buffert. Det här exemplet skickar sedan strukturen till metoden ID3D11Device::CreateBuffer . Variabeln m_constantBuffer är ett ID3D11Buffer-objekt .
// Create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};
// Multiple of 16 bytes
constantBufferDesc.ByteWidth = ((sizeof(ConstantBuffer) + 15) / 16) * 16;
constantBufferDesc.Usage = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags = 0;
constantBufferDesc.MiscFlags = 0;
// This will not be used as a structured buffer, so this parameter is ignored.
constantBufferDesc.StructureByteStride = 0;
DX::ThrowIfFailed(
m_deviceResources->GetD3DDevice()->CreateBuffer(
&constantBufferDesc,
nullptr, // leave the buffer uninitialized
&m_constantBuffer
)
);
Metoden MarbleMazeMain::Update uppdaterar senare ConstantBuffer-objekt, ett för labyrinten och ett för marmorn. Metoden MarbleMazeMain::Render binder sedan varje ConstantBuffer-objekt till konstantbufferten innan varje objekt återges. I följande exempel visas ConstantBuffer-strukturen , som finns i MarbleMazeMain.h.
// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
XMFLOAT4X4 model;
XMFLOAT4X4 view;
XMFLOAT4X4 projection;
XMFLOAT3 marblePosition;
float marbleRadius;
float lightStrength;
};
För att bättre förstå hur konstanta buffertar mappas till skuggningskod kan du jämföra ConstantBuffer-strukturen i MarbleMazeMain.h med ConstantBuffer-konstantbufferten som definieras av vertexshadern i BasicVertexShader.hlsl:
cbuffer ConstantBuffer : register(b0)
{
matrix model;
matrix view;
matrix projection;
float3 marblePosition;
float marbleRadius;
float lightStrength;
};
Layouten för ConstantBuffer-strukturen matchar cbuffer-objektet . Cbuffer-variabeln anger register b0, vilket innebär att konstanta buffertdata lagras i register 0. Metoden MarbleMazeMain::Render anger register 0 när den aktiverar konstantbufferten. Den här processen beskrivs mer detaljerat senare i det här dokumentet.
Mer information om konstanta buffertar finns i Introduktion till buffertar i Direct3D 11. Mer information om nyckelordet register finns i register.
Läser in meshmodeller
Marble Maze använder SDK-Mesh som körningsformat eftersom det här formatet är ett grundläggande sätt att läsa in mesh-data för exempelprogram. För produktionsanvändning bör du använda ett mesh-format som uppfyller de specifika kraven i ditt spel.
MarbleMazeMain::LoadDeferredResources-metoden läser in mesh-data när den har läst in hörn- och pixelskuggarna. Ett nät är en samling hörndata som ofta innehåller information som positioner, normala data, färger, material och strukturkoordinater. Nät skapas vanligtvis i 3D-redigeringsprogram och underhålls i filer som är separata från programkoden. Marmorn och labyrinten är två exempel på nät som spelet använder.
Marble Maze använder klassen SDKMesh för att hantera nät. Den här klassen deklareras i SDKMesh.h. SDKMesh tillhandahåller metoder för att läsa in, återge och förstöra nätdata.
Viktigt!
Marble Maze använder formatet SDK-Mesh och tillhandahåller endast SDKMesh-klassen som illustration. Även om det SDK-Mesh formatet är användbart för inlärning, och för att skapa prototyper, är det ett mycket grundläggande format som kanske inte uppfyller kraven för de flesta spelutveckling. Vi rekommenderar att du använder ett mesh-format som uppfyller de specifika kraven i ditt spel.
I följande exempel visas hur metoden MarbleMazeMain::LoadDeferredResources använder metoden SDKMesh::Create för att läsa in mesh-data för labyrinten och för bollen.
// Load the meshes.
DX::ThrowIfFailed(
m_mazeMesh.Create(
m_deviceResources->GetD3DDevice(),
L"Media\\Models\\maze1.sdkmesh",
false
)
);
DX::ThrowIfFailed(
m_marbleMesh.Create(
m_deviceResources->GetD3DDevice(),
L"Media\\Models\\marble2.sdkmesh",
false
)
);
Laddar kollisionsdata
Även om det här avsnittet inte fokuserar på hur Marble Maze implementerar fysiksimuleringen mellan kulan och labyrinten, observera att nätgeometri för fysiksystemet läses när näten laddas in.
// Extract mesh geometry for the physics system.
DX::ThrowIfFailed(
ExtractTrianglesFromMesh(
m_mazeMesh,
"Mesh_walls",
m_collision.m_wallTriList
)
);
DX::ThrowIfFailed(
ExtractTrianglesFromMesh(
m_mazeMesh,
"Mesh_Floor",
m_collision.m_groundTriList
)
);
DX::ThrowIfFailed(
ExtractTrianglesFromMesh(
m_mazeMesh,
"Mesh_floorSides",
m_collision.m_floorTriList
)
);
m_physics.SetCollision(&m_collision);
float radius = m_marbleMesh.GetMeshBoundingBoxExtents(0).x / 2;
m_physics.SetRadius(radius);
Sättet du läser in kollisionsdata på beror till stor del på vilket körningsformat du använder. Mer information om hur Marble Maze läser in kollisionsgeometrin från en SDK-Mesh fil finns i metoden MarbleMazeMain::ExtractTrianglesFromMesh i källkoden.
Uppdatera speltillstånd
Marble Maze separerar spellogik från renderingslogik genom att först uppdatera alla scenobjekt innan de återges.
Marble Maze-programstrukturen beskriver huvudspelsslingan. Uppdatering av scenen, som är en del av spelloopen, sker efter att Windows-händelser och indata har bearbetats och innan scenen återges. Metoden MarbleMazeMain::Update hanterar uppdateringen av användargränssnittet och spelet.
Uppdatera användargränssnittet
Metoden MarbleMazeMain::Update anropar metoden UserInterface::Update för att uppdatera användargränssnittets tillstånd.
UserInterface::GetInstance().Update(
static_cast<float>(m_timer.GetTotalSeconds()),
static_cast<float>(m_timer.GetElapsedSeconds()));
Metoden UserInterface::Update uppdaterar varje element i UI-samlingen.
void UserInterface::Update(float timeTotal, float timeDelta)
{
for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
{
(*iter)->Update(timeTotal, timeDelta);
}
}
Klasser som härleds från ElementBase (definieras i UserInterface.h) implementerar metoden Uppdatera för att utföra specifika beteenden. Metoden StopwatchTimer::Update uppdaterar till exempel den förflutna tiden med den angivna mängden och uppdaterar texten som visas senare.
void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
if (m_active)
{
m_elapsedTime += timeDelta;
WCHAR buffer[16];
GetFormattedTime(buffer);
SetText(buffer);
}
TextElement::Update(timeTotal, timeDelta);
}
Uppdatera scenen
Metoden MarbleMazeMain::Update uppdaterar spelet baserat på tillståndsdatorns aktuella tillstånd ( GameState, som lagras i m_gameState). När spelet är i aktivt tillstånd (GameState::InGameActive) uppdaterar Marble Maze kameran för att följa marmorn, uppdaterar visningsmatrisdelen av de konstanta buffertarna och uppdaterar fysiksimuleringen.
I följande exempel visas hur metoden MarbleMazeMain::Update uppdaterar kamerans position. Marble Maze använder m_resetCamera variabeln för att flagga att kameran måste återställas för att placeras direkt ovanför marmorn. Kameran återställs när spelet startar eller marmorn faller genom labyrinten. När huvudmenyn eller bildskärmen med höga poäng är aktiv ställs kameran in på en konstant plats. Annars använder Marble Maze parametern timeDelta för att interpolera kamerans position mellan dess aktuella positioner och målpositioner. Målpositionen är något ovanför och framför marmorn. Med den förflutna tidsramen kan kameran gradvis följa, eller jaga, marmorn.
static float eyeDistance = 200.0f;
static XMFLOAT3A eyePosition = XMFLOAT3A(0, 0, 0);
// Gradually move the camera above the marble.
XMFLOAT3A targetEyePosition;
XMStoreFloat3A(
&targetEyePosition,
XMLoadFloat3A(&marblePosition) - (XMLoadFloat3A(&g) * eyeDistance));
if (m_resetCamera)
{
eyePosition = targetEyePosition;
m_resetCamera = false;
}
else
{
XMStoreFloat3A(
&eyePosition,
XMLoadFloat3A(&eyePosition)
+ ((XMLoadFloat3A(&targetEyePosition) - XMLoadFloat3A(&eyePosition))
* min(1, static_cast<float>(m_timer.GetElapsedSeconds()) * 8)
)
);
}
// Look at the marble.
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
// Override camera position for menus.
XMStoreFloat3A(
&eyePosition,
XMLoadFloat3A(&marblePosition) + XMVectorSet(75.0f, -150.0f, -75.0f, 0.0f));
m_camera->SetViewParameters(
eyePosition,
marblePosition,
XMFLOAT3(0.0f, 0.0f, -1.0f));
}
else
{
m_camera->SetViewParameters(eyePosition, marblePosition, XMFLOAT3(0.0f, 1.0f, 0.0f));
}
I följande exempel visas hur metoden MarbleMazeMain::Update uppdaterar de konstanta buffertarna för marmorn och labyrinten. Labyrintens modell, eller värld, matris förblir alltid identitetsmatrisen. Förutom huvuddia diagonalen, vars element alla är ettor, är identitetsmatrisen en kvadratmatris som består av nollor. Marmorns modellmatris baseras på dess positionsmatris gånger dess rotationsmatris.
// Update the model matrices based on the simulation.
XMStoreFloat4x4(&m_mazeConstantBufferData.model, XMMatrixIdentity());
XMStoreFloat4x4(
&m_marbleConstantBufferData.model,
XMMatrixTranspose(
XMMatrixMultiply(
marbleRotationMatrix,
XMMatrixTranslationFromVector(XMLoadFloat3A(&marblePosition))
)
)
);
// Update the view matrix based on the camera.
XMFLOAT4X4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;
Information om hur metoden MarbleMazeMain::Update läser användarindata och simulerar marmorns rörelse finns i Lägga till indata och interaktivitet i Marble Maze-exemplet.
Återge scenen
När en scen återges ingår vanligtvis de här stegen.
- Ange djupstencilbufferten för det nuvarande återgivningsmålet.
- Rensa återgivnings- och stencilvyerna.
- Förbered hörn- och pixelskuggarna för ritning.
- Rendera 3D-objekten i scenen.
- Rendera alla 2D-objekt som du vill ska visas framför scenen.
- Visa den renderade bilden för övervakaren.
Metoden MarbleMazeMain::Render binder renderingsmålet och djupstencilvyerna, rensar dessa, ritar scenen och ritar sedan överlägget.
Förbereda återgivningsmålen
Innan du renderar scenen måste du ange den nuvarande djupstencilbufferten för återgivningsmålet. Om det inte är garanterat att scenen täcker varje bildpunkt på skärmen bör du även rensa återgivnings- och stencilvyerna. Marble Maze rensar återgivnings- och stencilvyerna på varje bildruta för att säkerställa att det inte finns några synliga artefakter från föregående bildruta.
I följande exempel visas hur metoden MarbleMazeMain::Render anropar metoden ID3D11DeviceContext::OMSetRenderTargets för att ange återgivningsmålet och djupstencilbufferten som de aktuella.
auto context = m_deviceResources->GetD3DDeviceContext();
// Reset the viewport to target the whole screen.
auto viewport = m_deviceResources->GetScreenViewport();
context->RSSetViewports(1, &viewport);
// Reset render targets to the screen.
ID3D11RenderTargetView *const targets[1] =
{ m_deviceResources->GetBackBufferRenderTargetView() };
context->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
// Clear the back buffer and depth stencil view.
context->ClearRenderTargetView(
m_deviceResources->GetBackBufferRenderTargetView(),
DirectX::Colors::Black);
context->ClearDepthStencilView(
m_deviceResources->GetDepthStencilView(),
D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL,
1.0f,
0);
Gränssnitten ID3D11RenderTargetView och ID3D11DepthStencilView stöder den strukturvymekanism som tillhandahålls av Direct3D 10 och senare. Mer information om texturvyer finns i Texturvyer (Direct3D 10). Metoden OMSetRenderTargets förbereder fasen för utdatafusion i Direct3D-pipelinen. Mer information om fasen för sammanslagning av utdata finns i Output-Merger stage.
Förbereda hörnen och pixelskuggarna
Innan du renderar scenobjekten utför du följande steg för att förbereda hörn- och pixelskuggarna för ritning:
- Ange skuggningsindatalayouten som aktuell layout.
- Ange hörn- och pixelskuggarna som aktuella skuggningar.
- Uppdatera alla konstanta buffertar med data som du måste skicka till skuggorna.
Viktigt!
Marble Maze använder ett par vertex- och pixel-shaders för alla 3D-objekt. Om ditt spel använder fler än ett par skuggningar måste du utföra dessa steg varje gång du ritar objekt som använder olika skuggningar. För att minska de kostnader som är associerade med att ändra skuggningstillståndet rekommenderar vi att du grupperar återgivningsanrop för alla objekt som använder samma skuggning.
Avsnittet Ladda skuggare i det här dokumentet beskriver hur indatalayouten skapas när vertex-skuggaren skapas. I följande exempel visas hur metoden MarbleMazeMain::Render använder metoden ID3D11DeviceContext::IASetInputLayout för att ange den här layouten som aktuell layout.
m_deviceResources->GetD3DDeviceContext()->IASetInputLayout(m_inputLayout.Get());
I följande exempel visas hur metoden MarbleMazeMain::Render använder metoderna ID3D11DeviceContext::VSSetShader och ID3D11DeviceContext::P SSetShader för att ange hörn- respektive pixelskuggarna som aktuella skuggningar.
// Set the vertex shader stage state.
m_deviceResources->GetD3DDeviceContext()->VSSetShader(
m_vertexShader.Get(), // use this vertex shader
nullptr, // don't use shader linkage
0); // don't use shader linkage
m_deviceResources->GetD3DDeviceContext()->PSSetShader(
m_pixelShader.Get(), // use this pixel shader
nullptr, // don't use shader linkage
0); // don't use shader linkage
m_deviceResources->GetD3DDeviceContext()->PSSetSamplers(
0, // starting at the first sampler slot
1, // set one sampler binding
m_sampler.GetAddressOf()); // to use this sampler
När MarbleMazeMain::Render anger skuggningsfilerna och deras indatalayout använder den metoden ID3D11DeviceContext::UpdateSubresource för att uppdatera den konstanta bufferten med modell-, vy- och projektionsmatriser för labyrinten. Metoden UpdateSubresource kopierar matrisdata från CPU-minne till GPU-minne. Kom ihåg att modell- och vykomponenterna i ConstantBuffer-strukturen uppdateras i metoden MarbleMazeMain::Update . Metoden MarbleMazeMain::Render anropar sedan metoderna ID3D11DeviceContext::VSSetConstantBuffers och ID3D11DeviceContext::PSSetConstantBuffers för att ange denna konstanta buffert som den aktuella.
// Update the constant buffer with the new data.
m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
m_constantBuffer.Get(),
0,
nullptr,
&m_mazeConstantBufferData,
0,
0);
m_deviceResources->GetD3DDeviceContext()->VSSetConstantBuffers(
0, // starting at the first constant buffer slot
1, // set one constant buffer binding
m_constantBuffer.GetAddressOf()); // to use this buffer
m_deviceResources->GetD3DDeviceContext()->PSSetConstantBuffers(
0, // starting at the first constant buffer slot
1, // set one constant buffer binding
m_constantBuffer.GetAddressOf()); // to use this buffer
Metoden MarbleMazeMain::Render utför liknande steg för att förbereda marmorn som ska återges.
Återge labyrinten och marmorn
När du har aktiverat de aktuella skuggningsobjekten kan du rita scenobjekten. Metoden MarbleMazeMain::Render anropar metoden SDKMesh::Render för att återge labyrintnätet.
m_mazeMesh.Render(
m_deviceResources->GetD3DDeviceContext(),
0,
INVALID_SAMPLER_SLOT,
INVALID_SAMPLER_SLOT);
Metoden MarbleMazeMain::Render utför liknande steg för att återge marmorn.
Som tidigare nämnts i det här dokumentet tillhandahålls klassen SDKMesh i demonstrationssyfte, men vi rekommenderar inte att den används i ett spel av produktionskvalitet. Observera dock att metoden SDKMesh::RenderMesh , som anropas av SDKMesh::Render, använder metoderna ID3D11DeviceContext::IASetVertexBuffers och ID3D11DeviceContext::IASetIndexBuffer för att ange det aktuella hörnet och indexbuffertarna som definierar nätet och ID3D11DeviceContext::D rawIndexed-metoden för att rita buffertarna. Mer information om hur du arbetar med hörn- och indexbuffertar finns i Introduktion till buffertar i Direct3D 11.
Rita användargränssnittet och överlägget
Efter att ha ritat 3D-scenobjekt ritar Marble Maze de 2D UI-element som visas framför scenen.
Metoden MarbleMazeMain::Render slutar med att rita användargränssnittet och överlägget.
// Draw the user interface and the overlay.
UserInterface::GetInstance().Render(m_deviceResources->GetOrientationTransform2D());
m_deviceResources->GetD3DDeviceContext()->BeginEventInt(L"Render Overlay", 0);
m_sampleOverlay->Render();
m_deviceResources->GetD3DDeviceContext()->EndEvent();
Metoden UserInterface::Render använder ett ID2D1DeviceContext-objekt för att rita användargränssnittselementen. Den här metoden anger ritningstillståndet, ritar alla aktiva gränssnittselement och återställer sedan det tidigare ritningstillståndet.
void UserInterface::Render(D2D1::Matrix3x2F orientation2D)
{
m_d2dContext->SaveDrawingState(m_stateBlock.Get());
m_d2dContext->BeginDraw();
m_d2dContext->SetTransform(orientation2D);
m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);
for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
{
if ((*iter)->IsVisible())
(*iter)->Render();
}
// We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
// is lost. It will be handled during the next call to Present.
HRESULT hr = m_d2dContext->EndDraw();
if (hr != D2DERR_RECREATE_TARGET)
{
DX::ThrowIfFailed(hr);
}
m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}
Metoden SampleOverlay::Render använder en liknande teknik för att rita överläggsbitmappen.
Presentera scenen
När du har ritat alla 2D- och 3D-scenobjekt presenterar Marble Maze den renderade bilden för bildskärmen. Den synkroniserar ritningen till det lodräta tomstrecket för att säkerställa att tiden inte ägnas tid åt att rita ramar som aldrig visas på skärmen. Marble Maze hanterar också enhetsändringar när den visar scenen.
När metoden MarbleMazeMain::Render returneras anropar spelloopen DX::DeviceResources::Present-metoden för att skicka den renderade bilden till bildskärmen eller skärmen. Metoden DX::DeviceResources::Present anropar IDXGISwapChain::Present för att utföra present-operationen, enligt följande exempel:
// 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);
I det här exemplet är m_swapChain ett IDXGISwapChain1-objekt . Initieringen av det här objektet beskrivs i avsnittet Initiera Direct3D och Direct2D i det här dokumentet.
Den första parametern för att IDXGISwapChain::P resent, SyncInterval, anger antalet lodräta blanksteg som ska vänta innan ramen presenteras. Marble Maze anger 1 så att den väntar till nästa lodräta tom.
Metoden IDXGISwapChain::P resent returnerar en felkod som anger att enheten har tagits bort eller på annat sätt misslyckats. I det här fallet initierar Marble Maze enheten igen.
// If the device was removed either by a disconnection or a driver upgrade, we
// must recreate all device resources.
if (hr == DXGI_ERROR_DEVICE_REMOVED)
{
HandleDeviceLost();
}
else
{
DX::ThrowIfFailed(hr);
}
Nästa steg
Läs Lägga till indata och interaktivitet i Marble Maze-exemplet för information om några av de viktigaste metoderna att tänka på när du arbetar med indataenheter. I det här dokumentet beskrivs hur Marble Maze stöder touch, accelerometer, spelkontrollanter och musinmatning.
Relaterade ämnen
- Lägga till indata och interaktivitet i Marble Maze-exemplet
- Marble Maze-programstruktur
- Utveckla Marble Maze, ett UWP-spel i C++ och DirectX