TBN Matrix: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
(Wie kann die TBN Matrix berechnet werden)
(Missverständnis Orthogonalität: Inhaltlich falschen Absatz entfernt und durch neuen ersetzt)
 
(26 dazwischenliegende Versionen von 4 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
{{Offline}}
+
==Was ist die TBN Matrix==
 +
 
 +
Die TBN-Matrix ist nach ihren Komponenten benannt, den Vektoren '''T'''angent, '''B'''itangent (auch Binormal) und '''N'''ormal. Sie ist in der Lage, Vektoren aus dem Texturspace in den Worldspace zu transformieren.
 +
Sie wird für alle Formen des [[Bumpmapping]]s im Fragmentshader benötigt, um die Normal- und Höhenmaps vom Texturespace in den Worldspace zu transformieren. In den meisten Fällen tritt sie in normalisierter Form auf und entspricht einer reinen Drehmatrix.
 +
 
 +
==Wie kann die TBN Matrix berechnet werden==
 +
 
 +
Da die einzigen Erklärungen, die mir bis jetzt bekannt sind, auf Englisch und noch dazu mit sehr unverständlichen Formeln gewürzt sind, die auch noch unaussprechliche Zeichen enthalten, versuche ich das Ganze mal so zu beschreiben, dass es auch von normalen Programmierern verstanden wird. 
 +
 
 +
[[Bild:triangle_im_texturspace.png]]
 +
 
 +
Bekannt sind zum Berechnen der TBN Matrix nur die Textur- und Weltkoordinaten des Dreiecks ABC, welches oben abgebildet ist. Die beiden blauen Vektoren, die auf das graue Kreuz gezeichnet sind, spannen zusammen mit der grauen gestrichelten Linie die Textur auf. Der horizontale Vektor repräsentiert zugleich die U-Achse der Textur, als auch den Tangentvektor. Der vertikale entspricht der V-Achse und Bitangent.
 +
Im Texturespace sieht es jetzt sehr leicht aus, das Problem ist jedoch, dass wir die TBN Matrix aus der Sicht des Worldspaces beschreiben müssen. Die einzigen Punkte, die wir aus dem Worldspace kennen, sind jedoch nur A,B und C.
 +
 
 +
Die Berechnung von Tangent und Bitangent ist fast gleich, da nur andere Komponenten eingesetzt werden müssen. Erst einmal nur Tangent:
 +
 
 +
Da unsere Vektoren 5 Komponenten haben: xyzuv, werden einzelne Komponenten durch u oder v markiert. Für die TBN Matrix an sich brauchen nur xyz berechnet zu werden.
 +
 
 +
Der Tangent entspricht dem Vektor (F-A), Da wir ihn nicht direkt kennen, müssen wir erst (E-A) berechnen. Um den Punkt E zu bekommen, muss der Vektor (C-B) so weit verlängert werden, dass er (E-B) ergibt. Um diesen Verlängerungsfaktor zu berechnen, nehmen wir die V-Komponenten der Texturkoordinaten zu Hilfe:
 +
 
 +
Da Av = Dv ist, muss (D-C)*(Cv-Bv) = (B-C)*(Cv-Av) sein. Das lösen wir nach D auf:
 +
 
 +
D = C + (B-C)*((Cv-Av)/(Cv-Bv))
  
 +
Da der Vektor (D-A) kleiner als 1.0 (im Texturespace!!!) ist, müssen wir ihn noch durch Teilen von (Du-Au) auf die richtige Länge bringen:
  
Warnung: Alles noch nicht überprüft
+
D = C + (B-C)*((Cv-Av)/(Cv-Bv))
 +
Tangent = (D-A)/(Du-Au)
  
==Was ist die TBN Matrix==
+
Die Bitangente lässt sich berechnen, indem B;C, D;E, F;G und u;v getauscht werden:
  
Die TBN Matrix ist nach ihren Komponenten benannt, den Vektoren Normal, Bitangent (auch Binormal) und Tangent. Sie ist in der Lage Vektoren aus dem Worldspace in den Texturspace zu transformieren.
+
E = B + (C-B)*((Bu-Au)/(Bu-Cu))
 +
Bitangent = (E-A)/(Ev-Av)
  
==Wie kann die TBN Matrix berechnet werden==
+
Alternativ lässt sich für normalisierte TBN-Matrizen Folgendes schreiben:
  
Da die einzigsten Erklärungen, die mir bis jetzt bekannt sind auch Englisch sind und dazu mit sehr unverständlichen Formeln gewürzt sind, versuch ich das ganze mal so zu beschreiben, dass es auch von normalen Programieren verstanden wird.  
+
  Tangent  = normalize(C - A + (B-C)*((Cv-Av)/(Cv-Bv)))
 +
Bitangent = normalize(B - A + (C-B)*((Bu-Au)/(Bu-Cu)))
  
[[Bild:triangle_im_texturspace.png]]
+
Dabei sollte beachtet werden, dass die Längenberechnung nicht so schnell ist wie die alternative Subtraktion.
  
Bekannt sind zum Berechnen der TBN Matrix nur die Textur und Weltkoordinaten des Dreiecks ABC, welches oben abgebildet ist. Die beiden blauen Vektoren, die auf das graue Kreutz gezeichnet sind spannen zusammen mit der grauen gestrichelten Linie die Textur auf. Der Horizontale Vektor repäsentiert Zugleich die U Achse der Textur, als auch den Tangentvektor. Der Vertikale entspricht der V Achse und Bitangent.
+
Es könnte sein, dass (C-B) parallel zu Tangent oder Bitangent ausgerichtet ist. Die Division durch 0 sollte man unbedingt abfangen.
Im Texturspace sieht es jetzt sehr leicht aus, das Problem ist jedoch, dass wir die TBN Matrix aus der Sicht des Worldspaces beschreiben müssen. Die einzigen Punkte, die wir aus dem Worldspace kennen sind jedoch nur AB und C.
 
  
Die Berechnung von Tangent und Bitangent ist fast gleich da nur andere Komponeten eingesetz werden müssen. Erst einmal nur Tangent:
+
Die am einfachsten zu berechnende Komponente ist der Normalvektor, er ist quasi unabhängig von den Texturkoordinaten. Es gibt zwei Möglichkeiten ihn zu berechnen. Entweder das normalisierte Kreuzprodukt der Vektoren (C-A)x(B-A) oder das Kreuzprodukt von Tangent x Bitangent.
  
Da unsere Vektoren 5 Komponenten haben xyzuv, werden einzelne komponenten durch ().u oder ().v makiert. Für die TBN Matrix an sich brauchen nur ().xyz berechnet werden.
+
==Interpolation==
  
Der Tangend entspricht dem Vektor (G-A), Da wir ih nicht direkt kennen müssen wir erst (E-A) berechnen. Um den Punkt E zu bekommen muss der Vektor (C-B) so weit verlänger werden, das er (E-B) ergibt. Um diesen Verlängerungsfakor zu Berechnen müssen wir die V Komonenten der Texturkoordinaten zu hilfe nehmen:
+
Wie Normalvektoren können auch die TBN Matrizen nur pro Triangle berechnet werden. Um runde Oberflächen zu erhalten, kann es sinnvoll sein, die TBN Matrizen am gemittelten Normalvektor auszurichten. Die die Ausrichtung von Tangent und Bitangent von den Texturkoordinaten abhängt, ist es nicht möglich, sie wie den Normalvektor zu interpolieren.
  
Da Av = Ev ist.  
+
Um die TBN Matrizen zu drehen, sollte die Drehachse mit dem Kreuzprodukt des nicht interpolierten Normalvektors und des interpolierten Normalvektors gebildet werden. Den Winkel kann man über das Skalarprodukt berechnen. Abschließend müssen Tangent und Bitangent nur noch um die Drehachse mit dem errechneten Winkel rotiert werden. Für Solid (nicht smooth) gekennzeichnete Flächen kann dieser Schritt komplett entfallen.
  
Muss (E-B)*(B-C).v = (C-B)*(B-A).v sein. Das lösen wir Nach E auf:
+
==Missverständnis Orthogonalität==
  
E = B + (C-B)*((B-A)/(B-C)).v
+
Auch wenn es sich bei der TBN-Matrix um eine Rotationsmatrix handelt, ist sie doch mehr als nur eine Drehung im Raum. Daher lässt sie sich auch '''nicht''' durch ein [[Quaternion]] ersetzen. Es ist außerdem '''nicht''' erlaubt, nur zwei der drei Vektoren zu speichern, um Speicherplatz zu sparen. Denn der dritte Vektor lässt sich nicht durch das Kreuzprodukt der anderen beiden rekonstruieren.
  
Da der Vektor (E-A) kleiner als 1.0 (im Texturspace!!!) ist. Müssen wir ihn noch durch Teilen von (E-A).u auf die Richtige Länge bringen:
+
Der Grund dafür ist, dass die drei Spaltenvektoren der TBN-Matrix nicht zwangsweise (sogar sehr häufig nicht) senkrecht aufeinander stehen. Dies ist beim Normalenvektor am offensichtlichsten: Er wird pro Vertex gespeichert und über die ganze Fläche interpoliert. Doch nicht alle drei Vertices eines Dreiecks müssen den gleichen Normalenvektor haben. Tatsächlich versucht man in der Praxis sogar absichtlich, Kurven runder aussehen zu lassen als sie sind, indem man 3 verschiedene Normalenvektoren über das Dreieck interpoliert (und damit auch das Licht).
  
E = B + (C-B)*((B-A)/(B-C)).v
+
Aber auch Tangente und Bitangente müssen nicht senkrecht zueinander sein. Zur Erinnerung: Die Tangente zeigt in Richtung der U-(Textur)Koordinate und die Bitangente i.R. der V-Achse. Niemand zwingt den Model-Designer dazu, die Texturen immer unverzerrt auf die Meshes zu kleben. Es gibt also keine Garantie, dass irgendeiner der drei Vektoren senkrecht auf irgendeinem anderen steht.
  
Tangent = (E-A)/(E-A).u
+
==Berechnung im Geometry Shader==
  
Bitantent lässt sich berechnen, in dem B;C, D;E, F;G und u;v getauscht werden:
+
Der einfachste Weg, im Shader an Tangente und Bitangente zu kommen, dürfte die Berechnung im [[Geometry Shader]] sein. Hier ist eine Implementierung des oben erklärten Verfahrens in [[GLSL]]:
 +
<source lang=glsl>#version 330
 +
layout(triangles) in;
 +
layout(triangle_strip, max_vertices=3) out;
  
D = C + (B-C)*((C-A)/C-B)).u
+
// Variablen, die vom Vertex- an den Geometry-Shader weitergereicht werden:
 +
