Aggiunta di contenuto visivo all'esempio di Marble Maze

Applies to Windows only

Questo documento descrive come il gioco Marble Maze usa Direct3D e Direct2D nell'ambiente delle app di Windows Store, fornendo informazioni sui modelli che ti consentiranno di adattarli al contenuto del tuo gioco. Per informazioni su come i componenti visivi del gioco si adattano alla struttura complessiva di Marble Maze, vedi Struttura dell'applicazione Marble Maze.

Nello sviluppo degli aspetti visivi di Marble Maze abbiamo seguito la procedura di base descritta di seguito:

  1. Creazione di un framework di base per l'inizializzazione degli ambienti Direct3D e Direct2D.
  2. Uso di programmi di modifica di immagini e modelli per progettare gli asset 2D e 3D che compaiono nel gioco.
  3. Verifica che gli asset 2D e 3D vengano caricati e visualizzati correttamente nel gioco.
  4. Integrazione dei vertex shader e dei pixel shader per migliorare la qualità visiva degli asset del gioco.
  5. Integrazione della logica del gioco, ad esempio l'animazione e l'input utente.

Abbiamo aggiunto prima gli asset 3D e poi quelli 2D. Ad esempio, ci siamo concentrati sulla logica di base del gioco prima di aggiungere i menu e il timer.

Durante il processo di sviluppo è stato necessario ripetere più volte alcuni di questi passaggi. Ad esempio, mentre apportavamo le modifiche ai modelli di mesh e di biglie, abbiamo dovuto modificare anche parte del codice degli shader a supporto di questi modelli.

Nota  Il codice di esempio che corrisponde a questo documento è disponibile alla pagina relativa all'esempio di gioco DirectX Marble Maze.

In questo argomento

Ecco alcuni aspetti fondamentali descritti in questo documento e relativi all'uso di DirectX e al contenuto visivo del gioco, in particolare l'inizializzazione delle librerie grafiche DirectX, il caricamento delle risorse della scena e l'aggiornamento e il rendering della scena:

  • L'aggiunta del contenuto del gioco di norma prevede numerosi passaggi, che in molti casi è necessario ripetere. Gli sviluppatori dei giochi spesso aggiungono il contenuto 3D prima di quello 2D.
  • Raggiungere un maggior numero di clienti e offrire loro una fantastica esperienza supportando la più ampia gamma di hardware grafico.
  • Separare nettamente i formati delle fasi di progettazione e di esecuzione. Strutturare gli asset della fase di progettazione per ottimizzare la flessibilità e consentire rapide iterazioni nel contenuto. Formattare e comprimere gli asset in modo che il caricamento e il rendering vengano effettuati nel modo più efficiente possibile in fase di esecuzione.
  • Puoi creare i dispositivi Direct3D e Direct2D in un'app di Windows Store in modo simile a quanto avviene in un'app desktop classica di Windows. Una differenza importante è rappresentata dal modo in cui la catena di scambio viene associata alla finestra di output.
  • Quando progetti il gioco, assicurati che il formato di mesh scelto supporti gli scenari principali. Ad esempio, se il gioco richiede gli urti, verifica che sia possibile ottenere i dati sugli urti dalle mesh.
  • Separa la logica del gioco dalla logica di rendering aggiornando tutti gli oggetti delle scene prima di eseguirne il rendering.
  • In genere si disegnano gli oggetti 3D della scena e quindi gli eventuali oggetti 2D visualizzati davanti alla scena.
  • Sincronizza il disegno in base al blanking verticale per evitare che il gioco perda tempo a disegnare fotogrammi che non verranno mai visualizzati.

Introduzione alla grafica DirectX

Durante la pianificazione del gioco in Windows Store Marble Maze, abbiamo scelto C++ e Direct3D 11.1 perché rappresentano le scelte migliori per la creazione di giochi 3D che richiedono il massimo controllo sul rendering e prestazioni elevate. DirectX 11.1 supporta l'hardware da DirectX 9 a DirectX 11 e pertanto consente di raggiungere un maggior numero di clienti in modo più efficiente, dato che non è necessario riscrivere il codice per ognuna delle versioni precedenti di DirectX.

Marble Maze usa Direct3D 11.1 per eseguire il rendering degli asset 3D del gioco, come la biglia e il labirinto. Usa inoltre Direct2D, DirectWrite e Windows Imaging Component (WIC) per disegnare gli asset 2D del gioco, ad esempio i menu e il timer. Infine, Marble Maze usa XAML per implementare una barra dell'app e permetterti di aggiungere controlli XAML.

Lo sviluppo del gioco richiede una pianificazione. Se non sei esperto di grafica DirectX, ti consigliamo di leggere il documento relativo alla creazione dei giochi DirectX per apprendere i concetti fondamentali relativi alla creazione di giochi DirectX per Windows Store. Durante la lettura di questo documento e l'analisi del codice sorgente di Marble Maze, puoi fare riferimento alle risorse seguenti per informazioni più dettagliate sulla grafica DirectX.

  • Direct3D 11 Graphics Descrive Direct3D 11, una potente API per la grafica 3D con accelerazione hardware per il rendering della geometria 3D nella piattaforma Windows.
  • Direct2D Descrive Direct2D, una potente API per la grafica 2D con accelerazione hardware che offre prestazioni elevate e rendering di alta qualità per la geometria 2D, le bitmap e il testo.
  • DirectWrite Descrive DirectWrite, che supporta il rendering del testo di alta qualità.
  • Windows Imaging Component Descrive WIC, una piattaforma estensibile che fornisce l'API di basso livello per le immagini digitali.

Livelli di funzionalità

