Visuele inhoud toevoegen aan het Marble Maze-voorbeeld

In dit document wordt beschreven hoe het Marble Maze-spel Direct3D en Direct2D gebruikt in de UWP-app-omgeving (Universal Windows Platform), zodat u de patronen kunt leren en aanpassen wanneer u met uw eigen game-inhoud werkt. Zie Marble Maze-toepassingsstructuur voor meer informatie over hoe visuele gameonderdelen passen in de algehele toepassingsstructuur van Marble Maze.

We hebben deze basisstappen gevolgd tijdens het ontwikkelen van de visuele aspecten van Marble Maze:

  1. Maak een basisframework waarmee de Direct3D- en Direct2D-omgevingen worden geïnitialiseerd.
  2. Gebruik programma's voor het bewerken van afbeeldingen en modellen om de 2D- en 3D-assets te ontwerpen die in het spel worden weergegeven.
  3. Zorg ervoor dat 2D- en 3D-assets correct worden geladen en weergegeven in het spel.
  4. Integreer hoekpunt- en pixel-shaders die de visuele kwaliteit van de game-assets verbeteren.
  5. Integreer gamelogica, zoals animatie en gebruikersinvoer.

We hebben ons ook eerst gericht op het toevoegen van 3D-assets en vervolgens op 2D-assets. We hebben ons bijvoorbeeld gericht op kernspellogica voordat we het menusysteem en de timer hebben toegevoegd.

We moesten ook enkele van deze stappen meerdere keren herhalen tijdens het ontwikkelingsproces. Als we bijvoorbeeld wijzigingen hebben aangebracht in de mesh- en marmeren modellen, moesten we ook enkele arceringscode wijzigen die deze modellen ondersteunt.

Opmerking

De voorbeeldcode die overeenkomt met dit document vindt u in het DirectX Marble Maze-gamevoorbeeld.

  Hier volgen enkele belangrijke punten die in dit document worden besproken wanneer u met DirectX en visuele game-inhoud werkt, namelijk wanneer u de DirectX-grafische bibliotheken initialiseert, scènebronnen laadt en de scène bijwerkt en weergeeft:

  • Het toevoegen van game-inhoud omvat doorgaans veel stappen. Voor deze stappen is ook vaak iteratie vereist. Gameontwikkelaars richten zich vaak eerst op het toevoegen van 3D-game-inhoud en vervolgens op het toevoegen van 2D-inhoud.
  • Bereik meer klanten en geef ze allemaal een geweldige ervaring door het grootste scala aan grafische hardware zo mogelijk te ondersteunen.
  • Netjes gescheiden indelingen voor ontwerptijd en runtime. Structureer uw ontwerptijdassets om de flexibiliteit te maximaliseren en snelle iteraties voor inhoud mogelijk te maken. Maak en comprimeer uw assets om tijdens runtime zo efficiënt mogelijk te laden en weer te geven.
  • U maakt de Direct3D- en Direct2D-apparaten in een UWP-app, net zoals in een klassieke Windows-desktop-app. Een belangrijk verschil is hoe de swap chain is gekoppeld aan het uitvoervenster.
  • Wanneer u uw game ontwerpt, moet u ervoor zorgen dat de mesh-indeling die u kiest, uw belangrijkste scenario's ondersteunt. Als uw spel bijvoorbeeld botsingen vereist, moet u ervoor zorgen dat u botsingsgegevens van uw meshes kunt verkrijgen.
  • Scheid gamelogica van renderinglogica door eerst alle scèneobjecten bij te werken voordat u ze weergeeft.
  • Normaal gesproken tekent u uw 3D-scèneobjecten en vervolgens eventuele 2D-objecten die voor de scène worden weergegeven.
  • Synchroniseer de tekening met het verticale vak om ervoor te zorgen dat uw game geen tijd besteedt aan tekenframes die nooit daadwerkelijk op het scherm worden weergegeven. Een verticale lege is de tijd tussen het moment waarop één frame klaar is met tekenen op de monitor en het volgende frame begint.

Aan de slag met DirectX-afbeeldingen

Toen we het UWP-spel (Marble Maze Universal Windows Platform) gepland hebben, hebben we C++ en Direct3D 11.1 gekozen omdat ze uitstekende keuzes zijn voor het maken van 3D-games waarvoor maximale controle over rendering en hoge prestaties is vereist. DirectX 11.1 ondersteunt hardware van DirectX 9 naar DirectX 11 en kan u daarom helpen meer klanten efficiënter te bereiken, omdat u geen code hoeft te herschrijven voor elk van de eerdere DirectX-versies.

Marble Maze gebruikt Direct3D 11.1 om de 3D spelactiva weer te geven, namelijk het marmer en het doolhof. Marble Maze maakt ook gebruik van Direct2D, DirectWrite en Windows Imaging Component (WIC) om de 2D-gameactiva te tekenen, zoals de menu's en de timer.

