VS85 Team's profileXNALearnersBlogListsGuestbook Tools Help

XNALearners

2/17/2009

XNA@DSI - Ingegneria del Software

Quest’anno al corso di Ingegneria del Software del dipartimento di Informatica dell’Universitá Ca’ Foscari di Venezia é stato svolto un interessante esperimento: affiancare al classico lavoro teorico di produzione di documenti tecnici sulla creazione di un progetto informatico anche la produzione del progetto stesso. In particolare, é stato chiesto agli studenti di produrre un videogioco (piú precisamente, un sistema che permettesse di giocare a vari giochi di carte): per lo sviluppo di tale videogioco é stata usata la tecnologia XNA.

Nonostante la maggior parte degli studenti non avesse alcuna esperienza pregressa con XNA (e, a volte, nemmeno con C#, il linguaggio che si accompagna ad XNA), i ragazzi sono riusciti a padroneggiare la tecnologia in breve tempo (la durata del corso), producendo dei progetti finali veramente degni di nota.

Abbiamo quindi chiesto ad un gruppo (“I Cordiali”) di presentarci il loro lavoro e di darci il loro feedback sull’esperienza, abbastanza “diversa” da quella di un classico corso universitario:

 

 

N.B.: Il corso di Ingegneria del Software é stato tenuto dal docente Agostino Cortesi, con l’assistenza di Giuseppe Maggiore e Giulia Costantini. Il gruppo dei “Cordiali” é formato da Francesco Boscariol, Mauro Barbon. Luca Cosmo, Chiara Poma e Nicola Loiolino (da sx a dx come appaiono nel video).

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.

 
Benvenuto nel mio Spaces!
Please wait...
Sorry, the comment you entered is too long. Please shorten it.
You didn't enter anything. Please try again.
Sorry, we can't add your comment right now. Please try again later.
To add a comment, you need permission from your parent. Ask for permission
Your parent has turned off comments.
Sorry, we can't delete your comment right now. Please try again later.
You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
Complete the security check below to finish leaving your comment.
The characters you type in the security check must match the characters in the picture or audio.
walter porcelliniwrote:
faccio i personali complimenti a tutto lo staff, giuseppe maggiore in particolar modo .
Ho scaricato ed installato xna3.0 con vc# express 2008 .
Be , che dire , rispetto al VS2003.net che usavo per supervisioni industriali , è tutto molto piu
"moderno" .Spero di avere il tempo per poter mettere in pratica i vostri
tutorial , i video sono eccellenti ed interessanti
grazie
walter
Oct. 13

Custom HTML

Visite registrate a partire dall'1 febbraio 2008
Free stats