Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Annotazioni
Questo argomento fa parte della serie di esercitazioni Creare un semplice gioco UWP (Universal Windows Platform) con DirectX. L'argomento in tale collegamento imposta il contesto per la serie.
Finora abbiamo illustrato come strutturare un gioco UWP (Universal Windows Platform) e come definire una macchina a stati per gestire il flusso del gioco. È ora possibile imparare a sviluppare il framework di rendering. Vediamo come il gioco di esempio esegue il rendering della scena del gioco usando Direct3D 11.
Direct3D 11 contiene un set di API che forniscono l'accesso alle funzionalità avanzate di hardware grafico ad alte prestazioni che possono essere usate per creare grafica 3D per applicazioni a elevato utilizzo di grafica, ad esempio giochi.
Il rendering della grafica del gioco significa fondamentalmente generare una sequenza di fotogrammi sullo schermo. In ogni fotogramma, è necessario eseguire il rendering degli oggetti visibili nella scena, in base alla prospettiva.
Per eseguire il rendering di un fotogramma, è necessario passare le informazioni necessarie sulla scena all'hardware in modo che possa essere visualizzato sullo schermo. Se vuoi visualizzare qualsiasi elemento sullo schermo, devi avviare il rendering non appena il gioco inizia l'esecuzione.
Obiettivi
Per configurare un framework di rendering di base per visualizzare l'output grafico per un gioco UWP basato su DirectX. È possibile suddividere in modo libero questi tre passaggi.
- Stabilire una connessione all'interfaccia grafica.
- Creare le risorse necessarie per disegnare la grafica.
- Visualizzare la grafica renderizzando il quadro.
In questo argomento viene illustrato il rendering della grafica, coprendo i passaggi 1 e 3.
Framework di rendering II: il rendering del gioco illustra il passaggio 2, come configurare il framework di rendering e come vengono preparati i dati prima che il rendering possa verificarsi.
Inizia subito
È consigliabile acquisire familiarità con i concetti di base relativi alla grafica e al rendering. Se non si ha familiarità con Direct3D e il rendering, vedere Termini e concetti per una breve descrizione della grafica e dei termini di rendering usati in questo argomento.
Per questo gioco, la classe GameRenderer rappresenta il renderer per questo gioco di esempio. È responsabile della creazione e della gestione di tutti gli oggetti Direct3D 11 e Direct2D usati per generare gli oggetti visivi del gioco. Mantiene inoltre un riferimento all'oggetto Simple3DGame utilizzato per recuperare l'elenco di oggetti da rendere, nonché lo stato del gioco per l'HUD (heads-up display).
In questa parte del tutorial ci concentreremo sul rendering di oggetti 3D nel gioco.
Stabilire una connessione all'interfaccia grafica
Per informazioni sull'accesso all'hardware per il rendering, consulta l'argomento "Definire il framework per le app UWP del gioco".
Il metodo App::Initialize
La funzione std::make_shared, come illustrato di seguito, viene usata per creare un shared_ptr a DX::DeviceResources, che fornisce anche l'accesso al dispositivo.
In Direct3D 11 viene usato un dispositivo per allocare ed eliminare oggetti, eseguire il rendering di primitive e comunicare con la scheda grafica tramite il driver grafico.
void Initialize(CoreApplicationView const& applicationView)
{
...
// At this point we have access to the device.
// We can create the device-dependent resources.
m_deviceResources = std::make_shared<DX::DeviceResources>();
}
Visualizzare la grafica tramite la renderizzazione del frame
La scena del gioco deve rendersi quando il gioco viene avviato. Le istruzioni per il rendering iniziano nel metodo GameMain::Run, come illustrato di seguito.
Il flusso semplice è questo.
- Aggiornamento
- Rendere
- Presente
Metodo GameMain::Run
void GameMain::Run()
{
while (!m_windowClosed)
{
if (m_visible) // if the window is visible
{
switch (m_updateState)
{
...
default:
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
Update();
m_renderer->Render();
m_deviceResources->Present();
m_renderNeeded = false;
}
}
else
{
CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
}
}
m_game->OnSuspending(); // Exiting due to window close, so save state.
}
Aggiornamento
Vedi l'argomento Gestione del flusso di gioco per ulteriori informazioni su come gli stati del gioco vengono aggiornati nel metodo GameMain::Update.
Rendering
Il rendering è implementato tramite la chiamata del metodo GameRenderer::Render all'interno di GameMain::Run.
Se il rendering stereo è abilitato, ci sono due passaggi di rendering: uno per l'occhio sinistro e uno per quello destro. In ogni passaggio di rendering, associamo la destinazione di rendering e la visualizzazione depth-stencil al dispositivo. Cancellamo anche la visualizzazione depth-stencil in un secondo momento.
Annotazioni
Il rendering stereo può essere ottenuto usando altri metodi, ad esempio stereo a singolo passaggio usando la creazione di istanze dei vertici o gli shader di geometria. Il metodo a due passaggi di rendering è un modo più lento ma più pratico per ottenere il rendering stereoscopico.
Quando il gioco è in esecuzione e le risorse vengono caricate, aggiorniamo la matrice di proiezione , una volta per ogni passaggio di rendering. Gli oggetti sono leggermente diversi in ogni visualizzazione. Successivamente, configuriamo la pipeline di rendering grafico .
Annotazioni
Per altre informazioni su come vengono caricate le risorse grafiche, vedere Creare e caricare risorse grafiche DirectX.
In questo gioco di esempio, il renderer è progettato per usare un layout di vertici standard in tutti gli oggetti. Questo semplifica la progettazione dello shader e consente di modificare facilmente gli shader, indipendentemente dalla geometria degli oggetti.
Metodo GameRenderer::Render
Impostiamo il contesto Direct3D per usare un layout dei vertici di input. Gli oggetti layout di input descrivono il modo in cui i dati del buffer dei vertici vengono veicolati nella pipeline di rendering .
Successivamente, impostiamo il contesto Direct3D per usare i buffer costanti definiti in precedenza, che vengono usati dal vertex shader fase della pipeline fase della pipeline e dalla fase pixel shader pipeline.
Annotazioni
Per altre informazioni sulla definizione dei buffer costanti, vedere Framework di rendering II: Rendering di giochi.
Poiché lo stesso layout di input e il set di buffer costanti vengono usati per tutti gli shader presenti nella pipeline, viene impostato una sola volta per ogni fotogramma.
void GameRenderer::Render()
{
bool stereoEnabled{ m_deviceResources->GetStereoState() };
auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };
int renderingPasses = 1;
if (stereoEnabled)
{
renderingPasses = 2;
}
for (int i = 0; i < renderingPasses; i++)
{
// Iterate through the number of rendering passes to be completed.
// 2 rendering passes if stereo is enabled.
if (i > 0)
{
// Doing the Right Eye View.
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetViewRight() };
// Resets render targets to the screen.
// OMSetRenderTargets binds 2 things to the device.
// 1. Binds one render target atomically to the device.
// 2. Binds the depth-stencil view, as returned by the GetDepthStencilView method, to the device.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargets
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
// Clears the depth stencil view.
// A depth stencil view contains the format and buffer to hold depth and stencil info.
// For more info about depth stencil view, go to:
// https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-stencil-view--dsv-
// A depth buffer is used to store depth information to control which areas of
// polygons are rendered rather than hidden from view. To learn more about a depth buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-buffers
// A stencil buffer is used to mask pixels in an image, to produce special effects.
// The mask determines whether a pixel is drawn or not,
// by setting the bit to a 1 or 0. To learn more about a stencil buffer,
// go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/stencil-buffers
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// Direct2D -- discussed later
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmapRight());
}
else
{
// Doing the Mono or Left Eye View.
// As compared to the right eye:
// m_deviceResources->GetBackBufferRenderTargetView instead of GetBackBufferRenderTargetViewRight
ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };
// Same as the Right Eye View.
d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);
// d2d -- Discussed later under Adding UI
d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmap());
}
const float clearColor[4] = { 0.5f, 0.5f, 0.8f, 1.0f };
// Only need to clear the background when not rendering the full 3D scene since
// the 3D world is a fully enclosed box and the dynamics prevents the camera from
// moving outside this space.
if (i > 0)
{
// Doing the Right Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetViewRight(), clearColor);
}
else
{
// Doing the Mono or Left Eye View.
d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), clearColor);
}
// Render the scene objects
if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
{
// This section is only used after the game state has been initialized and all device
// resources needed for the game have been created and associated with the game objects.
if (stereoEnabled)
{
// When doing stereo, it is necessary to update the projection matrix once per rendering pass.
auto orientation = m_deviceResources->GetOrientationTransform3D();
ConstantBufferChangeOnResize changesOnResize;
// Apply either a left or right eye projection, which is an offset from the middle
XMStoreFloat4x4(
&changesOnResize.projection,
XMMatrixMultiply(
XMMatrixTranspose(
i == 0 ?
m_game->GameCamera().LeftEyeProjection() :
m_game->GameCamera().RightEyeProjection()
),
XMMatrixTranspose(XMLoadFloat4x4(&orientation))
)
);
d3dContext->UpdateSubresource(
m_constantBufferChangeOnResize.get(),
0,
nullptr,
&changesOnResize,
0,
0
);
}
// Update variables that change once per frame.
ConstantBufferChangesEveryFrame constantBufferChangesEveryFrameValue;
XMStoreFloat4x4(
&constantBufferChangesEveryFrameValue.view,
XMMatrixTranspose(m_game->GameCamera().View())
);
d3dContext->UpdateSubresource(
m_constantBufferChangesEveryFrame.get(),
0,
nullptr,
&constantBufferChangesEveryFrameValue,
0,
0
);
// Set up the graphics pipeline. This sample uses the same InputLayout and set of
// constant buffers for all shaders, so they only need to be set once per frame.
// For more info about the graphics or rendering pipeline, see
// https://learn.microsoft.com/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline
// IASetInputLayout binds an input-layout object to the input-assembler (IA) stage.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
// Set up the Direct3D context to use this vertex layout. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetinputlayout
d3dContext->IASetInputLayout(m_vertexLayout.get());
// VSSetConstantBuffers sets the constant buffers used by the vertex shader pipeline stage.
// Set up the Direct3D context to use these constant buffers. For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-vssetconstantbuffers
ID3D11Buffer* constantBufferNeverChanges{ m_constantBufferNeverChanges.get() };
d3dContext->VSSetConstantBuffers(0, 1, &constantBufferNeverChanges);
ID3D11Buffer* constantBufferChangeOnResize{ m_constantBufferChangeOnResize.get() };
d3dContext->VSSetConstantBuffers(1, 1, &constantBufferChangeOnResize);
ID3D11Buffer* constantBufferChangesEveryFrame{ m_constantBufferChangesEveryFrame.get() };
d3dContext->VSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
ID3D11Buffer* constantBufferChangesEveryPrim{ m_constantBufferChangesEveryPrim.get() };
d3dContext->VSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
// Sets the constant buffers used by the pixel shader pipeline stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-pssetconstantbuffers
d3dContext->PSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
d3dContext->PSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
ID3D11SamplerState* samplerLinear{ m_samplerLinear.get() };
d3dContext->PSSetSamplers(0, 1, &samplerLinear);
for (auto&& object : m_game->RenderObjects())
{
// The 3D object render method handles the rendering.
// For more info, see Primitive rendering below.
object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
}
}
// Start of 2D rendering
...
}
}
Rendering primitivo
Quando si esegue il rendering della scena, si percorrono tutti gli oggetti che devono essere renderizzati. I passaggi seguenti vengono ripetuti per ogni oggetto (primitivo).
- Aggiornare il buffer costante (m_constantBufferChangesEveryPrim) con la matrice di trasformazione globale del modello e le informazioni sui materiali.
- Il m_constantBufferChangesEveryPrim contiene parametri per ogni oggetto. Include la matrice di trasformazione da oggetto a mondo, nonché le proprietà materiali, ad esempio il colore e l'esponente speculare per i calcoli di illuminazione.
- Impostare il contesto di Direct3D per utilizzare il layout dei vertici di input, affinché i dati dell'oggetto mesh vengano trasmessi nella fase di assemblatore di input (IA) della pipeline di rendering .
- Impostare il contesto Direct3D per usare un buffer di indice nella fase IA. Specificare le informazioni primitive: tipo, ordine dati.
- Effettuare una chiamata di disegno per disegnare la primitiva indicizzata non istanziata. Il metodo GameObject::Render aggiorna la primitiva buffer costante con i dati specifici di una determinata primitiva. Ciò comporta una chiamata DrawIndexed sul contesto per disegnare la geometria di ogni primitiva. In particolare, questa chiamata di disegno accoda comandi e dati all'unità di elaborazione grafica (GPU), come parametrizzato dai dati del buffer costante. Ogni chiamata di disegno esegue il vertex shader una volta per ogni vertice e quindi il pixel shader una volta per ogni pixel di ogni triangolo nella primitiva. Le trame fanno parte dello stato usato dal pixel shader per eseguire il rendering.
Ecco i motivi per l'uso di più buffer costanti.
- Il gioco usa più buffer costanti, ma deve aggiornare questi buffer una sola volta per ogni primitiva. Come accennato in precedenza, i buffer costanti sono come input per gli shader che vengono eseguiti per ogni primitiva. Alcuni dati sono statici (m_constantBufferNeverChanges); alcuni dati sono costanti sul fotogramma (m_constantBufferChangesEveryFrame), ad esempio la posizione della fotocamera; e alcuni dati sono specifici della primitiva, ad esempio il colore e le trame (m_constantBufferChangesEveryPrim).
- Il renderer del gioco separa questi input in buffer costanti diversi per ottimizzare la larghezza di banda di memoria usata dalla CPU e dalla GPU. Questo approccio consente anche di ridurre al minimo la quantità di dati di cui la GPU deve tenere traccia. La GPU ha un'ampia coda di comandi e ogni volta che il gioco chiama Draw, tale comando viene accodato insieme ai dati associati. Quando un gioco aggiorna il buffer costante di base ed emette il comando successivo Draw, il driver grafico aggiunge questo comando successivo e i dati associati alla coda. Se il gioco disegna 100 primitive, potrebbe avere potenzialmente 100 copie dei dati del buffer costante nella coda. Per ridurre al minimo la quantità di dati inviati dal gioco alla GPU, il gioco usa un buffer costante primitivo separato che contiene solo gli aggiornamenti per ogni primitiva.
Metodo GameObject::Render
void GameObject::Render(
_In_ ID3D11DeviceContext* context,
_In_ ID3D11Buffer* primitiveConstantBuffer
)
{
if (!m_active || (m_mesh == nullptr) || (m_normalMaterial == nullptr))
{
return;
}
ConstantBufferChangesEveryPrim constantBuffer;
// Put the model matrix info into a constant buffer, in world matrix.
XMStoreFloat4x4(
&constantBuffer.worldMatrix,
XMMatrixTranspose(ModelMatrix())
);
// Check to see which material to use on the object.
// If a collision (a hit) is detected, GameObject::Render checks the current context, which
// indicates whether the target has been hit by an ammo sphere. If the target has been hit,
// this method applies a hit material, which reverses the colors of the rings of the target to
// indicate a successful hit to the player. Otherwise, it applies the default material
// with the same method. In both cases, it sets the material by calling Material::RenderSetup,
// which sets the appropriate constants into the constant buffer. Then, it calls
// ID3D11DeviceContext::PSSetShaderResources to set the corresponding texture resource for the
// pixel shader, and ID3D11DeviceContext::VSSetShader and ID3D11DeviceContext::PSSetShader
// to set the vertex shader and pixel shader objects themselves, respectively.
if (m_hit && m_hitMaterial != nullptr)
{
m_hitMaterial->RenderSetup(context, &constantBuffer);
}
else
{
m_normalMaterial->RenderSetup(context, &constantBuffer);
}
// Update the primitive constant buffer with the object model's info.
context->UpdateSubresource(primitiveConstantBuffer, 0, nullptr, &constantBuffer, 0, 0);
// Render the mesh.
// See MeshObject::Render method below.
m_mesh->Render(context);
}
Metodo MeshObject::Render
void MeshObject::Render(_In_ ID3D11DeviceContext* context)
{
// PNTVertex is a struct. stride provides us the size required for all the mesh data
// struct PNTVertex
//{
// DirectX::XMFLOAT3 position;
// DirectX::XMFLOAT3 normal;
// DirectX::XMFLOAT2 textureCoordinate;
//};
uint32_t stride{ sizeof(PNTVertex) };
uint32_t offset{ 0 };
// Similar to the main render loop.
// Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
ID3D11Buffer* vertexBuffer{ m_vertexBuffer.get() };
context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
// IASetIndexBuffer binds an index buffer to the input-assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetindexbuffer.
context->IASetIndexBuffer(m_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);
// Binds information about the primitive type, and data order that describes input data for the input assembler stage.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetprimitivetopology.
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// Draw indexed, non-instanced primitives. A draw API submits work to the rendering pipeline.
// For more info, see
// https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexed.
context->DrawIndexed(m_indexCount, 0, 0);
}
Metodo DeviceResources::Present
Chiamiamo il metodo DeviceResources::Present per visualizzare il contenuto che abbiamo inserito nei buffer.
Usiamo il termine catena di scambio per una raccolta di buffer usati per la visualizzazione di frame all'utente. Ogni volta che un'applicazione presenta un nuovo frame per la visualizzazione, il primo buffer nella catena di scambio assume il posto del buffer visualizzato. Questo processo viene chiamato scambio o inversione. Per ulteriori informazioni, vedere Swap chains.
- Il metodo Present dell'interfaccia IDXGISwapChain1 istruisce DXGI di bloccare fino a quando non si verifica la sincronizzazione verticale (VSync), mettendo l'applicazione in sospensione fino alla sincronizzazione verticale successiva. In questo modo non si sprecano cicli di rendering dei fotogrammi che non verranno mai visualizzati sullo schermo.
- Il metodo dell'interfaccia
Discard ViewID3D11DeviceContext3 elimina il contenuto della destinazione di rendering . Si tratta di un'operazione valida solo quando il contenuto esistente verrà completamente sovrascritto. Se vengono usati rettangoli sporchi o di scorrimento, allora questa chiamata dovrebbe essere rimossa.
- Usando lo stesso metodo di DiscardView, rimuovere il contenuto dell'depth-stencil.
- Il metodo HandleDeviceLost viene usato per gestire lo scenario in cui il dispositivo viene rimosso. Se il dispositivo è stato rimosso da una disconnessione o da un aggiornamento del driver, è necessario ricreare tutte le risorse del dispositivo. Per ulteriori informazioni, vedere Gestire gli scenari di rimozione dei dispositivi in Direct3D 11.
Suggerimento
Per ottenere una frequenza dei fotogrammi uniforme, è necessario assicurarsi che la quantità di lavoro per il rendering di un fotogramma rientri nel tempo tra i VSync.
// Present the contents of the swap chain to the screen.
void DX::DeviceResources::Present()
{
// The first argument instructs DXGI to block until VSync, putting the application
// to sleep until the next VSync. This ensures we don't waste any cycles rendering
// frames that will never be displayed to the screen.
HRESULT hr = m_swapChain->Present(1, 0);
// Discard the contents of the render target.
// This is a valid operation only when the existing contents will be entirely
// overwritten. If dirty or scroll rects are used, this call should be removed.
m_d3dContext->DiscardView(m_d3dRenderTargetView.get());
// Discard the contents of the depth stencil.
m_d3dContext->DiscardView(m_d3dDepthStencilView.get());
// If the device was removed either by a disconnection or a driver upgrade, we
// must recreate all device resources.
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
{
HandleDeviceLost();
}
else
{
winrt::check_hresult(hr);
}
}
Passaggi successivi
In questo argomento è stato illustrato il rendering della grafica sullo schermo e viene fornita una breve descrizione per alcuni dei termini di rendering usati (di seguito). Scopri di più sul rendering nell'argomento Framework di rendering II: Rendering di giochi e impara come preparare i dati necessari per il rendering.
Termini e concetti
Scena di gioco semplice
Una scena di gioco semplice è costituita da alcuni oggetti con diverse sorgenti di luce.
La forma di un oggetto è definita da un set di coordinate X, Y, Z nello spazio. La posizione di rendering effettiva nel mondo del gioco può essere determinata applicando una matrice di trasformazione alle coordinate X, Y, Z posizionali. Può anche avere un set di coordinate texture—U e V—che specificano come un materiale viene applicato all'oggetto. Ciò definisce le proprietà di superficie dell'oggetto e consente di vedere se un oggetto ha una superficie grezza (come una palla da tennis) o una superficie lucida liscia (come una palla da bowling).
Le informazioni sulla scena e sull'oggetto vengono utilizzate dal framework di rendering per ricreare la scena fotogramma per fotogramma, rendendola viva sullo schermo.
Pipeline di rendering
La pipeline di rendering è il processo con cui le informazioni sulla scena 3D vengono convertite in un'immagine visualizzata sullo schermo. In Direct3D 11 questa pipeline è programmabile. È possibile adattare le fasi per supportare le esigenze di rendering. Le fasi che utilizzano core shader comuni possono essere programmate utilizzando il linguaggio di programmazione HLSL. È noto anche come pipeline di rendering grafico , oppure semplicemente pipeline .
Per facilitare la creazione di questa pipeline, è necessario avere familiarità con questi dettagli.
- HLSL. È consigliabile usare HLSL Shader Model 5.1 e versioni successive per i giochi DirectX UWP.
- shader.
- Vertex shader e Pixel shader.
- fasi shader.
- Vari formati di shader file.
Per altre informazioni, vedere Informazioni sulla pipeline di rendering direct3D 11 e pipeline grafica.
HLSL
HLSL è il linguaggio di shader di alto livello per DirectX. Usando HLSL, è possibile creare shader programmabili simili a C per la pipeline Direct3D. Per altre informazioni, vedere HLSL.
Shader
Uno shader può essere considerato come un set di istruzioni che determinano la modalità di visualizzazione della superficie di un oggetto durante il rendering. Quelli programmati con HLSL sono noti come shader HLSL. I file di codice sorgente per gli shader [HLSL](#hlsl) hanno l'estensione .hlsl. Questi shader possono essere compilati durante la fase di build o in fase di esecuzione, e configurati al momento dell'esecuzione nella fase appropriata della pipeline. Un oggetto shader compilato ha un'estensione di file .cso.
Gli shader Direct3D 9 possono essere progettati usando il modello di shader 1, il modello di shader 2 e il modello di shader 3; Gli shader Direct3D 10 possono essere progettati solo nel modello di shader 4. Gli shader Direct3D 11 possono essere progettati nel modello di shader 5. Direct3D 11.3 e Direct3D 12 possono essere progettati nel modello di shader 5.1 e Direct3D 12 possono essere progettati anche nel modello di shader 6.
Shader di vertice e shader di pixel
I dati entrano nella pipeline grafica come flusso di primitive e vengono elaborati da vari shader, ad esempio vertex shader e pixel shader.
I vertex shader elaborano i vertici, eseguendo in genere operazioni come trasformazioni, skinning e illuminazione. I pixel shader consentono tecniche di ombreggiatura avanzate, ad esempio l'illuminazione per pixel e la post-elaborazione. Combina variabili costanti, dati di trama, valori interpolati per vertice e altri dati per produrre output per pixel.
Fasi shader
Una sequenza di questi vari shader definiti per elaborare questo flusso di primitive è nota come fasi dello shader in una pipeline di rendering. Le fasi effettive dipendono dalla versione di Direct3D, ma in genere includono le fasi di vertice, geometria e pixel. Esistono anche altre fasi, ad esempio lo hull e gli shader di dominio per la tassellatura e lo shader di calcolo. Tutte queste fasi sono completamente programmabili usando HLSL. Per ulteriori informazioni, vedere la pipeline grafica .
Vari formati di file shader
Ecco le estensioni dei file di codice shader.
- Un file con l'estensione
.hlslcontiene il codice sorgente [HLSL])(#hlsl). - Un file con l'estensione
.csocontiene un oggetto shader compilato. - Un file con estensione
.hè un file di intestazione, ma in un contesto di codice shader questo file di intestazione definisce una matrice di byte che contiene i dati dello shader. - Un file con l'estensione
.hlslicontiene il formato dei buffer costanti. Nel gioco di esempio, il file è Shaders>ConstantBuffers.hlsli.
Annotazioni
È possibile incorporare uno shader caricando un file .cso in fase di esecuzione o aggiungendo un file .h nel codice eseguibile. Ma non useresti entrambi per lo stesso shader.
Conoscenza più approfondita di DirectX
Direct3D 11 è un set di API che possono aiutarci a creare grafica per applicazioni a elevato utilizzo di grafica, ad esempio giochi, in cui vogliamo avere una buona scheda grafica per elaborare un calcolo intensivo. Questa sezione illustra brevemente i concetti di programmazione grafica di Direct3D 11: risorsa, sottorisorsa, dispositivo e contesto di dispositivo.
Conto risorse
È possibile considerare le risorse (note anche come risorse del dispositivo) come informazioni su come eseguire il rendering di un oggetto, ad esempio trama, posizione o colore. Le risorse forniscono dati alla pipeline e definiscono il rendering durante la scena. Le risorse possono essere caricate dai supporti di gioco o generate dinamicamente durante l'esecuzione.
Una risorsa è infatti un'area in memoria accessibile dalla pipeline di Direct3D. Per consentire alla pipeline di accedere in modo efficiente alla memoria, i dati forniti alla pipeline , ad esempio la geometria di input, le risorse dello shader e le trame, devono essere archiviati in una risorsa. Esistono due tipi di risorse da cui derivano tutte le risorse Direct3D: un buffer o una trama. Per ogni fase della pipeline possono essere attive fino a 128 risorse. Per altre informazioni, vedere Risorse.
Sottorisorsa
Il termine sottorisorsa fa riferimento a un subset di una risorsa. Direct3D può fare riferimento a un'intera risorsa oppure può fare riferimento a subset di una risorsa. Per altre informazioni, vedere Subresource.
Profondità-stencil
Una risorsa depth-stencil contiene il formato e il buffer che contiene le informazioni sulla profondità e sullo stencil. Viene creato usando una risorsa texture. Per ulteriori informazioni su come creare una risorsa depth-stencil, consultare Configuring Depth-Stencil Functionality. Si accede alla risorsa depth-stencil tramite la visualizzazione depth-stencil implementata usando l'interfaccia ID3D11DepthStencilView.
Le informazioni di profondità indicano quali aree dei poligoni sono dietro le altre, in modo da poter determinare quali sono nascoste. Le informazioni sugli stencil indicano quali pixel sono mascherati. Può essere utilizzato per produrre effetti speciali perché determina se un pixel viene disegnato o meno; imposta il bit su 1 o 0.
Per ulteriori informazioni, vedere visualizzazione profondità-stencil, buffer di profonditàe buffer stencil.
Destinazione di rendering
Un target di rendering è una risorsa su cui possiamo scrivere alla fine di un passaggio di rendering. Viene comunemente creato utilizzando il metodo ID3D11Device::CreateRenderTargetView usando il buffer posteriore della catena di scambio (che è anche una risorsa) come parametro di input.
Ogni destinazione di rendering deve avere anche una visualizzazione depth-stencil corrispondente perché quando si usa OMSetRenderTargets per impostare la destinazione di rendering prima di usarla, è necessaria anche una visualizzazione depth-stencil. È possibile accedere alla risorsa di destinazione di rendering tramite la visualizzazione di destinazione di rendering implementata usando l'interfaccia ID3D11RenderTargetView.
Dispositivo
È possibile immaginare un dispositivo come un modo per allocare e distruggere oggetti, eseguire il rendering di primitive e comunicare con la scheda grafica tramite il driver grafico.
Per una spiegazione più precisa, un dispositivo Direct3D è il componente di rendering di Direct3D. Un dispositivo incapsula e archivia lo stato di rendering, esegue trasformazioni e operazioni di illuminazione e rasterizza un'immagine in una superficie. Per ulteriori informazioni, consultare Dispositivi
Un dispositivo è rappresentato dall'interfaccia ID3D11Device. In altre parole, l'interfaccia ID3D11Device rappresenta una scheda di visualizzazione virtuale e viene usata per creare risorse di proprietà di un dispositivo.
Esistono versioni diverse di ID3D11Device. ID3D11Device5 è la versione più recente e aggiunge nuovi metodi a quelli in ID3D11Device4. Per altre informazioni su come Direct3D comunica con l'hardware sottostante, vedere architettura WDDM (Windows Device Driver Model).
Ogni applicazione deve avere almeno un dispositivo; la maggior parte delle applicazioni crea solo una. Creare un dispositivo per uno dei driver hardware installati nel computer chiamando D3D11CreateDevice o D3D11CreateDeviceAndSwapChain e specificando il tipo di driver con il flag D3D_DRIVER_TYPE. Ogni dispositivo può usare uno o più contesti di dispositivo, a seconda della funzionalità desiderata. Per altre informazioni, vedere D3D11CreateDevice function.
Contesto del dispositivo
Un contesto dispositivo viene usato per impostare lo stato della pipeline e generare comandi di rendering utilizzando le risorse di proprietà di un dispositivo .
Direct3D 11 implementa due tipi di contesti di dispositivo, uno per il rendering immediato e l'altro per il rendering posticipato; entrambi i contesti sono rappresentati con un'interfaccia ID3D11DeviceContext.
Le interfacce ID3D11DeviceContext hanno versioni diverse; ID3D11DeviceContext4 aggiunge nuovi metodi a quelli in ID3D11DeviceContext3.
ID3D11DeviceContext4 è stato introdotto in Windows 10 Creators Update ed è la versione più recente dell'interfaccia ID3D11DeviceContext. Le applicazioni destinate a Windows 10 Creators Update e versioni successive devono usare questa interfaccia anziché le versioni precedenti. Per altre informazioni, vedere ID3D11DeviceContext4.
DX::D eviceResources
La classe DX::DeviceResources si trova nei file DeviceResources.cpp/.h e controlla tutte le risorse dispositive DirectX.
Memoria tampone
Una risorsa buffer è una raccolta di dati completamente tipizzati raggruppati in elementi. È possibile usare i buffer per archiviare un'ampia gamma di dati, tra cui vettori di posizione, vettori normali, coordinate di trama in un vertex buffer, indici in un buffer di indice o stato del dispositivo. Gli elementi del buffer possono includere valori di dati compressi (ad esempio R8G8B8A8 valori di superficie), interi a 8 bit singoli o quattro valori a virgola mobile a 32 bit.
Sono disponibili tre tipi di buffer: vertex buffer, index buffer e buffer costante.
Buffer del vertice
Contiene i dati dei vertici usati per definire la geometria. I dati dei vertici includono coordinate di posizione, dati di colore, dati delle coordinate di texture, dati normali e così via.
Buffer dell'indice
Contiene offset di tipo intero nei buffer dei vertici e vengono usati per rendere le primitive in modo più efficiente. Un buffer di indice contiene un set sequenziale di indici a 16 bit o a 32 bit; ogni indice viene usato per identificare un vertice in un buffer dei vertici.
Buffer di costanti o buffer di costanti dello shader
Consente di fornire in modo efficiente i dati dello shader alla pipeline. È possibile utilizzare buffer costanti come input per gli shader eseguiti di ogni primitiva e archiviare i risultati nella fase di stream output della pipeline di rendering. Concettualmente, un buffer costante è simile a un buffer di vertici a singolo elemento.
Progettazione e implementazione di buffer
È possibile progettare buffer in base al tipo di dati, ad esempio nel gioco di esempio, viene creato un buffer per i dati statici, un altro per i dati costanti nel frame e un altro per i dati specifici di una primitiva.
Tutti i tipi di buffer vengono incapsulati dall'interfaccia ID3D11Buffer
È possibile associare buffer in questi modi.
- Per la fase input-assembler, chiamando metodi come ID3D11DeviceContext::IASetVertexBuffers e ID3D11DeviceContext::IASetIndexBuffernel contesto di ID3D11DeviceContext.
- Alla fase di output del flusso tramite la chiamata di ID3D11DeviceContext::SOSetTargets.
- Per passare alla fase dello shader chiamando metodi shader, ad esempio ID3D11DeviceContext::VSSetConstantBuffers.
Per altre informazioni, vedere Introduzione ai buffer in Direct3D 11.
DXGI
Microsoft DirectX Graphics Infrastructure (DXGI) è un sottosistema che incapsula alcune delle attività di basso livello necessarie per Direct3D. È necessario prestare particolare attenzione quando si usa DXGI in un'applicazione multithreading per garantire che i deadlock non si verifichino. Per altre info, vedi Multithreading e DXGI
Livello di funzionalità
Il livello di funzionalità è un concetto introdotto in Direct3D 11 per gestire la diversità delle schede video nei computer nuovi ed esistenti. Un livello di funzionalità è un set ben definito di funzionalità dell'unità di elaborazione grafica (GPU).
Ogni scheda video implementa un determinato livello di funzionalità DirectX a seconda delle GPU installate. Nelle versioni precedenti di Microsoft Direct3D è possibile scoprire la versione di Direct3D implementata dalla scheda video e quindi programmare di conseguenza l'applicazione.
Con il livello di funzionalità, quando si crea un dispositivo, è possibile tentare di creare un dispositivo per il livello di funzionalità che si vuole richiedere. Se la creazione del dispositivo funziona, tale livello di funzionalità esiste, in caso contrario, l'hardware non supporta tale livello di funzionalità. È possibile provare a ricreare un dispositivo a un livello di funzionalità inferiore oppure scegliere di uscire dall'applicazione. Ad esempio, il livello di funzionalità 12_0 richiede Direct3D 11.3 o Direct3D 12 e il modello di shader 5.1. Per altre informazioni, vedere livelli di funzionalità Direct3D: Panoramica per ogni livello di funzionalità.
Usando i livelli di funzionalità, è possibile sviluppare un'applicazione per Direct3D 9, Microsoft Direct3D 10 o Direct3D 11 e quindi eseguirla su hardware 9, 10 o 11 (con alcune eccezioni). Per altre informazioni, vedere livelli di funzionalità Direct3D.
Rendering stereo
Il rendering stereo viene usato per migliorare l'illusione della profondità. Usa due immagini, una dall'occhio sinistro e l'altra dall'occhio destro per visualizzare una scena sullo schermo di visualizzazione.
Matematicamente, applichiamo una matrice di proiezione stereo, che consiste in un leggero offset orizzontale applicato a destra e a sinistra rispetto alla matrice di proiezione mono standard per ottenere questo risultato.
Abbiamo eseguito due passaggi di rendering per ottenere il rendering stereo in questo gioco di esempio.
- Associare al buffer di rendering corretto, applicare la proiezione adeguata, quindi renderizzare l'oggetto primitivo.
- Eseguire l'associazione alla destinazione di rendering sinistra, applicare la proiezione sinistra, quindi disegnare l'oggetto primitivo.
Fotocamera e spazio delle coordinate
Il gioco ha il codice per aggiornare il mondo nel proprio sistema di coordinate (talvolta chiamato spazio globale o spazio della scena). Tutti gli oggetti, inclusa la fotocamera, sono posizionati e orientati in questo spazio. Per altre informazioni, vedere Sistemi di coordinate.
Un vertex shader esegue la conversione pesante dalle coordinate del modello alle coordinate del dispositivo con l'algoritmo seguente (dove V è un vettore e M è una matrice).
V(device) = V(model) x M(model-to-world) x M(world-to-view) x M(view-to-device)
-
M(model-to-world)è una matrice di trasformazione dalle coordinate del modello a quelle del mondo, conosciuta anche come matrice di trasformazione del mondo . Questo viene fornito dalla primitiva. -
M(world-to-view)è una matrice di trasformazione dalle coordinate del mondo alle coordinate di visualizzazione, nota anche come matrice di trasformazione visualizzazione.- Questo viene fornito dalla matrice di visualizzazione della fotocamera. È definito dalla posizione della fotocamera insieme ai vettori di sguardo (il vettore guarda a che punta direttamente nella scena dalla fotocamera e il vettore guarda su che è perpendicolare verso l'alto rispetto a esso).
- Nel gioco di esempio m_viewMatrix è la matrice di trasformazione della visualizzazione e viene calcolata usando Camera::SetViewParams.
-
M(view-to-device)è una matrice di trasformazione dalle coordinate di visualizzazione a quelle del dispositivo, nota anche come matrice di proiezione di trasformazione .- Questo viene fornito dalla proiezione della telecamera. Fornisce informazioni sulla quantità di tale spazio effettivamente visibile nella scena finale. Il campo visivo (FoV), le proporzioni e i piani di ritaglio definiscono la matrice di trasformazione della proiezione.
- Nel gioco di esempio, m_projectionMatrix definisce la trasformazione alle coordinate di proiezione, calcolate usando Camera::SetProjParams (per la proiezione stereo, si utilizzano due matrici di proiezione distinte, una per la vista di ciascun occhio).
Il codice shader in VertexShader.hlsl viene caricato con questi vettori e matrici dai buffer costanti ed esegue questa trasformazione per ogni vertice.
Trasformazione delle coordinate
Direct3D usa tre trasformazioni per modificare le coordinate del modello 3D in coordinate pixel (spazio dello schermo). Queste trasformazioni sono trasformazione globale, trasformazione della visualizzazione e trasformazione della proiezione. Per ulteriori informazioni, consultare la panoramica di Transform.
Matrice di trasformazione globale
Una trasformazione globale modifica le coordinate dallo spazio del modello, in cui i vertici vengono definiti in relazione all'origine locale di un modello, allo spazio globale, in cui i vertici vengono definiti rispetto a un'origine comune a tutti gli oggetti di una scena. In sostanza, la trasformazione nel mondo posiziona un modello nel contesto globale; da cui il suo nome. Per altre informazioni, vedere Trasformazione globale.
Visualizzare la matrice di trasformazione
La trasformazione di visualizzazione individua il visualizzatore nello spazio globale, trasformando i vertici nello spazio della fotocamera. Nello spazio della fotocamera, la fotocamera o l'osservatore si trova all'origine, rivolta nella direzione positiva dell'asse z. Per ulteriori informazioni, vai a Visualizza trasformazione.
Matrice di trasformazione proiettiva
La trasformazione di proiezione converte il frustum di visualizzazione in una forma cuboide. Un frustum di visualizzazione è un volume 3D in una scena posizionata rispetto alla fotocamera del viewport. Un riquadro di visualizzazione è un rettangolo 2D in cui viene proiettata una scena 3D. Per altre informazioni, vedere Viewports and clipping
Poiché l'estremità vicina del frustum di visualizzazione è più piccola dell'estremità lontana, questo fa apparire gli oggetti più vicini alla fotocamera come più grandi; è così che viene applicata la prospettiva alla scena. Quindi gli oggetti più vicini al giocatore appaiono più grandi; gli oggetti che si trovano più lontano appaiono più piccoli.
Matematicamente, la trasformazione di proiezione è una matrice che in genere è sia una scala che una proiezione prospettica. Funziona come l'obiettivo di una fotocamera. Per ulteriori informazioni, vedere trasformazione di proiezione .
Stato del campionatore
Lo stato del campionatore determina il modo in cui i dati delle trame vengono campionati usando modalità di indirizzamento delle trame, filtri e livello di dettaglio. Il campionamento viene eseguito ogni volta che un pixel di trama (o texel) viene letto da una trama.
Una trama contiene una matrice di texel. La posizione di ogni texel è indicata da (u,v), dove u è la larghezza e v è l'altezza e viene mappata tra 0 e 1 in base alla larghezza e all'altezza della trama. Le coordinate di texture risultanti vengono usate per indirizzare un texel durante il campionamento di una texture.
Quando le coordinate della texture sono inferiori a 0 o superiori a 1, la modalità di indirizzo della texture definisce come la coordinata della texture determina la posizione di un texel. Ad esempio, quando si usa TextureAddressMode.Clamp, qualsiasi coordinata esterna all'intervallo 0-1 viene bloccata su un valore massimo pari a 1 e il valore minimo di 0 prima del campionamento.
Se la trama è troppo grande o troppo piccola per il poligono, la trama viene filtrata per adattare lo spazio. Un filtro di ingrandimento ingrandisce una trama, un filtro di minimizzazione riduce la trama per adattarsi a un'area più piccola. L'ingrandimento delle texture ripete il texel di esempio per uno o più indirizzi, il che comporta un'immagine più sfocata. La riduzione delle texture è più complessa perché richiede la combinazione di più valori di texel in un unico valore. Ciò può causare aliasing o bordi frastagliati a seconda dei dati della texture. L'approccio più diffuso per la minificazione consiste nell'usare un mipmap. Un mipmap è una trama a più livelli. La dimensione di ogni livello è una potenza di 2 più piccola rispetto al livello precedente, fino a raggiungere una texture di 1x1. Quando viene usata la minificazione, un gioco sceglie il livello mipmap più vicino alle dimensioni necessarie in fase di rendering.
Classe BasicLoader
BasicLoader è una classe loader semplice che fornisce supporto per il caricamento di shader, trame e mesh da file su disco. Fornisce sia metodi sincroni che asincroni. In questo gioco di esempio, i file BasicLoader.h/.cpp sono disponibili nella cartella utilità.
Per ulteriori informazioni, vedere Loader base.