Animierte Objekte nach XML exportieren
Nahezu jedes 3D-Spiel benötigt animierte Modelle - der Protagonist sollte beherzt zu seinem Schwert greifen und mit kräftigen Schlägen seinen Gegnern zu Leibe rücken. Ein Kampf ist nur so fesselnd wie die Animationen seine Dynamik abbilden können.
Oder auch der "Niedlichkeitsfaktor": Dieser hängt nicht zuletzt davon ab wie tapsig eine Figur auf dem Bildschirm umherspaziert.
In diesem Artikel möchte ich das Skript vorstellen mit dem ich meine 3D-Modelle aus Blender exportiere.
Es sind ein paar grundlegende Regeln zu beachten die erfüllt sein müssen damit das Model sicher exportiert und verwendet werden kann:
- Das Model sollte ein "normales" sein - das heisst, wenn es zum Beispiel per SubSurf erstellt wurde, muss dieser Modifier vor dem Export angewendet werden ("apply")
- Das Model darf nur Dreiecke verwenden
- Die Armature muss als Modifizierer beim Mesh eingetragen sein. Die Armature kann auch als Parent des Objekts festgelegt werden - dies war in älteren Blender-Versionen die einzige Möglichkeit. Inzwischen gibt es aber über die Modifizierer eine neue und in meinen Augen einfachere Möglichkeit. Daher unterstützt mein Skript auch nur die Zuweisung über den Modifizierer.
- Für jede Bone muss es eine gleichnamige Vertexgruppe geben die die Vertices beinhaltet die durch die Bone bewegt werden sollen.
- Das Model sollte genau eine Textur verwenden - und jeder Fläche des Models sollten Texturkoordinaten zugewiesen sein
- Die Armature darf nur genau eine Root Bone besitzen.
- Es werden (derzeit) keine Materialeigenschaften ausser den Texturen exportiert - das Model sollte also keine speziellen Materialeigenschaften benutzen.
Das Export-Skript
Zuerst definieren wir am Anfang des Skripts wie es heisst, welche Blenderversion es mindestens benötigt und in welchen Menüpunkt im Skript-Menü es eingebunden werden soll. Sobald Blender beim Durchsuchen der Standard-Python Pfade dieses Skript findet, wird es automatisch in das Menü eingehangen und kann dann von dort aufgerufen werden.
# """
# Name: 'Parrot Mesh Exporter'
# Blender: 242a
# Group: 'Export'
# """
Hier definieren wir ein paar Konstanten die wir später benutzen werden um die Events zu erkennen die ausgelöst wurden. Ebenso müssen wir Variablen definieren die die Einstellungen speichern können die den Export beeinflussen.
#
EVENT_EXIT = 90
EVENT_EXPORTIEREN = 91
EVENT_SUCHEN = 92
#
# The settings of this export skript
#
toggleBlendDir = 1
dateiname = "mesh.xml"
status = ""
error = 0
Ab hier werden die Export-Funktionen definiert. Dies sind die Methoden die aus Blender-Objekten XML-Tags machen. Diese Export-Funktionen werden später im Skript, wenn das gesamte Meshobjekt exportiert wird, nacheinander aufgerufen und bilden so die vollständige XML-Datei.
Hier mal als Beispiel das Einpacken einer 4x4-Matrix in XML. Die anderen Methoden sind ähnlich aufgebaut (aber teilweise viel komplexer):
txt = "<mat4>\n" + \
"<e>" + str(mat[0][0])+"</e>" + \
"<e>" + str(mat[1][0])+"</e>" + \
"<e>" + str(mat[2][0])+"</e>" + \
"<e>" + str(mat[3][0])+"</e>\n" + \
"<e>" + str(mat[0][1])+"</e>" + \
"<e>" + str(mat[1][1])+"</e>" + \
"<e>" + str(mat[2][1])+"</e>" + \
"<e>" + str(mat[3][1])+"</e>\n" + \
"<e>" + str(mat[0][2])+"</e>" + \
"<e>" + str(mat[1][2])+"</e>" + \
"<e>" + str(mat[2][2])+"</e>" + \
"<e>" + str(mat[3][2])+"</e>\n" + \
"<e>" + str(mat[0][3])+"</e>" + \
"<e>" + str(mat[1][3])+"</e>" + \
"<e>" + str(mat[2][3])+"</e>" + \
"<e>" + str(mat[3][3])+"</e>\n" + \
"</mat4>\n"
return txt
Mit der folgenden export()-Methode werden alle Daten aus dem Blenderobjekt gesammelt und so strukturiert wie wir dies für den Export benötigen. Anschließend werden die oben definierten Export-Teile nacheinander aufgerufen und die Ausgaben dieser Methoden in die Export-Datei geschrieben.
[...]
elements=[]
textures = []
bones=[]
vgroups=[]
animlist={}
#
# First, collect all the data we need to export. To achieve this
# we loop through the scene and watch out for things we can use.
#
for m in scene.objects:
#
# The Armature contains the bones
#
if m.getType() == "Armature" :
[...Hier werden die Daten der Armatur gelesen und zwischengespeichert...]
#
# The Mesh data contain the object itself
#
elif m.getType() == "Mesh":
[...Und hier werden die Meshdaten eingelesen...]
#
# Read all IPOs and actions
#
actionlist = Blender.Armature.NLA.GetActions()
for actionname in actionlist:
action = actionlist[actionname]
[...Hier werden die Actions eingelesen...]
#
# We now successfully collected all the data - lets write it to the file
#
file.write("<mesh>\n")
t = packTextures(textures)
if t:
file.write(t)
t = packVertices(meshMatrix, mesh.verts)
if t:
file.write(t)
t = packFaces(mesh, mesh.faces, textures)
if t:
file.write(t)
if len(bones) > 0:
t = packBones(bones, arma_mat)
if t:
file.write(t)
t = packVertexgroups(vgroups)
if t:
file.write(t)
t = packActions(animlist, arma_mat)
if t:
file.write(t)
file.write("</mesh>\n")
file.close()
if error==0:
status="OK - export done."
Das XML-Format
Hier ist ein Beispiel einer stark gekürzen XML Datei die von dem oben beschriebenen Skript exportiert wurde:
Das Hauptelement ist das mesh-Element. Darin enthalten sind die Elemente
materials- die Dateinamen der Texturenvertices- die Liste aller Punktefaces- die Liste aller Flächen mitsamt u/v Koordinaten, Textur-Id und Normalenbones- die Hierarchie der Bones und deren Ausrichtungvertexgroups- die Vertexgruppenanimations- die Animationen des Objekts
Materialien
Das material-Tag enthält alle Texturdateinamen die im Objekt verwendet werden. In der Reihenfolge in der die Texturen genannt werden, werden sie auch im face-Tag referenziert. Die erste Textur hat die ID 0.
<material>
<texture>papageitextur.png</texture>
</material>
</materials>
Punkte
Die Liste aller Punkte enthälten jeden Vertex des Objekts. Es werden hier die Punkte beginnend mit dem Index 0 aufsteigend aufgelistet.
<vertex><vec3><x>0.872342944145</x><y>-2.67046904564</y><z>3.1944270134</z></vec3></vertex>
<vertex><vec3><x>1.33608078957</x><y>-4.05989646912</y><z>4.44580411911</z></vec3></vertex>
<vertex><vec3><x>0.871967852116</x><y>-4.12228393555</y><z>4.1648812294</z></vec3></vertex>
<vertex><vec3><x>1.35051763058</x><y>-3.85010480881</y><z>4.86616516113</z></vec3></vertex>
<vertex><vec3><x>8.30130672455</x><y>-0.752014398575</y><z>2.60995078087</z></vec3></vertex>
<vertex><vec3><x>2.52973532677</x><y>-3.27205848694</y><z>4.61983537674</z></vec3></vertex>
</vertices>
Flächen
Jede Fläche des Objekts besteht aus Eckpunkten. Die Eckpunkte werden im Element index als Index in die oben stehende Punktliste definiert. Die Normale des Eckpunkts wird für die Beleuchtung verwendet. Anschließend definieren die Elemente u und v die Texturkoordinaten für diesen Eckpunkt. Für welche Textur diese Koordinaten gelten legt das material-Tag am Ende fest.
<face>
<point>
<index>0</index>
<normal><vec3><x>0.698019325733</x><y>-0.712057888508</y><z>-0.0756553858519</z></vec3></normal>
<uv><u>0.0561043843627</u><v>-0.814952075481</v></uv>
</point>
<point>
<index>7</index>
<normal><vec3><x>0.610644876957</x><y>0.539780855179</y><z>-0.579424440861</z></vec3></normal>
<uv><u>0.0592037215829</u><v>-0.765060245991</v></uv>
</point>
<point>
<index>9</index>
<normal><vec3><x>0.24246956408</x><y>-0.551560997963</y><z>-0.798059046268</z></vec3></normal>
<uv><u>0.0846587046981</u><v>-0.764737188816</v></uv>
</point>
<point>
<index>2</index>
<normal><vec3><x>0.786584079266</x><y>-0.599261462688</y><z>0.148716703057</z></vec3></normal>
<uv><u>0.0677612647414</u><v>-0.819422364235</v></uv>
</point>
<material>0</material>
</face>
</faces>
Bones
Jede Bone hat einen Namen - dieser wird benötigt damit eine Vertexgruppe eindeutig zugeordnet werden kann. Diese muss nämlich - wie auch in Blender - den gleichen Namen wie die Bone tragen für die sie gedacht ist. Über die Nennung der Parent-Bone wird die Hierarchie der Bones aufgebaut. Die 3x3-Matrix legt die Orientierung der Bone im Koordinatensystem der Parent-Bone fest. Die Länge ist die Länge der Bone, in der Richtung der Matrix und ausgehend vom Kopfpunkt (head) - alles im Koordinatensystem der Parent-Bone!
<name>Ruecken</name>
<parent>Root</parent>
<matrix>
<mat3>
<e>0.788795173168</e><e>0.614656090736</e><e>3.30548566296e-008</e>
<e>-0.614656090736</e><e>0.788795173168</e><e>2.47199949399e-008</e>
<e>-1.08792121978e-008</e><e>-3.98163813031e-008</e><e>1.0</e>
</mat3>
</matrix>
<length>2.99397587776</length>
<head><vec3><x>0.231302961707</x><y>0.802716493607</y><z>0.00468468666077</z></vec3></head>
</bone>
<bone>
<name>Root</name>
<matrix>
<mat3>
<e>4.37113989449e-008</e><e>4.37113776286e-008</e><e>1.0</e>
<e>-2.82129974494e-007</e><e>-1.0</e><e>4.37113882867e-008</e>
<e>1.0</e><e>-2.82129974494e-007</e><e>-4.37113882867e-008</e>
</mat3>
</matrix>
<length>1.0</length>
<head><vec3><x>0.0046506440267</x><y>-0.0255188941956</y><z>1.17653369904</z></vec3></head>
</bone>
</bones>
Vertexgroups
Eine Vertexgroup ist eine Liste von Vertex-Indizes denen ein Name gegeben wird. Dieser Name muss exakt dem einer Bone enstprechen. Dann ist gewährleistet, dass eine Bewegung der Bone die in der Vertexgroup genannten Punkte mitbewegt.
<vertexgroup>
<name>Ruecken</name>
<edges>
<edge>132</edge>
<edge>133</edge>
<edge>134</edge>
<edge>135</edge>
<edge>139</edge>
<edge>140</edge>
<edge>141</edge>
<edge>142</edge>
<edge>143</edge>
<edge>144</edge>
<edge>145</edge>
<edge>146</edge>
<edge>147</edge>
<edge>148</edge>
<edge>149</edge>
<edge>222</edge>
</edges>
</vertexgroup>
</vertexgroups>
Animationen
Jede Animation hat einen Namen - dies ist der Name der Action aus Blender. Diese Action hat einen Bereich von Frames in dem sie sich abspielt (from...to). In dieser Action bewegen sich einige Bones auf Kurvenbahnen um die Animation auszuführen. Diese Kurven werden durch Quaternionen beschrieben und deren Werte x,y,z und w werden jeweils durch eine Bezierkurve festgelegt. Es sind also vier Kurven nötig um eine Animation einer Bone zu definieren. Die Bezierkurven sind so definiert, dass es zu jeder Stützstelle zwei Steuerpunkte gibt die links und rechts von der Stützstelle liegen und der Kurve ihre gewünschte Form geben. Details zu Bezierkurven gibts auf Wikipedia.
<name>FluegelAnlegen</name>
<range><from>1.0</from><to>20.0</to></range>
<bone>
<name>LHand</name>
<curve>
<channel>QuatX</channel>
<interpolation>bezier</interpolation>
<point><vec3><x>-6.4178185463</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>1.0</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>8.41781806946</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>12.5821819305</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>20.0</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>27.4178180695</x><y>0.0</y><z>0.0</z></vec3></point>
</curve>
<curve>
<channel>QuatY</channel>
<interpolation>bezier</interpolation>
<point><vec3><x>-6.4178185463</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>1.0</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>8.41781806946</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>12.5821819305</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>20.0</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>27.4178180695</x><y>0.0</y><z>0.0</z></vec3></point>
</curve>
<curve>
<channel>QuatZ</channel>
<interpolation>bezier</interpolation>
<point><vec3><x>-6.4178185463</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>1.0</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>8.41781806946</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>12.5821819305</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>20.0</x><y>0.0</y><z>0.0</z></vec3></point>
<point><vec3><x>27.4178180695</x><y>0.0</y><z>0.0</z></vec3></point>
</curve>
<curve>
<channel>QuatW</channel>
<interpolation>bezier</interpolation>
<point><vec3><x>-6.4178185463</x><y>1.0</y><z>0.0</z></vec3></point>
<point><vec3><x>1.0</x><y>1.0</y><z>0.0</z></vec3></point>
<point><vec3><x>8.41781806946</x><y>1.0</y><z>0.0</z></vec3></point>
<point><vec3><x>12.5821819305</x><y>1.0</y><z>0.0</z></vec3></point>
<point><vec3><x>20.0</x><y>1.0</y><z>0.0</z></vec3></point>
<point><vec3><x>27.4178180695</x><y>1.0</y><z>0.0</z></vec3></point>
</curve>
</bone>
</animation>
Download und Installation
Das vollständige Skript gibt es hier zum download. Damit es funktioniert muss es in das .blender/skripts - Verzeichnis unterhalb des eigenen Homeverzeichnisses kopiert werden.