in vec3 vg_N[];
 +
in vec3 vg_Pos[];
 +
in vec2 vg_TexCoord[];
  
Bitangent = (D-A)/(D-A).v 
+
// Variablen, die vom Geometry- an den Fragment-Shader weitergereicht werden:
 +
out vec3 gf_T;
 +
out vec3 gf_B;
 +
out vec3 gf_N;
 +
out vec3 gf_Pos;
 +
out vec2 gf_TexCoord;
  
 +
vec3 GetTangent(vec3 A, vec3 B, vec3 C,  vec2 Auv, vec2 Buv, vec2 Cuv)
 +
{
 +
  float Bv_Cv = Buv.y - Cuv.y;
 +
  if(Bv_Cv == 0.0)
 +
    return (B-C)/(Buv.x-Cuv.x);
 +
 
 +
  float Quotient = (Auv.y - Cuv.y)/(Bv_Cv);
 +
  vec3 D  = C  + (B  -C)  * Quotient;
 +
  vec2 Duv = Cuv + (Buv-Cuv) * Quotient;
 +
  return (D-A)/(Duv.x - Auv.x);
 +
}
 +
vec3 GetBitangent(vec3 A, vec3 B, vec3 C,  vec2 Auv, vec2 Buv, vec2 Cuv)
 +
{
 +
  return GetTangent(A, C, B,  Auv.yx, Cuv.yx, Buv.yx);
 +
}
  