Voor gameontwikkeling is planning vereist. Als u nog niet bekend bent met DirectX-afbeeldingen, raden we u aan DirectX te lezen: Aan de slag om vertrouwd te raken met de basisconcepten van het maken van een UWP DirectX-game. Wanneer u dit document leest en de broncode van Marble Maze doorloopt, kunt u verwijzen naar de volgende bronnen voor uitgebreidere informatie over DirectX-afbeeldingen:

  • Direct3D 11 Graphics: Beschrijft Direct3D 11, een krachtige, hardware-versnelde 3D graphics-API voor het weergeven van 3D-geometrie op het Windows-platform.
  • Direct2D: Beschrijft Direct2D, een hardware-versnelde 2D-grafische API die hoge prestaties en hoogwaardige rendering biedt voor 2D-geometrie, bitmaps en tekst.
  • DirectWrite: Beschrijft DirectWrite, dat ondersteuning biedt voor tekstweergave van hoge kwaliteit.
  • Windows Imaging Component: Beschrijft WIC, een uitbreidbaar platform dat API op laag niveau biedt voor digitale afbeeldingen.

Functieniveaus

Direct3D 11 introduceert een paradigma met benoemde functieniveaus. Een functieniveau is een goed gedefinieerde set GPU-functionaliteit. Gebruik functieniveaus om uw game te richten op eerdere versies van Direct3D-hardware. Marble Maze ondersteunt functieniveau 9.1 omdat hiervoor geen geavanceerde functies van de hogere niveaus nodig zijn. We raden u aan om het grootste aantal hardwaremogelijkheden te ondersteunen en uw game-inhoud te schalen, zodat uw klanten die computers met hoge of lage computers hebben, allemaal een geweldige ervaring hebben. Zie Direct3D 11 op Downlevel Hardware voor meer informatie over functieniveaus.

Direct3D en Direct2D initialiseren

Een apparaat vertegenwoordigt de beeldschermadapter. U maakt de Direct3D- en Direct2D-apparaten in een UWP-app, net zoals in een klassieke Windows-desktop-app. Het belangrijkste verschil is hoe u de Direct3D-wisselketen verbindt met het venstersysteem.

De klasse DeviceResources is een basis voor het beheren van Direct3D en Direct2D. Deze klasse verwerkt algemene infrastructuur, niet gamespecifieke assets. Marble Maze definieert de klasse MarbleMazeMain voor het verwerken van gamespecifieke assets, die een verwijzing naar een DeviceResources-object heeft om het toegang te geven tot Direct3D en Direct2D.

Tijdens het initialisatieproces maakt de DeviceResources constructor apparaatonafhankelijke bronnen aan en creëert de Direct3D- en Direct2D-apparaten.

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

De klasse DeviceResources scheidt deze functionaliteit, zodat deze gemakkelijker kan reageren wanneer de omgeving verandert. Hiermee wordt bijvoorbeeld de methode CreateWindowSizeDependentResources aangeroepen wanneer de venstergrootte wordt gewijzigd.

De Direct2D-, DirectWrite- en WIC-fabrieken initialiseren

De methode DeviceResources::CreateDeviceIndependentResources maakt de factory's voor Direct2D, DirectWrite en WIC. In DirectX-afbeeldingen zijn factories de beginpunten voor het maken van grafische middelen. Marble Maze geeft D2D1_FACTORY_TYPE_SINGLE_THREADED aan omdat het alle tekening op de hoofddraad uitvoert.

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

De Direct3D- en Direct2D-apparaten maken

De methode DeviceResources::CreateDeviceResources roept D3D11CreateDevice aan om het apparaatobject te maken dat de Direct3D-weergaveadapter vertegenwoordigt. Omdat Marble Maze functieniveau 9.1 en hoger ondersteunt, geeft de methode DeviceResources::CreateDeviceResources niveaus 9.1 tot en met 11.1 op in de matrix featureLevels . Direct3D doorloopt de lijst op volgorde en geeft de app het eerste functieniveau dat beschikbaar is. Daarom worden de D3D_FEATURE_LEVEL matrixvermeldingen van hoog naar laag weergegeven, zodat de app het hoogste functieniveau beschikbaar krijgt. De methode DeviceResources::CreateDeviceResources verkrijgt het Direct3D 11.1-apparaat door een query uit te voeren op het Direct3D 11-apparaat dat wordt geretourneerd door 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)
    );

De methode DeviceResources::CreateDeviceResources maakt vervolgens het Direct2D-apparaat. Direct2D maakt gebruik van Microsoft DirectX Graphics Infrastructure (DXGI) om samen te werken met Direct3D. DXGI maakt het mogelijk om videogeheugenoppervlakken te delen tussen grafische runtimes. Marble Maze maakt gebruik van het onderliggende DXGI-apparaat van het Direct3D-apparaat om het Direct2D-apparaat te maken vanuit de Direct2D-fabriek.

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

Zie dxgi-overzicht en direct2D- en Direct3D-interoperabiliteitsoverzicht voor meer informatie over DXGI en interoperabiliteit tussen Direct2D en Direct3D.

Direct3D koppelen aan de weergave