Direct3D 11 introduce un paradigma denominato livelli di funzionalità. I livelli di funzionalità sono set ben definiti di funzionalità GPU, che possono essere usati per eseguire il gioco su versioni precedenti dell'hardware Direct3D. Marble Maze supporta il livello di funzionalità 9.1 perché non richiede le funzionalità avanzate dei livelli successivi. Ti consigliamo di supportare la più ampia gamma possibile di componenti hardware e adattare il contenuto del gioco, in modo da garantire un'esperienza fantastica sia ai clienti che dispongono di computer di fascia alta sia a quelli con computer di fascia bassa. Per altre informazioni sui livelli di funzionalità, vedi il documento relativo a Direct3D 11 su hardware di livello inferiore.

Inizializzazione di Direct3D e Direct2D

Un dispositivo rappresenta la scheda video. Puoi creare i dispositivi Direct3D e Direct2D in un'app di Windows Store in modo simile a quanto avviene in un'app desktop classica di Windows. La differenza principale consiste nel modo in cui la catena di scambio di Direct3D viene connessa al sistema di windowing.

Il modello Applicazione DirectX di Visual Studio (XAML) distingue alcune funzioni di rendering 3D e del sistema operativo generiche dalle funzioni specifiche del gioco. La classe DeviceResources rappresenta un elemento fondamentale per la gestione di Direct3D e Direct2D. Questa classe gestisce l'infrastruttura generale, non gli asset specifici del gioco. Marble Maze definisce la classe MarbleMaze per gestire gli asset specifici del gioco. Questa classe ha un riferimento a un oggetto DeviceResources per l'accesso a Direct3D e Direct2D.

Durante l'inizializzazione, il metodo DeviceResources::Initialize crea le risorse indipendenti dal dispositivo e i dispositivi Direct3D e Direct2D.


// Initialize the Direct3D resources required to run. 
void DeviceResources::DeviceResources(CoreWindow^ window, float dpi)
{
    m_window = window;

    CreateDeviceIndependentResources();
    CreateDeviceResources();
    CreateWindowSizeDependentResources();
    SetDpi(dpi);
}


La classe DeviceResources separa questa funzionalità in modo da rispondere più facilmente alle modifiche dell'ambiente. Ad esempio, chiama il metodo CreateWindowSizeDependentResources quando le dimensioni della finestra cambiano.

Inizializzazione di factory Direct2D, DirectWrite e WIC

Il metodo DeviceResources::CreateDeviceIndependentResources crea le factory per Direct2D, DirectWrite e WIC. Nella grafica DirectX le factory rappresentano il punto di partenza per creare le risorse grafiche. Marble Maze specifica D2D1_FACTORY_TYPE_SINGLE_THREADED perché esegue tutte le operazioni di disegno nel thread principale.


// These are the resources required independent of hardware. 
void DeviceResources::CreateDeviceIndependentResources()
{
    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

    DX::ThrowIfFailed(
        D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            __uuidof(ID2D1Factory1),
            &options,
            &m_d2dFactory
            )
        );

    DX::ThrowIfFailed(
        DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory),
            &m_dwriteFactory
            )
        );

    DX::ThrowIfFailed(
        CoCreateInstance(
            CLSID_WICImagingFactory,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_wicFactory)
            )
        );
}

Creazione di dispositivi Direct2D e Direct3D

Il metodo DeviceResources::CreateDeviceResources chiama D3D11CreateDevice per creare l'oggetto dispositivo che rappresenta la scheda video Direct3D. Dato che Marble Maze supporta il livello di funzionalità 9.1 e versioni successive, il metodo DeviceResources::CreateDeviceResources specifica i livelli da 9.1 a 11.1 nella matrice di valori \. Direct3D scorre l'elenco in ordine e fornisce all'app il primo livello di funzionalità disponibile. Le voci della matrice D3D_FEATURE_LEVEL sono pertanto elencate in ordine decrescente (dalla più alta alla più bassa) in modo che l'app ottenga il più alto livello di funzionalità disponibile. Il metodoDeviceResources::CreateDeviceResources ottiene il dispositivo Direct3D 11.1 eseguendo una query sul dispositivo Direct3D 11 restituito da D3D11CreateDevice.


// 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 DX11 API device object, and get a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;
DX::ThrowIfFailed(
    D3D11CreateDevice(
        nullptr,                    // specify null to use the default adapter
        D3D_DRIVER_TYPE_HARDWARE,
        0,                          // leave as 0 unless software device
        creationFlags,              // optionally set debug and Direct2D compatibility flags
        featureLevels,              // list of feature levels this app can support
        ARRAYSIZE(featureLevels),   // number of entries in above list
        D3D11_SDK_VERSION,          // always set this to D3D11_SDK_VERSION for modern
        &device,                    // returns the Direct3D device created
        &m_featureLevel,            // returns feature level of device created
        &context                    // returns the device immediate context
        )
    );    

// Get the Direct3D 11.1 device by querying the Direct3D 11 device.
DX::ThrowIfFailed(
    device.As(&m_d3dDevice)
    );

Il metodo DeviceResources::CreateDeviceResources crea quindi il dispositivo Direct2D. Direct2D usa l'infrastruttura Microsoft DirectX Graphics Infrastructure (DXGI) per l'interazione con Direct3D. DXGI consente la condivisione delle superfici di memoria video da parte dei runtime della grafica. Marble Maze usa il dispositivo DXGI sottostante del dispositivo Direct3D per creare il dispositivo Direct2D dalla factory Direct2D.


// Obtain the underlying DXGI device of the Direct3D 11.1 device.
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

// Obtain the Direct2D device for 2-D rendering.
DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

// And get its corresponding device context object.
DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );


// Obtain the underlying DXGI device of the Direct3D 11.1 device.
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

// Obtain the Direct2D device for 2-D rendering.
DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

// And get its corresponding device context object.
DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );


Per altre informazioni su DXGI e sull'interoperabilità tra Direct2D e Direct3D, vedi la panoramica di DXGI e la panoramica dell'interoperabilità tra Direct2D e Direct3D.

Associazione di Direct3D alla visualizzazione

Il metodo DeviceResources::CreateWindowSizeDependentResources crea le risorse grafiche che dipendono da una determinata dimensione della finestra, come la catena di scambio e le destinazioni di rendering Direct3D e Direct2D. Una delle differenze fondamentali tra un'app di Windows Store DirectX e un'app desktop è il modo in cui la catena di scambio viene associata alla finestra di output. La catena di scambio è responsabile della visualizzazione del buffer in cui il dispositivo esegue il rendering sullo schermo. Nel documento Struttura dell'applicazione Marble Maze vengono descritte le differenze del sistema di windowing tra un'app di Windows Store e un'app desktop. Dato che un'app di Windows Store non funziona con gli oggetti HWND, Marble Maze deve usare il metodo IDXGIFactory2::CreateSwapChainForCoreWindow per associare l'output del dispositivo alla visualizzazione. L'esempio seguente mostra la parte del metodoDeviceResources::CreateWindowSizeDependentResources che crea la catena di scambio.


// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
    dxgiFactory->CreateSwapChainForCoreWindow(
        m_d3dDevice.Get(),
        reinterpret_cast<IUnknown*>(m_window),
        &swapChainDesc,
        nullptr,    // allow on all displays
        &m_swapChain
        )
    );

Per ridurre al minimo il consumo energetico, fattore importante sui dispositivi alimentati a batteria come i laptop e i tablet, il metodo DeviceResources::CreateWindowSizeDependentResources chiama il metodo IDXGIDevice1::SetMaximumFrameLatency per fare in modo che il rendering del gioco venga eseguito solo dopo il blanking verticale. La sincronizzazione con il blanking verticale è descritta più dettagliatamente nella sezione Presentazione della scena in questo documento.


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


Il metodo DeviceResources::CreateWindowSizeDependentResources inizializza le risorse grafiche secondo modalità appropriate per la maggior parte dei giochi. Per un altro esempio dell'inizializzazione di un'app di Windows Store DirectX, vedi Come impostare l'app DirectX di Windows Store per la visualizzazione di una vista.

Nota  Il termine Il visualizzazione ha un significato diverso in Windows Runtime rispetto a quello che ha in Direct3D. In Windows Runtime, una visualizzazione fa riferimento alla raccolta di impostazioni dell'interfaccia utente per un'app, inclusi l'area di visualizzazione e i comportamenti di input, oltre al thread usato per l'elaborazione. Puoi specificare la configurazione e le impostazioni necessarie durante la creazione di una visualizzazione. La procedura per impostare la visualizzazione dell'app è descritta in Struttura dell'applicazione Marble Maze. In Direct3D, il termine visualizzazione ha più significati. Innanzitutto, una visualizzazione risorse definisce le risorse secondarie accessibili per una risorsa. Ad esempio, quando un oggetto trama viene associato alla visualizzazione risorse di uno shader, quest'ultimo può in seguito accedere alla trama. Un vantaggio offerto dalla visualizzazione risorse è la possibilità di interpretare i dati in modi diversi nelle differenti fasi della pipeline di rendering. Per altre informazioni sulle visualizzazioni delle risorse, vedi il documento relativo alle visualizzazioni delle trame (Direct3D 10). Se usato nel contesto di una trasformazione o di una matrice di trasformazione, il termine visualizzazione fa riferimento alla posizione e all'orientamento della fotocamera. Una trasformazione di visualizzazione ricolloca gli oggetti a livello globale intorno alla posizione e all'orientamento della fotocamera. Per altre informazioni sulle trasformazioni delle visualizzazioni, vedi il documento relativo alla trasformazione della visualizzazione (Direct3D 9). In questo argomento vengono forniti ulteriori dettagli sul modo in cui Marble Maze usa le visualizzazioni di risorse e matrici.

Caricamento delle risorse delle scene

Marble Maze usa la classe BasicLoader, dichiarata in BasicLoader.h, per caricare le trame e gli shader. Usa invece la classe SDKMesh per caricare le mesh 3D per il labirinto e la biglia.

Per assicurare la reattività dell'app, Marble Maze carica risorse delle scene in modo asincrono o in background. Mentre gli asset vengono caricati in background, il gioco può rispondere agli eventi di finestra. Questo processo è descritto in modo più dettagliato nella sezione Caricamento degli asset del gioco in background di questa guida.

Caricamento dell'interfaccia utente e della sovrimpressione 2D

In Marble Maze, la sovrimpressione è l'immagine visualizzata nella parte superiore dello schermo e compare sempre davanti alla scena. In Marble Maze, la sovrimpressione contiene il logo Windows e la stringa di testo "DirectX Marble Maze game sample". La gestione della sovrimpressione è eseguita dalla classe SampleOverlay, definita in SampleOverlay.h. Questo codice è simile a quello presente negli esempi di Windows Store Direct3D. Anche se usiamo la sovrimpressione negli esempi Direct3D, puoi adattare il codice per visualizzare qualsiasi immagine che compare davanti alla scena.

Un aspetto importante della sovrimpressione è che, poiché i relativi contenuti non cambiano, la classe SampleOverlay disegna, o memorizza nella cache, i contenuti in un oggetto ID2D1Bitmap1 durante l'inizializzazione. Nella fase di disegno, la classe SampleOverlay deve disegnare solo la bitmap sullo schermo. In questo modo, non è necessario eseguire per ogni fotogramma routine onerose, come il disegno del testo.

L'interfaccia utente è costituita da componenti 2D, come i menu e gli HUD (Heads-Up Display), che compaiono davanti alla scena. Marble Maze definisce i seguenti elementi dell'interfaccia utente:

  • Le voci di menu che consentono all'utente di iniziare il gioco o di visualizzare i punteggi alti.
  • Un timer che esegue il conto alla rovescia per tre secondi prima che inizi il gioco.
  • Un timer che tiene traccia del tempo trascorso.
  • Una tabella che elenca i migliori tempi di completamento.
  • Il testo "Paused" quando il gioco è in pausa.

