mercoledì 23 giugno 2010

Pausa...

Ciao a tutti!
come avrete notato il blog è fermo da un pò di mesi...Al momento sono totalmente assorbito dal lavoro e da altri progetti, quindi mi risulta impossibile continuare a studiare e scrivere argomenti legati a XNA. Lo dico veramente a malincuore, perchè lo studio di XNA mi ha veramente divertito e vedere che le visite sul blog aumentavano di settimana in settimana è stato senza dubbio motivo di profonda soddisfazione.

Non è un addio, perchè non si può mai sapere, ma sono sicuro che non mi cimenterò più su XNA per un pò. Per ora scriverò unicamente per http://www.yariok.com (programmazione web).

Grazie veramente tanto a tutti quelli che hanno seguito il blog fino ad ora, spero che possa comunque essere utile per chi vuole imparare le basi di XNA da zero e per chi ha bisogno di ripassare alcuni argomenti!

Ciao a tutti e grazie ancora
Yari

sabato 13 febbraio 2010

3D.5c) XNA Tutorial 3d: HLSL shader in XNA - Specular light

Ciao a tutti! rieccomi dopo il solito mesetto di silenzio! -.-
Prima di partire con il post di oggi ci terrei a segnalare IndieVault.it e relativo Forum attraverso il quale mi è tornata la voglia di lavorare un pò su XNA! Ci tengo a segnalarlo perchè è una delle poche realtà Italiane che tratta l'argomento XNA e dintorni (Aggiungo anche XNAitalia) fateci un salto! non ve ne pentirete.

Il post di oggi tratterà L'illuminazione Speculare, ovvero la capacità di un materiale di essere "Lucido", e quindi di riflettere la luce proveniente da una sorgente luminosa relativamente alla posizione dell'osservatore.

Da questa breve descrizione spiccano quindi 3 elementi:

1) un oggeto
2) una fonte di luce
3) un osservatore

La differenza rispetto agli shader visti fino ad ora è dunque la presenza di un osservatore, o meglio, il fatto che la sua posizione sia determinante per definire in che modo si comporti lo shader.
Prima di proseguire vi mostro il video di quanto andremo a sviluppare:


Dal video potete notare come il riflesso cambi posizione e forma in base al movimento della camera.Per capire come codificare questo effetto dobbiamo prima analizzarlo e capire come i vari attori descritti sopra interagiscono tra di loro:

Nell'immagine qui sotto sono presenti 5 elementi:
1) Vettore osservatore (dal'osservatore verso la superficie)
2) Vettore Luce (Direzione della luce diffuse)
3) Vettore Riflesso (Creato dal "rimbalzo" del vettore luce sulla superficie)
4) Normale della superficie
5) Angolo Alpha compreso tra l'osservatore e il riflesso


Il valore di luce speculare presente nel punto di incidenza dei vettori visti sopra varia in base all'angolo alpha, al crescere di alpha diminuisce e viceversa quanto più alpha tende a zero tanto più la luce speculare aumenta. In pratica portando il vettore dell'osservatore in concidenza del vettore riflesso la luce speculare sarà massima.

Passiamo al codice HLSL:
//0*********************************************************
float4x4 World;
float4x4 View;
float4x4 Projection;
float4x4 WorldInverseTranspose;

float4 AmbientColor = float4(1, 1, 0, 1);
float
AmbientIntensity = 0.2;

float3 DiffuseLightDirection = float3(1, 0, 0);
float4 DiffuseColor = float4(1, 1, 1, 1);
float
DiffuseIntensity = 1.0;
float3 ViewVector;

//1*********************************************************
float
Shininess = 30;
float4 SpecularColor = float4(1, 1, 1, 1);
float
SpecularIntensity = 0.3;


//2*********************************************************

struct VertexShaderInput
{

float4 Position : POSITION0;
float4 Normal : NORMAL0;
};


struct
VertexShaderOutput
{

float4 Position : POSITION0;
float4 Color : COLOR0;
float3 Normal : TEXCOORD0;
};


//3*********************************************************

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{

VertexShaderOutput output;

float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);
float4 normal = normalize(mul(input.Normal, WorldInverseTranspose));
float
lightIntensity = dot(normal, DiffuseLightDirection);
output.Color = saturate(DiffuseColor * DiffuseIntensity * lightIntensity);

output.Normal = normal;

return
output;
}


//4*********************************************************

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{

float3 light = normalize(DiffuseLightDirection);
float3 normal = normalize(input.Normal);
float3 r = normalize(2 * dot(light, normal) * normal - light);
float3 v = normalize(mul(normalize(ViewVector), World));
float
dotProduct = dot(r, v);

float4 specular = SpecularIntensity * SpecularColor * max(pow(dotProduct, Shininess), 0) * length(input.Color);

return
saturate(input.Color + AmbientColor * AmbientIntensity + specular);
}


