Rosario 3D

My developer blog

Lezione su gli shader, seconda parte [ITA]

Illuminazione dinamica

Con le tecniche visto fino adesso l’illuminazione era statica invariante dalle condizioni ambientali o di illuminazione. Il primo shader con illuminazione dinamica che vedremo è lo shader di tipo lambertiano. Lambert ha creato un modello di illuminazione dove la luminosità di una superficie dipende solo dal coseno dell’angolo formato tra la normale della superficie e la direzione di provenienza della luce. In questo vertex shader prendiamo la posizione della luce e un colore base.

Da notare che la normale viene letta tramite la variabile gl_Normal, siccome la normale è un vettore di tre elementi non può essere moltiplicata per la model view (che è 4×4), quindi bisogna usare la matrice gl_NormalMatrix.

uniform vec3 lightPos;
uniform vec3 baseColor;

varying vec3 color;

void main(){
  // posizione del vertice in eye space
  vec3 vPos = (gl_ModelViewMatrix * gl_Vertex).xyz;
  // direzione di provenienza della luce
  // attenzione, la posizione della luce deve essere passata in eye space
  vec3 lDir = normalize(lightPos - vPos);
  vec3 n = gl_NormalMatrix * gl_Normal;
  // L'intensità dell'illuminazione è data dal coseno tra i due vettori e non può
  // assumere valori negativi.
  color = baseColor * max(0.0, dot(n, lDir));

  gl_Position = ftransform();
}

Il fragment shader non farà altro che scrivere il colore calcolato sul frame buffer.

varying vec3 color;

void main()
{
  gl_FragColor = vec4(color, 1.0);
}

Al posto del colore uniforme possiamo usare una texture, lo shader cambia poco (l’impatto visivo cambia molto).

uniform vec3 lightPos;
varying vec2 uvMap;
varying float diff;

void main()
{
  vec3 vPos = (gl_ModelViewMatrix * gl_Vertex).xyz;
  vec3 lDir = normalize(lightPos - vPos);
  vec3 n = gl_NormalMatrix * gl_Normal;
  uvMap = gl_MultiTexCoord0.xy;
  diff = max(0.0, dot(n, lDir));

  gl_Position = ftransform();
}

Anche il fragment shader è simile all’esempio della texture visto nella prima parte, la cosa da notare è che il colore è modulato in base all’illuminazione.

sampler2D diffuse;
varying float diff;
varying vec2 uvMap;

void main(){
  vec4 diffMap = texture2D(diffuse, uvMap);
  gl_FragColor = vec4(diff* diffMap.xyz, 1.0);
}

Questo modello di illuminazione è molto semplice ma poco realistico, la luce dalla superficie è come se fosse emessa uniformemente in tutte le direzioni. Questo shader si adatta a pochi materiali, principalmente a oggetti non riflettenti (terracotta, intonaco, pietre), in cui per motivi di performance non vengono considerate le micro sfaccettature.

Phong shader

Questo shader aggiunge alla componente diffusiva una componente speculare. Si basa sull’assunzione che la luce verrà riflessa principalmente lungo vettore simmetrico a quello di direzione della luce (usando come asse di simmetria la normale della superficie) e l’intensità andrà diminuendo man mano che ci si scosta da questo vettore. Per calcolare quanto la telecamera è vicina alla luce riflessa possiamo calcolare la bisettrice tra posizione della telecamera e direzione della luce, useremo il coseno tra la bisettrice e la normale come indice di vicinanza tra i due vettori. La luce speculare decade più o meno velocemente in base alla rugosità della superficie, useremo un fattore per regolare il decadimento della specularità.

uniform vec3 lightPos;
uniform float shininess;

varying vec2 uvMap;
varying float lum;

void main(){
  // calcolo la componente diffusiva, niente di nuovo
  vec3 vPos = (gl_ModelViewMatrix * gl_Vertex).xyz;
  vec3 lDir = normalize(lightPos - vPos);
  vec3 n = gl_NormalMatrix * gl_Normal;
  uvMap = gl_MultiTexCoord0.xy;
  float diff = max(0.0, dot(n, lDir));

  float specular = 0.0;
  // la componente speculare va calcolata solo se la parte diffusiva è maggiore di zero
  if(diff > 0.0){
    // calcolo la bisettrice
    vec3 vHalf = normalize(lDir - vPos);
    // coseno tra bisettrice e normale limitato inferiormente a zero (non vogliamo valori negativi)
    float nDotH = max(dot(n, vHalf), 0.0);
    // applico il decadimento (phong usa una potenza)
    specular = pow(nDotH, shininess);
  }
  // sommo le due componenti
  lum = diff + specular;
  gl_Position = ftransform();
}

Il fragment shader a parte il cambio di nome di una variabile è uguale al precedente. Il vero problema di questo shader è che tutte le componenti del illuminazione vengono calcolate nel vertex shader. La specularità è fortemente non lineare (c’è un coseno e una potenza) e le interpolazioni lineari causano dei vistosi artefatti.

Per risolvere il problema possiamo spostare i calcoli nel fragment shader e lasciare il vertex shader l’unico compito di passare i parametri da uno stage all’altro. Questo vertex shader non fa altro che passare i parametri (direzione della luce e della telecamera) da vertice a fragment.

uniform vec3 lightPos;

varying vec3 eyeDir;
varying vec3 lightDir;
varying vec3 norm;
varying vec2 uvMap;

void main(){
  eyeDir   = -(gl_ModelViewMatrix * gl_Vertex).xyz;
  lightDir = normalize(lightPos + eyeDir);
  norm = gl_NormalMatrix * gl_Normal;
  uvMap = gl_MultiTexCoord0.xy;

  gl_Position = ftransform();
}

Tutti i calcoli vengono fatti per pixel. Il fragment shader è molti simile al vertex shader precedente, la cosa importante da notare è che i vettori norm, lightDir, eyeDir che sono assunti come normalizzati nel vertex shader devono essere nuovamente normalizzati perché la loro lunghezza potrebbe essere modificata dall’interpolazione tra vertex e fragment.

uniform float shininess;
uniform sampler2D diffuse;

varying vec3 eyeDir;
varying vec3 lightDir;
varying vec3 norm;
varying vec2 uvMap;

void main(){
  vec3 diffMap = texture2D(diffuse, uvMap).xyz;
  vec3 n = normalize(norm);
  vec3 ld = normalize(lightDir);
  vec3 ed = normalize(eyeDir);
  float diff = max(0.0, dot(n, ld));
  float sp = 0.0;
  if(diff > 0.0){
    // per calcolare la "distanza" dal vettore riflesso si calcola proprio il vettore
    // usando la funzione reflect
    vec3 R = reflect(-ld, n);
    float nDotH = max(dot(ed, R), 0.0);
    sp = pow(nDotH, shininess);
  }
  gl_FragColor = vec4((diff + sp) * diffMap.xyz, 1.0);
}

Gli oggetti non sono quasi mai speculari uniformemente, usare una mappa per la specularità aumenta notevolmente il realismo dell’oggetto e toglie l’effetto plasticoso tipico dello shader phong.

Potete trovare un ottimo tutorial su come creare empiricamente le specular map a questo link.

uniform float shininess;
uniform sampler2D diffuse;
uniform sampler2D specular;

//...

  gl_FragColor = vec4(diff *diffMap + sp * specMap, 1.0);
}

Normal mapping

Le normal map sono delle texture su cui viene salvata la normale perturbata, cioè il discostamento tra la normale dei poligoni e la vera normale della superficie. Le normal map vengono salvate spesso con riferimento al poligono, quindi tutte le posizioni e direzioni che sono normalmente in eye coordinate devono essere trasformate in quello che viene chiamato tangent space. Per usare il tangent space oltre alla normale bisogna avere una terna ortonormale formata da normale, tangente e binormale. Questa terna viene ricavata dalle normali e le uvMap (se ci sono più uvMap ci possono essere più tangent space per oggetto) e indica lungo quali direzioni sono orientate le normali nella normal map.

La tangente non è un attributo tra quelli predefiniti, dobbiamo usare un nuovo tipo di variabile, il tipo attribute. Un attribute è una variabile che rappresenta un valore associato ad un vertice. Ovviamente anche se semanticamente scorretto potremmo usare uno dei dati non usati (per esempio una texture coordinate) per passare i valori di tangent.

uniform vec3 lightPos;
attribute vec3 tangent;

varying vec3 eyeDir;
varying vec3 lightDir;
varying vec2 uvMap;

void main(){
  vec3 n = gl_NormalMatrix * gl_Normal;
  // anche tangent è un vettore di 3 ed è solidale alla normale
  vec3 t = gl_NormalMatrix * tangent;
  // la binormale può essere passata con il suo attribute o può essere ricavata come prodotto vettoriale
  vec3 b = cross(n, t);

  vec3 v = (gl_ModelViewMatrix * gl_Vertex).xyz;
  vec3 temp = -v;
  // trasformo la eyeDir in tangent space
  eyeDir.x   = dot(temp, t);
  eyeDir.y   = dot(temp, b);
  eyeDir.z   = dot(temp, n);

  temp = normalize(lightPos - v);
  // anche la direzione della luce deve essre in tantent space
  lightDir.x = dot(temp, t);
  lightDir.y = dot(temp, b);
  lightDir.z = dot(temp, n);

  uvMap = gl_MultiTexCoord0.xy;

  gl_Position = ftransform();
}

Notare che il varying per la normale è stato tolto visto che leggeremo la normale dal una texture. A parte questo il fragment shader non cambia molto rispetto all’esempio precedente.

uniform float shininess;
uniform sampler2D diffuse;
uniform sampler2D specular;
uniform sampler2D normal;

varying vec3 eyeDir;
varying vec3 lightDir;
varying vec2 uvMap;

void main(){
  vec3 diffMap = texture2D(diffuse, uvMap).xyz;
  vec3 specMap = texture2D(specular, uvMap).xyz;
  // La texture può memorizzare sono valori positivi, per questo dobbiamo fare
  // questo tipo di conversione
  vec3 normMap = texture2D(normal, uvMap).xyz * 2.0 - 1.0;

  vec3 n = normalize(normMap);
  vec3 ld = normalize(lightDir);
  vec3 ed = normalize(eyeDir);
  float diff = max(0.0, dot(n, ld));
  float sp = 0.0;
  if(diff > 0.0){
    vec3 R = reflect(-ld, n);
    float nDotH = max(dot(ed, R), 0.0);
    sp = pow(nDotH, shininess);
  }

  gl_FragColor = vec4(diff *diffMap + sp * specMap, 1.0);
}

Potete trovare un tutorial molto dettagliato che spiega GLSL (in inglese) su questo sito.

Advertisements

One response to “Lezione su gli shader, seconda parte [ITA]

  1. Francesco 5 May 2010 at 3:01 PM

    Perfetto, grazie 1000 🙂 Un link per il download in pdf sarebbe stato il top 😛

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: