Nelle scorse lezioni abbiamo preparato il terreno, ora dobbiamo collocare gli oggetti nel "mondo" del gioco, come posizionare la telecamera e infine definire il tipo di "lente" da utilizzare per riprendere la scena. Per questo occorre inizializzare, rispettivamente, le matrici World, View e Projection del nostro BasicEffect
.
Per quanto riguarda le coordinate dell'oggetto (la matrice World), per il momento manterrremo la geometria così come creata dallo SpriteBatch 3D, senza operare traslazioni, rotazioni o altre operazioni di sorta:
bfx.World = Matrix.Identity;
Per quanto riguarda il posizionamento della telecamera (la matrice View
), la collocheremo leggermente spostata verso l'osservatore rispetto all'origine degli assi, con l'obiettivo puntato verso l'origine stessa e orientata in posizione verticale:
bfx.View = Matrix.CreateLookAt(Vector3.Forward * 2.0f, Vector3.Zero, Vector3.Up);
Infine, per la matrice di proiezione utilizzeremo un'ampiezza del campo visivo pari a π/2
(~=1.5
), un rapporto LARGHEZZA/ALTEZZA
dello schermo fissato in 3/3
e un range di visibilità che va da 0.1
a 10.000
:
bfx.Projection = Matrix.CreatePerspectiveFieldOfView(1.5f,
3.0f/3.0f,
0.1f, 10000.0f);
Iniziamo il nostro ciclo di disegno invocando il metodo SpriteBatch.Begin
, configurando la pipeline in modo da usare le impostazioni corrette per il nostro BasicEffect
:
sprite_batch.Begin(SpriteSortMode.Immediate, BlendState.Additive,
SamplerState.LinearWrap, DepthStencilState.None, null, bfx);
Esaminiamo i parametri accettati dal metodo SpriteBatch.Begin
.
SpriteSortMode: indica se le operazioni di disegno sono effettuate immediatamente l'una di seguito all'altra, oppure se sono accumulate ed eseguite tutte insieme al momento della chiamata del metodo SpriteBatch.End
, ed eventualmente in che ordine. In questo caso abbiamo optato per la prima opzione.
BlendState: permette di decidere se utilizzare o meno la trasparenza nel caso di sovrapposizione di texture. Le opzioni sono le seguenti:
Opzione | Descrizione |
---|---|
AlphaBlend |
fonde le parti delle texure sovrapposte utilizzando le informazioni sulla trasparenza |
Additive |
utilizza la trasparenza, ma sommando il colore dei pixel sovrapposti |
Opaque |
realizza una semplice sovrapposizione, senza utilizzare trasparenze o apportare modifiche nel colore dei pixel |
NonPremultiplied |
i colori di un'immagine possono essere trascritti in modo premultiplied (è così che vengono generalmente importate le immagini in XNA) oppure no.Nel primo caso, le informazioni relative alla trasparenza sono memorizzate sia nel canale alpha, sia nei canali RGB, i quali risultano così già moltiplicati per la trasparenza (ad esempio, un rosso con una trasparenza del 50% verrà indicato con (0.5 , 0 , 0 , 0.5 )). Il vantaggio derivante da questa tecnica consiste principalmente nella semplificazione dei calcoli durante la composizione digitale.Nel secondo caso, invece, le informazioni sulla trasparenza sono contenute unicamente nel canale alpha (lo stesso rosso comparirà come ( 1 , 0 , 0 , 0.5 )). Il vantaggio, in questo caso, è che l'opacità di un'immagine può essere modificata semplicemente modificando il valore del canale alpha.Quale che sia il tipo di texture con cui lavoriamo, è importante scegliere i parametri corretti, perché quando immagini premultiplied vengono trattate in modalità non premultiplied, tendono parzialmente a mantenere il colore di sfondo (come si vede nell'esempio). |
SamplerState: definisce le impostazioni circa il campionamento delle texture, ossia come vengono tradotte le coordinate di queste in colore. Esistono una serie di impostazioni predefinite: LinearWrap
, in particolare, indica che le coordinate vengono usate per realizzare un'interpolazione lineare dei pixel vicini alla coordinate, e che se le coordinate risultano negative o maggiori di 1, viene considerata soltanto la loro componente frazionaria (a riguardo del SamplerState
si veda anche l'articolo dedicato allo SpriteBatch 2D.
DepthStencilState: indica se vogliamo usare o meno lo Z-Buffering, ossia le informazioni relative alla posizione lungo l'asse delle Z
(in particolare, mentre il depth buffer ci dice quale pixel si trova più vicino alla telecamera, lo stencil buffer consente, ad esempio, di limitare ulteriormente l'area di rendering).
Il penultimo parametro, impostato a null nel nostro esempio, è il cosiddetto RasterizerState, il quale determina come vogliamo disegnare i triangoli che compongono la scena (ad esempio usando il wireframe).
Come ultimo parametro, specifichiamo quale effetto usare per il disegno.
Ora possiamo disegnare un quadrato di plasma, non prima di averlo scalato in modo che abbia una dimensione unitaria (1/larghezza
, 1/altezza
). La posizione dell'oggetto lungo gli assi X
e Y
è indicata dal parametro passato per secondo (Vector2.Zero
), mentre la posizione dell'oggetto lungo l'asse delle Z
(layer depth) è indicato dall'ultimo parametro passato (in questo caso pari a 0.2f
).
sprite_batch.Draw(plasma, Vector2.Zero, null, Color.White, 0.0f, Vector2.Zero, new Vector2(1.0f / plasma.Width, 1.0f / plasma.Height), SpriteEffects.None, 0.2f);
Se modifichiamo il valore del layer depth, noteremo che l'oggetto rimpicciolisce, allontanandosi da noi. Ricordiamoci infine di chiudere il ciclo di disegno:
sprite_batch.End();