Il presente articolo è stato tradotto automaticamente.
DirectX Factor
Spezzare la barriera Z con gli effetti Direct2D
Scaricare il codice di esempio
Come i bambini, si impara a disegnare prima di imparare a leggere e scrivere, e noi indubbiamente spigolare alcune lezioni dall'esperienza.Scopriamo che il processo temporale della pittura si riflette nel fare uno strato di vernice sulla tela. Dipingiamo precedenti possa essere parzialmente coperto e oscurato dalla cosa dipingiamo più tardi.
Per questo motivo, probabilmente anche qualcuno completamente familiarità con la meccanica della computer grafica può intuire come l'immagine in Figura 1 è stato eseguito il rendering: Ovviamente, lo sfondo era colorata grigio prima; poi venne il triangolo blu, seguito dal verde, e l'ultima dal rosso, che è davanti a tutto il resto. Non è una sorpresa che il processo di rendering figure da posteriore ad anteriore è noto come il "algoritmo del pittore".
Figura 1 tre triangoli sovrapposti
I tre triangoli in Figura 1 potrebbe essere anche pezzi di carta colorata di costruzione disposti in una pila. Se si dovesse aggiungere più triangoli allo stack, essi sarebbe costruire in un mucchio, e quello che era iniziato come una superficie bidimensionale acquisirebbe una terza dimensione.
Anche nella grafica 2D, c'è un rudimentale concetto di un asse Z — uno spazio virtuale ortogonale al schermo bidimensionale o tela. La stratificazione di oggetti 2D piatto è governata dall'ordine"Z" delle figure. In ambienti basati su XAML, ad esempio, la proprietà associata determina quali elementi sembrano sedersi sopra gli altri, ma è davvero solo di ZIndex controlla l'ordine che vengono eseguito il rendering degli elementi sullo schermo.
Il problema è questo: In grafica 2D, un indice Z si applica sempre a figura intera. Si non è possibile utilizzare questo tipo di ordinamento Z disegnare tre figure come quelle di Figura 2, con la prima in cima la seconda, il secondo in cima al terzo, ma il terzo sopra il primo.
Figura 2 triangoli reciprocamente sovrapposte
Che il cambiamento in un angolo di un triangolo sembra leggero, ma ciò che un mondo di differenza rappresenta! L'immagine in Figura 2 potrebbe essere facile con costruzione di carta, ma non è così facile con pittura — o nella vita reale che nella programmazione di grafica 2D. Uno di questi triangoli deve essere reso in due parti con coordinate attentamente calcolate, o usando il ritaglio basato su uno degli altri triangoli.
Gli effetti e la GPU
Il rendering di Figura 2 può essere aiutato enormemente prendendo in prestito alcuni concetti del mondo della grafica 3D.
Tali figure non possono avere indici Z uniformi; invece, le figure devono avere le coordinate Z variabile nel corso della loro intere superficie. Il processo di disegno può quindi mantenere un insieme di coordinate Z (chiamato un Z-buffer o un buffer di profondità) che comprende ogni pixel della superficie di rendering. Come viene eseguito il rendering di ogni figura, la coordinata Z di ogni pixel della figura viene confrontata con la Z corrispondente coordinate in questo buffer di profondità. Se il pixel è in cima la coordinata Z nel buffer di profondità, è disegnato il pixel e la nuova coordinata Z viene memorizzata nel buffer di profondità. Se così non fosse, il pixel viene ignorato.
Questo suona computazionalmente costoso — non solo per il confronto in sé ma per il calcolo di Z le coordinate per ogni pixel di ogni figura grafica — e che è una valutazione accurata. Ecco perché è un lavoro ideale per cosegnare le capacità computazionali parallele della GPU moderna.
Il calcolo di Z coordina per ogni pixel di una figura concettualmente è piuttosto facile se la figura sembra essere un triangolo — e tenere a mente che ogni poligono può essere scomposta in triangoli. Tutto ciò che è necessario è di dare a ciascuno dei tre vertici del triangolo un punto di coordinate 3D, e quindi qualsiasi punto all'interno del triangolo può essere calcolato come media ponderata delle coordinate dei tre vertici. (Si tratta di coordinate baricentriche — non il solo concetto utilizzato in computer grafica sviluppato dal matematico tedesco agosto Ferdinand Möbius.)
Quello stesso processo di interpolazione può ombreggiare il triangolo, pure. Se ogni vertice viene assegnato un colore specifico, quindi ogni pixel all'interno di tale triangolo è una media ponderata di quei tre colori, come mostrato Figura 3.
Figura 3 il Display del programma ThreeTriangles
Che tipo di sfumatura di colore è anche una caratteristica importante della programmazione 3D, perché permette di triangoli essere ombreggiato ad assomigliare a superfici curve. Ma non è un tipo di sfumatura che è comune nella programmazione 2D convenzionale.
L'immagine in Figura 3 è stato creato da un programma scaricabile chiamato ThreeTriangles che gira sotto Windows 8.1 e 8.1 di Windows Phone. (La soluzione è stata creata in Visual Studio 2013 Update 2 utilizzando il nuovo modello Universal App che permette di condividere un sacco di codice tra Windows 8.1 e 8.1 di Windows Phone).
La grafica del programma ThreeTriangles è fatto interamente in Direct2D, utilizzando una caratteristica di Direct2D chiamato effetti o (quando è codice te) effetti personalizzati. Utilizzando effetti personalizzati è possibile ottenere molto più vicina alla programmazione 3D autentico rispetto altrimenti possibile con Direct2D.
Quando si scrive un effetto personalizzato per Direct2D, acquisire un privilegio normalmente limitato ai programmatori 3D: È possibile scrivere codice che viene eseguito su GPU. Questo codice prende la forma di piccoli programmi chiamati shader, che si scrive utilizzando un linguaggio di shading di alto livello (HLSL) che assomiglia a C. Questi shader sono compilati da Visual Studio in file oggetto (.cso) shader compilato durante la compilazione del progetto normale e quindi eseguire sulla GPU quando il programma viene eseguito.
Infatti, gli effetti Direct2D sono talvolta descritte come poco più di wrapper per shader! Gli effetti personalizzati sono l'unico modo che è possibile utilizzare gli shader nel contesto della programmazione Direct2D per realizzare immagini 3D-come.
Tre diversi tipi di shader sono disponibili per l'uso di effetti di Direct2D:
- Un vertex shader che esegue operazioni su vertici. Ogni triangolo ha tre vertici. I vertici sempre coinvolgono un punto di coordinate, ma potrebbero includere altre informazioni, quali il colore.
- Un pixel shader che esegue operazioni su tutti i pixel all'interno di questi triangoli. Tutte le informazioni fornite con i vertici sono interpolate automaticamente sopra la superficie del triangolo in preparazione per il pixel shader.
- Un compute shader che utilizza la GPU per eseguire l'elaborazione parallela pesante. Non discuteremo il compute shader in questo articolo.
Lo shader utilizzato per effetti Direct2D hanno requisiti alquanto diversi rispetto gli shader associati programmazione Direct3D, ma molti dei concetti sono gli stessi.
Effetti incorporati ed effetti personalizzati
Direct2D comprende circa 40 effetti predefiniti incorporati, che eseguono varie manipolazioni di elaborazione di immagini bitmap, come sfocare o contrastare, o vari tipi di manipolazione del colore.
Ciascuno di questi effetti incorporati è identificato da un ID di classe che si utilizza per creare un effetto di quel tipo. Si supponga, ad esempio, che si desidera utilizzare l'effetto della matrice di colori, che consente di specificare una trasformazione per modificare i colori in un'immagine bitmap. Voi probabilmente sarete dichiarare un oggetto di tipo ID2D1Effect come un campo privato nella classe rendering:
Microsoft::WRL::ComPtr<ID2D1Effect> m_colorMatrixEffect;
Nel metodo CreateDeviceDependentResources, è possibile creare questo effetto facendo riferimento a ID classe documentato:
d2dContext->CreateEffect(
CLSID_D2D1ColorMatrix, &m_colorMatrixEffect);
A quel punto, è possibile chiamare SetInput l'oggetto effetto per impostare una bitmap e SetValue per specificare una matrice di trasformazione. Si esegue il rendering di questa bitmap colore-spostato chiamando:
d2dContext->DrawImage(m_colorMatrixEffect.Get());
Tutti gli effetti incorporati coinvolgono ingresso bitmap, e una delle caratteristiche di Direct2D effetti è che è possibile concatenare insieme per applicare una serie di effetti a una bitmap.
Se sei interessato a scrivere i tuo effetti personalizzati, c'è una soluzione Windows 8.1 Visual Studio preziosa chiamata Direct2D personalizzato effetti campione immagine che include tre progetti distinti per dimostrare i tre tipi di shader disponibili per effetti Direct2D. Tutti e tre i programmi richiedono le bitmap come input.
Pertanto, si vuoi essere perdonato per supponendo che Direct2D effetti sempre eseguono operazioni di input bitmap. Ma questo non è così. Il ThreeTriangles che ha creato l'immagine nel programma Figura 3 non richiede input bitmap.
Si sarebbe anche essere perdonato per assumendo Direct2D effetti coinvolgono un solo tipo di shader. Certamente, il built-in effetti sembrano coinvolgere un vertex shader o un pixel shader, ma non entrambi. Tuttavia, il programma ThreeTriangles è diverso in questo senso, anche: Esso definisce un effetto personalizzato che utilizza sia un vertex shader e un pixel shader.
Registrarsi, creare, disegnare
Poiché gli effetti Direct2D sono progettati per essere preregistrate e creato da un ID di classe, un effetto personalizzato deve offrire quella stessa capacità. L'effetto personalizzato nel programma ThreeTriangles è una classe denominata SimpleTriangleEffect, che definisce un metodo statico per la registrazione della classe. Questo metodo viene chiamato dal costruttore della classe ThreeTrianglesRenderer, ma l'effetto potrebbe essere registrato ovunque nel programma:
SimpleTriangleEffect::RegisterEffectAsync(d2dFactory)
Questo metodo di registrazione è asincrono perché ha bisogno di caricare i file compilati shader, e l'unico metodo previsto a tal fine nella classe DirectXHelper è ReadDataAsync.
Proprio come quando si utilizza un effetto incorporato, la classe ThreeTrianglesRenderer dichiara un oggetto di ID2D1Effect come un campo privato nel suo file di intestazione:
Microsoft::WRL::ComPtr<ID2D1Effect> m_simpleTriangleEffect;
Il metodo CreateDeviceDependentResources crea l'effetto personalizzato alla stessa stregua di un effetto incorporato:
d2dContext->CreateEffect(
CLSID_SimpleTriangleEffect, &m_simpleTriangleEffect)
Precedente registrazione dell'effetto personalizzato associato tale ID di classe per l'effetto.
Il SimpleTriangleEffect non ha alcun input.(Che è parte di ciò che lo rende "semplice"!) L'effetto è reso come un effetto incorporato:
d2dContext->DrawImage(m_simpleTriangleEffect.Get());
Forse il semplice utilizzo di questo effetto personalizzato suggerisce alcuni della complessità all'interno della stessa classe di effetto. Un effetto personalizzato come SimpleTriangleEffect deve implementare l'interfaccia di (implementazione di effetto) di ID2D1EffectImpl. Un effetto può essere composte da più passaggi, che sono denominati trasformazioni, e ciascuno di essi è solitamente rappresentato da un'implementazione di ID2D1DrawTransform. Se viene utilizzata una classe unica per entrambe le interfacce — che è il caso di SimpleTriangleEffect — allora deve implementare IUnknown (tre metodi), ID2D1EffectImpl (tre metodi), ID2D1TransformNode (un metodo), ID2D1Transform (tre metodi) e ID2D1DrawTransform (1 metodo).
Che è una notevole quantità di overhead, oltre ad alcuni XML che identifica l'effetto e suo autore quando l'effetto è prima registrato. Fortunatamente, per semplici effetti — e questo certamente si qualifica — molti dei metodi effetto può avere implementazioni abbastanza facile. I lavori più importanti della classe effetto comportano carico e registrando il codice compilato shader (e l'associazione shader del GUID per consultarle in futuro), e definisce un vertex buffer, che deve anche essere associato un GUID.
Da Vertex Buffer...
Un vertex buffer è un insieme di vertici assemblati per l'elaborazione. Ogni vertice include sempre un punto di coordinate 2D o 3D, ma solitamente altri oggetti pure. I dati associati a ciascun vertice e come è organizzato è chiamato "layout" del vertex buffer e, in generale, il programma di ThreeTriangles definisce tre diverse — ma equivalente — i tipi di dati per descrivere questo layout di vertice.
La prima rappresentazione di dati questo vertice è mostrata Figura 4. Questa è una struttura semplice denominata Vertex che include un coordinamento 3Dpunto nate e un colore RGB. Una matrice di queste strutture definisce i tre triangoli visualizzati dal programma. (Questa matrice è hardcoded nel metodo Initialize richiesto dei sempliciclasse TriangleEffect; in un programma vero classe effetto consentirebbe una matrice di vertici da immettere per l'effetto.)
Figura 4 Vertex definizione in SimpleTriangleEffect
// Define Vertex for simple initialization
struct Vertex
{
float x;
float y;
float z;
float r;
float g;
float b;
};
// Each triangle has three points and three colors
static Vertex vertices [] =
{
// Triangle 1
{ 0, -1000, 0.0f, 1, 0, 0 },
{ 985, -174, 0.5f, 0, 1, 0 },
{ 342, 940, 1.0f, 0, 0, 1 },
// Triangle 2
{ 866, 500, 0.0f, 1, 0, 0 },
{ -342, 940, 0.5f, 0, 1, 0 },
{ -985, -174, 1.0f, 0, 0, 1 },
// Triangle 3
{ -866, 500, 0.0f, 1, 0, 0 },
{ -643, -766, 0.5f, 0, 1, 0 },
{ 643, -766, 1.0f, 0, 0, 1 }
};
// Define layout for the effect
static const D2D1_INPUT_ELEMENT_DESC vertexLayout [] =
{
{ "MESH_POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12 },
};
X e y valori si basano sui seni e dei coseni degli angoli con incrementi di 40 gradi, con un raggio di 1.000. Tuttavia, si noti che le coordinate z sono tutti impostati tra 0 e 1, in modo che i vertici di rossi hanno un valore z 1, i vertici di verdi sono 0,5 e i vertici di blu sono 0. Ulteriori informazioni su questo un po' più tardi.
Seguendo tale matrice è la matrice di poco un altro, ma quest'ultimo definisce le informazioni di vertice in maniera più formale, necessaria per la creazione e registrazione del vertex buffer.
Il vertex buffer e vertex shader sono entrambi riferimento nel metodo SetDrawInfo di SimpleTriangleEffect. Ogni volta che viene eseguito il rendering dell'effetto, questi nove vertici sono passati al vertex shader.
... A Vertex Shader...
Figura 5 illustrato il vertex shader per la SimpleTriangleEffect. Esso è costituito da tre strutture e una funzione chiamata principale. La funzione principale viene chiamata per ogni vertice nel vertex buffer; in questo caso, che è solo nove vertici, ma spesso ci sono molti di più.
Figura 5 il File SimpleTriangleEffectVertexShader.hlsl
// Per-vertex data input to the vertex shader
struct VertexShaderInput
{
float3 position : MESH_POSITION;
float3 color : COLOR0;
};
// Per-vertex data output from the vertex shader
struct VertexShaderOutput
{
float4 clipSpaceOutput : SV_POSITION;
float4 sceneSpaceOutput : SCENE_POSITION;
float3 color : COLOR0;
};
// Information provided for Direct2D vertex shaders
cbuffer ClipSpaceTransforms : register(b0)
{
float2x1 sceneToOutputX;
float2x1 sceneToOutputY;
}
// Called for each vertex
VertexShaderOutput main(VertexShaderInput input)
{
// Output structure
VertexShaderOutput output;
// Append a 'w' value of 1 to the 3D input position
output.sceneSpaceOutput = float4(input.position.xyz, 1);
// Standard calculations
output.clipSpaceOutput.x =
output.sceneSpaceOutput.x * sceneToOutputX[0] +
output.sceneSpaceOutput.w * sceneToOutputX[1];
output.clipSpaceOutput.y =
output.sceneSpaceOutput.y * sceneToOutputY[0] +
output.sceneSpaceOutput.w * sceneToOutputY[1];
output.clipSpaceOutput.z = output.sceneSpaceOutput.z;
output.clipSpaceOutput.w = output.sceneSpaceOutput.w;
// Transfer the color
output.color = input.color;
return output;
}
Ciascuna delle tre strutture contiene campi identificati con un tipo di dati HLSL, un nome di membro e maiuscola semantica che identificano il ruolo del campo particolare.
La struttura denominata VertexShaderInput è l'ingresso principale, ed è lo stesso come il layout del vertex buffer che hai appena visto, ma con tipi di dati HLSL per la posizione 3D e il colore RGB.
La struttura denominata VertexShaderOutput definisce l'output del principale.I primi due campi sono necessari per gli effetti di Direct2D. (Un terzo richiesto campo sarebbe presente se l'effetto coinvolto una bitmap di input). Ho chiamato il campo sceneSpaceOutput è basato su coordinate dell'ingresso. Qualche cambiamento di effetti che coordinano; Questo effetto non lo fa e semplicemente si trasforma le coordinate 3D di input in una coordinata omogenea 4D con un valore w 1:
output.sceneSpaceOutput = float4(input.position.xyz, 1);
L'output di vertex shader comprende anche un campo non obbligatorio chiamato colore, semplicemente impostato dal colore di input:
output.color = input.color;
Ho chiamato il campo di output richiesto clipSpaceOutput descrive ogni coordinata del vertice in termini di coordinate normalizzate utilizzato in 3D. Queste coordinate sono le stesse come coordinate generate dalle trasformazioni in proiezione fotocamera che descritto nella rata del mese scorso di questa colonna. In queste coordinate clipSpaceOutput, x intervallo di valori da – 1 a sinistra dello schermo e 1 a destra; y valori vanno da – 1 sul fondo a 1 nella superiore; e z i valori vanno da 0 per coordinate più vicine allo spettatore a 1 per coordinate più lontano via. Come suggerisce il nome del campo, queste coordinate normalizzate sono utilizzate per la scena 3D sullo schermo di ritaglio.
Per aiutarvi a calcolare queste coordinate clipSpaceOutput, una terza struttura viene fornito automaticamente per voi che ho chiamato ClipSpaceTransforms. Si tratta di quattro numeri basati su larghezza di pixel e altezza dello schermo, e qualsiasi contesto dispositivo trasformazioni che sono in vigore quando DrawImage rende l'effetto.
Tuttavia, le trasformazioni fornite sono solo per x e y coordinate e che è perché ho definito le coordinate z, nel vertex buffer originale per avere valori compresi tra 0 e 1. Un altro approccio consiste nell'utilizzare una trasformazione di proiezione effettiva fotocamera nel vertex shader (come illustrerò in un prossimo articolo).
Questi valori di z vengono utilizzati anche automaticamente in un buffer di profondità cosicché pixel con le coordinate z inferiore oscurare i pixel con valori più elevati di z. Ma questo si verifica solo se il metodo SetDrawInfo della classe effetto chiama SetVertexProcessing con il flag D2D1_VERTEX_OPTIONS_USE_DEPTH_BUFFER. (Questo succede anche comportare errori COM che appare nella finestra di Output di Visual Studio , mentre il programma è in esecuzione, ma che succede anche con il codice Microsoft esempio Direct2D effetto).
… A Pixel Shader
Ogni volta che viene eseguito il rendering dell'effetto (e nel caso generale, che è presso il frame rate del display dei), vertex shader viene chiamato per ogni vertice nel vertex buffer, in questo caso nove volte.
L'uscita dal vertex shader ha lo stesso formato come input per il pixel shader. Come si può vedere in pixel shader in Figura 6, la struttura PixelShaderInput è lo stesso come la struttura VertexShaderOutput nel vertex shader.
Figura 6 il File SimpleTriangleEffectPixelShader.hlsl
// Per-pixel data input to the pixel shader
struct PixelShaderInput
{
float4 clipSpaceOutput : SV_POSITION;
float4 sceneSpaceOutput : SCENE_POSITION;
float3 color : COLOR0;
};
// Called for each pixel
float4 main(PixelShaderInput input) : SV_TARGET
{
// Simply return color with opacity of 1
return float4(input.color, 1);
}
Tuttavia, il pixel shader viene chiamato per ogni pixel in triangoli, e tutti i campi della struttura sono stati interpolati sopra la superficie di quel triangolo. La funzione principale in pixel shader deve restituire un colore di quattro componenti che include opacità, in modo che il colore RGB interpolato viene semplicemente modificato aggiungendo un campo di opacità. Quel colore è uscita al display.
Ecco una variazione interessante per il pixel shader: Le coordinate di z della gamma campo sceneSpaceOutput da 0 a 1, quindi è possibile visualizzare la profondità di ciascun triangolo utilizzando tale coordinata per costruire una gradazione di grigio e restituirlo dal metodo principale:
float z = input.sceneSpaceOutput.z;
return float4(z, z, z, 1);
Miglioramenti?
Il SimpleTriangleEffect taglia alcuni angoli. Occorre più versatile includendo un metodo per impostare l'input di vertice. Alcune altre caratteristiche sia non sarebbe male: Il vertex shader è un ottimo posto per effettuare trasformazioni di matrice — ad esempio trasformazioni di rotazione o fotocamera — perché le moltiplicazioni di matrice vengono eseguite sulla GPU.
Alcuni programmatori sono in grado di resistere alla tentazione di implementare i miglioramenti del codice, specialmente quelli che trasformare un'immagine statica in un uno animato.
Charles Petzold è un collaboratore di lunga data di MSDN Magazine e autore di "Programmazione Windows, 6a edizione" (Microsoft Press, 2013), un libro sulla scrittura di applicazioni per Windows 8. Il suo sito Web è charlespetzold.com.
Grazie al seguente Microsoft esperto tecnico per la revisione di questo articolo: Doug Erickson
Doug Erickson è uno scrittore di programmazione di piombo per team di documentazione dello sviluppatore di Microsoft OSG. Quando non scritto e sviluppo codice grafica DirectX e contenuto, sta leggendo articoli come Charles', perché è come egli ama trascorrere il suo tempo libero. Beh, che e moto in sella.