Nel disegnare i nostri modelli occorre tenere presente che l'effect applicato può non essere sempre di tipo BasicEffect: nel nostro caso (il carro armato), visto che sappiamo per certo che il modello caricato utilizza solo questo tipo di effetto, nel codice possiamo ragionevolmente dare per scontato che in ogni mesh troveremo solo BasicEffects
.
Non sempre è così, visto che meshparts differenti possono contenere effetti diversi (ad esempio per disegnare materiali trasparenti). Se non siamo sicuri del tipo di effetti contenuti nel modello, dovremmo usare qualche precauzione in più. Ad esempio, potremmo sostituire le righe precedenti con le seguenti:
foreach (ModelMesh mesh in tank.Meshes)
{
foreach (Effect effect in mesh.Effects)
{
BasicEffect be = effect as BasicEffect;
if (be != null)
{
// Codice
}
}
}
Ciò precisato, impostiamo adesso i parametri dell'effect, in particolare le matrici World, View e Projection:
effect.World = boneTransforms[mesh.ParentBone.Index];
effect.View = view;
effect.Projection = projection;
Come si vede, la matrice World è ricavata dal bone associato alla mesh corrente. Ora che abbiamo iterato tutti gli effects, possiamo finalmente invocare il metodo che disegna la mesh:
mesh.Draw();
In realtà, BasicEffect supporta ben più che le solite matrici world, view e projection. tra cui vari effetti di illuminazione. In particolare, il BasicEffect supporta tre diverse sorgenti di illuminazione, denominate DirectionalLight0, DirectionalLight1 e DirectionalLight2.
Queste corrispondono a un'ormai classica combinazione di luci (molto utilizzata in ambito fotografico e cinematografico) basata su tre distinti punti di illuminazione:
- luce chiave (o key light), rappresenta l'illuminazione più intensa e viene normalmente posizionata in modo da corrispondere alla sorgente di luce, naturale o artificiale, che illumina la scena (una lampada, un lampione, il sole, ecc.). Per via della sua intensità, tende a produrre ombre decisamente marcate
- luce di riempimento (o fill light), viene invece impiegata per illuminare in modo diffuso la scena, attenunando così le ombre prodotte dalla luce chiave
- luce di retroscena (o back light), illumina l'oggetto da dietro (in direzione della telecamera), contribuendo a staccarlo dal resto dello sfondo
É possibile utilizzare tutti e tre i punti luce con valori di default semplicemente invocando il metodo EnableDefaultLighting
del nostro BasicEffect
, ma è anche possibile accendere, spegnere o modificare ciascun punto luce singolarmente.
Ciò premesso, vediamo alcune possibili combinazioni di luci. La prima e più semplice opzione è quella di non avere alcuna luce, utilizzando unicamente le texture. Come si può vedere, il risultato finale è decisamente piatto (ma almeno è molto veloce):
effect.LightingEnabled = false;
Il passo immediatamente successivo è rappresentato da una sola luce per ciascun vertice. Si tratta di una tecnica che conferisce un senso della trimensionalità ridotto (ma comunque maggiore rispetto a nessuna illuminazione), é poco preciso ma veloce dal punto di vista della GPU. Per utilizzare questa tecnica è sufficiente "spegnere" due dei tre punti luce, lasciando solo il terzo attivo.
effect.EnableDefaultLighting();
effect.PreferPerPixelLighting = false;
effect.DirectionalLight1.Enabled = false;
effect.DirectionalLight2.Enabled = false;
La proprietà PreferPerPixelLighting
, in particolare, ci permette di specificare se preferiamo utilizzare una illuminazione per vertice o per pixel.
Nel primo caso, l'illuminazione viene calcolata solo in corrispondenza dei vertici (il cui colore risulterà pertanto più chiaro o più scuro, a seconda dell'illuminazione), dopodiché i risultati vengono interpolati per calcolare il riempimento del triangolo. Si tratta di una tecnica che consente di risparmiare risorse preziose per la GPU, ma che può dare risultati anche molto approssimativi (per ovviare ai quali si usano in genere altre tecniche, come le lightmap
, che vedremo più avanti).
Nel secondo caso, invece, l'effetto prodotto dall'illuminazione sul modello viene calcolata pixel per pixel, con un notevole aumento della resa grafica, anche se con costi notevoli in termini di carico computazionale.
Proviamo adesso a utilizzare l'illuminazione tradizionale composta da tre punti luce, in questo caso per vertice. L'accuratezza è buona, anche se non precisa, con un risultato di tipo "cinematografico":
effect.EnableDefaultLighting();
effect.PreferPerPixelLighting = false;
Infine, sperimentiamo l'illuminazione a tre luci per pixel. Si tratta di una tecnica lenta e precisa, con un effetto risultante "cinematografico" e ad altissima qualità (Figura 6):
effect.EnableDefaultLighting();
effect.PreferPerPixelLighting = true;
Il BasicEffect comprende anche numerose altre opzioni. Ecco un elenco sommario delle opzioni possibili:
Effetto trasparenza:
float Alpha { get; set; }
Illuminazione ambientale, vale a dire il colore che assumono i pixel quando non sono illuminati da una luce direzionale (ma solo da quella ambientale):
public Vector3 AmbientLightColor { get; set; }
Colore di base dell'oggetto, moltiplicato per luce e texture:
public Vector3 DiffuseColor { get; set; }
Tre luci, configurabili separatamente:
public DirectionalLight DirectionalLight0 { get; }
public DirectionalLight DirectionalLight1 { get; }
public DirectionalLight DirectionalLight2 { get; }
Colore emesso dall'oggetto:
public Vector3 EmissiveColor { get; set; }
Effetto nebbia per far sparire gli oggetti distanti:
public Vector3 FogColor { get; set; }
public bool FogEnabled { get; set; }
public float FogEnd { get; set; }
public float FogStart { get; set; }
Impostazioni di illuminazione:
public bool LightingEnabled { get; set; }
public bool PreferPerPixelLighting { get; set; }
Colore e intensità dei riflessi:
public Vector3 SpecularColor { get; set; }
public float SpecularPower { get; set; }
Abilitazione (o meno) delle texture:
public bool TextureEnabled { get; set; }
Uso (o meno) del colore di ogni vertice nel computo del colore finale di ciascun pixel:
public bool VertexColorEnabled { get; set; }