Met de methode DeviceResources::CreateWindowSizeDependentResources worden de grafische resources gemaakt die afhankelijk zijn van een bepaalde venstergrootte, zoals de wisselketen en Direct3D- en Direct2D-renderdoelen. Een belangrijke manier waarop een DirectX UWP-app verschilt van een desktop-app is hoe de wisselketen is gekoppeld aan het uitvoervenster. Een swap chain is verantwoordelijk voor het weergeven van de buffer die door het apparaat op de monitor wordt gerenderd. De toepassingsstructuur Marble Maze beschrijft hoe het venstersysteem voor een UWP-app verschilt van een desktop-app. Omdat een UWP-app niet werkt met HWND-objecten , moet Marble Maze de methode IDXGIFactory2::CreateSwapChainForCoreWindow gebruiken om de apparaatuitvoer aan de weergave te koppelen. In het volgende voorbeeld ziet u het deel van de methode DeviceResources::CreateWindowSizeDependentResources waarmee de wisselketen wordt gemaakt.

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

Als u het energieverbruik wilt minimaliseren, wat belangrijk is voor apparaten die op batterijen werken, zoals laptops en tablets, roept de DeviceResources::CreateWindowSizeDependentResources methode de IDXGIDevice1::SetMaximumFrameLatency methode aan om ervoor te zorgen dat het spel alleen na de verticale blanco wordt weergegeven. Synchroniseren met de verticale blanking wordt uitgebreid beschreven in de sectie De scène presenteren in dit document.

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

De methode DeviceResources::CreateWindowSizeDependentResources initialiseert grafische resources op een manier die voor de meeste games werkt.

Opmerking

De term weergave heeft een andere betekenis binnen de Windows Runtime dan in Direct3D. In Windows Runtime verwijst een weergave naar de verzameling gebruikersinterface-instellingen voor een app, inclusief het weergavegebied en het invoergedrag, plus de thread die wordt gebruikt voor verwerking. U geeft de configuratie en instellingen op die u nodig hebt wanneer u een weergave maakt. Het proces voor het instellen van de app-weergave wordt beschreven in de toepassingsstructuur van Marble Maze. In Direct3D heeft de term 'weergave' meerdere betekenissen. Een resourceweergave definieert de subresources waartoe een resource toegang heeft. Wanneer een texture-object bijvoorbeeld is gekoppeld aan een shaderresourceweergave, kan die shader later toegang krijgen tot de texture. Een voordeel van een resourceweergave is dat u gegevens op verschillende manieren in verschillende fasen in de renderingpijplijn kunt interpreteren. Zie Resourceweergavenvoor meer informatie over resourceweergaven. Wanneer een weergavetransformatie of weergavetransformatie matrix wordt gebruikt, verwijst 'weergave' naar de locatie en oriëntatie van de camera. Een weergavetransformatie verplaatst objecten in de wereld rond de positie en stand van de camera. Zie Weergavetransformatie (Direct3D 9) voor meer informatie over weergavetransformaties. Hoe Marble Maze resource- en matrixweergaven gebruikt, wordt in dit onderwerp uitgebreider beschreven.

 

Het laden van scènebronnen

Marble Maze maakt gebruik van de BasicLoader-klasse , die wordt gedeclareerd in BasicLoader.h, om patronen en shaders te laden. Marble Maze gebruikt de SDKMesh-klasse om de 3D-meshes voor het doolhof en het marmer te laden.

Om een responsieve app te garanderen, laadt Marble Maze scèneresources asynchroon of op de achtergrond. Wanneer assets op de achtergrond worden geladen, kan uw game reageren op vensterevenementen. Dit proces wordt uitgebreid beschreven in het laden van gameactiva op de achtergrond in deze handleiding.

De 2D-overlay en de gebruikersinterface laden

In Marble Maze is de overlay de afbeelding die boven aan het scherm wordt weergegeven. De overlay verschijnt altijd voor de scène. In Marble Maze bevat de overlay het Windows-logo en de tekenreeks DirectX Marble Maze-spelvoorbeeld. Het beheer van de overlay wordt uitgevoerd door de klasse SampleOverlay , die is gedefinieerd in SampleOverlay.h. Hoewel we de overlay gebruiken als onderdeel van de Direct3D-voorbeelden, kunt u deze code aanpassen om elke afbeelding weer te geven die voor uw scène wordt weergegeven.

Een belangrijk aspect van de overlay is dat, omdat de inhoud ervan niet verandert, de klasse SampleOverlay tijdens de initialisatie de inhoud tekent of in de cache opneemt in een ID2D1Bitmap1 object. Tijdens het tekenen hoeft de klasse SampleOverlay alleen de bitmap naar het scherm te tekenen. Op deze manier hoeven dure routines zoals teksttekeningen niet voor elk frame te worden uitgevoerd.

De gebruikersinterface (UI) bestaat uit 2D-onderdelen, zoals menu's en hoofdweergaven (HUD's), die voor uw scène worden weergegeven. Marble Maze definieert de volgende ui-elementen:

  • Menu-items waarmee de gebruiker de game kan starten of hoge scores kan bekijken.
  • Een timer die drie seconden aftelt voordat het afspelen begint.
  • Een timer waarmee de verstreken afspeeltijd wordt bijgehouden.
  • Een tabel met de snelste eindtijden.
  • Tekst die Gepauzeerd verschijnt wanneer het spel is gepauzeerd.