//5*********************************************************
technique Specular
{

pass Pass1
{

VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_2_0 PixelShaderFunction();
}
}


Come per gli altri post, ho diviso il codice in macro aree
0)Definizione della variabili
In questo blocco definiamo le informazioni che riguardano le matrici (world view projection) e le luci Ambient e Diffuse (se non sapete cosa siano, seguite i due post precedenti nell'area 3d dal menu a destra) .
Come voce nuova vediamo invece ViewVector che identifica il vettore osservatore.

1)Definizione della luce speculare
Shininess: Lucidezza (più è alto più piccola sarà il riflesso sulla superficie)
SpecularColor: Colore della luce
SpecularIntensity: Quantità di specularità( più è alto più la luce viene riflessa)

2)Definizione delle strutture VS input e VS Output
Da notare soltanto che al VS output aggiungiamo il valore della normale del vertice. Che è appunto la normale segnata nell'immagine sopra.

3) VertexShader
E quindi nel vertexshader aggiungiamo in output l'informazione relativa alla normale del vertice.

4) PixelShader
Tramite il pixel shader andiamo ad effettuare tutti i calcoli necessari per determinare la luce per i vari pixel. Dopo aver normalizzato i vettori light e normal calcoliamo il vettore riflesso "r" che rappresenta la freccia verde nell'immagine sopra e il vettore "v" che rappresenta la posizione dell'osservatore (nella scena).
Ora attraverso il prodotto dot calcoliamo la quantità presente tra i due vettori (alpha) e possiamo quindi calcolare il valore specular elevando il dotProduct al valore di Shininess e poi moltiplicando per gli altri fattori che definisco la nostra luce speculare.

Le formule presenti nel punto 4 non sono assolutamente banali, e comunque non di immediata comprensione, l'importante è capire almeno quali siano gli elementi che fanno variare la luce speculare e in che modo.


Per quanto riguarda il codice di game1.cs invece è necessario soffermarsi solo su piccoli aspetti:
        protected override void Update(GameTime gameTime)
{

//Opzioni per la camera
GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
MouseState mouseState = Mouse.GetState();
KeyboardState keyState = Keyboard.GetState();
fpsCam.Update(mouseState, keyState, gamePadState);

world = Matrix.Identity * Matrix.CreateRotationY(0);

//Da usare per lo shader specular
viewVector = fpsCam.cameraPosition;
viewVector.Normalize();

base.Update(gameTime);
}

Nella funzione Update aggiungiamo le informazioni relative alla posizione della camera (osservatore).

//Funzione per utilizzare lo shader specular
private void DrawModelSpecular(Model model, Matrix world)
{

foreach (ModelMesh mesh in model.Meshes)
{

foreach (ModelMeshPart part in mesh.MeshParts)
{

part.Effect = effect;

worldInverseTransposeMatrix = Matrix.Transpose(Matrix.Invert(mesh.ParentBone.Transform * world));
effect.Parameters["WorldInverseTranspose"].SetValue(worldInverseTransposeMatrix);
effect.Parameters["ViewVector"].SetValue(viewVector);

effect.Parameters["World"].SetValue(world * mesh.ParentBone.Transform );
effect.Parameters["View"].SetValue(fpsCam.ViewMatrix);
effect.Parameters["Projection"].SetValue(fpsCam.ProjectionMatrix);
}

mesh.Draw();
}
}
Mentre nella funzione draw chiameremo la funzione DrawModelSpecular dove richiameremo l'effetto passando il nuovo parametro ViewVector.

Fonte:
http://rbwhitaker.wikidot.com/specular-lighting-shader

Sorgenti:
imparandoxna_3d.5specular.hlsl.zip

venerdì 6 novembre 2009

TwitXNA


Ciao A tutti,
Vi segnalo http://www.thinkandbuild.it/twitxna, un mini progetto che sto curando (è più che altro un esperimento).
Si tratta di una pagina web che raccoglie le discussioni di Twitter relative a XNA. L'idea è partita giusto per provare le API di twitter, ma in realtà mi sono reso conto che è interessante vedere come nel resto del mondo la gente sia più avanti sul discorso XNA :P. C'è un gran via vai di informazioni e ogni tanto si trova qualche tutorial interessante difficilmetne reperibile con Google.

La pagina viene aggiornata automaticamente, lasciate scorrere le notizie fino a quando non spariscono in dissolvenza.. quando meno di 3/4 news sono presenti nell'area ne vengono ricaricate altre. Per ora è ancora un esperimento fatto in due serate... a breve lo migliorerò rendendolo un pò più usabile :D

lunedì 2 novembre 2009

3D.5b) XNA Tutorial 3d: HLSL shader in XNA - Diffuse light

Ciao a tutti!
Nello scorso Post vi ho raccontato i concetti base per capire come funzionano gli shader in XNA e abbiamo prodotto uno shader Ambient lighting.
Oggi vedremo invece come far interagire in maniera leggermente più complessa una semplice fonte di luce con una mesh attraverso il diffuse lighting.



Diffuse Lighting
Questo tipo di illuminazione è generato da una fonte di luce che ha unicamente informazione di direzione e intensità, quindi non è associabile a una posizione specifica nello spazio. Potrebbe essere rappresentata da infiniti raggi paralleli. La logica tramite la quale questa luce interagisce con la mesh è la seguente:
Più è piccolo l'angolo di incidenza tra la normale della superficie e il vettore della luce, maggiore sarà il riflesso della luce sulla superficie. Quindi una luce diretta perfettamente verso la normale di una superficie verrà riflessa al massimo, una perpedicolare non verrà riflessa.




Per cercare di spiegarvi meglio il concetto ho creato queste 3 semplici immagini, nella prima potete notare che l'angolo che si crea tra la normale e la luce è molto ampio, in questo caso la luce verrà riflessa molto poco, mentre nell'ultimo caso sarà quasi totalmente riflessa.

Passiamo direttamente al codice:

diffuse.fx------------------------------------------------------------
float4x4 World;
float4x4 View;
float4x4 Projection;

//1 Nuove variabili--------------------------------------------
float4x4 WorldInverseTranspose;

float3 DiffuseLightDirection = float3(1, 0.5, 0);
float4 DiffuseColor = float4(0.4, 0.5, 0.9, 1);
float
DiffuseIntensity = 0.09;

//2 Variabili relative all'ambient light------------------------
float4 AmbientColor = float4(0.3,0.3,0.3,0.3);
float
AmbientIntensity = 0.1;


//3-------------------------------------------------------------
struct
VertexShaderInput
{

float4 Position : POSITION0;
float4 Normal : NORMAL0;
};



struct
VertexShaderOutput
{

float4 Position : POSITION0;
float4 Color : COLOR0;
};


//4---------------------------------------------------------------
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{

VertexShaderOutput output;
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);

float4 normal = mul(input.Normal, WorldInverseTranspose);
float
lightIntensity = dot(normal, DiffuseLightDirection);
output.Color = DiffuseColor * DiffuseIntensity * lightIntensity;

return
output;
}



//5--------------------------------------------------------------
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{

return
input.Color + AmbientColor * AmbientIntensity;
}




technique Technique1
{

pass Pass1
{


VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_1_1 PixelShaderFunction();
}
}


Come per lo scorso tutorial ho diviso il codice in 4 blocchi, analizziamolo velocemente

1 Nuove variabili
WorldInverseTranspose;
Prima abbiamo parlato di "normale della superficie", in realtà quello che interessa non è solo la normale delle superficie, ma la normale posizionata nello spazio della nostra scena. La variabile WorldInverseTranspose servirà per definire questa informazione.

float3 DiffuseLightDirection = float3(1, 0.5, 0);
float4 DiffuseColor = float4(0.4, 0.5, 0.9, 1);
float
DiffuseIntensity = 0.09;
Sono le variabili che identificano la luce definendone la direzione, il colore e l'intensita.

2 variabili ambientLight
Sono le variabili che abbiamo visto nella lezione scorsa utili per definire la luce ambientale. Mi sono dimenticato di dirvi che utilizzeremo anche questa luce... quindi andremo a sommare due tipi di materiali per produrre un effetto più completo.

3 definizioni della struttura vertex shader (in e out)
In questo tipo di materiale avremo bisogno di avere in ingresso oltre che l'informazione di posizione del vertice anche l'informazione della normale, che come vi spiegavo prima, verrà messa a confronto con la direzione della luce per il calcolo dell'intensità.
L'output del vertex shader riporterà informazione di posizione e colore.Infatti a seguito dei calcoli relativi all'angolo di incidenza dei due vettori, andremo a modificare il colore del vertice per garantire l'effetto di "riflessione della luce".

4 Vertex shader
All'inizio della funzione andremo a definire la posizione del vertice in base alla nostra world.
(posizioneremo il vertice nella scena)
nelle righe che riporto qui sotto andremo invece a definire l'effetto della luce sul vertice:
  float4 normal = mul(input.Normal, WorldInverseTranspose);
