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

3 commenti:

Alta ha detto...

Ciao, davvero degli ottimi tutorial.
Ho una sola domanda da farti :

quando utilizzo lo shader standard, con un modello da me creato viene correttamente visualizzata anche la texture.
Quando invece utilizzo uno di questi shader, ad esempio il Diffuse.fx la texture "scompare" : viene quindi visualizzato il solo modello "grezzo" e l'effetto processato correttamente.

Sai per caso a cosa è dovuto ciò e come potrei risolvere?

Ciao e grazie ancora

Yari ( ImparandoXNA ) ha detto...

Per utilizzare la tua texture è necessario fare uno step in più, questi shader sostituiscono il basic Effect che utilizzi normalmente con XNA (dove appunto è definito tutto il passaggio per la definizione delle texture).
Lo step in più lo trovi qui :
http://rbwhitaker.wikidot.com/texturing-shader

Alta ha detto...

ti ringrazio per la dritta ^^

Posta un commento