Marble Maze definieert spelspecifieke UI-elementen in UserInterface.h. Marble Maze definieert de ElementBase-klasse als basistype voor alle UI-elementen. De ElementBase-klasse definieert kenmerken zoals de grootte, positie, uitlijning en zichtbaarheid van een UI-element. Ook wordt bepaald hoe elementen worden bijgewerkt en weergegeven.

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

Door een algemene basisklasse voor UI-elementen op te geven, hoeft de UserInterface-klasse , die de gebruikersinterface beheert, alleen een verzameling ElementBase-objecten te bevatten, die het beheer van de gebruikersinterface vereenvoudigt en een gebruikersinterfacebeheer biedt dat herbruikbaar is. Marble Maze definieert typen die zijn afgeleid van ElementBase die spelspecifieke gedragingen implementeren. HighScoreTable definieert bijvoorbeeld het gedrag voor de tabel met hoge score. Raadpleeg de broncode voor meer informatie over deze typen.

Opmerking

Omdat u met XAML eenvoudiger complexe gebruikersinterfaces kunt maken, zoals die in simulatie- en strategiegames, kunt u overwegen of u XAML kunt gebruiken om uw gebruikersinterface te definiëren. Zie voor informatie over hoe je een gebruikersinterface in XAML kunt ontwikkelen in een DirectX UWP-game, Breid het spelvoorbeeld uit, wat verwijst naar het DirectX 3D-schietspelvoorbeeld.

 

Shaders laden

Marble Maze maakt gebruik van de methode BasicLoader::LoadShader om een shader uit een bestand te laden.

Shaders zijn tegenwoordig de fundamentele eenheid van GPU-programmering in games. Bijna alle 3D-grafische verwerking wordt aangestuurd door shaders, of het nu gaat om modeltransformatie en scèneverlichting, of complexere geometrieverwerking, van karakterhuidverwerking tot tesellatie. Zie HLSLvoor meer informatie over het shader-programmeermodel.

Marble Maze maakt gebruik van hoekpunt- en pixel-shaders. Een hoekpunt-shader werkt altijd op één invoerpunt en produceert één hoekpunt als uitvoer. Een pixel-shader gebruikt numerieke waarden, bitmapgegevens, geïnterpoleerde waarden per hoekpunt en andere gegevens om een pixelkleur als uitvoer te produceren. Omdat een shader één element tegelijk transformeert, kan grafische hardware die meerdere shaderpijplijnen biedt, sets van elementen parallel verwerken. Het aantal parallelle pijplijnen dat beschikbaar is voor de GPU, kan aanzienlijk groter zijn dan het aantal dat beschikbaar is voor de CPU. Daarom kunnen zelfs eenvoudige shaders de doorvoer aanzienlijk verbeteren.

De methode MarbleMazeMain::LoadDeferredResources laadt één hoekpunt-shader en één pixel-shader nadat de overlay is geladen. De ontwerptijdversies van deze shaders worden gedefinieerd in respectievelijk BasicVertexShader.hlsl en Basic PixelShader.hlsl. Marble Maze past deze shaders toe op zowel de bal als het doolhof tijdens de renderingfase.

Het Project Marble Maze bevat zowel .hlsl-versies (de ontwerptijdnotatie) als .cso -versies (de runtime-indeling) van de shader-bestanden. Tijdens de build gebruikt Visual Studio de fxc.exe effect-compiler om uw HLSL-bronbestand te compileren in een .cso binaire shader. Zie Effect-Compiler Tool voor meer informatie over het hulpprogramma effect-compiler.

De hoekpunt-shader maakt gebruik van het geleverde model, de weergave- en projectiematrices om de geometrie van de invoer te transformeren. Positiegegevens van de invoergeometrie worden twee keer getransformeerd en de uitvoer wordt tweemaal uitgevoerd: eenmaal in de schermruimte, die nodig is voor rendering, en opnieuw in de wereldruimte om de pixel-shader in staat te stellen verlichtingsberekeningen uit te voeren. De normale oppervlaktevector wordt getransformeerd naar wereldruimte, die ook wordt gebruikt door de pixel-shader voor verlichting. De patrooncoördinaten worden ongewijzigd doorgegeven aan de pixel-shader.

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

De pixelshader ontvangt de uitvoer van de vertexshader als invoer. Deze shader voert verlichtingsberekeningen uit om een spotlight met zachte randen te simuleren die boven het doolhof zweeft en is uitgelijnd met de positie van de bal. Verlichting is sterk voor oppervlakken die rechtstreeks naar het licht wijzen. De diffuse component neemt af tot nul wanneer de oppervlaknormaal loodrecht op het licht komt te staan, en de omgevingsterm neemt af wanneer de normaal van het licht af wijst. Punten dichter bij het marmer (en dus dichter bij het midden van de spotlight) worden sterker verlicht. Verlichting wordt echter gemoduleerd voor punten onder het marmer om een zachte schaduw te simuleren. In een echte omgeving zou een object zoals het witte marmer de spotlight op andere objecten in de scène verspreiden. Dit is bij benadering voor de oppervlakken die te zien zijn op de heldere helft van het marmer. De extra verlichtingsfactoren liggen in relatieve hoek en afstand tot het marmer. De resulterende pixelkleur is een samenstelling van het gemonsterde patroon met het resultaat van de belichtingsberekeningen.

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

