Boneanimation per Vertexshader
Bitte haben Sie etwas Geduld und nehmen Sie keine Änderungen vor, bis der Artikel hochgeladen wurde. |
Inhaltsverzeichnis
Vorwort
Dieser Artikel richtet sich an alle, die schon weit Fortgeschritten sind. Ein sicherer Umgang mit Shadern und Vertexbufferobjekten ist hier Vorrausetzung. Bei der Boneanimation per Vertexshader geht es darum, möglichst viel Arbeit auf die Grafikkarte auszulagern. Jedoch sollten variablen die über das ganze Model gleich bleiben, auf der CPU berechnet werden.
Wer schon immer mal einen hochdetailierten Dinosaurier oder Octobus sein Unwesen treiben lassen will ist hier genau richtig.
Grundlagen
Was sind Bones
Bones entsprechen nicht nur Knochen. Mathematisch gesehen repräsenteriert jeder Bone eine Transformationsmatrix, der einen gewichteten Einfluss auf verschiedene Vertices nehmen kann. Da Bones sehr häufig in einem Animationsskelet zusammengefast sind, werden sie durch Gelenke (Joints) verbunden, die Drehpunkten der Matrizen entsprechen. Sehr häufig werden Bones als Stab oder ähnliches dargestellt, sie können jedoch auch komplexer Formen haben (z.B. das Becken einen Menschen). Bones die durch die durch ein festes unbewegliches Gelenk verbunden sind sollten umbedingt vermieden werden, da sie nur Resurcen kosten ohne einen Nutzen zu bringen. Grundsätzlich sollte alles was sich unabhängig voneinander bewegen können muss durch einen eigenen Bone dargestellt werden. Neben den nachgebildeten Kochen, gehören auch andere bewegliche Modelteile dazu wie Mimik und Augen.
Woher kommen die Animationsdaten?
Die Animationen können entweder aufgezeichnete Daten sein, durch Inverse Kinematik erzeugt werde oder aus einer Physikengine stammen. Bei einfachen Modellen ist es auch möglich diese Daten per Scripsprache zu erzeugen. In diesem Artikel werden die Bones eher als Schnittstelle dienen und Animationsdaten aus Blender übernommen.
Limits
Aktuelle Grafikkarten erlauben 256 bis 1024 Uniformvariablend des Typs Float4. Für einen Bone werden zwei bis vier Float4 vektoren benötigt. Damit lassen sich etwa 80 bis 500 Bones gleichzeitig verwenden. Wird dies schon beim Entwurf des Modells beachtet, stellen diese Werte kaum ein Limit dar. Bei einem symetrischem Modell lässt sich dieser Wert durch Spiegelung fast verdoppeln. Wenn das nicht ausreicht macht es Sinn bei Objekte mit vielen Bones Teile wie Hände oder Gesichter getrennt zu Rendern. Vorausgesetzt werden sollte eine Grafikkarte, die wenigstends das Shadermodel 2.0 unterstützt.
Technik
Modeldaten
Das Vertexbufferobjekt muss zu den sonst verwendeten Daten wie Vertices, Normalen, Texturkoordinaten und gegebenfalls Tangent und Bitangent noch zusätzliche Daten über die Abhängigkeit zu den Bones gespeichert werden.
Im einfachstem Fall kann jedem Vertex nur ein Bone zugewiesen werden. Hier genügt ein einzelner Integer, der als Index dient.
Wenn zwischen meheren Bones interpoliert werden soll, ist als erstes ein Wert nötig nötig, der angibt wie viele Bones einen Einfluss auf den Vertex haben. Dann muss für jeden Bone die Gewichtung als Float und der Index als Int gespeichert werden. In den meisten Fällen sollte der Einfluss von vier Bones ausreichen. Um die größe der Geometriedaten (Attribute) klein zu halten macht es Sinn. Den Index mit der Gewichtung zu addieren. Da die Summe aller gewichungen 1.0 ist, ist das trennen im shader kein problem. Lediglich der Sonderfall der Gewichtung von 1.0, muss auf zwei Gewichtungen zu 0.5 aufgeteilt werden.
In den Meisten Fällen beinflussen nur zwei Bones einen Vertex. Ein Knie sieht etwas seltsam aus wenn am Gelenk linear interpoliert wird. Hier kann ein Quaternion basierendes Animationsystem zu mehr Qualität helfen.
Matrix basierende Boneanimation
Zum gewichten werden pro Matrix werden 4 Vector MADDs (Multiplikation und Addition) benötigt.
attribute vec4 Bones; attribute vec3 Tangent; uniform mat4 Pose[32]; varying vec3 T,B,N; void main(void){ mat4 mat = 0.0; for ( int i = 0; i < 4; i++){ mat += Pose[int(Bones[i])] * fract(Bones[i]); } gl_Position = gl_ModelViewProjectionMatrix * (mat *gl_Vertex); mat3 m3 = mat3(mat[0].xyz, mat[0].xyz, mat[0].xyz); // "mat3(mat)" N = gl_NormalMatrix * (m3 * gl_Normal); //T = gl_NormalMatrix * (m3 * Tangent); //B = cross (T,N); gl_TexCoord[0] = gl_MultiTexCoord0; }
Als Optimierung kann es sinvoll sein die Modelviewmatrix mit den Bonematrizen zu multiplizieren, da so der Vertexshader entlasted werden kann:
gl_Position = gl_ProjectionMatrix * (mat *gl_Vertex); N = m3 * gl_Normal; //T = m3 * Tangent;
Quaternionen im Vertexshader
Lange gab es die diskusion ob Quaternion in shadern überhaupt Sinn machen, bei der Boneanimation im Vertexshader, hat sich herausgestellt, das die Anzahl der benötigten Instruktionen ähnlich ist wie bei der Matrix basierenden Variante. Der entscheidene Vorteil ist das sie für organische strukturen qualitativ besser ist und das nur zwei Uniform Float4 Vektoren pro Joint/Bone verbraucht werden. Etwas problematisch ist, dass die Vertexpositionen nicht mehr absolut zum Modelnullpunkt, sondern relativ zu dem Joint angegeben werden muss um das eine spherische Interpolation durchgeführt werden soll. Die W-komponente wird dabei durch die Indexnummer des Joints ersetzt.
attribute vec4 Bones; attribute vec3 Tangent; uniform vec3 Joints[32]; uniform vec4 Quaternions[32]; varying vec3 T,B,N; vec3 qrot( vec4 q, vec3 v ){ return v + 2.0*cross(q.xyz, cross(q.xyz ,v) + q.w*v); } void main(void){ vec4 quaternion = vec4(0.0, 0.0, 0.0, 0.0); for ( int i = 0; i < 4; i++){ quaternion += Quaternions[Bones[i]] * fract(Bones[i]); } normalize(quaternion); vec4 vert = vec4(qrot(quaternion,gl_Vertex.xyz) + Joints[gl_Vertex.w] ,1.0) gl_Position = gl_ModelViewProjectionMatrix * vert; N = gl_NormalMatrix * qrot(quaternion, gl_Normal); //T = gl_NormalMatrix * qrot(quaternion, Tangent); //B = cross (T,N); gl_TexCoord[0] = gl_MultiTexCoord0; }
Die qrot Funktion rotiert einen Vektor mit hilfe eines Quaternions.
Auch hier kann es sinnvoll sein, die Joints in den Modelviewspace zu transformieren und die Quaternionen mit der Normalmatrix (gegebenfals in ein Quaternion umwandeln) zu rotieren:
gl_Position = gl_ProjectionMatrix * vert; N = qrot(quaternion, gl_Normal); //T = qrot(quaternion, Tangent);
Externe Links
http://lumina.sourceforge.net/?id=27 Beispiel in Lumina. Der mit Lumina mitgelieferte Blenderexporter ist in der Lage die für