Il presente articolo è stato tradotto automaticamente.
DirectX Factor
Trasformazioni 3D su bitmap 2D
Scaricare il codice di esempio
Programmazione grafica tridimensionale è principalmente una questione di creare illusioni ottiche. Immagini di visualizzazione a schermo piatto che consiste di una matrice bidimensionale di pixel, ma questi oggetti devono apparire per avere una terza dimensione con profondità.
Probabilmente il più grande contributore all'illusione del 3D è ombreggiatura — l'arte e la scienza di colorare i pixel affinché superfici assomigliano a texture reali con luci e ombre.
Sotto tutto ciò che, tuttavia, è un'infrastruttura virtuale oggetti descritti da coordinate 3D. In definitiva, queste coordinate 3D sono appiattite in un spazio 2D, ma fino a quel passaggio finale, coordinate 3D sono spesso sistematicamente modificate in vari modi attraverso l'uso di trasformazioni. Matematicamente, trasformazioni sono operazioni in algebra matriciale. Raggiungimento di una padronanza di queste trasformazioni 3D è cruciale per chi vuole diventare un programmatore di grafica 3D.
Recentemente, ho esplorando modi diversi che 3D è supportato nel componente di DirectX Direct2D. Esplorare 3D all'interno della relativa familiarità e comfort di Direct2D consente di acquisire familiarità con i concetti 3D prima il tuffo profondo molto spaventoso in Direct3D.
Bitmap in 3D?
Uno dei modi diversi che 3D è supportato nelle Direct2D è nascosto come ultimo argomento DrawBitmap metodi definiti da ID2D1DeviceContext. Questo argomento consente di applicare una trasformazione 3D a una bitmap 2D. (Questa caratteristica è speciale per ID2D1DeviceContext. Non è supportato dai metodi DrawBitmap definiti da ID2D1RenderTarget o altre interfacce che derivano da quello.)
I metodi di DrawBitmap definiti da ID2D1DeviceContext avere un argomento di tipo D2D1_MATRIX_4X4_F, ovvero una matrice di trasformazione di 4 × 4 che esegue una trasformazione 3D su bitmap, come il rendering sullo schermo finale:
void DrawBitmap(ID2D1Bitmap *bitmap,
D2D1_RECT_F *destinationRectangle,
FLOAT opacity,
D2D1_INTERPOLATION_MODE interpolationMode,
const D2D1_RECT_F *sourceRectangle,
const D2D1_MATRIX_4X4_F *perspectiveTransform)
Questo sembra essere il solo scopo di D2D1_MATRIX_4X4_F. Esso non viene utilizzato altrove in DirectX. Nella programmazione Direct3D, la libreria DirectX matematica viene utilizzata invece per rappresentare le trasformazioni 3D.
DD2D1_MATRIX_4X4_F è un typedef per D2D_MATRIX_4X4_F, che è definito in Figura 1. È fondamentalmente una raccolta di 16 valori di float, disposte in quattro file di quattro colonne. Si può fare riferimento al valore nella terza riga e la seconda colonna utilizzando i dati membro _32, o si può ottenere presso lo stesso valore come l'elemento di matrice in base zero m [2] [1].
Figura 1 il 3D trasformare Matrix applicato alle bitmap
typedef struct D2D_MATRIX_4X4_F
{
union
{
struct
{
FLOAT _11, _12, _13, _14;
FLOAT _21, _22, _23, _24;
FLOAT _31, _32, _33, _34;
FLOAT _41, _42, _43, _44;
} ;
FLOAT m[4][4];
};
} D2D_MATRIX_4X4_F;
Tuttavia, quando mostrandovi la matematica della trasformazione, io sarò invece fare riferimento all'elemento nella terza riga e la seconda colonna della matrice come m32. L'intera matrice può essere rappresentata nella notazione di matrice tradizionale, in questo modo:
La trasformazione della bitmap 3D Direct2D è alla base di una struttura simile in Windows Runtime, dove si presenta come la struttura Matrix3D e la proprietà di proiezione definito da UIElement. I due meccanismi sono così simili che potete cross-fertilize la vostra conoscenza e di esperienza tra i due ambienti.
La trasformazione lineare
Molte persone incontrando le trasformazioni 3D per la prima volta si chiedono: Perché è una matrice 4 × 4? Non dovrebbe una matrice 3 × 3 essere adeguata per il 3D?
Per rispondere a questa domanda mentre esplorare la trasformazione 3D su DrawBitmap, ho creato un programma chiamato BitmapTransformExperiment che è incluso con il codice scaricabile di questo articolo. Questo programma contiene controlli spinner in casa che consentono di selezionare i valori di 16 elementi della matrice di trasformazione e vedere come la matrice colpisce la visualizzazione di un'immagine bitmap. Nella Figura 2 è illustrata una visualizzazione tipica.
Nella figura 2 il programma di BitmapTransformExperiment
Per le sperimentazioni iniziali, limitare la vostra attenzione per la prime tre righe e tre colonne a sinistra della matrice. Questi costituiscono una matrice 3 × 3 che esegue la seguente trasformazione:
La matrice di 1 × 3 a sinistra rappresenta una coordinata 3D. Per le bitmap, il valore di x compreso tra 0 e la larghezza della bitmap; y varia da 0 all'altezza della bitmap; e z è 0.
Quando la matrice 1 × 3 viene moltiplicata per la matrice di trasformazione di 3 × 3, la moltiplicazione di matrici standard risultato trasformate coordinate:
La matrice di identità — in cui gli elementi diagonali di m11, m22 e m33 sono tutti 1 e tutto il resto è 0 — si traduce in nessuna trasformazione.
Perché sto partendo con una bitmap piatta, la coordinata z è 0, quindi m31, m32 e m33 non hanno alcun effetto sul risultato. Quando viene eseguito il rendering della bitmap trasformata sullo schermo, la (x*', y', z') risultato è crollato su un sistema di coordinate piane 2D ignorando la z'* coordinare, il che significa che m13, m23 e m33 non hanno effetto. Ecco perché la terza riga e la terza colonna sono inattive fuori nel programma BitmapTransformExperiment. È possibile impostare valori per questi elementi di matrice, ma non influenzano rendering bitmap.
Quello che scoprirete è che m11 è un fattore di scala orizzontale con un valore predefinito 1. Renderlo più grande o più piccolo per aumentare o diminuire la larghezza della bitmap, o renderlo negativo per capovolgere l'immagine bitmap attorno all'asse verticale. Analogamente, la m22 è un fattore di scala verticale.
Il valore di m21 è un fattore di inclinazione verticale: Valori diversi da 0 trasformano la bitmap rettangolare in un parallelogramma, come il bordo destro è spostato verso l'alto o verso il basso. Analogamente, m12 è un fattore di inclinazione orizzontale.
Una combinazione di inclinazione orizzontale e inclinazione verticale può provocare in rotazione. Per ruotare la bitmap in senso orario da una particolare angolazione, impostare m11 e m22 al coseno dell'angolo, impostata il seno dell'angolo m21 e impostato il seno negativo m12. Figura 2 dimostra la rotazione di 30 gradi.
Nel contesto del 3D, la rotazione indicata Figura 2 è in realtà una rotazione intorno all'asse Z, che concettualmente si estende fuori dallo schermo. Per un angolo α, la matrice di trasformazione assomiglia a questo:
È anche possibile ruotare la bitmap intorno all'asse Y o all'asse X. Rotazione intorno all'asse Y non influenza la coordinata y, quindi la matrice di trasformazione è questa:
Se si tenta questa con il programma BitmapTransformExperiment, scoprirete che solo il valore di m11 ha effetto. L'impostazione per il coseno di un angolo di rotazione semplicemente diminuisce la larghezza della bitmap renderizzata. Tale diminuzione della larghezza è coerente con la rotazione attorno all'asse Y.
Allo stesso modo, questa è la rotazione intorno all'asse X:
Nel programma BitmapTransformExperiment, questo si traduce nel ridurre l'altezza della bitmap.
I segni dei fattori due seno le matrici di trasformazione governano il senso di rotazione. Concettualmente, si presume che l'asse Z positivo per estendere dallo schermo, e le rotazioni seguono la regola della mano sinistra: Allineare il pollice della mano sinistra con l'asse di rotazione e puntarlo verso valori positivi; la curva delle altre dita indica il senso di rotazione per gli angoli positivi.
Il tipo di trasformazione 3D rappresentata da questa matrice di trasformazione di 3 × 3 è conosciuto come una trasformazione lineare. La trasformazione comporta solo costanti, moltiplicati per x, y e z le coordinate. Non importa quali numeri si entra nelle prime tre righe e colonne della matrice di trasformazione, la bitmap non viene mai trasformata in qualcosa di più esotico di un parallelogramma; in tre dimensioni, un cubo è sempre trasformato in un parallelepipedo.
Hai visto come bitmap può essere scalata, distorta e ruotato, ma in tutto questo esercizio, l'angolo superiore sinistro della bitmap è rimasta fissa in un unico luogo. (Tale posizione è governata da una trasformazione 2D impostata nel metodo Render prima della chiamata di DrawBitmap). L'incapacità di spostare l'angolo superiore sinistro della bitmap deriva dalla matematica di trasformazione lineare. Non c'è niente della formula di trasformazione che può spostare un (0, 0, 0) un altro percorso, che è un tipo di trasformazione noto come traduzione.
Raggiungimento di traduzione
Per capire come ottenere la traduzione in 3D, brevemente pensiamo trasformazioni 2D.
In due dimensioni, una trasformazione lineare è una matrice 2 × 2, ed è capace di ridimensionamento, inclinazione e rotazione. Per ottenere anche la traduzione, la grafica 2D si presuppone che esiste nello spazio 3D, ma su un piano 2D dove la coordinata z sempre uguale a 1. Per accogliere la dimensione aggiuntiva, la matrice di trasformazione lineare 2D è espansa in una matrice 3 × 3, ma solitamente è fissato l'ultima riga:
I risultati di moltiplicazione di matrice in questi trasformano formule:
I fattori di m31 e m32 sono i fattori di traduzione. Il segreto dietro questo processo è che la traduzione in due dimensioni è equivalente all'inclinazione in tre dimensioni.
Un processo analogo è utilizzato per la grafica 3D: In realtà si presuppone che le coordinate 3D esiste nello spazio 4D dove la coordinata della quarta dimensione è 1. Ma per rappresentare un punto di coordinate 4D, avete un sciocco problema poco pratico: È un punto 3D (x, y, z) e nessuna lettera viene dopo z, così che lettera si usa per la quarta dimensione? La lettera disponibile più vicina è w, quindi è la coordinata 4D (x, y, z, w).
La matrice di trasformazione lineare 3D può essere esteso a 4 × 4 per accogliere la dimensione extra:
Ora sono le formule di trasformazione:
Ora avete la traslazione lungo gli assi X, Y e Z con m41, m42 e m43. Il calcolo sembra verificarsi nello spazio 4D, ma in realtà è ristretto ad uno spaccato 3D dello spazio 4D dove la coordinata w è sempre 1.
Coordinate omogenee
Se si gioca con i valori m41 e m42 in BitmapTransformExperiment, vedrai che infatti provocano traduzione orizzontale e verticale.
Ma quella ultima riga della matrice? Cosa succede se non limitare tale ultima riga al 0s e 1s? Ecco la trasformazione completa 4 × 4 applicata ad un punto 3D:
Le formule per x*', y'* e z*'* rimangono le stesse, ma w*'* ora è calcolato come questo:
E questo è un problema reale. In precedenza, un piccolo trucco è stato impiegato per utilizzare una sezione 3D dello spazio 4D dove la coordinata w sempre uguale a 1. Ma ora la coordinata W non è più 1, e tu hai stato azionato fuori quella sezione 3D. Sei perso nello spazio 4D, e dovete tornare a quella sezione 3D dove w è uguale a 1.
Non non c'è nessuna necessità di costruire una macchina di trans-dimensionale spazio-temporale, tuttavia. Fortunatamente, si può fare il salto matematicamente dividendo tutte le coordinate trasformate per w*'*:
Ora w*'* è uguale a 1 e sei tornata casa!
Ma a quale costo? Ora avete una divisione nelle formule di trasformazione, ed è facile vedere come quel denominatore potrebbe essere 0 in alcune circostanze. Che si tradurrebbe in coordinate infinite.
Beh, forse che è una buona cosa.
Quando matematico tedesco agosto Ferdinand Möbius (1790-1868) ha inventato il sistema che vi ho appena descritto (chiamato "coordinate omogenee" o "coordinate proiettive"), uno dei suoi obiettivi era quello di rappresentare infinite coordinate utilizzando numeri finiti.
La matrice con la 0 e 1 nell'ultima riga viene chiamata una trasformazione affine, significato che non portare a infinito, quindi una trasformazione capace di infinito è chiamata una trasformazione non affine.
In grafica 3D, trasformazioni non affini sono estremamente importanti, per questo è come la prospettiva è realizzato. Tutti sanno che nella vita reale, gli oggetti più lontani dal vostro occhio sembrano essere più piccoli. In grafica 3D, ottenere quell'effetto con un denominatore che non è una costante 1.
Provalo in BitmapTransformExperiment: Se fate m14 un piccolo numero positivo — e solo piccoli valori sono necessari per ottenere risultati interessanti — quindi i valori di x e y sono proporzionalmente diminuita come x viene ingrandito. Rendere m14, un piccolo numero negativo e più grandi valori di x e y sono aumentati. Figura 3 dimostra che l'effetto combinato con un valore diverso da zero m12. Rendering bitmap non è più un parallelogramma, e la prospettiva suggerisce che il bordo destro ha oscillato più vicino ai vostri occhi.
Prospettiva figura 3 in BitmapTransformExperiment
Analogamente, valori diversi da zero di m24 possono fare la parte superiore o inferiore della bitmap apparentemente battente verso di voi o più lontano. Nella programmazione 3D reale, è il valore di m34 comunemente utilizzato, per che consente agli oggetti di aumentare o diminuire in dimensioni sulla base delle loro coordinate z — loro distanza dagli occhi dello spettatore.
Quando viene applicata una trasformazione 3D di oggetti 2D, il valore di m44 solitamente è lasciato a 1, ma può funzionare come un fattore di scala globale. In 3D reale programmazione, m44 solitamente è impostata su 0 quando la prospettiva è coinvolto perché concettualmente la telecamera è all'origine. Un valore 0 di m44 funziona solo se gli oggetti 3D non hanno le coordinate z 0, ma quando si lavora con oggetti 2D, la coordinata z è sempre 0.
Ogni quadrilatero convesso
L'applicazione di questa matrice di trasformazione di 4 × 4 a una bitmap piatta, sei solo facendo uso di mezzo gli elementi della matrice. Anche così, Figura 3 Mostra qualcosa che non si può fare con il normale bidimensionale Direct2D matrice di trasformazione, che consiste nell'applicare una trasformazione che trasforma un rettangolo in qualcosa di diverso da un parallelogramma. Infatti, gli oggetti di trasformazione utilizzati nella maggior parte dei Direct2D sono chiamati D2D1_MATRIX_3X2_F e Matrix3x2F, enfatizzando l'inaccessibilità della terza fila e l'incapacità di effettuare trasformazioni non affini.
Con D2D1_MATRIX_4X4_F, è possibile derivare una trasformazione che associa una bitmap in ogni quadrilatero convesso — vale a dire qualsiasi arbitrario quadrangolare figura dove i lati non incrociate e dove angoli interni ai vertici sono meno di 180 gradi.
Se non mi credi, provare a giocare con il programma NonAffineStretch. Si noti che questo programma è adattato da un programma di Windows Runtime, chiamato anche NonAffineStretch, nel capitolo 10 del mio libro, "Programmazione Windows, 6a edizione" (Microsoft Press, 2013).
Figura 4 Mostra NonAffineStretch in uso. È possibile utilizzare il mouse o le dita per trascinare i punti verdi in qualsiasi posizione sullo schermo. Finché si mantiene la figura di un quadrilatero convesso, una trasformazione di 4 × 4 può essere derivata basato sulle posizioni dot. Tale trasformazione è utilizzato per disegnare l'immagine bitmap e viene anche visualizzato nell'angolo inferiore destro. Sono coinvolti solo otto valori; gli elementi nella terza riga e terza colonna sono sempre valori predefiniti e m44 è sempre 1.
Figura 4 il programma NonAffineStretch
La matematica dietro questo sono un po' peloso, ma la derivazione dell'algoritmo è mostrata nel capitolo 10 del mio libro.
Classe Matrix4x4F
Per rendere un po' più facile lavorare con la struttura di D2D1_MATRIX_4X4_F, la Matrix4x4F dello spazio dei nomi D2D1 deriva da tale struttura. Questa classe definisce un costruttore e un operatore di moltiplicazione (che ho usato l'algoritmo di NonAffineStretch), e trasformano diversi utili metodi statici per la creazione di comuni matrici. Ad esempio, il metodo Matrix4x4F::RotationZ accetta un argomento che è un angolo in gradi e restituisce una matrice che rappresenta la rotazione di tale angolo intorno all'asse Z:
Altre funzioni Matrix4x4F creano matrici per rotazione intorno agli assi X e Y e la rotazione attorno ad un asse arbitrario, che è una matrice molto più difficile.
Una funzione chiamata Matrix4x4F::PerspectiveProjection ha un argomento denominato profondità. Restituisce la matrice è:
Questo significa che la formula di trasformazione per x' è:
E similmente per y*'* e z*'*, che significa che ogni volta che la coordinata z è uguale profondità, il denominatore è 0 e tutte le coordinate diventano infinite.
Concettualmente, questo significa che stai visualizzando lo schermo del computer a distanza delle unità di profondità, dove le unità sono le stesse come lo schermo stesso, significato pixel o unità indipendenti del dispositivo. Se un oggetto grafico ha una coordinata z uguale alla profondità, è unità di profondità davanti allo schermo, che è a destra nell'occhio! Tale oggetto deve apparire molto grande a voi — matematicamente infinito.
Ma aspetta un attimo: L'unico scopo del D2D1_MATRIX_4X4_Fstructure e la classe Matrix4x4F è per le chiamate a DrawBitmap, e le bitmap hanno sempre le coordinate z 0. Così come fa questo valore di M34 pari a 1/profondità abbia effetto a tutti?
Se la matrice PerspectiveProjection è utilizzata da sola nella chiamata DrawBitmap, esso infatti non avrà effetto. Ma è destinato a essere utilizzato in combinazione con altre trasformazioni di matrice. Trasformazioni di matrice possono essere aggravate moltiplicando li. Anche se la bitmap originale non ha nessuna coordinate z e z coordinate vengono ignorate per il rendering, le coordinate z certamente possono svolgere un ruolo nella capitalizzazione delle trasformazioni.
Vediamo un esempio. Il programma RotatingText crea un oggetto bitmap con il testo "ROTATE", con una larghezza che è quasi la metà della larghezza dello schermo. Gran parte dei metodi Update e Render sono mostrati Figura 5.
Figura 5 il codice da RotatingTextRenderer.cpp
void RotatingTextRenderer::Update(DX::StepTimer const& timer)
{
...
// Begin with the identity transform
m_matrix = Matrix4x4F();
// Rotate around the Y axis
double seconds = timer.GetTotalSeconds();
float angle = 360 * float(fmod(seconds, 7) / 7);
m_matrix = m_matrix * Matrix4x4F::RotationY(angle);
// Apply perspective based on the bitmap width
D2D1_SIZE_F bitmapSize = m_bitmap->GetSize();
m_matrix = m_matrix *
Matrix4x4F::PerspectiveProjection(bitmapSize.width);
}
void RotatingTextRenderer::Render()
{
...
ID2D1DeviceContext* context =
m_deviceResources->GetD2DDeviceContext();
Windows::Foundation::Size logicalSize =
m_deviceResources->GetLogicalSize();
context->SaveDrawingState(m_stateBlock.Get());
context->BeginDraw();
context->Clear(ColorF(ColorF::DarkMagenta));
// Move origin to top center of screen
Matrix3x2F centerTranslation =
Matrix3x2F::Translation(logicalSize.Width / 2, 0);
context->SetTransform(centerTranslation *
m_deviceResources->GetOrientationTransform2D());
// Draw the bitmap
context->DrawBitmap(m_bitmap.Get(),
nullptr,
1.0f,
D2D1_INTERPOLATION_MODE_LINEAR,
nullptr,
&m_matrix);
...
}
Nel metodo Update, il metodo Matrix4x4F::RotationY crea la seguente trasformazione:
Moltiplicate questo per la matrice mostrata precedentemente restituito dal metodo Matrix4x4F::PerspectiveProjection, e si otterrà:
Le formule di trasformazione sono:
Queste comprendono sicuramente la prospettiva, e si può vedere il risultato in Figura 6.
Figura 6 il Display RotatingText
Attenzione: L'argomento di profondità a Matrix4x4F::Perspectiveproiezione è impostare la larghezza della bitmap, così come bitmap rotante oscilla intorno, esso potrebbe venire molto vicino al vostro naso.
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 ai seguenti esperti tecnici Microsoft per la revisione di questo articolo: Jim Galasyn e Mike ricchezze