Marble Maze definisce gli elementi dell'interfaccia utente specifici del gioco in UserInterface.h. Marble Maze definisce la classe ElementBase come tipo di base per tutti gli elementi dell'interfaccia utente. La classe ElementBasedefinisce attributi quali dimensione, posizione, allineamento e visibilità degli elementi dell'interfaccia utente, oltre a determinare il modo in cui questi ultimi vengono aggiornati e sottoposti a rendering.


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

Fornendo una classe comune di base per gli elementi dell'interfaccia utente, la classe UserInterface che gestisce l'interfaccia utente, deve solo contenere una raccolta di oggetti ElementBase. Ciò semplifica la gestione dell'interfaccia utente e fornisce per la stessa un gestore riutilizzabile. Marble Maze definisce i tipi derivati daElementBase che implementano comportamenti specifici del gioco. Ad esempio, HighScoreTable definisce il comportamento della tabella dei punteggi elevati. Per altre info su questi tipi, fare riferimento al codice sorgente.

Nota  XAML semplifica la creazione delle interfacce utente complesse, come quelle dei giochi di simulazione e strategia, e può rappresentare una buona scelta. Per info su come sviluppare un'interfaccia utente in XAML in un gioco di Windows Store DirectX, vedi Estensione dell'esempio di gioco (Windows). In questo documento si fa riferimento all'esempio del gioco di spari 3D DirectX.

Caricamento di shader

Marble Maze usa il metodo BasicLoader::LoadShader per caricare uno shader da un file.

Gli shader rappresentano l'unità fondamentale della programmazione GPU nei giochi odierni. Quasi tutta l'elaborazione della grafica 3D viene eseguita tramite gli shader, sia che si tratti di trasformazione dei modelli e illuminazione della scena che di più complesse attività di elaborazione della geometria, dalla definizione delle interfacce dei personaggi alla geometria a mosaico. Per altre informazioni sul modello di programmazione degli shader, vedi HLSL.

Marble Maze usa i vertex shader e i pixel shader. Un vertex shader opera sempre su un singolo vertice di input e produce un unico vertice come output. Un pixel shader accetta valori numerici, dati delle trame, valori interpolati a livello di singolo vertice e altri dati per produrre un colore pixel come output. Dato che lo shader trasforma un elemento alla volta, l'hardware grafico che fornisce più pipeline di shader è in grado di elaborare i set di elementi in parallelo. Il numero di pipeline parallele disponibili per la GPU può essere molto più alto rispetto a quello disponibile per la CPU. Di conseguenza, anche gli shader di base possono contribuire notevolmente all'aumento della velocità effettiva.

Il metodo MarbleMaze::LoadDeferredResources carica un vertex shader e un pixel shader al termine del caricamento della sovrimpressione. Le versioni della fase di progettazione di questi shader sono definite rispettivamente in BasicVertexShader.hlsl e BasicPixelShader.hlsl. Marble Maze applica tali shader sia alla biglia che al labirinto durante la fase di rendering.

Il progetto Marble Maze include sia la versione HLSL (il formato della fase di progettazione) che la versione CSO (il formato della fase di esecuzione) dei file di shader. In fase di compilazione, Visual Studio usa il compilatore degli effetti fxc.exe per compilare il file di origine HLSL in uno shader binario CSO. Per altre informazioni su questo strumento, vedi il documento relativo allo strumento di compilazione degli effetti.

Il vertex shader usa le matrici di modelli, visualizzazioni e proiezioni fornite per trasformare la geometria di input. I dati sulla posizione della geometria di input vengono trasformati e restituiti due volte: una volta nello spazio della schermata, per consentire il rendering, e una volta nello spazio globale per consentire al pixel shader di eseguire i calcoli relativi all'illuminazione. Il vettore della normale alla superficie viene trasformato nello spazio globale, usato anche dal pixel shader per l'illuminazione. Le coordinate della trama vengono passate senza modifiche al 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;
}


Quest'ultimo riceve l'output del vertex shader come input. Questo shader esegue calcoli di illuminazione per simulare un riflettore con angoli smussati sospeso sul labirinto e allineato alla posizione della biglia. L'illuminazione è più forte per le superfici rivolte direttamente verso la luce. La riflessione diffusiva diminuisce fino ad arrivare a zero man mano che la normale della superficie diventa perpendicolare alla luce, mentre la riflessione di ambiente diminuisce man mano che la direzione della normale si allontana dalla fonte luminosa. I punti più vicini alla biglia, e pertanto più vicini al centro del riflettore, sono più illuminati. Tuttavia, l'illuminazione è modulata per i punti sotto la biglia per simulare un'ombreggiatura sfumata. In un ambiente reale, un oggetto come la biglia bianca rifletterebbe la luce del riflettore in modo diffuso su altri oggetti della scena. Questo fattore è approssimativo per le superfici esposte alla metà luminosa della biglia. I fattori di illuminazione aggiuntivi sono a una distanza e un'angolazione relative rispetto alla biglia. Il colore risultante del pixel è una composizione della trama campionata con il risultato dei calcoli di illuminazione.


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


Attenzione  Il pixel shader compilato contiene 32 istruzioni aritmetiche e un'unica istruzione trama e offre buone prestazioni sui computer desktop e sui tablet di fascia alta. Tuttavia, un computer di fascia bassa potrebbe non essere in grado di elaborare questo shader e fornire comunque una frequenza dei fotogrammi interattiva. Considera l'hardware tipico del mercato di riferimento e progetta gli shader in modo che siano compatibili con le capacità di tale hardware.

Il metodo MarbleMaze::LoadDeferredResources usa il metodo BasicLoader::LoadShader per caricare gli shader. L'esempio seguente carica il vertex shader. Il formato della fase di esecuzione per questo shader è BasicVertexShader.cso. La variabile membro m_vertexShader è un oggetto ID3D11VertexShader.


\loader->LoadShader(
    L"BasicVertexShader.cso",
    layoutDesc,
    ARRAYSIZE(layoutDesc),
    &m_vertexShader,
    &m_inputLayout
    );