float
lightIntensity = dot(normal, DiffuseLightDirection);
output.Color = DiffuseColor * DiffuseIntensity * lightIntensity;
normal: moltiplichiamo la normale del vertice per la matrice che identifica la matrice inversa, trasposta della world (in realtà ho notato che basta anche l'inversa....) .Questa moltiplicazione ha lo scopo di definire la normale non solo sul modello... ma sul posizionamento della mesh nella scena, tenendo dunque conto di spostamenti e rotazioni.

lightIntensity : effettueremo il prodotto dot tra la normale e la direzione della luce (riguardate l'immagine con le 3 situazioni....) per definire quanto sia l'incidenza tra la normale e la luce.
http://en.wikipedia.org/wiki/Dot_product#Geometric_interpretation guardare l'immagine su wikipedia.. potrebbe chiarirvi le idee riguardo questa operazione.

output.Color : otteniamo il colore del vertice moltiplicando i valori che abbiamo impostato per la luce diffuse e sopratutto aggiungiamo il fattore intensità definito tramite all'interazione tra normale e luce.

5 Pixel Shader
Non resta che definire il pixel shader utilizzando le informazioni di colore ricevute dal vertex shader e aggiungendo i parametri di luce ambientale definiti in testa al file.

return input.Color + AmbientColor * AmbientIntensity;

A questo punto dobbiamo fare qualche piccola delucidazione sul file game1.cs ... ma niente di difficile, il grosso lo avete visto sopra.

Prima di tutto modifichiamo la funzione Draw per chiamare La DrawModelDiffuse invece che la funzione ambient del tutorial precedente ( se scaricate i sorgenti a fondo pagina troverete già tutto pronto...)

Draw
        protected override void Draw(GameTime gameTime)
{

device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.SlateGray, 1, 0);

DrawModelDiffuse(myModel, world);
base.Draw(gameTime);
}

DrawModelDiffuse

        //Funzione per utilizzare lo shader diffuse
private void DrawModelDiffuse(Model model, Matrix world)
{

foreach (ModelMesh mesh in model.Meshes)
{

foreach (ModelMeshPart part in mesh.MeshParts)
{

part.Effect = effect;

worldInverseTransposeMatrix = Matrix.Transpose(Matrix.Invert(mesh.ParentBone.Transform * world));
effect.Parameters["WorldInverseTranspose"].SetValue(worldInverseTransposeMatrix);

effect.Parameters["World"].SetValue(world * mesh.ParentBone.Transform);
effect.Parameters["View"].SetValue(fpsCam.ViewMatrix);
effect.Parameters["Projection"].SetValue(fpsCam.ProjectionMatrix);
}

mesh.Draw();
}
}
Questa funzione setta alcuni dei parametri che avete visto dentro l'effetto.

Ricordate nella loadContent di decommentare il caricamente dello shader diffuse
effect = Content.Load("effect/Diffuse");

Nel prossimo post vedremo come gestire lo shader con specular light.


IMPORTANTE:
Il materiale di questa introduzione agli shader è stato prodotto grazie a questo ottimo blog : http://rbwhitaker.wikidot.com/diffuse-lighting-shader

Mi sono dimenticato di segnalarlo nel post precedente! quindi colgo l'occasione per farlo ora.


Sorgenti:
imparandoxna_3d.4diffuse.hlsl.zip

giovedì 8 ottobre 2009

3D.5) XNA Tutorial 3d: HLSL shader in XNA

Ciao a Tutti!

Eccomi di nuovo dopo una lunghissima pausa !!!
Mi dispiace aver atteso tanto per creare il nuovo tutorial, ma il tempo libero è stato veramente poco e se devo dire la verità anche la voglia non era molta... :P (w la sincerità) .
Questo periodo di blackout è stato parecchio utile, ne ho approfittato per studiacchiare un pò il discorso shader e HLSL, andiamo al dunque senza girarci troppo intorno e cerchiamo di capire di cosa si tratta:


PROCESSO SHADER

Questa immagine rappresenta il percorso che viene seguito per convertire i dati creati dall'applicazione in informazioni visualizzate a Monitor.


Noterete che le funzionalità di Vertex e Pixel shader sono svolte come pipeline nella GPU.


VERTEX SHADER
Attraverso i vertex shader vengono visitati tutti i vertici nella scena, effettuando su questi eventuali operazioni che possono andare dalla modifica delle informazioni di posizione fino alla definizione del colore del vertice.

Vi ricordo che definire il colore di un vertice in XNA fa si che si crei un'interpolazione tra i colori dei vari vertici... lo abbiamo visto in questo tutorial riporto qui il risultato:


Una volta che sono state svolte le operazione nel vertex shader, viene lancianto il processo Rasterizzazione.


