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

3 commenti:

Odino ha detto...

Ottimo articolo :)
E grazie anche dei link riportati!
Mi sa che appena è passata la sessione esami mi iscriverò ai suddetti forum!

A presto

Yari ( ImparandoXNA ) ha detto...

ehehe dai ci vedremo anche sul Forum! E' un ottimo punto di ritrovo!
Allora buono studio, e grazie per i commenti fanno sempre piacere!

Alex ha detto...

l'ho detto anche in passato ma ci tengo a ribadirlo: è una delle migliori serie di tutorial in italiano sull'argomento. poche chiacchere e tanta sostanza... altri si diluingano troppo per spiegare anche le minime cose.

parlo da completo neofita... c'ho provato in tutti i modi, con tutti i tutorial e tutte le guidesponibili ma niente da fare... ho rinunciato dopo pochissimo... invece da quando ho cominciato a leggere questi tutorial non mi sono mai stancato.

Non posso che farti i miei complimenti ancora una volta!

Posta un commento