9.7. Distorsione in base ad una geometria

5
Il tuo voto: Nessuno Media: 5 (1 vote)

Questo post-processo permette di distorcere un'immagine sorgente sulla base delle normali per-pixel calcolate a partire da una geometria. È utile nel rendering di effetti speciali di distorsione come bolle in scene subacquee o di materiali trasparenti che generano rifrazione soprattutto nel deferred-shading renderizzando gli oggetti trasparenti all'ultimo step; nel secondo caso il risultato ottenuto non è ovviamente fisicamente corretto, tuttavia l'individuazione di una corretta rifrazione rispetto ad una generata senza basarsi su leggi fisiche è motlo difficile specialmente se la geometria utilizzata è complessa.

Per realizzare questo effetto è necessario renderizzare su un render-target le normali per-pixel della geometria, che verrà utilizzata come distorsione o come oggetto trasparente, in view-space; ovvero è necessario trasformare la normale in ingresso al Vertex Shader con la matrice View della telecamera in uso.

  1 float4x4 matViewProjection;
  2 float4x4 matView;
  3 
  4 struct VS_OUTPUT
  5 {
  6     float4 Position : POSITION;
  7     float3 Normal : TEXCOORD0;
  8 };
  9 
 10 VS_OUTPUT vs_main(float4 Position : POSITION,
 11                     float3 Normal : NORMAL)
 12 {
 13     VS_OUTPUT Output;
 14 
 15     // Projezione i screen-space
 16     Output.Position = mul(Position, matViewProjection);
 17     // Passaggio della normale in VIEW-SPACE al pixel shader
 18     Output.Normal = mul(Normal, matView);
 19 
 20     return Output;
 21 }

Visualizzazione del render-target delle normali:

In questo modo le componenti x ed y del vettore normale rappresenteranno direttamente la quantità di spostamento sul piano dello schermo e potranno essere utilizzate come valori da aggiungere alle coordinate di texture durante il recupero del texel dall'immagine sorgente. È importante sottolineare che, qualora la geometria non copra tutta la superficie del render-target, prima di eseguire il rendering dell'oggetto, sarà necessario cancellarla con il colore (0.5, 0.5, 1.0) che corrisponde, una volta espanso, alla normale (0.0, 0.0, 1.0) che non produce distorsione.

  1 // Sampler dell'immagine sorgente
  2 sampler2D sourceSampler;
  3 // Sampler delle normali per-pixel
  4 sampler2D normalSampler;
  5 
  6 // Intensità della distorsione
  7 uniform float warpStrength;
  8 
  9 float4 ps_main(float2 TexCoords : TEXCOORD0) : COLOR0
 10 {
 11     // Recupero le informazioni dal render-target
 12     float3 normal = tex2D(normalSampler, TexCoords) * 2.0 - 1.0;
 13 
 14     // Spostamento delle coordinate di texture secondo
 15     // le componenti x e y della normale del pixel corrente
 16     // ridimensionate secondo l'intensità della distorsione
 17     TexCoords += normal.xy * warpStrength;
 18 
 19     float4 color = tex2D(sourceSampler, TexCoords);
 20 
 21     return color;
 22 }

Risultato dell'implementazione:

Se utilizzato per la realizzazione della pseudo-rifrazione-riflessione di un oggetto trasparente, per rendere più credibile l'effetto, sarà utile modificare lo shader precedente con il calcolo dei vettori di riflessione e rifrazione rispetto alla normale perpendicolare al piano dello schermo (0.0, 0.0, -1.0).

  1 // Sampler dell'immagine sorgente
  2 sampler2D sourceSampler;
  3 // Sampler delle normali per-pixel
  4 sampler2D normalSampler;
  5 
  6 // Intensità della distorsione per la rifrazione/riflessione
  7 uniform float refractStrength;
  8 
  9 // Indice di rifrazione
 10 uniform float IOR;
 11 
 12 // Normale in screen-space
 13 const float3 screenNormal = float3(0.0, 0.0, -1.0);
 14 
 15 float4 ps_main(float2 TexCoords : TEXCOORD0) : COLOR0
 16 {
 17     // Recupero le informazioni dal render-target
 18     float4 bgColor = tex2D(sourceSampler, TexCoords);
 19     float4 normal = tex2D(normalSampler, TexCoords);
 20 
 21     // Se all'interno della geometria
 22     if (normal.w > 0.0)
 23     {
 24         // Espando la normale
 25         normal.xyz = normal.xyz * 2.0 - 1.0;
 26 
 27         // Calcolo la componente fresnel in modo molto approssimativo
 28         float fres = 1 - saturate(dot(normal.xyz, screenNormal));
 29 
 30         // Calcolo i vettore riflesso e rifratto rispetto alla normale
 31         float3 reflVec = reflect(screenNormal, normal);
 32         float3 refrVec = refract(screenNormal, normal, IOR);
 33 
 34         // Inverto l'asse verticale perchè le coordinate di texture
 35         // incrementano dall'alto verso il basso
 36         reflVec.y *= -1;
 37         refrVec.y *= -1;
 38 
 39         // Spostamento delle coordinate di texture secondo
 40         // le componenti x e y dei vettori di riflessione e rifrazione
 41         // ridimensionate secondo l'intensità della distorsione
 42         // Recupero il texel secondo la distorsione
 43         float4 reflColor = tex2D(sourceSampler, TexCoords + reflVec.xy * refractStrength);
 44         float4 refrColor = tex2D(sourceSampler, TexCoords + refrVec.xy * refractStrength);
 45 
 46         // Interpolazine tra riflesso e rifrazione
 47         return lerp(refrColor, reflColor, fres);
 48     }
 49 
 50     return bgColor;
 51 }

Risultato dell'implementazione: