Blenderexporter
Bitte haben Sie etwas Geduld und nehmen Sie keine Änderungen vor, bis der Artikel hochgeladen wurde. |
Inhaltsverzeichnis
Vorwort
Hier werde ich einige Codeschnipsel zeigen, mit denen man sich einen eigenen Blenderexporter bauen kann. Der Schwerpunkt liegt dabei darauf, die Daten so vorzubereiten, dass sie direkt als VertexBufferObjekt in die Grafikkarte hochgeladen werden können.
Dieses Tutorial, soll kein festes Format beschreiben. Es ist sowohl möglich die Daten sauber in XML zu kapseln, als auch ganz dirty mal eben ein Includierbares C oder Pascalfile zu erzeugen. Der Compiler wird einen dafür aber mit erheblich längeren compelierungszeiten bestrafen.
Prinzipell bin ich der Meinung, das eine eigene Engine nicht zwangshaft mit einem Universalformat wie Collada verwendet werden muss. Auch beliebte Formate wie 3DS haben gewaltige Nachteile, da sie nicht alle Daten speichern können, die beim Arbeiten mit Shadern benötigt werden. 3DS ist eine gut wahl, solang man nicht viel mehr als Vertices, Normals und UV Coordinaten benötigt. Sobald Bones Vertexgruppen, TBN Matrizen und möglicherweise weitere eigene Daten gespeichert werden müssen gibt es große Probleme.
Aufbau eines Exporters
Jeder Blenderexporter verfügt über einen Header, in dem Daten stehen, wie er in die Menüstruktur eingefügt wird. Anschließen folt der Python code. Näheres dazu steht in den Wikibooks.
#!BPY """ Name: 'DGL Wiki' Blender: 241 Group: 'Export' Tooltip: 'DGL Wiki Exporter' """ import Blender def write(file): out = file(file, 'w') obj = Blender.Object.GetSelected()[0] msh = obj.getData() #hier wird Code eingefügt, der die zu exportierenden Daten in das File schreibt out.close() Blender.Window.FileSelector(write, "Export")
Wichtig ist, das man bei Python die Einrückungen beachtet. Als erstes wird eine Funktion definiert, die vom Fileexportdialog aus aufgerufen wird. Dabei wird der Filename als Argument übergeben. Die folgenden Zeilen öffnen ein File zum schreiben und hohlen sich die Meshdaten des erstem selektiertem Objektes. Starten tut das Script eigendlich erst in der letzten Zeile, die den Exportdialog aufruft.
Exportieren der Daten
hier beschreiben ich wie man die wichtigsten Daten exportieren kann. Um ein anderes Format zu erhalten, müssen die Stings entsprechend angepasst werden. Eine neue Zeile erhält man durch "\n" In den Beispielen werde ich mich weitgehend an Werten gekapselt in einem XMLformat halten. Durch simple Modifikationen sind auch andere Formate kein problem.
Eine besonder Problematik ist, dass Blender Quads beforzugt, jedoch Quads und Triangles gemischt vorliegen können. Um diese Mischung zu vermeiden sollte man entweder den Exporter so schreiben, dass er die Quads in 3 Triangles zerlegt oder vor dem Exportieren alle Quads in Triangles umwandelt und Speichert (Intern bleiben die Daten sonst anscheined immer noch Quads)
Einfachen Text, der keine zu exportierenden Daten enhält lässt sich so in die Datei schreiben. Hier ein möglicher begin der XML Datei:
out.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<vbo>\n')
Die letzte Zeile sollte das VBO tag wieder schließen:
out.write('<vbo>\n')
Wichtig ist hier wieder die Einrückung. Sie muss genauso weit eingerück sein wie die vorige Zeile, oder weiter eingerück werden wenn es sich um eine Schleife handelt.
Eine kleines Problem kann entstehen, wenn die letzte Zeile eines Datensatzes kein Komma enthalten darf. Der einfachste Weg ist eine Dummyzeile mit Nullwerten anzuhängen.
Name des Objektes
In einem XML format ist es nicht alzuwichtig den Objektnamen aus Blender zu behalten. Jedoch kann es sehr nützlich sein wenn man andere Formate schreibt oder mehrere Objekte in einer Datei speichert:
out.write('<name>%s</name>\n' % (mesh.name))
Anzahl der Dreiecke
Wenn nur Dreiecke vorhanden sind kann man so die Anzahl der Dreiecke speichern:
out.write('<triangles>%i</triangles>\n' % (len(mesh.faces)))
Mit folgendem Code kann man berücksichtigen, wie viele Triangles entstehen wenn die Quads gesplittet werden:
count = 0 for face in msh.faces: count += len (face.v)-2 out.write('<triangles>%i</triangles>\n' % (count)))
Vertices
Da man den Index eines Vertexarray nicht in einem VBO verpacken kann. Ist es besser die Triangles nicht indiziert in einem VBO zu speichern. Der Speicherbedarf ist hier Allerdings höher. Trotz der erhöten Datenmenge scheint diese Variante schneller zu sein, da hier keine Array vom Prozessor abgearbeitet werden müssen. Unter der Annahme, das nur Triangles vorhanden sind reicht dieser Code:
out.write('<vertices>\n') for face in msh.faces: for vert in face.v:
out.write( ' %f %f %f' % (msh.verts[vert.index].co.x, msh.verts[vert.index].co.y, msh.verts[vert.index].co.z) )
out.write('\n') out.write('</vertices>\n')
Komplizierter wird es wenn möglicher weise auch Quads vorhanden sind. Es gibt zwei Möglichkeiten sie zu splitten. Die bessere Möglichkeit ist, die kürzere Diagolnale zu suchen und den Quad dor in zwei Triangles zu splitten:
out.write('<vertices>\n') for face in mesh.faces: #Ein einfaches Triangle if (len(face.v) == 3) for vert in face.v: out.write( ' %f %f %f' % (msh.verts[vert.index].co.x, msh.verts[vert.index].co.y, msh.verts[vert.index].co.z)) out.write('\n') #Quads else #ermitteln der kürzeren diagonale durch pytagoras ohne Wurzel d1 =(msh.verts[face.v[0]].co.x - msh.verts[face.v[2]].co.x) ** 2 d1+=(msh.verts[face.v[0]].co.y - msh.verts[face.v[2]].co.y) ** 2 d1+=(msh.verts[face.v[0]].co.z - msh.verts[face.v[2]].co.z) ** 2 d2 =(msh.verts[face.v[1]].co.x - msh.verts[face.v[3]].co.x) ** 2 d2+=(msh.verts[face.v[1]].co.y - msh.verts[face.v[3]].co.y) ** 2 d2+=(msh.verts[face.v[1]].co.z - msh.verts[face.v[3]].co.z) ** 2 if (d1<d2) out.write( ' %f %f %f' % (msh.verts[face.v[0]].co.x, msh.verts[face.v[0]].co.y, msh.verts[face.v[0]].co.z)) out.write( ' %f %f %f' % (msh.verts[face.v[1]].co.x, msh.verts[face.v[1]].co.y, msh.verts[face.v[1]].co.z)) out.write( ' %f %f %f' % (msh.verts[face.v[2]].co.x, msh.verts[face.v[2]].co.y, msh.verts[face.v[2]].co.z)) out.write('\n') out.write( ' %f %f %f' % (msh.verts[face.v[0]].co.x, msh.verts[face.v[0]].co.y, msh.verts[face.v[0]].co.z)) out.write( ' %f %f %f' % (msh.verts[face.v[2]].co.x, msh.verts[face.v[2]].co.y, msh.verts[face.v[2]].co.z)) out.write( ' %f %f %f' % (msh.verts[face.v[3]].co.x, msh.verts[face.v[3]].co.y, msh.verts[face.v[3]].co.z)) out.write('\n') else out.write( ' %f %f %f' % (msh.verts[face.v[0]].co.x, msh.verts[face.v[0]].co.y, msh.verts[face.v[0]].co.z)) out.write( ' %f %f %f' % (msh.verts[face.v[1]].co.x, msh.verts[face.v[1]].co.y, msh.verts[face.v[1]].co.z)) out.write( ' %f %f %f' % (msh.verts[face.v[3]].co.x, msh.verts[face.v[3]].co.y, msh.verts[face.v[3]].co.z)) out.write('\n') out.write( ' %f %f %f' % (msh.verts[face.v[1]].co.x, msh.verts[face.v[1]].co.y, msh.verts[face.v[1]].co.z)) out.write( ' %f %f %f' % (msh.verts[face.v[2]].co.x, msh.verts[face.v[2]].co.y, msh.verts[face.v[2]].co.z)) out.write( ' %f %f %f' % (msh.verts[face.v[3]].co.x, msh.verts[face.v[3]].co.y, msh.verts[face.v[3]].co.z)) out.write('\n') out.write('</vertices>\n')
Normals
Normalvektoren sind in Blender sowohl per Vertex als auch per Face abgespeichert. Abhängig, davon ob eine Fläche als Smooth (per Vertex) oder Solid (per Face) dargestellt wird, werden die die entsprechenden Normalvektoren ausgewählt. Damit auch Klötze mit Kanten dargestellt werden können müssen beide Fälle berücksichtigt werden. Im Fall Solid müssen für alle Vertices der Fläche der Normalvektor der Fläche verwendet werden. Im Fall Smooth muss hier der Normalvektor der Vertices benutz werden. Auf keinen Fall sollte man nun die Vervielfachung der Vertices als Problem ansehen, da die verschiedenen Normalvektoren, diesen Nachteil wieder volkommen ausgleichen.
Tip: Da ganze Flächen als Solid keinerlei Optische Rundungen zulassen, ist der einzige Weg, echte Kanten an runden Objekten zu erzeugen in dem man das Mesch an der Stelle splittet. Leider muss man zum Splitten einen vollständigen Edgeloop auswählen. An Stellen die nicht kantig sein sollen muss man dann beide Vertices Makieren und mit "remove doubles" vereinen.
Auch hier ersst einmal ein einfaches Beispiel was haufig schon genügen sollte:
out.write('<vertices>\n') for face in msh.faces: for vert in face.v: out.write( ' %f, %f, %f,' % (msh.verts[vert.index].no.x, msh.verts[vert.index].no.y, msh.verts[vert.index].no.z) ) out.write('\n') out.write('</vertices>\n')
Besser wird es, wenn Smooth/Solid berücksichtigt wird:
out.write('<vertices>\n') for face in msh.faces: if (face.smooth == 1) for vert in face.v: out.write( ' %f, %f, %f,' % (msh.verts[vert.index].no.x, msh.verts[vert.index].no.y, msh.verts[vert.index].no.z)) out.write('\n') else for vert in face.v: out.write( ' %f, %f, %f,' % (face.no.x, face.no.y, face.no.z)) out.write('\n') out.write('</vertices>\n')
Nun bleibt noch der Fall in dem noch Triangles und Quads gemischt sind: