Normal Mapping 1562 Visite) DirectX 11
L’introduzione delle texture nel rendering delle scene è stato il primo stratagemma per aumentare la qualità dei modelli senza incrementare il numero di poligoni da elaborare. Una texture però non risente delle luci in modo realistico perché la luce viene calcolata utilizzando le normali salvate nei vertici. Per ovviare a questo limite da ormai decenni si utilizzano le Normal Map, texture che anziché contenere un’immagine utilizzano i valori RGB come vettori normali. Potendo variare le direzioni in ogni pixel dell’oggetto si potranno simulare illuminazione di dettagli a cui non corrisponde alcun triangolo.
In questa immagine si può vedere come un cubo (fatto di soli 12 triangoli) possa sembrare un poligono ben più complesso quando illuminato utilizzando una normal map.
Una texture tuttavia può essere utilizzata in più punti di un modello, occorre quindi variare il modo con cui calcolare le luci facendo in modo che la posizione delle luci sia riferita sempre alla faccia da illuminare e non al modello. Questo si chiama spazio tangente.
Spazio Tangente
Lo spazio tangente è uno dei tanti possibili spazi su cu possono essere trasformati i vettori. Lo spazio tangente ruota i vettori rispetto alla posizione del triangolo che sarà il centro. Lo spazio tangente viene usato per le normal map. Nelle normal map infatti è il pixel shader a calcolare la luce ma lo fa utilizzando come normale il colore della normal map caricata. Il colore estratto dalla texture RGB viene convertito in XYZ semplicemente con
Float3 N= colore.rgb*2-1
La necessità è quella di ruotare la direzione della luce in modo che sia corretta rispetto alla texture che non è possibile far ruotare. La stessa normal map è identica indipendentemente da dove è posizionata nel modello e quindi la soluzione ottimale è quella di ruotare la direzione della luce o della telecamera in modo che abbia l’angolo corretto rispetto alla texture che è in posizione base.
In questa immagine è possibile vedere oltre al vettore normale (N) anche altri due vettori, quello tangente (T) che come dice la parola è tangente al triangolo e la binormale (B) anch’esso tangente ma che forma 90° con gli altri 2.
I 3 vettori vengono posizionati a formare una matrice 3x3. Un vettore moltiplicato per questa matrice sarà il vettore in tangent space.
Ora la luce è trasformata secondo lo spazio tangente. Avendo N estratta dalla texture è possibile utilizzare tutte le formule di illuminazione senza problemi. Eventuali altri vettori andranno trasformati nello stesso modo.
I vettori Tangente e Binormali vanno calcolati in fase di generazione del modello ed inseriti nel Vertex Buffer. Per generare i due vettori tangente e binormale si usano diversi algoritmi. Il più comune è solitamente il seguente
Vector3D[] tan1 = new Vector3D[vertexCount * 2];
Vector3D[] tan2 = tan1 + vertexCount;
for (long a = 0; a < triangleCount; a++)
{
long i1 = triangle.index[0];
long i2 = triangle.index[1];
long i3 = triangle.index[2];
Point3D v1 = position[i1];
Point3D v2 = position [i2];
Point3D v3 = position [i3];
Point2D w1 = texcoord[i1];
Point2D w2 = texcoord[i2];
Point2D w3 = texcoord[i3];
float x1 = v2.x - v1.x;
float x2 = v3.x - v1.x;
float y1 = v2.y - v1.y;
float y2 = v3.y - v1.y;
float z1 = v2.z - v1.z;
float z2 = v3.z - v1.z;
float s1 = w2.x - w1.x;
float s2 = w3.x - w1.x;
float t1 = w2.y - w1.y;
float t2 = w3.y - w1.y;
float r = 1.0F / (s1 * t2 - s2 * t1);
Vector3D sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
Vector3D tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);
tan1[i1] += sdir;
tan1[i2] += sdir;
tan1[i3] += sdir;
tan2[i1] += tdir;
tan2[i2] += tdir;
tan2[i3] += tdir;
triangle++;
}
for (long a = 0; a < vertexCount; a++)
{
Vector3D n = normal[a];
Vector3D t = tan1[a];
tangent[a] = (t - n * Dot(n, t)).Normalize();
tangent[a].w = (Dot(Cross(n, t), tan2[a]) < 0.0F) ? -1.0F : 1.0F;
binormal[a]=cross(n,tangent[a]);
}
Vi rimando al tutorial numero 9 della serie (Github o in fondo dal primo tutorial)