La variabile membro m_inputLayout è un oggetto ID3D11InputLayout. L'oggetto del layout di input incapsula lo stato di input della fase di assemblaggio input. Una delle funzioni di tale fase è rendere gli shader più efficienti usando valori generati dal sistema, noti anche come semantica, per elaborare solo le primitive o i vertici che non sono già stati elaborati. Usa il metodo ID3D11Device::CreateInputLayout per creare un layout di input da una matrice di descrizioni degli elementi di input. La matrice contiene uno o più elementi di input, ognuno dei quali descrive un elemento dei dati di vertice di un vertex buffer. L'intero set di descrizioni degli elementi di input descrive tutti gli elementi dei dati di vertice di tutti i vertex buffer che verranno associati alla fase di assemblaggio input. Nell'esempio seguente viene mostrata la descrizione del layout usata da Marble Maze che descrive un vertex buffer contenente quattro elementi di dati di vertice. Le parti importanti di ogni voce della matrice sono il nome della semantica, il formato dei dati e l'offset di byte. Ad esempio, l'elemento POSITION specifica la posizione del vertice nello spazio dell'oggetto. Inizia in corrispondenza dell'offset di byte 0 e contiene tre componenti a virgola mobile (per un totale di 12 byte). L'elemento NORMAL specifica il vettore normale. Inizia in corrispondenza dell'offset di byte 12 perché si trova subito dopo POSITION nel layout, che richiede 12 byte. L'elemento NORMAL contiene un intero senza segno a 32 bit e a quattro componenti.


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

Confronta il layout di input con la struttura di sVSInput definita dal vertex shader, come mostrato nell'esempio seguente. La struttura sVSInput definisce gli elementi POSITION, NORMAL e TEXCOORD0. Il runtime DirectX esegue il mapping di ogni elemento nel layout alla struttura di input definita dallo 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;
}


Nel documento Semantics vengono descritte in modo più dettagliato le semantiche disponibili.

Nota  In un layout, puoi specificare componenti aggiuntivi non usati per consentire a più shader di condividere lo stesso layout. Ad esempio, l'elemento TANGENT non è usato dallo shader. Puoi usare l'elementoTANGENT per provare tecniche come il normal mapping. Tale tecnica, nota anche come bump mapping, consente di creare l'effetto delle protuberanze sulle superfici degli oggetti. Per altre informazioni su questa tecnica, vedi il documento relativo al bump mapping (Direct3D 9).

Per altre informazioni sullo stato della fase di assemblaggio input, vedi il documento relativo alla fase di assemblaggio input e l'introduzione alla fase di assemblaggio input.

La procedura di uso dei vertex shader e dei pixel shader per eseguire il rendering della scena è descritta nella sezione Esecuzione del rendering della scena più avanti in questo documento.

Creazione del constant buffer

Un buffer Direct3D raggruppa una raccolta di dati. Un constant buffer è un tipo di buffer che puoi usare per passare i dati agli shader. Marble Maze usa un constant buffer per contenere la visualizzazione del modello (o globale) e le matrici di proiezione per l'oggetto attivo della scena.

L'esempio seguente mostra come il metodo MarbleMaze::LoadDeferredResources crea un constant buffer che successivamente conterrà i dati della matrice. Viene creata una struttura D3D11_BUFFER_DESC che usa il flag D3D11_BIND_CONSTANT_BUFFER per specificare l'uso come constant buffer. Tale struttura viene quindi passata al metodo ID3D11Device::CreateBuffer. La variabile m_constantBuffer è un oggetto ID3D11Buffer.


// create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};
constantBufferDesc.ByteWidth           = ((sizeof(ConstantBuffer) + 15) / 16) * 16; // multiple of 16 bytes
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_d3dDevice->CreateBuffer(
        &constantBufferDesc,
        nullptr,             // leave the buffer uninitialized
        &m_constantBuffer
        )
    );

Il metodo MarbleMaze::Update aggiorna in seguito gli oggetti ConstantBuffer, uno per il labirinto e uno per la biglia. Il metodo MarbleMaze::Render associa quindi ogni oggetto ConstantBuffer al constant buffer prima dell'esecuzione del rendering di ogni oggetto. L'esempio seguente mostra la struttura diConstantBuffer, che si trova in MarbleMaze.h.


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

    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};


Per comprendere in che modo viene eseguito il mapping dei constant buffer, confronta la struttura ConstantBuffer con il constant buffer SimpleConstantBuffer definito dal vertex shader in BasicVertexShader.hlsl:


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


Il layout della struttura di ConstantBuffer corrisponde all'oggetto cbuffer. La variabile cbuffer specifica il registro b0 e questo significa che i dati del constant buffer vengono archiviati nel registro 0. Il metodo MarbleMaze::Render specifica il registro 0 quando attiva il constant buffer. Questo processo viene descritto in modo più dettagliato più avanti in questo documento.

Per altre informazioni sui constant buffer, vedi l'introduzione ai buffer in Direct3D 11. Per altre informazioni sulla parola chiave register, vedi register.

Caricamento di mesh

Marble Maze usa SDK-Mesh come formato della fase di esecuzione, perché rende disponibile un metodo semplice per caricare i dati di mesh per le applicazioni di esempio. Negli ambienti di produzione dovresti usare un formato di mesh che soddisfi le esigenze specifiche del tuo gioco.

Il metodo MarbleMaze::LoadDeferredResources carica i dati di mesh dopo aver caricato i vertex shader e i pixel shader. Una mesh è una raccolta di dati relativi ai vertici che includono spesso informazioni quali le posizioni, i dati sulle normali, i colori, i materiali e le coordinate delle trame. Le mesh vengono generalmente create con software 3D e mantenute in file separati dal codice dell'applicazione. La biglia e il labirinto sono due esempi di mesh usate dal gioco.

