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*********************************************************Come per gli altri post, ho diviso il codice in macro aree
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();
}
}
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)Nella funzione Update aggiungiamo le informazioni relative alla posizione della camera (osservatore).
{
//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);
}
//Funzione per utilizzare lo shader specularMentre nella funzione draw chiameremo la funzione DrawModelSpecular dove richiameremo l'effetto passando il nuovo parametro ViewVector.
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();
}
}
Fonte:
http://rbwhitaker.wikidot.com/specular-lighting-shader
Sorgenti:
imparandoxna_3d.5specular.hlsl.zip
3 commenti:
Ottimo articolo :)
E grazie anche dei link riportati!
Mi sa che appena è passata la sessione esami mi iscriverò ai suddetti forum!
A presto
ehehe dai ci vedremo anche sul Forum! E' un ottimo punto di ritrovo!
Allora buono studio, e grazie per i commenti fanno sempre piacere!
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