Waarschuwing

De gecompileerde pixel-shader bevat 32 rekenkundige instructies en 1 patrooninstructie. Deze shader moet goed presteren op desktopcomputers of tablets met een hogere prestaties. Sommige computers kunnen deze shader echter mogelijk niet verwerken en nog steeds een interactieve framesnelheid bieden. Houd rekening met de typische hardware van uw doelgroep en ontwerp uw shaders om te voldoen aan de mogelijkheden van die hardware.

 

De methode MarbleMazeMain::LoadDeferredResources maakt gebruik van de methode BasicLoader::LoadShader om de shaders te laden. In het volgende voorbeeld wordt de vertex shader geladen. De runtime-indeling voor deze shader is BasicVertexShader.cso. De m_vertexShader lidvariabele is een ID3D11VertexShader-object.

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

De m_inputLayout lidvariabele is een ID3D11InputLayout-object. Het invoerlayout-object omvat de invoertoestand van de IA-fase (input assembler). Een taak van de IA-fase is om shaders efficiënter te maken met behulp van door het systeem gegenereerde waarden, ook wel semantiek genoemd, om alleen die primitieven of hoekpunten te verwerken die nog niet zijn verwerkt.

Gebruik de methode ID3D11Device::CreateInputLayout om een invoerindeling te maken op basis van een matrix met beschrijvingen van invoerelementen. De matrix bevat een of meer invoerelementen; elk invoerelement beschrijft één hoekpuntgegevenselement van één hoekpuntbuffer. In de volledige set beschrijvingen van invoerelementen worden alle hoekpuntgegevenselementen van alle hoekpuntbuffers beschreven die aan de IA-fase gebonden zullen worden.

layoutDesc in het bovenstaande codefragment toont de indelingsbeschrijving die Marble Maze gebruikt. In de indelingsbeschrijving wordt een hoekpuntbuffer beschreven die vier hoekpuntgegevenselementen bevat. De belangrijke onderdelen van elke vermelding in de matrix zijn de semantische naam, de gegevensindeling en de byte-offset. Het element POSITION geeft bijvoorbeeld de hoekpuntpositie in objectruimte op. Het begint bij byte offset 0 en bevat drie drijvendekommaonderdelen (voor een totaal van 12 bytes). Het element NORMAL geeft de normale vector aan. Het begint bij byte offset 12 omdat deze direct na POSITION in de indeling wordt weergegeven, waarvoor 12 bytes nodig zijn. Het element NORMAL bevat een 32-bits niet-ondertekend geheel getal met vier onderdelen.

Vergelijk de invoerindeling met de sVSInput-structuur die is gedefinieerd door de hoekpunt-shader, zoals wordt weergegeven in het volgende voorbeeld. De sVSInput-structuur definieert de elementen POSITION, NORMAL en TEXCOORD0 . De DirectX-runtime wijst elk element in de indeling toe aan de invoerstructuur die is gedefinieerd door de shader.

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

De Semantiek van het document beschrijft elk van de beschikbare semantiek in meer detail.

Opmerking

In een indeling kunt u extra onderdelen opgeven die niet worden gebruikt om meerdere shaders in te schakelen om dezelfde indeling te delen. Het element TANGENT wordt bijvoorbeeld niet door de shader gebruikt. U kunt het element TANGENS gebruiken als u wilt experimenteren met technieken zoals normal mapping. Met behulp van normale toewijzing, ook wel bekend als hobbeltoewijzing, kunt u het effect van stoten op de oppervlakken van objecten creëren. Voor meer informatie over Bump Mapping (Direct3D 9), zie .

 

Voor meer informatie over de invoerassemblyfase, zie Input-Assembler Fase en Aan de slag met de Input-Assembler Fase.

Het proces van het gebruik van de hoekpunt- en pixel-shaders om de scène weer te geven, wordt beschreven in de sectie De scène weergeven later in dit document.

De constante buffer maken

Direct3D-buffer groepeerde een verzameling gegevens. Een constante buffer is een soort buffer die u kunt gebruiken om gegevens door te geven aan shaders. Marble Maze gebruikt een constante buffer om de modelweergave (of wereldweergave) en de projectiematrices voor het actieve scèneobject vast te houden.

In het volgende voorbeeld ziet u hoe de methode MarbleMazeMain::LoadDeferredResources een constante buffer maakt die later matrixgegevens bevat. In het voorbeeld wordt een D3D11_BUFFER_DESC structuur gemaakt die gebruikmaakt van de D3D11_BIND_CONSTANT_BUFFER vlag om het gebruik op te geven als een constante buffer. In dit voorbeeld wordt die structuur vervolgens doorgegeven aan de methode ID3D11Device::CreateBuffer . De m_constantBuffer variabele is een ID3D11Buffer-object .

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

De methode MarbleMazeMain::Update werkt later ConstantBuffer-objecten bij, één voor het doolhof en één voor de marmeren. De methode MarbleMazeMain::Render bindt vervolgens elk ConstantBuffer-object aan de constante buffer voordat elk object wordt weergegeven. In het volgende voorbeeld ziet u de ConstantBuffer-structuur , die zich in MarbleMazeMain.h bevindt.

// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
    XMFLOAT4X4 model;
    XMFLOAT4X4 view;
    XMFLOAT4X4 projection;

    XMFLOAT3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

Als u beter wilt begrijpen hoe constante buffers worden toegewezen aan shader-code, vergelijkt u de ConstantBuffer-structuur in MarbleMazeMain.h met de ConstantBuffer-constante buffer die is gedefinieerd door de vertex shader in BasicVertexShader.hlsl.

cbuffer ConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

De indeling van de ConstantBuffer-structuur komt overeen met het cbuffer-object . De variabele cbuffer specificeert register b0, wat betekent dat de constante buffergegevens worden opgeslagen in register 0. De methode MarbleMazeMain::Render specificeert register 0 wanneer de constante buffer wordt geactiveerd. Dit proces wordt verderop in dit document uitgebreider beschreven.

Zie Inleiding tot buffers in Direct3D 11 voor meer informatie over constante buffers. Voor meer informatie over het trefwoord 'register', zie register.

Netten laden

Marble Maze gebruikt SDK-Mesh als runtime-indeling, omdat deze indeling een eenvoudige manier biedt om mesh-gegevens te laden voor voorbeeldtoepassingen. Voor productiegebruik moet u een mesh-indeling gebruiken die voldoet aan de specifieke vereisten van uw game.

Met de methode MarbleMazeMain::LoadDeferredResources worden mesh-gegevens geladen nadat het hoekpunt en pixel-shaders zijn geladen. Een mesh is een verzameling hoekpuntgegevens die vaak informatie bevatten zoals posities, normale gegevens, kleuren, materialen en patrooncoördinaten. Meshes worden doorgaans gemaakt in 3D-creatiesoftware en onderhouden in bestanden die gescheiden zijn van toepassingscode. Het marmer en het doolhof zijn twee voorbeelden van meshes die het spel gebruikt.

Marble Maze maakt gebruik van de SDKMesh-klasse om meshes te beheren. Deze klasse wordt gedeclareerd in SDKMesh.h. SDKMesh biedt methoden voor het laden, weergeven en vernietigen van mesh-gegevens.

Belangrijk

Marble Maze maakt gebruik van de SDK-Mesh-indeling en biedt alleen de SDKMesh-klasse voor illustratie. Hoewel de SDK-Mesh-indeling nuttig is voor het leren en voor het maken van prototypen, is het een zeer eenvoudige indeling die mogelijk niet voldoet aan de vereisten van de meeste gameontwikkeling. We raden u aan een mesh-indeling te gebruiken die voldoet aan de specifieke vereisten van uw game.

 

In het volgende voorbeeld ziet u hoe de methode MarbleMazeMain::LoadDeferredResources de methode SDKMesh::Create gebruikt om mesh-gegevens te laden voor het doolhof en voor de bal.

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

Collisiegegevens laden

Hoewel deze sectie zich niet richt op hoe Marble Maze de fysicasimulatie tussen het marmer en het doolhof implementeert, moet u er rekening mee houden dat meshgeometrie voor het fysicasysteem wordt gelezen wanneer de meshes worden geladen.

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

De manier waarop u botsingsgegevens laadt, is grotendeels afhankelijk van de runtime-indeling die u gebruikt. Zie de MarbleMazeMain::ExtractTrianglesFromMesh methode in de broncode voor meer informatie over hoe Marble Maze de botsingsgeometrie laadt uit een SDK-Mesh bestand.

De gamestatus bijwerken

Marble Maze scheidt gamelogica van renderinglogica door eerst alle scèneobjecten bij te werken voordat ze worden weergegeven.

De toepassingsstructuur van Marble Maze beschrijft de hoofdspellus. Het bijwerken van de scène, die deel uitmaakt van de gamelus, vindt plaats nadat Windows-gebeurtenissen en -invoer zijn verwerkt en voordat de scène wordt weergegeven. De methode MarbleMazeMain::Update verwerkt de update van de gebruikersinterface en het spel.

De gebruikersinterface bijwerken

De methode MarbleMazeMain::Update roept de methode UserInterface::Update aan om de status van de gebruikersinterface bij te werken.

UserInterface::GetInstance().Update(
    static_cast<float>(m_timer.GetTotalSeconds()), 
    static_cast<float>(m_timer.GetElapsedSeconds()));

De methode UserInterface::Update werkt elk element in de gebruikersinterfaceverzameling bij.

void UserInterface::Update(float timeTotal, float timeDelta)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        (*iter)->Update(timeTotal, timeDelta);
    }
}

Klassen die zijn afgeleid van ElementBase (gedefinieerd in UserInterface.h) implementeren de updatemethode om specifiek gedrag uit te voeren. Met de methode StopwatchTimer::Update wordt bijvoorbeeld de verstreken tijd bijgewerkt op basis van de opgegeven hoeveelheid en wordt de tekst bijgewerkt die later wordt weergegeven.

void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
    if (m_active)
    {
        m_elapsedTime += timeDelta;

        WCHAR buffer[16];
        GetFormattedTime(buffer);
        SetText(buffer);
    }

    TextElement::Update(timeTotal, timeDelta);
}

De scène bijwerken

De methode MarbleMazeMain::Update werkt het spel bij op basis van de huidige status van de statusmachine (de GameState, opgeslagen in m_gameState). Wanneer het spel actief is (GameState::InGameActive), werkt Marble Maze de camera bij om het marmer te volgen, werkt het weergavematrixgedeelte van de constante buffers bij en werkt de fysicasimulatie bij.

In het volgende voorbeeld ziet u hoe de methode MarbleMazeMain::Update de positie van de camera bijwerkt. Marble Maze gebruikt de m_resetCamera variabele om te markeren dat de camera opnieuw moet worden ingesteld om direct boven het marmer te worden geplaatst. De camera wordt opnieuw ingesteld wanneer het spel begint of het marmer door het doolhof valt. Wanneer het hoofdmenu of het scherm met een hoge score actief is, wordt de camera ingesteld op een constante locatie. Anders gebruikt Marble Maze de timeDelta-parameter om de positie van de camera tussen de huidige en doelposities te interpoleren. De doelpositie ligt iets boven en voor het marmer. Door de verstreken frametijd te gebruiken, kan de camera het marmer geleidelijk volgen of achtervolgen.

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

In het volgende voorbeeld ziet u hoe de methode MarbleMazeMain::Update de constante buffers voor het marmer en het doolhof bijwerkt. Het doolhofmodel, of de wereld, blijft altijd de identiteitsmatrix. Met uitzondering van de hoofddiagonaal, waarvan de elementen allemaal enen zijn, is de identiteitsmatrix een vierkante matrix die bestaat uit nullen. De modelmatrix van marmer is gebaseerd op de positiematrix en de draaimatrix.

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

Voor informatie over hoe de methode MarbleMazeMain::Update gebruikersinvoer leest en de beweging van de knikker simuleert, zie Invoer en interactiviteit toevoegen aan het Marble Maze-voorbeeld.

De scène weergeven

Wanneer een scène wordt weergegeven, worden deze stappen meestal opgenomen.

  1. Stel de huidige render-doeldiepte-stencilbuffer in.
  2. Wis de rendering- en stencilweergaven.
  3. Bereid de vertex- en pixel-shaders voor op het tekenen.
  4. Geef de 3D-objecten in de scène weer.
  5. Geef een 2D-object weer dat u vóór de scène wilt weergeven.
  6. Presenteer de weergegeven afbeelding aan de monitor.

De methode MarbleMazeMain::Render verbindt de weergavedoel- en dieptestencilweergaven, wist deze weergaven, tekent de scène en tekent vervolgens de overlay.

De renderdoelen voorbereiden

Voordat u uw scène rendert, moet u de huidige renderdoel diepte-stencilbuffer instellen. Als uw scène niet gegarandeerd elke pixel op het scherm tekent, wis dan ook de render- en stencilweergaven. Marble Maze maakt de render- en stencilweergaven in elk frame leeg om ervoor te zorgen dat er geen zichtbare artefacten uit het vorige frame zijn.

In het volgende voorbeeld ziet u hoe de methode MarbleMazeMain::Render de methode ID3D11DeviceContext::OMSetRenderTargets aanroept om het renderdoel en de dieptestencilbuffer in te stellen als de huidige.

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

De interfaces ID3D11RenderTargetView en ID3D11DepthStencilView ondersteunen het patroonweergavemechanisme dat wordt geleverd door Direct3D 10 en hoger. Zie Texture Views (Direct3D 10)voor meer informatie over texture weergaven. Met de methode OMSetRenderTargets wordt de fase voor uitvoerfusie van de Direct3D-pijplijn voorbereid. Zie Output-Merger Fase voor meer informatie over de output-merger fase.

De vertex en pixel shaders voorbereiden

Voordat u de scèneobjecten weergeeft, moet u de volgende stappen uitvoeren om het hoekpunt en de pixel-shaders voor te bereiden voor tekenen:

  1. Stel de shader-invoerindeling in als de huidige indeling.
  2. Stel de hoekpunt- en pixel-shaders in als de huidige shaders.
  3. Werk eventuele constante buffers bij met gegevens die u moet doorgeven aan de shaders.

Belangrijk

Marble Maze gebruikt één paar hoekpunten en pixel-shaders voor alle 3D-objecten. Als uw game meer dan één paar shaders gebruikt, moet u deze stappen uitvoeren telkens wanneer u objecten tekent die verschillende shaders gebruiken. Om de overhead die gepaard gaat met het wijzigen van de shader-toestand te verminderen, raden we aan de render-aanroepen te groeperen voor alle objecten die dezelfde shaders gebruiken.

 

In de sectie Shaders laden in dit document wordt beschreven hoe de invoerindeling wordt gemaakt wanneer de vertex shader wordt gemaakt. In het volgende voorbeeld ziet u hoe de methode MarbleMazeMain::Render de methode ID3D11DeviceContext::IASetInputLayout gebruikt om deze indeling in te stellen als de huidige indeling.

m_deviceResources->GetD3DDeviceContext()->IASetInputLayout(m_inputLayout.Get());