Nota  Marble Maze usa il tipo di file AutoDesk FBX Interchange File (.fbx) come formato della fase di progettazione e il tipo di file SDK-Mesh (.sdkmesh) come formato della fase di esecuzione. SDK-Mesh, usato anche in altri esempi DirectX, è un formato binario che fornisce un metodo di base per il caricamento dei dati di mesh. Per altre informazioni sul formato SDK-Mesh, vedi il documento relativo all'aggiornamento dell'utilità di esportazione del contenuto degli esempi.

Marble Maze usa la classe SDKMesh per gestire le mesh. Questa classe è dichiarata in SDKMesh.h. SDKMesh fornisce metodi per il caricamento, il rendering e l'eliminazione dei dati di mesh.

Importante  Marble Maze usa il formato SDK-Mesh e fornisce la classe SDKMesh solo a scopo illustrativo. Sebbene sia utile per l'apprendimento e per la creazione di prototipi, SDK-Mesh è un formato molto elementare che potrebbe non soddisfare i requisiti per lo sviluppo della maggior parte dei giochi. Consigliamo di usare un formato di mesh che soddisfi le esigenze specifiche del tuo gioco.

L'esempio seguente mostra in che modo il metodo MarbleMaze::LoadDeferredResources usa il metodo SDKMesh::Create per caricare i dati di mesh per il labirinto e per la palla.


// Load the meshes.
DX::ThrowIfFailed(
    m_mazeMesh.Create(
        m_d3dDevice.Get(),
        L"Media\\Models\\maze1.sdkmesh",
        false
        )
    );

DX::ThrowIfFailed(
    m_marbleMesh.Create(
        m_d3dDevice.Get(),
        L"Media\\Models\\marble2.sdkmesh",
        false
        )
    );

Caricamento dei dati per gli urti

Lo scopo di questa sezione non è descrivere il modo in cui Marble Maze implementa la simulazione della fisica tra la biglia e il labirinto. Tieni però presente che la geometria di mesh per il sistema della fisica viene letta durante il caricamento delle mesh.


// Extract mesh geometry for 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);


Il modo in cui vengono caricati i dati sugli urti dipende dal formato della fase di esecuzione da usare. Per altre informazioni sul caricamento della geometria degli urti da un file SDK-Mesh in Marble Maze, vedi il metodoMarbleMaze::ExtractTrianglesFromMesh nel codice sorgente.

Aggiornamento dello stato del gioco

Marble Maze separa la logica del gioco dalla logica di rendering aggiornando tutti gli oggetti della scena prima di eseguirne il rendering.

Nel documento Struttura dell'applicazione Marble Maze viene descritto il ciclo del gioco principale. L'aggiornamento della scena, che fa parte del ciclo del gioco, si verifica dopo l'elaborazione dell'input e degli eventi di Windows e prima dell'esecuzione del rendering della scena. Il metodo MarbleMaze::Update gestisce l'aggiornamento dell'interfaccia utente e del gioco.

Aggiornamento dell'interfaccia utente

Il metodo MarbleMaze::Update chiama il metodo UserInterface::Update per aggiornare lo stato dell'interfaccia utente.


UserInterface::GetInstance().Update(timeTotal, timeDelta);


Il metodo UserInterface::Update aggiorna ogni elemento nella raccolta dell'interfaccia utente.


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


Le classi che derivano da ElementBase implementano il metodo Update per eseguire comportamenti specifici. Ad esempio, il metodo StopwatchTimer::Update aggiorna il tempo trascorso in base alla quantità fornita e il testo visualizzato in seguito.


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

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

    TextElement::Update(timeTotal, timeDelta);
}


Aggiornamento della scena

Il metodo MarbleMaze::Update aggiorna il gioco in base allo stato corrente della macchina a stati. Quando lo stato del gioco è attivo, vengono aggiornati la fotocamera (in modo che segua la biglia), la parte della matrice di visualizzazione dei constant buffer e la simulazione della fisica.

L'esempio seguente di seguito mostra come il metodo MarbleMaze::Update aggiorna la posizione della fotocamera. Marble Maze usa la variabile m_resetCamera per indicare che è necessario reimpostare la fotocamera in modo che venga posizionata direttamente sopra la biglia. La fotocamera viene reimpostata al riavvio del gioco o quando la biglia cade nel labirinto. Quando è attivo il menu principale o la schermata di visualizzazione dei punteggi elevati, la fotocamera è impostata su una posizione costante. In caso contrario, viene usato il parametro timeDelta per interpolare la fotocamera tra la posizione corrente e quella di destinazione. Quest'ultima è leggermente al di sopra della biglia e davanti alla stessa. L'uso del tempo trascorso del fotogramma consente alla fotocamera di seguire gradualmente, o inseguire, la biglia.


static float eyeDistance = 200.0f;
static float3 eyePosition = float3(0, 0, 0);

// Gradually move the camera above the marble.
float3 targetEyePosition = marblePosition - (eyeDistance * float3(g.x, g.y, g.z));
if (m_resetCamera)
{
    eyePosition = targetEyePosition;
    m_resetCamera = false;
}
else
{
    eyePosition = eyePosition + ((targetEyePosition - eyePosition) * min(1, timeDelta * 8));
}

// Look at the marble. 
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
    // Override camera position for menus.
    eyePosition = marblePosition + float3(75.0f, -150.0f, -75.0f);
    m_camera->SetViewParameters(eyePosition, marblePosition, float3(0.0f, 0.0f, -1.0f));
}
else
{
    m_camera->SetViewParameters(eyePosition, marblePosition, float3(0.0f, 1.0f, 0.0f));
}


L'esempio seguente mostra come il metodo MarbleMaze::Update aggiorna i constant buffer per la biglia e il labirinto. La matrice del modello, o globale, del labirinto resta sempre la matrice identità, una matrice quadrata in cui tutti gli elementi della diagonale principale sono costituiti dal numero 1, mentre i restanti elementi sono costituiti dal numero 0. La matrice del modello della biglia è basata sulla matrice posizione moltiplicata per la matrice rotazione. Le funzioni mul e translation sono definite in BasicMath.h.