Lo scopo di questo processo è convertire i triangoli che definiscono la scena ( o l'oggetto) in Pixel da renderizzare a monitor.


Nel caso di XNA, uno dei compiti principali del Vertex Shader (non ricordo assolutamente se questo valga anche per DirectX) è quello di posizionare il modello ricevendo i dati View, World e Projection Matrix.



PiXEL SHADER
Ora che la scena è stata convertita in Pixel è il turno del Pixel Shader, che effettuerà operazioni su tutti i pixel visibili.

Al termine di questa operazione l'immagine è pronta per essere presentata a Video. La differenza principale tra i due passaggi è dunque data dal punto nel quale opereremo, Vertici e Pixel!

HLSL
Poco fa parlavo di "operazioni" eseguite dai vertex/pixel shader su ogni vertice/pixel disegnato. Tutto quello che viene eseguito durante questi passaggi è definito attraverso linguaggio HLSL (High Level Shader Language). Il modo migliore per raccontarvi qualcosa su questo linguaggio e su come scrivere i vostri shader penso che sia la pratica diretta sul codice.... quindi ecco qui quello che faremo:

1) Creeremo una scena 3d con una delle solite bellissime astronavi di Battlestar Galactica :)
2) Creeremo un Ambient light Shader, che darà alla scena un colore omogeneo e piatto, basato su una luce ambientale non direzionale, regolabile per colore e intensità.....in poche parole una mer*a :P dato che come vedrete non si distingueranno le facce dell'oggetto.... ma per ora serve per imparare quindi ben venga!!


Prima di tutto vi presento il file ambient.fx che è il punto nel quale definiremo il nostro shader:


ambient.fx

// 1 Variabili settate tramite l'applicazione-----------------------------------

extern float4x4 World;
extern float4x4 View;
extern float4x4 Projection;

//Impostazioni (provare a giocare con questi parametri)---------------------
float4 AmbientColor = float4(1,1,1,1); //Colore passato tramite l'ambiente
float AmbientIntesity = 0.2; //intesità



// 2 VERTEX SHADER--------------------------------------------------------------
//Struttura per definire l'input per il vertex shader
struct VertexShaderInput
{
float4 Position : POSITION0;
};
//Struttura per definire l'output del vertex shader
struct VertexShaderOutput
{
float4 Position : POSITION0;
};
//La nostra funzione vertex shader
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection) ;
return output;
}


// 3 PIXEL SHADER----------------------------------------------------------------
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
return (AmbientColor * AmbientIntesity);
}

// 4 TECNICA---------------------------------------------------------------------
technique Technique1
{
pass Pass1
{
VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_1_1 PixelShaderFunction();
}
}



Prima di farvi vedere il codice della scena 3d (che non è lo scopo centrale di questo tutorial...
ma che comunque spiegherò passo passo giusto per non farvi incazzare come vipere :P)
analiziamo questo codice.

Come noterete l'ho diviso in 4 Macro Blocchi
1) variabili passate dall'applicazione
2) vertex shader
3) pixel shader
4) tecnica

Uno alla volta

1)Variabili settate dall'applicazione
Queste variabili sono definite a livello di applicazione, il che significa che tramite un
costrutto particolare, scriveremo direttamente nel nostro game1.cs del codice che invierà queste
informazioni allo shader fx ( vedremo dopo come... per ora sappiate solo che sono dati definiti in game1.cs).

La sintassi della definizione della variabili è simile a C, ma noterete che i tipi di dato sono
leggermente diversi. A dire il vero devo studiarli ancora per bene...(qui la reference) comunque quel float4x4 com'è facile immaginare fa riferimento
a una matrica 4x4 composta di float.


Per ora osservate soltanto il fatto che si tratta delle 3 matrici view, projection e world...
e se fate un passo indietro di un tutorial... noterete che sono proprio i valori che passavamo al basic effect.
Ecco qui un ripassino :

foreach (BasicEffect be in mesh.Effects) {
be.EnableDefaultLighting();
be.Projection = camera.projection;
be.View = camera.view;
be.World = GetWorld() * mesh.ParentBone.Transform;
}

2) vertex shader
Qui abbiamo 3 blocchi:
2 strutture e 1 funzione.

Diamo un'occhiata all'immagine del processo shader, notiamo che la funzione vertex shader ha 1 freccia entrante e una uscente...
quindi un input e un output.

Le due strutture identificano proprio le 2 frecce.

Sia shader input che shader output hanno lo stesso contenuto, in questo caso:

float4 Position : POSiTiON0;

-float4 identifica un vettore di 4 float x,y,z,w ( se non sapete cosa sia w, date un'occhiata a questo link)
-Mentre per :POSiTiON0 dobbiamo fare un paio di riflessioni in più:
Abbiamo detto che lo scopo del vertex shader è quello di elaborare ogni vertice, e abbiamo anche detto
che possiamo modificare informazioni di posizione e colore per ogni vertice elaborato....la voce (chiamata SEMANTICA) :POSITION0 identifica
che quella variabile è
associata all'informazioni POSIZIONE del vertice.

Una lista completa delle semantiche la trovate a questo link.
Quindi facciamo un passo indietro, nel vertex shader entrano dei dati, definiti dalla struttura VertexShaderInput.
La struttura è composta da una variabile float4 con semantica :POSITION0, questo vuol dire che i dati che entranno
nella vertex shader sono i dati di posizione del vertice.
Mettiamo caso che stiamo disegnando triangolo; la funzione VertexShader ricevere a ciclo le posizioni dei 3 vertici che lo compongono.

Quindi diciamo che POSITION0 è una funzionalità predefinità dal linguaggio HLSL che serve proprio per collegare la logica definita nell'applicazione con lo shader.

La struttura VertexShaderOutput (quella che identifica la freccia uscente dalla funzione VertexShader) è composta come dicevamo prima nello stesso identico modo,
ma in questo caso identifica un output....quindi facciamo due più due e otteniamo che in questo caso siamo di fronte a una funzione VerteXShader che riceve in ingresso la posizione di un vertice

la elabora (attraverso world -view - projection) e quindi restituisce la nuova posizione del vertice.

Ricordate che si possono modificare anche altri paramentri del vertice....(in questo esempio lavoriamo solo sulla posizione).
Ora guardiamo in che modo la funzione VertexShader elabora la posizione del vertice in ingresso:

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection) ;
return output;
}

Ok leggete il prototipo e capiamo subito che .... l'output è un VertexShaderOutput, la funzione si chiama
VertexShaderFunction e riceve in ingresso un VertexShaderInput.
La funzione non fa altro che far passare la posizione del vertice attraverso una moltiplicazione con le 3 matrici principali, ricevute dall'applicazione (vi ricordate? il blocco 1 visto qualche riga fa... )
Mentre la funzione mul è un costrutto predefinito di hlsl che si occupa di moltiplicare 2 matrici.

3) pixel shader
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
return (AmbientColor * AmbientIntesity);
}

La scopo di questa funzione è di dare a ogni pixel disegnato, un colore basato sulla luce ambientale.
Quindi tramite la moltiplicazione AmbientColor * AmbientIntensity ogni pixel assumerà lo stesso color, la cosa interessante da notare è questo colore
non è definito sull'oggetto tramite materiali, ma è definito attraverso il nostro effetto!


La sintassi della funzione e leggermente diversa dalla Vertex Shader ma solo a scopo dimostrativo... infatti le due sintassi sono interscambiabili.
Ma vediamo dove sta la differenza. Noterete che in questo caso la Semantica :COLOR0 è posizionata in linea al nome funzione prima della definizione.
Questo sta ad indicare che l'OUTPUT della funzione sarà il COLORE del pixel elaborato.
Logica quindi del tutto identica al vertex shader, notate che in questo caso l'output del vertexShader viene diretto come input del PixelShader.

4)Tecnica
Anche se è l'ultima parte che tratteremo in realtà questa è la sezione del codice che viene letta per prima.

technique Technique1
{
pass Pass1
{
VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_1_1 PixelShaderFunction();
}
}

Andiamo ad analizzare il codice partendo dall'interno.
Una tecnica richiede che per ogni passo vengano settati i paramentri VertexShader e PixelShader che, come è facile immaginare, identificano le funzioni che
associamo a questi due processi.
Nel nostro caso andremo ad associare le funzioni create precedentemente.

La sintassi è un pò particolare :
compile vs_1_1 indica che la funzione VertexShaderFunction() viene compilata utilizzando la versione 1 di vertex shader.

Alcune funzionalità potrebbero essere supportate solo da versione successive (2,3) di vertex shader.
E' buona pratica utilizzare sempre la versione più bassa disponibile in grado di supportare le nostre necessità, questo farà si che anche chi utilizza
schede video "datate" possa visualizzare i nostri shader.


Una tecnica può essere composta da più passi che verranno eseguiti in successione, in ogni passo potrete definire le vostre funzioni, i risultati delle quali si sommerrano
per otterenere un effetto finale composto appunto da più passi.

All'interno di uno shader possono essere create diverse tecniche e tramite l'applicazione si potrebbe decidere di utilizzare una tecnica piuttosto che un'altra.
Questa scelta potrebbe essere guidata dal tipo di scheda video dell'utente che utilizzerà la nostra applicazione, potremmo infatti decidere che in base al tipo di dettaglio grafico scelto venga
caricata una tecnica più leggera o pià pesante.


Ora che abbiamo analizzato lo shader in tutti suoi dettagli vediamo il codice dell'applicazione.

Come sopra.... non fate copia e incolla! usate i sorgenti a fondo articolo :D


using System;
using System.Collections.Generic;
using System.Linq;
......................etcetcetc
namespace hlsl_start
{
public class Game1 : Microsoft.Xna.Framework.Game
{
GraphicsDeviceManager graphics;
GraphicsDevice device;

//L'effetto Basic (standard di xna)

BasicEffect basicEffect;

//Il nostro effetto

Effect effect;

//Ottima camera da libro xna 3.0 recipes

QuakeCamera fpsCam;

//Il modello 3d

Model myModel;

//Matrici

Matrix world = Matrix.Identity;
Matrix projection;
Matrix worldInverseTransposeMatrix;
Matrix[] modelTransforms;

//Varie

float angle = 0.0f;
Vector3 viewVector;
private Vector3 Position = Vector3.One;
float aspectRatio;


public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = "Content";
}


protected override void Initialize()
{
//istanzia la camera
fpsCam = new QuakeCamera(GraphicsDevice.Viewport, new Vector3(10, 10, 50), 0, 0);

//setta ratio e pojection

aspectRatio = graphics.GraphicsDevice.Viewport.Width / graphics.GraphicsDevice.Viewport.Height;
projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), aspectRatio, 1.0f, 10000.0f);
base.Initialize();
}


protected override void LoadContent()
{
device = graphics.GraphicsDevice;
//Imposta il modello 3d
myModel = Content.Load<Model>("model/cylon");
modelTransforms = new Matrix[myModel.Bones.Count];
myModel.CopyAbsoluteBoneTransformsTo(modelTransforms);
//Imposta gli effetti (ne utilizzaremo uno per volta... quindi commentate e decommentate a seconda del caso
//basicEffect = new BasicEffect(device, null);
//effect = Content.Load("effect/Specular");
//effect = Content.Load("effect/Specular");
effect = Content.Load<Effect>("effect/Ambient");
}


protected override void Update(GameTime gameTime)
{
//Opzioni per la camera
GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
MouseState mouseState = Mouse.GetState();
KeyboardState keyState = Keyboard.GetState();
fpsCam.Update(mouseState, keyState, gamePadState);

//per shader specular ora non ci interessa...
Vector3 cameraLocation = 2* new Vector3(0, 3, 0);
Vector3 cameraTarget = new Vector3(0, 0, 0);

//Rotazione del modello

angle += 0.01f;
if (angle > 359) {
angle = 0;
}

world = Matrix.CreateRotationY(angle) * Matrix.CreateScale(0.05f, 0.05f, 0.05f) ;

//Da usare per lo shader specular

viewVector = Vector3.Transform(cameraTarget - cameraLocation, fpsCam.ViewMatrix);
viewVector.Normalize();
base.Update(gameTime);
}


protected override void Draw(GameTime gameTime)
{
device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.SlateGray, 1, 0);
DrawModelAmbient(myModel, world);
base.Draw(gameTime);
}


//Disegna il modello con lo shader standard
public void DrawModel(Model model, Matrix w){
foreach (ModelMesh mesh in model.Meshes)
{
foreach (BasicEffect effect in mesh.Effects)
{
effect.EnableDefaultLighting();
effect.World = modelTransforms[mesh.ParentBone.Index] * w ;
effect.View = fpsCam.ViewMatrix;
effect.Projection = fpsCam.ProjectionMatrix;
}
mesh.Draw();
}
}


//funzione per utilizzare lo shader Ambient
private void DrawModelAmbient(Model model, Matrix world)
{
foreach (ModelMesh mesh in model.Meshes)
{
foreach (ModelMeshPart part in mesh.MeshParts)
{
part.Effect = effect;
effect.Parameters["World"].SetValue(world * mesh.ParentBone.Transform);
effect.Parameters["View"].SetValue(fpsCam.ViewMatrix);
effect.Parameters["Projection"].SetValue(fpsCam.ProjectionMatrix);
}
mesh.Draw();
}
}


//Funzione per utilizzare lo shader diffuse
private void DrawModelDiffuse(Model model, Matrix world)
{
foreach (ModelMesh mesh in model.Meshes)
{
foreach (ModelMeshPart part in mesh.MeshParts)
{
part.Effect = effect;
worldInverseTransposeMatrix = Matrix.Transpose(Matrix.Invert(mesh.ParentBone.Transform * world));
effect.Parameters["WorldInverseTranspose"].SetValue(worldInverseTransposeMatrix);
effect.Parameters["World"].SetValue(world * mesh.ParentBone.Transform);
effect.Parameters["View"].SetValue(fpsCam.ViewMatrix);
effect.Parameters["Projection"].SetValue(fpsCam.ProjectionMatrix);
}
mesh.Draw();
}
}


//Funzione per utilizzare lo shader specular
private void DrawModelSpecular(Model model, Matrix world)
{
foreach (ModelMesh mesh in model.Meshes)
{
foreach (ModelMeshPart part in mesh.MeshParts)
{
part.Effect = effect;
worldInverseTransposeMatrix = Matrix.Transpose(Matrix.Invert(mesh.ParentBone.Transform * world));
effect.Parameters["WorldInverseTranspose"].SetValue(worldInverseTransposeMatrix);
effect.Parameters["ViewVector"].SetValue(viewVector);
effect.Parameters["World"].SetValue(world * mesh.ParentBone.Transform);
effect.Parameters["View"].SetValue(fpsCam.ViewMatrix);
effect.Parameters["Projection"].SetValue(fpsCam.ProjectionMatrix);
}
mesh.Draw();
}
}
}
}

Caricamento del modello e Camera 3d:
Per questo tutorial non ho usato il model manager che abbiamo creato nelle scorse lezioni (Sarebbe stato scomodo presentare la gestione degli shader... e vorrei fare in modo che anche
chi non fosse interessato alle altre lezioni possa comunque leggere e seguire il post).


La logica per caricare il modello è molto semplice:
1) nella funzione LoadContent carico il modello e gestisco le informazione sulle bones
2) nella funzione Draw richiamo la funzione DrawAmbient che si occupa di scorrere le componenti della mesh e renderizzarle utilizzando il nostro materiale.
3) nella funzione update incrementiamo il valore angle, che verrà adottato insieme alla funzione createRotationY per costruire la matrice world e far ruotare l'oggetto.

La camera che ho utilizzato è la QuakeCamera (prelevata pari pari dal libro xna 3.0 game programming recipes) che viene istanziata nella funzione initialize ricevendo come secondo paramentro la posizione della camera.
Nella funzione Update passeremo alla camera le informazioni relative agli input(keyboard, mouse, gamepad) permettendo alla classe di ricostruire la nuova posizione e la nuova matrice View.


Effect:
Ora che abbiamo visto come viene caricato il modello, possiamo tornare allo scopo del post, gli shader.
Prima di tutto per creare un file fx dovete aggiungere il file nella vostra cartella content (io ho creato una cartella effect che ospiterà questi e i prossimi effetti che vi racconterò).
Per aggiungere l'effetto cliccate nella gestione file del progetto e "Add New Item" quindi scegliete file .fx.
Il caricamento di un file effect utilizza la funzione Load della pipeline xna, posizioniamo la funzione nella LoadContent:

effect = Content.Load("effect/Ambient")

Ora facciamo una passo indietro.. nel blocco 1) del file Ambient.fx abbiamo parlato di alcune variabili da definire attraverso l'applicazione:

extern float4x4 World;
extern float4x4 View;
extern float4x4 Projection;

La voce extern serve proprio ad indicare che queste variabili sono definite tramite un processo esterno al file, ed è quello che faremo nella funzione drawAmbient:

foreach (ModelMeshPart part in mesh.MeshParts)
{
part.Effect = effect;
effect.Parameters["World"].SetValue(world * mesh.ParentBone.Transform);
effect.Parameters["View"].SetValue(fpsCam.ViewMatrix);
effect.Parameters["Projection"].SetValue(fpsCam.ProjectionMatrix);
}

Per ogni Mesh destineremo alle varie parti l'effetto ambient e imposteremo i parametri dell'effetto tramite la funzione setValue(); Quindi i valori World View e Projection andranno in automatico a
valorizzare le variabili dichiarate extern a inizio codice ambient.fx.


Tutto qui! :D

I codici che incorporerò a fondo articolo comprendo già altri 2 materiali (Specular e Diffuse) che vedremo nei prossimi post.
Voi potete iniziare a testarli modificando la chiamata alla funzione che si occupa di disegnare il modello (seguite i commenti nella funzione draw)


Questo è il risultato che otterete lanciando il codice:



brutto forte eh?...bhe diciamo che non è cool come le altre volte :P però siamo alle basi dell'HLSL.. andando avanti magari uscirà qualcosa di più interessante!!



Direi che è tutto... non esitate a fare domande nel caso in cui ci fossero passaggi poco chiari !

Ciao! Alla prossima

Yari

Sorgenti:
imparandoxna_3d.4.hlsl.zip


Lezione in PDF