Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 53 di 53
  • livello principiante
Indice lezioni

Ombre proiettive

Aumentare il realismo della scena sfruttando la proiezione di ombre di oggetti 3D
Aumentare il realismo della scena sfruttando la proiezione di ombre di oggetti 3D
Link copiato negli appunti

XNA ci mette a disposizione una potente funzione per creare una matrice world in grado di proiettare un modello su una superficie piana, specificando anche il punto di origine della proiezione.

Si tratta di un sistema molto simile a quello usato dalla matrice projection per riportare sulla superficie bidimensionale dello schermo i punti di una scena tridimensionale, scalandoli rispetto alla distanza dal punto di osservazione per dare l'impressione di profondità. A differenza di quest'ultima, però, la tecnica che esamineremo in questa lezione è usata per la proiezione di ombre di oggetti 3D, aumentando l'effetto di realismo.

Come sempre, cominciamo con il caricare il nostro modello di carro armato:

tank = Content.Load<Model>("tank");

Nel metodo Draw, impostiamo le matrici view e projection:

const float cameraDistance = 2000;
const float cameraRotation = 45;
const float cameraArc = -60.0f;
Matrix view = Matrix.CreateTranslation(0, -40, 0) * 
Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) * 
Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance), new Vector3(0, 0, 0), Vector3.Up);
Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1, 10000);

Disegnamo ora il modello una prima volta, utilizzando il metodo già visto in altri articoli:

tank.Root.Transform = Matrix.Identity;
tank.CopyAbsoluteBoneTransformsTo(boneTransforms);
foreach (ModelMesh mesh in tank.Meshes)
{
  foreach (BasicEffect effect in mesh.Effects)
  {
    effect.World = boneTransforms[mesh.ParentBone.Index];
    effect.View = view;
    effect.Projection = projection;
    effect.DiffuseColor = Color.White.ToVector3();
    effect.TextureEnabled = true;
    effect.LightingEnabled = true;
  }
  mesh.Draw();
}

Adesso viene la parte più interessante. Per poter proiettare l'ombra del modello su una superficie, abbiamo bisogno innanzitutto del punto di origine della proiezione ossia, per semplificare, il punto da cui proviene la luce destinata a proiettare l'ombra.

A questo scopo, utilizzeremo un vettore arbitrario "normalizzato" (la normalizzazione di un vettore consiste nella sua riduzione a un vettore con modulo pari a uno, in modo che indichi una particolare direzione e verso, e per questo indicato anche come versore) e lo ruoteremo in base al trascorrere del tempo (computato a partire dal lancio dell'applicazione mediante il metodo TotalGameTime esposto dalla classe GameTime):

var light_direction = Vector3.TransformNormal(Vector3.Normalize(Vector3.Up + Vector3.Left), Matrix.CreateRotationY((float)game_time.TotalGameTime.TotalSeconds * 0.5f));

Consideriamo il piano orizzontale che passa per gli assi X e Z; tale piano potrebbe corrispondere ad un ipotetico suolo o ad un pavimento, su cui gli oggetti circostanti proiettano ombre. Un piano é individuato da due caratteristiche: la sua normale e la sua distanza dall'origine rispetto al vettore normale.

Il nostro piano, in particolare, ha come normale il vettore Up (0, 1, 0) e (rispetto a tale direzione) passa per un punto a distanza 0 dall'origine. Con questi dati possiamo sfruttare la struttura Plane per creare il nostro piano:

var plane = new Plane(Vector3.Up, 0.0f);

Figura 14. Definizione di un piano (fonte: MSDN)
(clic per ingrandire)


Definizione di un piano (fonte: MSDN)

Creiamo adesso la matrice di proiezione dell'ombra utilizzando il metodo statico CreateShadow esposto dalla classe Matrix e che richiede come parametri il punto di origine della proiezione e il piano su cui proiettare l'ombra del modello:

var shadow_matrix = Matrix.CreateShadow(light_direction, plane);

Disegnamo nuovamente il modello, ma questa volta impostiamo come matrice world la matrice di ombra appena creata:

tank.Root.Transform = shadow_matrix;
tank.CopyAbsoluteBoneTransformsTo(boneTransforms);
foreach (ModelMesh mesh in tank.Meshes)
{
  foreach (BasicEffect effect in mesh.Effects)
  {
    effect.World = boneTransforms[mesh.ParentBone.Index];
    effect.View = view;
    effect.Projection = projection;
    effect.DiffuseColor = Color.Black.ToVector3();
    effect.TextureEnabled = false;
    effect.LightingEnabled = false;
  }
  mesh.Draw();
}

Premendo F5 otteniamo esattamente la stessa ombra che ci aspetteremmo di vedere proiettata su un pavimento.

Figura 15. Esempio di ombra proiettiva
(clic per ingrandire)


Esempio di ombra proiettiva

Questa tecnica consente soltanto di proiettare ombre su superfici piane, mentre le ombre non compaiono né sull'oggetto originale (cosiddette ombre "auto-portate"), né su eventuali altre geometrie si trovino nel mezzo tra l'oggetto in questione e il piano di proiezione. Per realizzare questi effetti particolari, si possono impiegare tecniche più avanzate, come lo shadow mapping e gli shadow volumes, che richiedono conoscenze approfondite di vertex e pixel shaders.

Ciononostante, la tecnica illustrata in questo articolo si rivela in molti casi più che suffiente per raggiungere il risultato voluto, senza contare che è decisamente efficiente dal punto di vista del carico computazionale, soprattuto se confrontata con le teniche avanzate sopra menzionate.

Ti consigliamo anche