// Update the model matrices based on the simulation.
m_mazeConstantBufferData.model = identity();
m_marbleConstantBufferData.model = mul(
    translation(marblePosition.x, marblePosition.y, marblePosition.z),
    marbleRotationMatrix
    );

// Update the view matrix based on the camera.
float4x4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;


Per informazioni sul modo in cui il metodo MarbleMaze::Update legge l'input dell'utente e simula lo spostamento della biglia, vedi Aggiunta di input e interattività all'esempio di Marble Maze.

Esecuzione del rendering della scena

Di seguito sono elencati i passaggi da generalmente inclusi nel rendering di una scena.

  1. Impostare il buffer depth-stencil della destinazione di rendering corrente.
  2. Cancellare le visualizzazioni di rendering e stencil.
  3. Preparare i vertex shader e i pixel shader per il disegno.
  4. Eseguire il rendering degli oggetti 3D nella scena.
  5. Eseguire il rendering degli eventuali oggetti 2D che vuoi visualizzare davanti alla scena.
  6. Presentare l'immagine di cui è stato eseguito il rendering sul monitor.

Il metodo MarbleMaze::Render associa la destinazione di rendering e le visualizzazioni depth-stencil, cancella tali visualizzazioni, disegna la scena e quindi disegna la sovrimpressione.

Preparazione delle destinazioni di rendering

Prima di eseguire il rendering della scena, è necessario impostare il buffer depth-stencil della destinazione di rendering corrente. Se non è garantito che la scena disegni ogni pixel sullo schermo, cancella anche le visualizzazioni di rendering e stencil. Marble Maze cancella tali visualizzazioni per ogni fotogramma per fare in modo che non siano presenti elementi visibili del fotogramma precedente.

L'esempio seguente mostra come il metodo MarbleMaze::Render chiama il metodo ID3D11DeviceContext::OMSetRenderTargets per impostare la destinazione di rendering e il buffer depth-stencil come correnti. La variabile membro m_renderTargetView, un oggetto ID3D11RenderTargetView, e la variabile membro m_depthStencilView, un oggetto ID3D11DepthStencilView, vengono definite e inizializzate dalla classe DirectXBase.


// Bind the render targets.
m_d3dContext->OMSetRenderTargets(
    1,
    m_renderTargetView.GetAddressOf(),
    m_depthStencilView.Get()
    );

// Clear the render target and depth stencil to default values. 
const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };

m_d3dContext->ClearRenderTargetView(
    m_renderTargetView.Get(),
    clearColor
    );

m_d3dContext->ClearDepthStencilView(
    m_depthStencilView.Get(),
    D3D11_CLEAR_DEPTH,
    1.0f,
    0
    );


Le interfacce ID3D11RenderTargetView e ID3D11DepthStencilView supportano il meccanismo di visualizzazione delle trame fornito da Direct3D 10 e versioni successive. Per altre informazioni sulle visualizzazioni delle trame, vedi il documento relativo alle visualizzazioni delle trame (Direct3D 10). Il metodo OMSetRenderTargets prepara la fase di unione output della pipeline Direct3D. Per altre informazioni su questa fase, vedi il documento relativo alla fase di unione output.

Preparazione dei vertex shader e dei pixel shader

Prima di eseguire il rendering degli oggetti della scena, completa i passaggi seguenti per preparare i vertex shader e i pixel shader per il disegno.

  1. Impostare il layout di input dello shader come layout corrente.
  2. Impostare i vertex shader e i pixel shader come shader correnti.
  3. Aggiornare i constant buffer con i dati da passare agli shader.

Importante  Marble Maze usa una coppia di vertex shader e pixel shader per tutti gli oggetti 3D. Se il gioco usa più di una coppia di shader, devi eseguire queste operazioni ogni volta che disegni oggetti che usano shader differenti. Per ridurre il sovraccarico associato alla modifica dello stato dello shader, consigliamo di raggruppare le chiamate del rendering per tutti gli oggetti che usano gli stessi shader.

La sezione Caricamento degli shader in questo documento descrive la creazione del layout di input durante la creazione del vertex shader. L'esempio seguente mostra come il metodoMarbleMaze::Render usa il metodo ID3D11DeviceContext::IASetInputLayout per impostare il layout come layout corrente.


m_d3dContext->IASetInputLayout(m_inputLayout.Get());


L'esempio seguente illustra come il metodo MarbleMaze::Render usa i metodi ID3D11DeviceContext::VSSetShader e ID3D11DeviceContext::PSSetShader per impostare rispettivamente i vertex shader e i pixel shader come shader correnti.


// Set the vertex shader stage state.
m_d3dContext->VSSetShader(
    m_vertexShader.Get(),   // use this vertex shader 
    nullptr,                // don't use shader linkage
    0                       // don't use shader linkage
    );

// Set the pixel shader stage state.
m_d3dContext->PSSetShader(
    m_pixelShader.Get(),    // use this pixel shader 
    nullptr,                // don't use shader linkage
    0                       // don't use shader linkage
    );

m_d3dContext->PSSetSamplers(
    0,                       // starting at the first sampler slot
    1,                       // set one sampler binding
    m_sampler.GetAddressOf() // to use this sampler
    );


Dopo l'impostazione degli shader e del relativo layout di input, MarbleMaze::Render usa il metodo ID3D11DeviceContext::UpdateSubresource per aggiornare il constant buffer con le matrici di modello, visualizzazione e proiezione per il labirinto. Il metodo UpdateSubresource copia i dati della matrice dalla memoria della CPU alla memoria del GPU. Tieni presente che i componenti view e model della struttura ConstantBuffer vengono aggiornati nel metodo MarbleMaze::Update. Il metodo MarbleMaze::Render chiama quindi i metodi ID3D11DeviceContext::VSSetConstantBuffers e ID3D11DeviceContext::PSSetConstantBuffers per impostare il constant buffer come corrente.