In het volgende voorbeeld ziet u hoe de methode MarbleMazeMain::Render de methoden ID3D11DeviceContext::VSSetShader en ID3D11DeviceContext::P SSetShader gebruikt om respectievelijk de hoekpunt- en pixel-shaders in te stellen als de huidige shaders.

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

Nadat MarbleMazeMain::Render de shaders en de invoerindeling heeft ingesteld, wordt de methode ID3D11DeviceContext::UpdateSubresource gebruikt om de constante buffer bij te werken met het model, de weergave en de projectiematrices voor het doolhof. De methode UpdateSubresource kopieert de matrixgegevens van het CPU-geheugen naar het GPU-geheugen. Denk eraan dat het model en de weergaveonderdelen van de ConstantBuffer-structuur worden bijgewerkt in de methode MarbleMazeMain::Update . De MarbleMazeMain::Render methode roept vervolgens de ID3D11DeviceContext::VSSetConstantBuffers en ID3D11DeviceContext::PSSetConstantBuffers methoden aan om deze constante buffer als de huidige in te stellen.

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

De methode MarbleMazeMain::Render voert vergelijkbare stappen uit om het marmer voor te bereiden dat moet worden weergegeven.

Het doolhof en het marmer weergeven

Nadat u de huidige shaders hebt geactiveerd, kunt u uw scèneobjecten tekenen. De methode MarbleMazeMain::Render roept de methode SDKMesh::Render aan om de doolhof-mesh weer te geven.

m_mazeMesh.Render(
    m_deviceResources->GetD3DDeviceContext(), 
    0, 
    INVALID_SAMPLER_SLOT, 
    INVALID_SAMPLER_SLOT);

De methode MarbleMazeMain::Render voert vergelijkbare stappen uit om het marmer weer te geven.

Zoals eerder vermeld in dit document, wordt de SDKMesh-klasse geleverd voor demonstratiedoeleinden, maar we raden het niet aan voor gebruik in een game van productiekwaliteit. U ziet echter dat de methode SDKMesh::RenderMesh , die wordt aangeroepen door SDKMesh::Render, gebruikt de methoden ID3D11DeviceContext::IASetVertexBuffers en ID3D11DeviceContext::IASetIndexBuffer-methoden om de huidige hoekpunt- en indexbuffers in te stellen die de mesh definiëren en de ID3D11DeviceContext::D rawIndexed-methode om de buffers te tekenen. Zie Inleiding tot buffers in Direct3D 11 voor meer informatie over het werken met hoekpunt- en indexbuffers.

De gebruikersinterface en overlay tekenen

Nadat u 3D-scèneobjecten hebt getekend, tekent Marble Maze de 2D UI-elementen die voor de scène worden weergegeven.

De methode MarbleMazeMain::Render eindigt door de gebruikersinterface en de overlay te tekenen.

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

De methode UserInterface::Render maakt gebruik van een ID2D1DeviceContext-object om de UI-elementen te tekenen. Met deze methode stelt u de tekenstatus in, tekent u alle actieve ui-elementen en herstelt u vervolgens de vorige tekenstatus.

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

De methode SampleOverlay::Render gebruikt een vergelijkbare techniek om de overlay-bitmap te tekenen.

De scène presenteren

Nadat alle 2D- en 3D-scèneobjecten zijn getekend, presenteert Marble Maze de gerenderde afbeelding aan de monitor. Hiermee wordt de tekening gesynchroniseerd met de verticale lege waarde om ervoor te zorgen dat er geen tijd wordt besteed aan tekenframes die nooit daadwerkelijk op de weergave worden weergegeven. Marble Maze verwerkt ook apparaatwijzigingen wanneer deze de scène presenteert.

Nadat de methode MarbleMazeMain::Render is geretourneerd, roept de game-lus de methode DX::DeviceResources::Present aan om de gerenderde afbeelding naar de monitor of weergave te verzenden. De DX::DeviceResources::Present methode roept IDXGISwapChain::Present aan om de presentatiebewerking uit te voeren, zoals in het volgende voorbeeld te zien is.

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

In dit voorbeeld is m_swapChain een IDXGISwapChain1-object . De initialisatie van dit object wordt beschreven in de sectie Direct3D en Direct2D in dit document initialiseren.

De eerste parameter voor IDXGISwapChain::Present, SyncInterval, geeft het aantal verticale blanking-intervallen aan waarop moet worden gewacht voordat het frame wordt gepresenteerd. Marble Maze specificeert 1 zodat het wacht tot de volgende verticale blanco.

De methode IDXGISwapChain::Present retourneert een foutcode die aangeeft dat het apparaat is verwijderd of anderszins heeft gefaald. In dit geval initialiseert Marble Maze het apparaat opnieuw.

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

Volgende stappen

Lees Het toevoegen van invoer en interactiviteit aan het Marble Maze-voorbeeld voor informatie over enkele van de belangrijkste procedures waarmee u rekening moet houden wanneer u met invoerapparaten werkt. In dit document wordt beschreven hoe Marble Maze ondersteuning biedt voor aanraak-, accelerometer-, gamecontrollers en muisinvoer.