Die am einfachsten zu berechnende Komponente ist der Normalvektor, er ist quasi unabhängig von den Texturkoordinaten. Es gibt zwei Möglichkeiten ihn zu berechnen. Entweder das Kreutzprodukt der Verktoren (C-A)x(B-A) oder das Kreutzprodukt von Tangent x Bitangent.
+
void main(void)
Wären der Normalvektor bei der Lichtberechnung einfach normalisiert wurde, ist dass bei der TBN Matrix nur sinvoll, wenn alle drei Komponenten normalisiert werden. In diesem Fall ist die Matrix aber nur noch eingeschränkt zu verwendbar. Wenn die Matrix nur zum Rotieren von Normalvektoren einer Normalmap benöigt wird, ist es sogar Erwünscht, da sich dann deren Länge nicht ändert.
+
{
Sobald aber eine Textur 3Dimensional gemappt wird. Sollte die TBN Matrix die Weltkoordinaten korrekt auf den Texturspace abbilden. Dies gilt aber hauptsächlich nur für Paralax und Reliefmapping.
+
  vec3 T = GetTangent(vg_Pos[0], vg_Pos[1], vg_Pos[2],
 +
                      vg_TexCoord[0], vg_TexCoord[1], vg_TexCoord[2]);
 +
  vec3 B = GetBitangent(vg_Pos[0], vg_Pos[1], vg_Pos[2],
 +
                        vg_TexCoord[0], vg_TexCoord[1], vg_TexCoord[2]);
 +
 
 +
  for(int i=0; i<3; ++i) {
 +
    gf_T = T;
 +
    gf_B = B;
 +
    gf_TexCoord = vg_TexCoord[i];
 +
    gf_N = vg_N[i];
 +
    gf_Pos = vg_Pos[i];
 +
    gl_Position = gl_in[i].gl_Position;
 +
    EmitVertex();
 +
  }
 +
}</source>
 +
Es gibt andere Methoden, im Fragment Shader an Tangente und Bitangente zu kommen. Dazu gibt es im Forum eine [http://www.delphigl.com/forum/viewtopic.php?f=2&t=11082 Diskussion].

Aktuelle Version vom 17. Januar 2014, 17:54 Uhr

Was ist die TBN Matrix

Die TBN-Matrix ist nach ihren Komponenten benannt, den Vektoren Tangent, Bitangent (auch Binormal) und Normal. Sie ist in der Lage, Vektoren aus dem Texturspace in den Worldspace zu transformieren. Sie wird für alle Formen des Bumpmappings im Fragmentshader benötigt, um die Normal- und Höhenmaps vom Texturespace in den Worldspace zu transformieren. In den meisten Fällen tritt sie in normalisierter Form auf und entspricht einer reinen Drehmatrix.

Wie kann die TBN Matrix berechnet werden

Da die einzigen Erklärungen, die mir bis jetzt bekannt sind, auf Englisch und noch dazu mit sehr unverständlichen Formeln gewürzt sind, die auch noch unaussprechliche Zeichen enthalten, versuche ich das Ganze mal so zu beschreiben, dass es auch von normalen Programmierern verstanden wird.

triangle im texturspace.png

Bekannt sind zum Berechnen der TBN Matrix nur die Textur- und Weltkoordinaten des Dreiecks ABC, welches oben abgebildet ist. Die beiden blauen Vektoren, die auf das graue Kreuz gezeichnet sind, spannen zusammen mit der grauen gestrichelten Linie die Textur auf. Der horizontale Vektor repräsentiert zugleich die U-Achse der Textur, als auch den Tangentvektor. Der vertikale entspricht der V-Achse und Bitangent. Im Texturespace sieht es jetzt sehr leicht aus, das Problem ist jedoch, dass wir die TBN Matrix aus der Sicht des Worldspaces beschreiben müssen. Die einzigen Punkte, die wir aus dem Worldspace kennen, sind jedoch nur A,B und C.

Die Berechnung von Tangent und Bitangent ist fast gleich, da nur andere Komponenten eingesetzt werden müssen. Erst einmal nur Tangent:

Da unsere Vektoren 5 Komponenten haben: xyzuv, werden einzelne Komponenten durch u oder v markiert. Für die TBN Matrix an sich brauchen nur xyz berechnet zu werden.

Der Tangent entspricht dem Vektor (F-A), Da wir ihn nicht direkt kennen, müssen wir erst (E-A) berechnen. Um den Punkt E zu bekommen, muss der Vektor (C-B) so weit verlängert werden, dass er (E-B) ergibt. Um diesen Verlängerungsfaktor zu berechnen, nehmen wir die V-Komponenten der Texturkoordinaten zu Hilfe:

Da Av = Dv ist, muss (D-C)*(Cv-Bv) = (B-C)*(Cv-Av) sein. Das lösen wir nach D auf:

D = C + (B-C)*((Cv-Av)/(Cv-Bv))

Da der Vektor (D-A) kleiner als 1.0 (im Texturespace!!!) ist, müssen wir ihn noch durch Teilen von (Du-Au) auf die richtige Länge bringen:

D = C + (B-C)*((Cv-Av)/(Cv-Bv))
Tangent = (D-A)/(Du-Au)

Die Bitangente lässt sich berechnen, indem B;C, D;E, F;G und u;v getauscht werden:

E = B + (C-B)*((Bu-Au)/(Bu-Cu))
Bitangent = (E-A)/(Ev-Av)  

Alternativ lässt sich für normalisierte TBN-Matrizen Folgendes schreiben:

Tangent   = normalize(C - A + (B-C)*((Cv-Av)/(Cv-Bv)))
Bitangent = normalize(B - A + (C-B)*((Bu-Au)/(Bu-Cu)))

Dabei sollte beachtet werden, dass die Längenberechnung nicht so schnell ist wie die alternative Subtraktion.

Es könnte sein, dass (C-B) parallel zu Tangent oder Bitangent ausgerichtet ist. Die Division durch 0 sollte man unbedingt abfangen.

Die am einfachsten zu berechnende Komponente ist der Normalvektor, er ist quasi unabhängig von den Texturkoordinaten. Es gibt zwei Möglichkeiten ihn zu berechnen. Entweder das normalisierte Kreuzprodukt der Vektoren (C-A)x(B-A) oder das Kreuzprodukt von Tangent x Bitangent.

Interpolation

Wie Normalvektoren können auch die TBN Matrizen nur pro Triangle berechnet werden. Um runde Oberflächen zu erhalten, kann es sinnvoll sein, die TBN Matrizen am gemittelten Normalvektor auszurichten. Die die Ausrichtung von Tangent und Bitangent von den Texturkoordinaten abhängt, ist es nicht möglich, sie wie den Normalvektor zu interpolieren.

Um die TBN Matrizen zu drehen, sollte die Drehachse mit dem Kreuzprodukt des nicht interpolierten Normalvektors und des interpolierten Normalvektors gebildet werden. Den Winkel kann man über das Skalarprodukt berechnen. Abschließend müssen Tangent und Bitangent nur noch um die Drehachse mit dem errechneten Winkel rotiert werden. Für Solid (nicht smooth) gekennzeichnete Flächen kann dieser Schritt komplett entfallen.

Missverständnis Orthogonalität

Auch wenn es sich bei der TBN-Matrix um eine Rotationsmatrix handelt, ist sie doch mehr als nur eine Drehung im Raum. Daher lässt sie sich auch nicht durch ein Quaternion ersetzen. Es ist außerdem nicht erlaubt, nur zwei der drei Vektoren zu speichern, um Speicherplatz zu sparen. Denn der dritte Vektor lässt sich nicht durch das Kreuzprodukt der anderen beiden rekonstruieren.

Der Grund dafür ist, dass die drei Spaltenvektoren der TBN-Matrix nicht zwangsweise (sogar sehr häufig nicht) senkrecht aufeinander stehen. Dies ist beim Normalenvektor am offensichtlichsten: Er wird pro Vertex gespeichert und über die ganze Fläche interpoliert. Doch nicht alle drei Vertices eines Dreiecks müssen den gleichen Normalenvektor haben. Tatsächlich versucht man in der Praxis sogar absichtlich, Kurven runder aussehen zu lassen als sie sind, indem man 3 verschiedene Normalenvektoren über das Dreieck interpoliert (und damit auch das Licht).

Aber auch Tangente und Bitangente müssen nicht senkrecht zueinander sein. Zur Erinnerung: Die Tangente zeigt in Richtung der U-(Textur)Koordinate und die Bitangente i.R. der V-Achse. Niemand zwingt den Model-Designer dazu, die Texturen immer unverzerrt auf die Meshes zu kleben. Es gibt also keine Garantie, dass irgendeiner der drei Vektoren senkrecht auf irgendeinem anderen steht.

Berechnung im Geometry Shader

Der einfachste Weg, im Shader an Tangente und Bitangente zu kommen, dürfte die Berechnung im Geometry Shader sein. Hier ist eine Implementierung des oben erklärten Verfahrens in GLSL:

#version 330
layout(triangles) in;
layout(triangle_strip, max_vertices=3) out;

// Variablen, die vom Vertex- an den Geometry-Shader weitergereicht werden:
in vec3 vg_N[];
in vec3 vg_Pos[];
in vec2 vg_TexCoord[];

// Variablen, die vom Geometry- an den Fragment-Shader weitergereicht werden:
out vec3 gf_T;
out vec3 gf_B;
out vec3 gf_N;
out vec3 gf_Pos;
out vec2 gf_TexCoord;

vec3 GetTangent(vec3 A, vec3 B, vec3 C,  vec2 Auv, vec2 Buv, vec2 Cuv)
{
  float Bv_Cv = Buv.y - Cuv.y;
  if(Bv_Cv == 0.0)
    return (B-C)/(Buv.x-Cuv.x);
  
  float Quotient = (Auv.y - Cuv.y)/(Bv_Cv);
  vec3 D   = C   + (B  -C)   * Quotient;
  vec2 Duv = Cuv + (Buv-Cuv) * Quotient;
  return (D-A)/(Duv.x - Auv.x);
}
vec3 GetBitangent(vec3 A, vec3 B, vec3 C,  vec2 Auv, vec2 Buv, vec2 Cuv)
{
  return GetTangent(A, C, B,  Auv.yx, Cuv.yx, Buv.yx);
}

void main(void)
{
  vec3 T = GetTangent(vg_Pos[0], vg_Pos[1], vg_Pos[2],
                      vg_TexCoord[0], vg_TexCoord[1], vg_TexCoord[2]);
  vec3 B = GetBitangent(vg_Pos[0], vg_Pos[1], vg_Pos[2],
                        vg_TexCoord[0], vg_TexCoord[1], vg_TexCoord[2]);
  
  for(int i=0; i<3; ++i) {
    gf_T = T;
    gf_B = B;
    gf_TexCoord = vg_TexCoord[i];
    gf_N = vg_N[i];
    gf_Pos = vg_Pos[i];
    gl_Position = gl_in[i].gl_Position;
    EmitVertex();
  }
}

Es gibt andere Methoden, im Fragment Shader an Tangente und Bitangente zu kommen. Dazu gibt es im Forum eine Diskussion.