// Update the constant buffer with the new data.
m_d3dContext->UpdateSubresource(
    m_constantBuffer.Get(),
    0,
    nullptr,
    &m_mazeConstantBufferData,
    0,
    0
    );

m_d3dContext->VSSetConstantBuffers(
    0,                // starting at the first constant buffer slot
    1,                // set one constant buffer binding
    m_constantBuffer.GetAddressOf() // to use this buffer
    );

m_d3dContext->PSSetConstantBuffers(
    0,                // starting at the first constant buffer slot
    1,                // set one constant buffer binding
    m_constantBuffer.GetAddressOf() // to use this buffer
    );


Il metodo MarbleMaze::Render esegue operazioni simili per preparare la biglia di cui eseguire il rendering.

Esecuzione del rendering del labirinto e della biglia

Dopo aver attivato gli shader correnti, puoi disegnare gli oggetti della scena. Il metodoMarbleMaze::Render chiama il metodo SDKMesh::Render per eseguire il rendering della mesh del labirinto.


m_mazeMesh.Render(m_d3dContext.Get(), 0, INVALID_SAMPLER_SLOT, INVALID_SAMPLER_SLOT);


Il metodo MarbleMaze::Render esegue operazioni simili per eseguire il rendering della biglia.

Come accennato in precedenza in questo documento, la classe SDKMesh viene fornita a scopo dimostrativo, ma ne sconsigliamo l'uso negli ambienti di produzione. Nota, tuttavia, che il metodo SDKMesh::RenderMesh, chiamato da SDKMesh::Render, usa i metodi ID3D11DeviceContext::IASetVertexBuffers e ID3D11DeviceContext::IASetIndexBuffer per impostare i vertex buffer e gli index buffer correnti che definiscono la mesh e il metodo ID3D11DeviceContext::DrawIndexed per disegnare i buffer. Per altre informazioni su come usare i vertex buffer e gli index buffer, vedi l'introduzione ai buffer in Direct3D 11.

Disegno dell'interfaccia utente e della sovrimpressione

Dopo aver disegnato gli oggetti 3D della scena, Marble Maze disegna gli elementi 2D dell'interfaccia utente visualizzati davanti alla scena.

Il metodo MarbleMaze::Render infine disegna l'interfaccia utente e la sovrimpressione.


// Draw the user interface and the overlay.
UserInterface::GetInstance().Render();

m_sampleOverlay->Render();


Il metodo UserInterface::Render usa un oggetto ID2D1DeviceContext per disegnare gli elementi dell'interfaccia utente. Questo metodo imposta lo stato del disegno, disegna tutti gli elementi attivi dell'interfaccia utente e quindi ripristina lo stato precedente del disegno.


void UserInterface::Render()
{
    m_d2dContext->SaveDrawingState(m_stateBlock.Get());
    m_d2dContext->BeginDraw();
    m_d2dContext->SetTransform(D2D1::Matrix3x2F::Identity());
    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if ((*iter)->IsVisible())
            (*iter)->Render();
    }

    m_d2dContext->EndDraw();
    m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}


Il metodo SampleOverlay::Render usa una tecnica simile per disegnare la bitmap di sovrimpressione.

Presentazione della scena

Dopo avere disegnato tutti gli oggetti 2D e 3D della scena, Marble Maze mostra l'immagine di cui è stato eseguito il rendering sul monitor. Inoltre, sincronizza il disegno rispetto al blanking verticale per evitare che venga sprecato tempo per disegnare fotogrammi che non verranno mai visualizzati. Marble Maze gestisce inoltre le modifiche del dispositivo durante la presentazione della scena.

Quando il metodo MarbleMaze::Render restituisce il controllo, il ciclo del gioco chiama il metodo MarbleMaze::Present per inviare l'immagine di cui è stato eseguito il rendering al monitor o allo schermo. La classe MarbleMaze non esegue l'override del metodo DirectXBase::Present. Il metodo DirectXBase::Present chiama IDXGISwapChain1::Present per eseguire l'operazione corrente, come mostrato nell'esempio seguente:


// The application may optionally specify "dirty" or "scroll" rects 
// to improve efficiency in certain scenarios. 
// In this sample, however, we do not utilize those features.
DXGI_PRESENT_PARAMETERS parameters = {0};
parameters.DirtyRectsCount = 0;
parameters.pDirtyRects = nullptr;
parameters.pScrollRect = nullptr;
parameters.pScrollOffset = nullptr;

// 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->Present1(1, 0, &parameters);


In questo esempio, m_swapChain è un oggetto IDXGISwapChain1. L'inizializzazione di questo oggetto è descritta nella sezione Inizializzazione di Direct3D e Direct2D in questo documento.

Il primo parametro in IDXGISwapChain1::Present, SyncInterval, specifica il numero di blanking verticali di attesa prima della presentazione del fotogramma. Marble Maze specifica 1 in modo da attendere fino al blanking verticale successivo. Il blanking verticale è l'intervallo di tempo tra la fine della visualizzazione di un fotogramma sul monitor e l'inizio della visualizzazione del fotogramma successivo.

Il metodo IDXGISwapChain1::Present1 restituisce un codice di errore che indica che il dispositivo è stato rimosso o si è verificato un errore. In questo caso, Marble Maze reinizializza il dispositivo.


// Reinitialize the renderer if the device was disconnected  
// or the driver was upgraded. 
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
    Initialize(m_window, m_dpi);
}
else
{
    DX::ThrowIfFailed(hr);
}


Passaggi successivi

Per informazioni su alcune delle procedure fondamentali da tenere presente quando si usano i dispositivi di input, leggere Aggiunta di input e interattività all'esempio di Marble Maze. Questo documento descrive il supporto di tocco, accelerometro, controller Xbox 360 e input del mouse in Marble Maze.

Argomenti correlati

Aggiunta di input e interattività all'esempio di Marble Maze
Struttura dell'applicazione Marble Maze
Sviluppo di Marble Maze, un gioco di Windows Store scritto in C++ e DirectX

 

 

Mostra:
© 2014 Microsoft