Boneanimation per Vertexshader: Unterschied zwischen den Versionen
Oc2k1 (Diskussion | Beiträge) (→Kompression der Matrizen) |
Oc2k1 (Diskussion | Beiträge) (→Code) |
||
Zeile 72: | Zeile 72: | ||
==Code== | ==Code== | ||
− | Einfacher Vertexshader für Bones (ca | + | Einfacher Vertexshader für Bones (ca 48 Instruktions): |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<cpp> | <cpp> | ||
+ | #version 120 | ||
attribute vec4 weight; | attribute vec4 weight; | ||
− | attribute | + | attribute vec4 index; |
attribute vec3 tangent; | attribute vec3 tangent; | ||
Zeile 144: | Zeile 84: | ||
void main(void){ | void main(void){ | ||
+ | |||
mat4 mat= mat4(0.0, 0.0, 0.0, 0.0, | mat4 mat= mat4(0.0, 0.0, 0.0, 0.0, | ||
0.0, 0.0, 0.0, 0.0, | 0.0, 0.0, 0.0, 0.0, | ||
0.0, 0.0, 0.0, 0.0, | 0.0, 0.0, 0.0, 0.0, | ||
0.0, 0.0, 0.0, 1.0); | 0.0, 0.0, 0.0, 1.0); | ||
+ | |||
+ | vec4 temp1 = vec4(0.0, 0.0, 0.0, 0.0); | ||
+ | vec4 temp2 = vec4(0.0, 0.0, 0.0, 0.0); | ||
for (int i = 0; i<4; i++){ | for (int i = 0; i<4; i++){ | ||
− | + | temp1 += weight[i] * bones[int(index[i]*2.0)]; | |
− | + | temp2 += weight[i] * bones[int(index[i]*2.0+1.0)]; | |
+ | |||
} | } | ||
+ | //Matrix decompression | ||
+ | mat[0].xyz = temp1.xyz; | ||
+ | mat[1].xyz = vec3(-temp1.y, temp1.w, temp2.w); | ||
+ | mat[2].xyz = cross (mat[0].xyz,mat[1].xyz); | ||
+ | mat[3].xyz = temp2.xyz; | ||
− | + | gl_Position = gl_ModelViewProjectionMatrix * (mat * gl_Vertex); | |
− | |||
− | |||
− | + | //Untested TBN Code | |
− | N = gl_NormalMatrix * (mat3(mat) * gl_Normal); | + | N = gl_NormalMatrix * (mat3(mat) * gl_Normal); |
− | T = gl_NormalMatrix * (mat3(mat) * vec3(tangent)); | + | T = gl_NormalMatrix * (mat3(mat) * vec3(tangent)); |
− | B = cross (N | + | B = cross (T,N); |
gl_TexCoord[0] = gl_MultiTexCoord0; | gl_TexCoord[0] = gl_MultiTexCoord0; | ||
Zeile 167: | Zeile 115: | ||
} | } | ||
</cpp> | </cpp> | ||
− | + | Da code für die TBN Matrix kann bei bedarf teilweise gelöscht werden. Nur mit Normal werden etwa 38 Instruktions benötig und komplet ohne werden nur noch 32 instruktions benötigt. |
Version vom 27. September 2006, 02:01 Uhr
Bitte haben Sie etwas Geduld und nehmen Sie keine Änderungen vor, bis der Artikel hochgeladen wurde. |
Inhaltsverzeichnis
Vorwort
Dieser Artikel richted 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. 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 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 Gelenk verbunden sind sollten umbedingt vermiedern 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 Uniformvariablend des Typs Float4. Für einen Bone werden 3 Float4 benötigt. Damit lassen sich etwa 80 Bones gleichzeitig verwenden. Wird dies schon beim Entwurf des Modells beachtet, stellt diese Wert kaum ein Limit dar. Bei einem symetrischem Modell lässt sich dieser Wert fast verdoppeln. Wenn die nicht ausreicht macht es Sinn Objekte mit vielen Bones wie Hände oder Gesichter getrennt zu Rendern. Vorausgesetzt werden sollte eine Grafikkarte, die wenigstends das Shadermodel 2.0 unterstützt. Besser ist jedoch eine Grafikkarte, die Shadermodel3.0 unterstützt, damit der Code durch dynamisches Branching teilweise übersprungen werden kann.
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. Sollten die Atributevariablen knapp werden ist es sinvoll die Gewichtung mit 0.999 zu Multiplizieren und die Indexnummer dazuzuaddieren. Solang dies nicht nötig ist koste es jedoch nur unötig Performance beim trennen.
In den Meisten Fällen beinflussen nur zwei Bones einen Vertex. Ein Knie sieht etwas seltsam aus wenn am Gelenk linear interpoliert wird. Hier lohnt sich der Aufwand die Gelenkposition zu speichern und den Abstand nach der Interpolation zu korrigieren.
Gewichten der Bones
Gerade im Vertexshader muss man genau überlegen, wo man Multiplikationen oder höhere Operationen einsparen kann prinzipell gibt es zwei Möglichkeiten. Die erste ist, dass die Matrizen anhand Ihrer Gewichtungen zu einer durchschnittlichen Matrix zusammenzurechnet werden. Pro Matrix werden 12 Multiplikationen und 12 Additionen benötigt, von denen je 3 aus Symetriegründen Wegrationalisiert werden können. Bei der zweiten Variante werden erst die Vektoren mit den Matrizen multipliziert und anschließend gewichtet. Jedoch werden dann pro Matrix * Vektor Multiplikation 12 Multiplikationen gebraucht. Da aber neben dem Vertex auch noch der Normal und gegebenfals Tangentvektoren Multipliziert werden müssen, ist der Aufwand der ersten Variante deutlich geringer.
Quaternionen im Vertexshader
Der Sinn von Quaternionen im Vertexshader ist sehr fraglich. Sie wären möglich, jedoch langsamer, lediglicht die Multiplikation von Quaternionen mit einem zweitem Quartenion ist deulich schneller als eine Matrix * Matrixmultiplikation. Gerade diese Operationen werden sollten jedoch vor dem Vertexshader durchgeführt werden, da sie die Form des ganzen Skelletes beeinflussen und sich über das Frame nicht ändern. Da mit bleibt nur noch ein einziger Vorteil: Es werden nur noch 2 Flot4 Vektoren zum Speichern eines Bones benötigt. Der Preis sind allerdings 27 Multiplikationen und 18 Additionen für die Umwandlung.
Kompression der Matrizen
Eine vollständige Transformationsmatrix benötigt 16 Komponenten. 4 können direkt entfallen, da uns die W Komponente der Vektoren nicht interresiert. Der Rotationsteil der Matrix ist jedoch immernoch sehr redundant. Die 3 Sinuskomponenten sind mit umgekehrtem Vorzeichen doppelt verhanden. Es bleiben noch 9 Komponenten über, die sich dummerweise nicht auf zwei float4 Vektoren aufteilen lassen. Es wäre möglich die Cosinuswerte mit Hilfe des Pytagoras neu zu berechnen, allerdings geht hierbei das Vorzeichen verloren, so dass keine Rotationen von mehr als 90 Grad mehr möglich wären.
Sinvoller ist es einen Vektor aus dem Rotationsteil mit Hilfe eines Kreutzproduktes neu zu berechnen. Da bei reinen Rotations/Skalierungsmatrizen Die SUmmer von rot01 und rot10 Null ergibt, kann einer von beiden den den dritten Wert des Translationsteil aufnehmen.
Da nun nur noch 8 Werte gemittel werden müssen und der Aufwand für die Rekonstruktion sehr einfach ist, kann hier sowohl Platz in den Uniforvariablen als auch Rechenleistung eingespart werden. Wichtig ist, das beachtet wird, das Opengl transponierte Matrizen verwendet.
Normale Transformationsmatrix:
|
Gepackte Transformationsmatrix:
|
Code
Einfacher Vertexshader für Bones (ca 48 Instruktions):
#version 120 attribute vec4 weight; attribute vec4 index; attribute vec3 tangent; uniform vec4 bones[2*32]; varying vec3 T,B,N; void main(void){ mat4 mat= mat4(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); vec4 temp1 = vec4(0.0, 0.0, 0.0, 0.0); vec4 temp2 = vec4(0.0, 0.0, 0.0, 0.0); for (int i = 0; i<4; i++){ temp1 += weight[i] * bones[int(index[i]*2.0)]; temp2 += weight[i] * bones[int(index[i]*2.0+1.0)]; } //Matrix decompression mat[0].xyz = temp1.xyz; mat[1].xyz = vec3(-temp1.y, temp1.w, temp2.w); mat[2].xyz = cross (mat[0].xyz,mat[1].xyz); mat[3].xyz = temp2.xyz; gl_Position = gl_ModelViewProjectionMatrix * (mat * gl_Vertex); //Untested TBN Code N = gl_NormalMatrix * (mat3(mat) * gl_Normal); T = gl_NormalMatrix * (mat3(mat) * vec3(tangent)); B = cross (T,N); gl_TexCoord[0] = gl_MultiTexCoord0; gl_TexCoord[1] = gl_MultiTexCoord1; }
Da code für die TBN Matrix kann bei bedarf teilweise gelöscht werden. Nur mit Normal werden etwa 38 Instruktions benötig und komplet ohne werden nur noch 32 instruktions benötigt.