VS85 Team's profileXNALearnersBlogListsGuestbook Tools Help

Blog


    1/22/2009

    SpriteBatch per effetti speciali - parte 4

    By Davide Luzzu – Webcast by Giuseppe Maggiore

     

    Il (resto del) codice C#

     

          // Generiamo un array di float che sono le altezze (y) dei punti di

     controllo.

          var tmp =

            Enumerable.Range(0, controlPoints.Width)

            // i [0,controlPoints.Width-1]

            .Select(i => i / (float)controlPoints.Width)

            // x [0,1]

            .Select(x => sin(-4.0f * t + 32.0f * pi * sigmoid(x + 0.5f) * x)

              * 0.1f + 0.5f)

            // y é l'altezza di un punto di controllo. La convertiamo in formato

      integrale.

            .Select(y => (UInt16)(y * (UInt16.MaxValue - 1)))

            .ToArray();

          // Salviamo l'array di altezze nella texture.

          controlPoints.SetData<UInt16>(tmp);

     

    Per dare la forma al serpente definiamo i punti di controllo sulle x e sulle y. Per fare ciò ci serviremo delle funzioni sin e sigmoid, che abbiamo definito in precedenza. Definiamo un range da 0 a 256, e ne facciamo il rapporto tra 0 e 1 in modo che x [0,1]. Dato che x è nel range [0,1] possiamo definire la forma del serpente usando il seno. Tale forma varia in base al tempo    ( 4.0f * t…) ed ha, ad ogni istante di tempo, una forma curva definita da  “…32.0f * pi * sigmoid(x + 0.5f) * x…”; variando tali parametri determineremo un’alterazione a piacimento della forma o del tempo di esecuzione del serpente.

    Una delle peculiarità derivanti da questa porzione di codice è il fatto che, ciò che viene disegnato, è ad una dimensione algoritmicamente, ma a due dimensioni visivamente; la x difatti è un seno, e ciò è sufficiente per disegnare il serpente. Ma la x è l’unica variabile da cui dipende effettivamente il controllo dello spazio. Quindi è monodimensionale.

    A questo punto la y è libera, e la si utilizza per inserire i dati di illuminazione, quelli che avevamo previsto inserendo SurfaceFormat.Luminance16 nella definizione della texture. Convertiamo la y in intero a 16 bit Unsigned, e trasformiamo la struttura in un array. Quindi la variabile “tmp” è un array di interi a 16 bit Unsigned.

    Non resta che inserire i dati dei punti di controllo nella Texture2D.

     

          // Carichiamo la texture di sfondo.

          var background = Content.Load<Texture2D>("background");

          /// Disegnamo la texture di sfondo. Questa operazione non dipende in alcun

     modo da come poi disegneremo il serpente.

     

          spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate,

                      SaveStateMode.SaveState);

          spriteBatch.Draw(background, new Rectangle(0, 0, 800, 600), Color.White);

          spriteBatch.End();

     

    Per rendere l’applicazione più gradevole prevediamo uno sfondo, di nostro gradimento, che disegnamo con lo SpriteBatch. Ci preme ribadire che tale operazione non dipende in alcun modo da come disegneremo il serpente.

     

          /// Disegnamo il serpente con il nostro shader in alpha blending sulla

     scena precedente. L'operazione di disegno genera un serpente che passa

     per i punti di controllo della texture "controlPoints".

     

          new Vector3[] { new Vector3(0, 1, 1}

            .Select((color, index) => new {color, index})

            .ToList().ForEach(snake =>

            {

              fx.Parameters["SnakeColor"].SetValue(snake.color);

     

              spriteBatch.Begin(SpriteBlendMode.AlphaBlend,

                            SpriteSortMode.Immediate, SaveStateMode.SaveState);

     

              fx.Begin();

              pass(0, p => p.Begin());

     

              spriteBatch.Draw(controlPoints, new Rectangle(0, 100 + 100 *

                                  snake.index, 800, 400), Color.White);

     

              pass(0, p => p.End());

              fx.End();

     

              spriteBatch.End();

            });

     

          base.Draw(gameTime);

    }

     

    Finalmente disegnamo il serpente!

    Definiamo innanzittutto il colore con un vettore, un azzurro tenue.

    Inseriamo il colore in ogni posizione di un array di Vector3 e lo modifichiamo con la tecnica dello Shader.

    Il primo passo è impostare il parametro “SnakeColor” dello Shader con l’azzurro appena definito.

    Iniziamo lo SpriteBatch con il metodo “Begin()” e avviamo il rendering dello shader con

     

    fx.Begin();

     

    che provoca, inoltre, la sostituzione automatica dello Shader di default nello SpriteBatch.

    A questo punto non resta che aprire il passo zero di rendering con

     

    pass(0, p => p.Begin());

     

    e disegnamo sullo schermo la Texture2D.

    Chiudiamo il passo e indichiamo che lo shader ha terminato la sua tecnica con

     

    fx.End();

     

    ricordandoci di chiudere anche le funzioni dello SpriteBatch con la funzione “End()”.

     

    SpriteBatch per effetti speciali - parte 3

    By Davide Luzzu – Webcast by Giuseppe Maggiore

    Il codice C#

     

    Effect fx = null;

    Texture2D controlPoints;

    const int numControlPoints = 256;

    protected override void LoadContent()

    {

      /// Costruiamo il renderer 2D.

      spriteBatch = new SpriteBatch(GraphicsDevice);

      /// Costruiamo la texture di control points, in cui carichiamo interi

      /// unsigned a 16 bit. La texture é una matrice numControlPoints × 1,

      /// cioè 256 colonne e una riga.

      controlPoints = new Texture2D(GraphicsDevice, numControlPoints, 1, 1,

            TextureUsage.None, SurfaceFormat.Luminance16);

     

      // Carichiamo lo shader HLSL.

    fx = Content.Load<Effect>("snake shader");

    }

     

    La prima operazione da compiere è ottenere la configurazione grafica ( GraphicsDevice ) e applicarla ad uno SpriteBatch. Il GraphicsDevice ci permette di ottenere informazioni riguardo alla risoluzione grafica con cui si sta eseguendo l’applicazione, e, di conseguenza, impostare il Pixel Shader. Lo SpriteBatch è il render 2D che ci consentirà di disegnare il nostro serpente.

    Il secondo passaggio fondamentale è creare una Texture2D che contiene dei punti di controllo (256), che daranno la forma al nostro “serpente”. Questi punti di controllo possono essere concettualmente associati ai key frames di una animazione; cioè definiamo quali sono le posizioni per cui la nostra animazione dovrà assolutamente passare, il resto viene fatto per interpolazione. Sarebbe, infatti, impensabile dover inserire tutti e 25 i frames ( riferito ad un video in formato PAL) di ogni secondo della animazione, poiché, per ottenere un minuto di animazione, servirebbe agire su 1500 singoli frames! Allo stesso modo quello che facciamo noi è definire dei “frames” (detti anche nodi o punti di controllo ) e nessun dato nella Texture 2D, in modo da avere la possibilità di inserirli a nostro piacimento. Per poter manipolare una elevata quantità di informazioni di illuminazione sulla texture, indichiamo che il formato della superficie è Luminance16; è un formato che comprende 16 bit di dati solo per illuminazione, cioè potremo manipolare 216 combinazioni di colore.!

    L’ultima operazione da effettuare è il caricamento dello Shader. Si fa riferimento alla classe Content che contiene il metodo ad accesso statico Load. Tale metodo riceve in input una stringa che designa il percorso di accesso al file (*.fx ) che contiene il codice dello Shader. Ricordiamo che il nome dello Shader va indicato senza l’estensione. Il metodo Load è parametrizzato con un tipo Effect; ciò indica che la variabile di ritorno conterrà dati di tipo Effect.

     

    protected override void Draw(GameTime gameTime)

    {

          //Resettiamo lo sfondo dell'applicazione ad azzurro.

          graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

     

          // Alcuni piccoli helper trigonometrici e non.

          Func<float, float> sin = x => (float)Math.Sin((double)x);

          Func<float, float> sigmoid = x =>

                                 (float)MathHelper.SmoothStep(0.5f, 0.1f, x);

     

          var pi = (float)Math.PI;

     

          // Da quanti secondi l'applicazione é in esecuzione?

          var t = (float)gameTime.TotalGameTime.TotalSeconds;

     

          // Helper di attivazione/disattivazione di un passo dello shader HLSL.

          Action<int, Action<EffectPass>> pass = (i, a) =>

                                                    a(fx.CurrentTechnique.Passes[i]);

     

    Eseguiamo l’override del metodo Draw di default, dove scriviamo tutto il codice per effettuare il disegno, senza demandare nulla ad altri metodi.

    Definiamo innanzitutto due funzioni per aiutarci nei calcoli, una (sin) che calcolerà il seno per definire la forma del serpente, e l’altra (sigmoid) per interpolare i dati.

    Di particolare interesse, in questa porzione di codice, è la definizione dei passi dello Shader HLSL.

     

    Action<int, Action<EffectPass>> pass=(i, a) => a(fx.CurrentTechnique.Passes[i]);

     

    Quello che intendiamo fare è definire in modo elegante l’attivazione e la disattivazione del passo dello Shader. Il codice ML-style ci viene incontro in questo senso, poiché creiamo una labda expression. Diremo che l’azione pass deve compiere una certa azione ad un determinato indice. L’azione che pass eseguirà la chiameremo (a), e (a) effettua un passo della tecnica dello Shader; dato che la tecnica può contenere più di un passo, dovremo indicare quale è l’indice del passo che (a) deve effettuare, e lo indichiamo con (i). Come abbiamo visto, la nostra tecnica contiene un singolo passo, quindi non dovremo aggiornare l’indice ( che sarà sempre zero ). In altre parole piuttosto che scrivere

     

    fx.Begin();

    fx.CurrentTechnique.Passes[0].Begin();

     

    //…code…

     

    fx.CurrentTechnique.Passes[0].End();

    fx.End();

     

    avremo una espressione decisamente più elegante

     

    fx.Begin();

    pass(0, p => p.Begin());

     

    //…code…

     

    pass(0, p => p.End());

    fx.End();

     

    L’azione che (a) esegue è di tipo EffectPass, cioè un tipo di effetto derivante dalla tecnica dello shader in uso.

     

    SpriteBatch per effetti speciali - parte 2

    Implementazione

    By Davide Luzzu – Webcast by Giuseppe Maggiore

    Lo Shader

     

    sampler ControlPoints : register(s0) = sampler_state

    {

      MipFilter = LINEAR;

      MagFilter = LINEAR;

      MinFilter = LINEAR;

     

      AddressU = CLAMP;

      AddressV = CLAMP;

    };

     

    Il “serpente” che andremo a definire è composto da svariati punti di controllo che contengono, non solo una posizione, ma anche un valore che ne determina il colore. Abbiamo però necessità di definire come interpretare il colore nelle posizioni tra un punto di controllo ed un altro; tale operazione viene eseguita con una semplice interpolazione lineare, lasciando invariati i colori di partenza. Per specificare l’immutabilità dei colori di partenza scriveremo:

     

    AddressU = CLAMP; // punto di controllo di partenza

    AddressV = CLAMP; // punto di controllo di arrivo

     

    dove CLAMP indica appunto che tali colori non dovranno variare.

    Queste specifiche le inseriamo in un sampler, cioè indichiamo che la modalità di accesso ai pixel del nostro PixelShader  è attraverso un campionamento. Dato che esistono più sampler di base, indichiamo con

     

    : register(s0)

     

    che quello che ci interessa è all’indice 0 (zero), più precisamente nel registro “s0”, detto in codice assembly (dove abbiamo a che fare direttamente con i registri).

     

    float4 PixelShaderFunction(float2 input : TEXCOORD0) : COLOR0

    {

      float cp;

      cp = tex2D(ControlPoints, float2(input.x, 0.0f));

      float alpha = pow(saturate(1.0f - abs(input.y - cp)), 32.0f);

      return float4(SnakeColor.xyz,alpha);

    }

     

    Implementiamo la funzione del PixelShader, passando in ingresso i dati sulle coordinate di texture, ottenuti dal VertexShader. Come possiamo notare, il parametro di ingresso è una coordinata bidimensionale di texture, e la x del parametro rappresenta la posizione sull’asse delle ascisse del punto di controllo che dovremo andare a trovare. La posizione sull’asse delle ordinate la dobbiamo invece ricavare sfruttando i dati che abbiamo:

     

      cp = tex2D(ControlPoints, float2(input.x, 0.0f));

     

    Non ci resta che calcolare un valore alpha, che rappresenta la trasparenza del colore che dà la “fluorescenza” all’immagine di base. Questo colore trasparente avrà una saturazione maggiore quanto più si è vicino all’immagine del serpente, ed al contrario avrà una intensità minore tanto più è lontano. Questa “aura” che impostiamo attorno al serpente è prodotta con:

     

      float alpha = pow(saturate(1.0f - abs(input.y - cp)), 32.0f);

     

    Infine restituiamo un colore a quattro canali, per cui, i primi tre rappresentano il colore originale, e il quarto il canale alpha.

     

    technique Technique1

    {

        pass Pass1

        {

            PixelShader = compile ps_2_0 PixelShaderFunction();

        }

    }

     

    Uno Shader, abbiamo già detto, che è formato da una technique e da uno o più pass; l’ultimo passaggio, appunto, è destinato a definire tale technique e il pass che ci interessa.

    Il PixelShader è compilato con la versione 2_0, e ciò che deve essere tradotto in assembly è la funzione PixelShaderFunction.

    SpriteBatch per effetti speciali - parte 1

    By Davide Luzzu – Webcast by Giuseppe Maggiore

     

    Abstract

    Uno dei problemi più pressanti, nella creazione di mondi virtuali 3D, è rendere realistica la percezione del mondo. I dettagli stanno alla base di tutto. Più sono curati i dettagli più l’utente si troverà a proprio agio nell’ambiente 3D. Per meglio comprendere vediamo un esempio. Ammettiamo che si debba rappresentare un oggetto radioattivo. Come faremo capire all’utente che esso è radioattivo? Lo coloriamo di verde? Non basterebbe. Aggiungiamo una luce all’oggetto per indicare che esso reagisce in modo “strano” al contatto con l’aria circostante? Può andare, ma si può fare di più. Dovremo, in questo caso, creare una vera e propria fluorescenza dell’oggetto radioattivo.

    Esistono svariati modi per creare una fluorescenza, ma, dal momento che si tratta di un dettaglio, non potrà avere un ruolo predominante nella scena 3D, cioè non può occupare troppe risorse.

    Si presenta dunque la necessità di possedere uno strumento che esegua del codice a basso livello, (cioè che ci permetta un grande risparmio di memoria e risorse) chiamato genericamente Shader.

    Potremmo dire che uno Shader è quella parte di codice che valuta come gli oggetti visuali devono rispondere alle luci; in altre parole con uno Shader determiniamo la distribuzione dei colori sugli oggetti visuali, in modo tale da ottenere effetti di tridimensionalità, fluorescenza, luminescenza ecc… Per meglio comprendere l’importanza degli Shader proviamo ad immaginare un mondo virtuale in cui venga rappresentata una tavola imbandita con ogni sorta di cibi, insomma un ambiente 3D abbastanza complesso. Se in tale ambiente non avessimo nessuna luce, niente ci vieterebbe di pensare non solo che non c’è tridimensionalità, ma che addirittura si tratta di un mondo virtuale vuoto; questo perché in assenza di luce lo percepiamo completamente nero.

    Al contrario se avessimo una luce intensissima lo percepiremo completamente bianco.

    La soluzione dunque risiede nel corretto bilanciamento delle luci e delle ombre.

    Quello che presentiamo in questo articolo è un Pixel Shader HLSL (  High Level Shading Language ), e ci servirà appunto per il controllo di alcuni effetti di luce, applicati ad una texture.

    Uno Shader in HLSL è formato da una technique (tecnica), che a sua volta è formato da vari pass (passi). Nel pass vengono specificate quali funzioni devono essere utilizzate, e a quale tipo di shader si intende fare riferimento (Pixel/Vertex Shader).

     

     

     

     

    clip_image001
     

    clip_image003

     


    clip_image004Figura 1  Screenshot dell’applicazione.

     

     

     

     

    Struttura di un nuovo progetto

    Ogni progetto XNA 3.0 possiede una sotto-struttura logica (cioè dove mettere i dati), chiamata Content, nella quale dovremo copiare il file (*.fx) che contiene il codice dello Shader. La copia può avvenire attraverso una semplice operazione di dragging.

    E’ importante inserire i dati nella struttura apposita Content, poiché essa contiene delle References statiche alle impostazioni di libreria, ed esse ci consentiranno, senza alcuno sforzo, il caricamento dei dati (audio, effetti, textures…), utili al nostro progetto.

     

    clip_image005
    clip_image007

     

    clip_image008
     


    Figura 1 References statiche contenute nella struttura logica Content.

     

     

    L’approccio che seguiremo per la definizione del progetto è bottom-up, cioè definiremo prima lo shader, poi come si inserisce nel contesto dell’applicazione, ed infine come si controlla e si visualizza con un esempio.

    L’idea è quella di creare un serpente dal colore fluorescente, la cui forma è definita solo in alcuni punti, detti punti di controllo.

     

    clip_image008
     

    clip_image010

     


    clip_image005Figura 2 Esempio di Punti di Controllo nel serpente. I punti, in questo caso, sono 13 e sono inseriti nel range [0,1].

     

     

     

     

    Tali punti di controllo verranno definiti nel codice. Lo Shader dovrà contenere una funzione generica, che imposti una texture per alcuni punti di controllo definiti altrove. L’intera forma del serpente è derivata dall’interpolazione delle posizioni dei punti di controllo.

    Il nostro primo obbiettivo è dunque creare uno Shader e copiarlo nella struttura logica Content.

     

    Info aggiuntive

    Per quanto riguarda luci e ombre, è necessario precisare che esistono due tipi di ombre:

    • ombre sugli oggetti: danno la sensazione di tridimensionalità ( una sfera non si distinguerebbe da una semplice circonferenza se non avesse una ombreggiatura )
    • ombre riportate: rappresentano l’interazione tra gli oggetti visuali, e vengono chiamate ombre volumetriche.

    Per quanto riguarda gli Shader, abbiamo fatto riferimento a due diversi tipi:

    • Pixel Shader (o Fragment Shader ) valuta l’interazione della luce con ogni singolo pixel che colora l’oggetto visuale, ed ha un costo computazionale particolarmente elevato. Tuttavia tale shader ha un vantaggio intrinseco. Il pixel shader è legato alla risoluzione con cui si sta visualizzando l’applicazione; più la densità di pixel nell’applicazione è elevata più lo shader è preciso e più il costo computazionale aumenta. Al contrario è banale diminuire la risoluzione, e dunque il peso che tale shader possiede nell’esecuzione.
    • Vertex Shader valuta l’intensità di luce su ogni vertice delle mesh dell’oggetto visuale, per cui il colore risultante sarà dato da:
      • Media dei colori tra:
        • Colore della mesh (risultante dall’interpolazione dei colori dei tre vertici)
        • Colore della luce (diffuse color)
      • Moltiplicazione del colore medio per l’intensità di luce sulla mesh

     

    Uno Shader HLSL permette di scrivere complessi calcoli grafici che possono essere eseguiti con grande rapidità dalla GPU, poiché sono istruzioni di basso livello.

    Queste istruzioni vengono impartite direttamente alla scheda video, pertanto è necessario assicurarsi che essa supporti tali istruzioni.

    Uno Shader HLSL, fortunatamente, ha una sintassi C-like style, ed è abbastanza semplice da comprendere; le operazioni che supporta sono le comuni operazioni aritmetiche, più gran parte delle comuni funzioni trigonometriche. Inoltre, per le espressioni di controllo, vale la stessa sintassi del C; per i booleani si usano gli stessi operatori: &&, ||, <, >, ==, !=, <=, >=.

    La versione dello Shader utilizzata per questo esempio è la 2_0, che permette solitamente una maggiore ottimizzazione del codice nella traduzione in assembly.

    1/16/2009

    Camera And Input

    Xna Web Tour - Basic

    By Davide Luzzu, Webcast di Giuseppe Maggiore

    Camera e Input

    Abstract

    Come la maggior parte di voi avrà capito Xna è un’avanzatissima piattaforma per sviluppare videogiochi. Quello che ci si aspetterebbe da un videogioco è che fosse interattivo; se così non fosse la sarebbe un’animazione! Dopo aver constatato questa “sottile” differenza, i più arguti tra i lettori, noteranno anche che finora abbiamo creato un mondo virtuale, in cui eravamo impossibilitati nel movimento, cioè non potevamo navigare nel mondo virtuale. Ma diremo di più. Non si poteva proprio interagire in alcun modo con la scena 3D!

    La base dell’interazione risiede nell’abilitare l’input da dispositivi come tastiera, gamepad, joystick, o mouse. Ogni volta che l’applicazione riceve un input da uno di questi dispositivi, dovrà essere in grado di analizzare l’input ed eseguire una azione conseguente. L’analisi viene effettuata automaticamente da Xna, ed al programmatore è lasciato il compito di definire l’azione derivante dall’input.

    In un gioco 3D, come si diceva poc’anzi, è fondamentale potersi muovere all’interno della scena, cioè la nostra “telecamera” deve essere “mobile”. Tale telecamera è manovrata manipolando la matrice View.

    Una affermazione che potrà sembrare strana è che in realtà quando ci si muove all’interno di un mondo 3D, non è il player che si sposta, ma il mondo intero! In parole povere il player resta fermo e il mondo si muove in senso contrario all’input ricevuto, così da dare l’impressione di muoversi nella direzione desiderata; quando ad esempio il player preme il tasto direzionale “freccia destra” il mondo si muoverà verso sinistra, e così via. E’ un po’ come svitare una lampadina facendo ruotare l’intero palazzo!

    In questo articolo presentiamo quindi i principi fondamentali per consentire la navigazione, e l’interazione in generale, all’interno di un mondo virtuale.

    Struttura di un nuovo progetto

    Per la creazione di questo progetto ci baseremo su “Models”, l’articolo per la gestione dei modelli.

    Info aggiuntive

    Per quanto riguarda i dispositivi di input sono necessarie alcune precisazioni.

    Xna Game Studio 3.0 è una piattaforma per lo sviluppo di videogiochi su XBox 360 e Windows Platform, e supporta 3 dispositivi di base: tastiera generica, mouse generico e gamepad XBox. Salvo poche eccezioni altri gamepad non funzioneranno sulle vostre applicazioni. Tuttavia su internet esistono svariate librerie di supporto gratuite, per integrare differenti gamepad da quello XBox 360.
     
     
     

    Implementazione

     
     

    Vector2 eyeRotation2D = new Vector2(1.5f, 0.0f);

    Quaternion eyeRotation

    {

      get

      {

         return Quaternion.CreateFromYawPitchRoll(eyeRotation2D.X,

                                                  eyeRotation2D.Y,

                                                  0.0f);

      }

    }

     

    Riguardo al controllo della visuale del player Xna 3.0 offre una soluzione davvero semplice ed intuitiva. Prima però è necessario introdurre una piccola nota di teoria; esistono tre differenti movimenti rigidi che la telecamera può compiere per riprodurre i movimenti che farebbe la testa del giocatore per guardarsi attorno: Yaw, Pitch e Roll.

    o  Yaw è il movimento di rotazione della testa che compiamo quando ad esempio guardiamo a destra e sinistra prima di attraversare la strada.

    o  Pitch è il movimento di rotazione che compiremmo se, guardando dritti di fronte a noi, inclinassimo la testa avvicinandola ad una spalla.

    ·     Roll è il movimento di rotazione che compiamo quando guardiamo in alto e in basso

     

    Un’interessante digressione sull’argomento lo potete trovare su  http://en.wikipedia.org/wiki/Yaw,_pitch,_and_roll

    Per controllare queste tre rotazioni ci serviamo di un quaternione, che non è altro che una matrice 4x4 ottimizzata per alcuni calcoli 3D. Il quaternione è creato attraverso il metodo statico CreateFromYawPitchRoll(yaw, pitch, roll) al quale passiamo in ingresso le nostre rotazioni.

     
     

    Vector3 eyePosition = Vector3.Right * 6.0f + Vector3.Up * 2.0f;

    Matrix view

    {

       get

      {

          return Matrix.CreateTranslation(-eyePosition) *

                 Matrix.CreateFromQuaternion(Quaternion.Conjugate(eyeRotation));

       }

    }

     

    Abbiamo accennato al fatto che il mondo si muove così come abbiamo immaginato di svitare una lampadina: al contrario! Il calcolo di tale movimento lo eseguiamo in una matrice che controllerà la nostra vista. Definiamo innanzitutto la posizione del vettore di vista e ne creiamo la traslazione inversa con

     

    Matrix.CreateTranslation(-eyePosition)

     

    e lo moltiplichiamo per l’inverso ( il coniugato ) della rotazione data dal quaternione “eyeRotation” in questo modo:

     

    Matrix.CreateFromQuaternion(Quaternion.Conjugate(eyeRotation))

     
     

    protected override void Update(GameTime gameTime)

    {         

       if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)

           this.Exit();

     

       var keyboardState = Keyboard.GetState();

     

      if (keyboardState.IsKeyDown(Keys.Escape)) this.Exit();

     

       var dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

     

       if (keyboardState.IsKeyDown(Keys.Right))

           eyeRotation2D.X += -dt * 1.0f;

       if (keyboardState.IsKeyDown(Keys.Left))

           eyeRotation2D.X += +dt * 1.0f;

       if (keyboardState.IsKeyDown(Keys.Up))

           eyeRotation2D.Y += -dt * 1.0f;

       if (keyboardState.IsKeyDown(Keys.Down))

           eyeRotation2D.Y += +dt * 1.0f;

     

       if (keyboardState.IsKeyDown(Keys.Space))

           eyePosition += dt * Matrix.CreateFromQuaternion(eyeRotation).Forward;

     

       base.Update(gameTime);

    }

     

    Le direttive del movimento in base all’input vanno codificate nel metodo apposito Update, di cui eseguiamo l’override.

    Il metodo GetState() ci consente di recuperare le informazioni dello stato del dispositivo di input, sia esso Keyboard o GamePad. Nel nostro caso abbiamo salvato lo stato in una variabile che ci servirà per controllare la pressione dei tasti direzionali della tastiera.

    La nostra variabile “keyboardState” contiene un metodo “IsKeyDown(int)” al quale passeremo il tipo di tasto (sottoforma di intero) che vogliamo controllare.

    L’ultimo passaggio prevede il movimento consono all’azione del tasto premuto.

    Buon divertimento!

     

     

    1/13/2009

    Models

    Xna Web Tour – Basics

    By Davide Luzzu- Webcast di Giuseppe Maggiore

     

    Abstract

    Tutti gli oggetti visuali nel mondo virtuale 3D sono composti da triangoli. Tali oggetti visuali possono essere più o meno complessi, e tale complessità implica un numero considerevole di triangoli, anche svariate centinaia di migliaia. E’ comprensibile che posizionare manualmente ad uno ad uno tutti i triangoli potrebbe essere terribilmente noioso, se non impraticabile!

    Ecco dunque la necessità di possedere uno strumento nelle librerie di sviluppo che ci renda possibile il caricamento di modelli 3D, creati con software appositi. Tale strumento viene genericamente chiamato Loader (caricatore), ed esegue una azione di Load (caricamento).

    XNA offre un’interessante opportunità per compiere azioni di Load, infatti, consente il caricamento e l’interazione con i modelli 3D attraverso poche righe di codice.

    In questo articolo viene dunque presentato il caricamento da file di modelli tridimensionali, la loro visualizzazione all’interno della scena ed infine il posizionamento nel mondo virtuale.

     

    Struttura di un nuovo progetto

    Ogni progetto XNA 3.0 possiede una sotto-struttura logica (cioè dove mettere i dati), chiamata Content, nella quale dovremo copiare i dati dei modelli. La copia può avvenire attraverso una semplice operazione di dragging.

    E’ importante inserire i dati nella struttura apposita Content, poiché essa contiene delle References statiche alle impostazioni di libreria, ed esse ci consentiranno, senza alcuno sforzo, il caricamento dei dati (audio, effetti, textures…), utili al nostro progetto.

     

    Info aggiuntive

    Una delle caratteristiche più interessanti relative all’import e alla visualizzazione dei modelli in XNA 3.0, è il fatto che la compilazione dei modelli è incrementale. Per compilazione intendiamo l’elaborazione della struttura dei modelli, per crearne una rappresentazione nel nostro ambiente virtuale. Con incrementale indichiamo il principio per cui, una volta compilati, le successive elaborazioni dell’ambiente virtuale saranno più veloci, poiché la struttura non deve essere rivisitata completamente. E’ una caratteristica di enorme importanza, poiché fa risparmiare una quantità enorme di tempo in fase di creazione e  debugging del nostro mondo virtuale.

     

    Implementazione

     

      Model imperialDestroyer, rebelCruiser;

      protected override void LoadContent()

      {

         //Indichiamo da dove caricare i modelli

         imperialDestroyer = Content.Load<Model>(@"Palpatine Destroyer\model");

         rebelCruiser = Content.Load<Model>(@"Mon Calamari II\model");

    }

     

     

    La parte principale del caricamento dei modelli è contenuta nel metodo LoadContent creato di default dall’applicazione.

    In questa porzione di codice abbiamo inizialmente creato due variabili di tipo Model, le quali conterranno i dati dei nostri modelli.

    L’istanziamento delle variabili imperialDestroyer e rebelCruiser è decisamente semplice: si fa riferimento ad una classe Content che contiene un metodo Load ad accesso statico. Tale metodo statico Load riceve in input una stringa che designa il percorso di accesso al modello (che deve essere indicato senza l’estensione).

    Da notare che Load è parametrizzato con un tipo Model; ciò indica all’applicazione che il caricamento, che si sta tentando di effettuare, dovrà rendere un Model.

     

      void Draw(Model model, Matrix world)

      {

         foreach (var mesh in model.Meshes)

         {               

            foreach (var effect in mesh.Effects)

            {

                (effect as BasicEffect).World = world;                   

                (effect as BasicEffect).View = Matrix.CreateLookAt(

    Vector3.Right * 6.0f + Vector3.Up * 2.0f,

    Vector3.Zero, Vector3.Up);

     

                 float openAngle = 1.5f; //ampiezza dell'obiettivo

                 float aspectRatio = 1.333f; //rapporto altezza/larghezza obiettivo

                 float startFocus = .1f;//dove inizia il focus (front clip plane)

                 float endFocus = 10000.0f; //dove finisce il focus                     (effect as BasicEffect).Projection =

           Matrix.CreatePerspectiveFieldOfView(

    openAngle, aspectRatio, startFocus, endFocus);

     

               (effect as BasicEffect).LightingEnabled = true;

                (effect as BasicEffect).DirectionalLight0.Enabled = true;

                (effect as BasicEffect).DirectionalLight0.Direction =

    Vector3.Right + Vector3.Down;

                (effect as BasicEffect).DirectionalLight0.DiffuseColor =

                         Color.OrangeRed.ToVector3();

                (effect as BasicEffect).DirectionalLight0.SpecularColor=

                         Color.Violet.ToVector3();

                 //-----------------------------------------------------------        }//end foreach mesh.Effects

              mesh.Draw();

          }//end foreach model.Meshes

     

       }//end method

     

    Questo metodo Draw, da noi definito, prende in ingresso due parametri: Model e Matrix. Il parametro di tipo Model indica il modello caricato a cui applicare le trasformazioni affini, contenute nel parametro di tipo Matrix.

    In aggiunta alle trasformazioni affini, applichiamo anche delle luci, per rendere più realistica la percezione del modello nel mondo virtuale.

    Senza addentrarci troppo nell’argomento luci, potremmo dire genericamente che l’assenza di luci dà una sensazione di “finto” nella visualizzazione degli oggetti visuali.

    Riguardo alle luci utilizzate in questo tutorial notiamo che viene abilitata un'unica luce direzionale, con un colore (diffuse color), ed un colore Violet di risposta alla luce sugli oggetti (specular color).

    Trasformazioni affini e luci sono contenuti nell’interfaccia generica BasicEffect, che applichiamo effettuando un casting esplicito:

     

     

    (effect as BasicEffect).MetodoDaRichiamare( params… );

     

     

    Il metodo Draw, da noi definito, contiene due foreach annidati per cui:

    • quello esterno scorre tutte le mesh contenute nel modello,
    • quello interno lo useremo per applicare gli Effect ad ogni mesh.

    La complessità computazionale di tale esecuzione è O(n2).

    Una delle parti più interessanti in questa porzione di codice è l’utilizzo delle matrici World, View e Projection.

     

    ·               World è la matrice che rappresenta le coordinate di traslazione e rotazione del modello rispetto al mondo virtuale ( world coordinates ). Esiste una matrice world per ogni modello, così da poterli controllare singolarmente.

    ·               View   è la matrice che indica la proiezione di vista, in altre parole rappresenta la telecamera attraverso la quale noi osserviamo il mondo virtuale. Al momento abbiamo inserito una sola View, ma in giochi multiplayer possono coesistere più View, una per ogni giocatore.

    ·               Projection è la matrice che rappresenta le caratteristiche dell’obiettivo della nostra telecamera. Le impostazioni base di questo obiettivo sono: il suo angolo di apertura, il rapporto altezza/larghezza, il front clip plane (cioè il punto di inizio del focus), il back clip plane (cioè il punto di fine del focus). Back e front clip plane indicano, in altre parole, dove noi iniziamo a vedere (ad esempio 0,1 cm dal nostro occhio), e sin dove si spinge la nostra visuale (ad esempio 3 km, 10 km ecc…).

     protected override void Draw(GameTime gameTime)

     {

        GraphicsDevice.Clear(Color.CornflowerBlue);

     

        //traslazione del modello verso sinistra di un tot di unità a piacimento

        Draw(imperialDestroyer, Matrix.CreateTranslation(Vector3.Left * 3.0f));

     

       //posizionamento del modello e rotazione (sfruttando le proprietà della

       billboard) verso "l'Imperial Destroyer"

        var rebelCruiserPosition = Vector3.Right * 3.0f;

        Draw(rebelCruiser, Matrix.CreateBillboard(

    rebelCruiserPosition, Vector3.Left, Vector3.Up, null));

     

     

        base.Draw(gameTime);

    }

     

     

    Draw è il metodo di cui eseguiamo l’override per disegnare gli oggetti visuali nella scena. In questo caso contiene poche righe di codice, poiché demandiamo tutto ad un altro metodo Draw, descritto in precedenza.

    Al metodo Draw, da noi definito, passiamo dunque il modello e la matrice World, dopodiché disegniamo la scena chiamando:

     

      base.Draw(gameTime);

     

    che si occupa di lanciare l’implementazione di default del metodo Draw fatta da Xna e che si occupa di effettuare tutte le operazioni “standard” di finalizzazionedel disegno della scena in DirectX.