Blenderexporter: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
(Optimierung der Daten)
K (python Tags ergänzt)
Zeile 12: Zeile 12:
  
 
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.
 
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.
 
+
<python>
 
   #!BPY
 
   #!BPY
 
   """
 
   """
Zeile 29: Zeile 29:
 
     out.close()
 
     out.close()
 
   Blender.Window.FileSelector(write, "Export")
 
   Blender.Window.FileSelector(write, "Export")
 +
</python>
 
code getestet
 
code getestet
  
Zeile 48: Zeile 49:
  
 
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:
 
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:
 
+
 
 +
<python>
 
   out.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<vbo>\n')
 
   out.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<vbo>\n')
 +
</python>
  
 
Die letzte Zeile vor dem schließen der Datei sollte das VBO tag wieder schließen:
 
Die letzte Zeile vor dem schließen der Datei sollte das VBO tag wieder schließen:
  
 +
<python>
 
   out.write('</vbo>\n')
 
   out.write('</vbo>\n')
 +
</python>
  
 
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.
 
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.
Zeile 63: Zeile 68:
 
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:
 
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:
  
 +
<python>
 
   out.write('<name>%s</name>\n' % (msh.name))
 
   out.write('<name>%s</name>\n' % (msh.name))
 +
</python>
  
 
===Anzahl der Dreiecke===
 
===Anzahl der Dreiecke===
Zeile 69: Zeile 76:
 
Wenn nur Dreiecke vorhanden sind kann man so die Anzahl der Dreiecke speichern:
 
Wenn nur Dreiecke vorhanden sind kann man so die Anzahl der Dreiecke speichern:
  
 +
<python>
 
   out.write('<triangles>%i</triangles>\n' % (len(mesh.faces)))  
 
   out.write('<triangles>%i</triangles>\n' % (len(mesh.faces)))  
 +
</python>
  
 
Mit folgendem Code kann man berücksichtigen, wie viele Triangles entstehen wenn die Quads gesplittet werden:
 
Mit folgendem Code kann man berücksichtigen, wie viele Triangles entstehen wenn die Quads gesplittet werden:
  
 +
<python>
 
  count = 0
 
  count = 0
 
  for face in msh.faces:
 
  for face in msh.faces:
 
       count += len (face.v)-2
 
       count += len (face.v)-2
 
  out.write('<triangles>%i</triangles>\n' % (count))
 
  out.write('<triangles>%i</triangles>\n' % (count))
 
+
</python>
 
===Vertices===
 
===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.
 
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:
 
Unter der Annahme, das nur Triangles vorhanden sind reicht dieser Code:
  
 +
<python>
 
   out.write('<vertices>\n')
 
   out.write('<vertices>\n')
 
   for face in msh.faces:
 
   for face in msh.faces:
Zeile 88: Zeile 99:
 
       out.write('\n')
 
       out.write('\n')
 
   out.write('</vertices>\n')
 
   out.write('</vertices>\n')
 +
</python>
  
 
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:
 
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:
  
 +
<python>
 
   out.write('<vertices>\n')
 
   out.write('<vertices>\n')
 
   for face in msh.faces:
 
   for face in msh.faces:
Zeile 123: Zeile 136:
 
         out.write('\n')
 
         out.write('\n')
 
   out.write('</vertices>\n')
 
   out.write('</vertices>\n')
 +
</python>
 
code getested
 
code getested
  
Zeile 129: Zeile 143:
 
Kaum schwerer als die Vertexdaten sind die Texturkoordinaten zu exportieren. Da die Texturkoordinaten innerhalb der Faces gespeichert werden. Ist der Code abgesehen von der Quadkonvertierung sogar etwas leichter. Wieder erst der einfache Code:
 
Kaum schwerer als die Vertexdaten sind die Texturkoordinaten zu exportieren. Da die Texturkoordinaten innerhalb der Faces gespeichert werden. Ist der Code abgesehen von der Quadkonvertierung sogar etwas leichter. Wieder erst der einfache Code:
  
 +
<python>
 
   out.write('<texturecoords>\n')
 
   out.write('<texturecoords>\n')
 
   for face in msh.faces:
 
   for face in msh.faces:
 
     out.write( ' %f %f %f %f %f %f\n' % (face.uv[0][0],face.uv[0][1],face.uv[1][0],face.uv[1][1],face.uv[2][0],face.uv[2][1]) )
 
     out.write( ' %f %f %f %f %f %f\n' % (face.uv[0][0],face.uv[0][1],face.uv[1][0],face.uv[1][1],face.uv[2][0],face.uv[2][1]) )
 
   out.write('</texturecoords>\n')
 
   out.write('</texturecoords>\n')
 +
</python>
  
 
Um die konvertierung der Quads in Triangles auch in den Texturkoordinaten zu berücksichtigen wird der Code etwas umfangreicher:
 
Um die konvertierung der Quads in Triangles auch in den Texturkoordinaten zu berücksichtigen wird der Code etwas umfangreicher:
  
 +
<python>
 
   out.write('<texturecoords>\n')
 
   out.write('<texturecoords>\n')
 
   for face in msh.faces:
 
   for face in msh.faces:
Zeile 158: Zeile 175:
 
         out.write( ' %f %f %f %f %f %f\n' % (face.uv[1][0],face.uv[1][1],face.uv[2][0],face.uv[2][1],face.uv[3][0],face.uv[3][1]) )
 
         out.write( ' %f %f %f %f %f %f\n' % (face.uv[1][0],face.uv[1][1],face.uv[2][0],face.uv[2][1],face.uv[3][0],face.uv[3][1]) )
 
   out.write('</texturecoords>\n')
 
   out.write('</texturecoords>\n')
 +
</python>
 
code getestet
 
code getestet
  
Zeile 169: Zeile 187:
 
Auch hier ersst einmal ein einfaches Beispiel was haufig schon genügen sollte:
 
Auch hier ersst einmal ein einfaches Beispiel was haufig schon genügen sollte:
  
 +
<python>
 
   out.write('<vertices>\n')
 
   out.write('<vertices>\n')
 
   for face in msh.faces:
 
   for face in msh.faces:
Zeile 175: Zeile 194:
 
     out.write('\n')
 
     out.write('\n')
 
   out.write('</vertices>\n')
 
   out.write('</vertices>\n')
 +
</python>
  
 
Besser wird es, wenn Smooth/Solid berücksichtigt wird.  
 
Besser wird es, wenn Smooth/Solid berücksichtigt wird.  
 +
<python>
 
   out.write('<vertices>\n')
 
   out.write('<vertices>\n')
 
   for face in msh.faces:
 
   for face in msh.faces:
Zeile 188: Zeile 209:
 
       out.write('\n')
 
       out.write('\n')
 
   out.write('</vertices>\n')
 
   out.write('</vertices>\n')
 +
</python>
  
 
Nun bleibt noch der Fall in dem noch Triangles und Quads gemischt sind. Die Entscheidung wie das Quad gesplitted wird muss die gleiche sein, wie bei den Vertices. Der Code wird allerdings noch ein wenig länger, da noch mehr Fälle vorkommen können:
 
Nun bleibt noch der Fall in dem noch Triangles und Quads gemischt sind. Die Entscheidung wie das Quad gesplitted wird muss die gleiche sein, wie bei den Vertices. Der Code wird allerdings noch ein wenig länger, da noch mehr Fälle vorkommen können:
  
 +
<python>
 
   out.write('<normals>\n')
 
   out.write('<normals>\n')
 
   for face in msh.faces:
 
   for face in msh.faces:
Zeile 237: Zeile 260:
 
         out.write('\n')
 
         out.write('\n')
 
   out.write('</normals>\n')
 
   out.write('</normals>\n')
 +
</python>
 
code getested
 
code getested
  
Zeile 250: Zeile 274:
 
Da das umformen der Daten etwas komplizierter ist erst mal ein wenig Code:
 
Da das umformen der Daten etwas komplizierter ist erst mal ein wenig Code:
  
 +
<python>
 
   out.write('<vertexgroups>\n')
 
   out.write('<vertexgroups>\n')
 
     groups = msh.getVertGroupNames()
 
     groups = msh.getVertGroupNames()
Zeile 269: Zeile 294:
 
         out.write( ' %i %i %i %i \n' % (list[0][1],list[1][1],list[2][1],list[3][1]))
 
         out.write( ' %i %i %i %i \n' % (list[0][1],list[1][1],list[2][1],list[3][1]))
 
   out.write('</vertexgroups>\n')
 
   out.write('</vertexgroups>\n')
 +
</python>
  
 
Bei Mangel an "attribute" Variablen macht es Sinn, die Gewichtung (muss kleiner als 1 sein) mit den Indizies zu addieren. Im Shader kann man die dann wieder herausrechnen.
 
Bei Mangel an "attribute" Variablen macht es Sinn, die Gewichtung (muss kleiner als 1 sein) mit den Indizies zu addieren. Im Shader kann man die dann wieder herausrechnen.
 
Da der Code nun schon recht unüberschau bar ist und es ein völliges Caos geben würden wenn noch die Quads in Triangles zerlegt werden, werden im nächstem Codesample die Vertexgruppen vorab zusammengesammlt:
 
Da der Code nun schon recht unüberschau bar ist und es ein völliges Caos geben würden wenn noch die Quads in Triangles zerlegt werden, werden im nächstem Codesample die Vertexgruppen vorab zusammengesammlt:
  
 +
<python>
 
   vertGroupData = []
 
   vertGroupData = []
 
   groups = msh.getVertGroupNames()
 
   groups = msh.getVertGroupNames()
Zeile 286: Zeile 313:
 
       list += [(0.0, 0),(0.0, 0),(0.0, 0),(0.0, 0)]
 
       list += [(0.0, 0),(0.0, 0),(0.0, 0),(0.0, 0)]
 
       vertGroupData += [(list[0],list[1],list[2],list[3])]
 
       vertGroupData += [(list[0],list[1],list[2],list[3])]
 +
</python>
 
code getested
 
code getested
 +
 
Zu jedem Vertex liegen nun die vier Vertexgruppen mit der höchsten Gewichtung als Listen in vertGroupData. Das schreiben in eine Datei ist nun sehr ähnlich zu den vorigen Codesamples aufgrund  der Datenmenge allerdings etwas länger:
 
Zu jedem Vertex liegen nun die vier Vertexgruppen mit der höchsten Gewichtung als Listen in vertGroupData. Das schreiben in eine Datei ist nun sehr ähnlich zu den vorigen Codesamples aufgrund  der Datenmenge allerdings etwas länger:
  
 +
<python>
 
   out.write('<verrtexgroups>\n')
 
   out.write('<verrtexgroups>\n')
 
   out.write('<weight>\n')
 
   out.write('<weight>\n')
Zeile 379: Zeile 409:
 
   out.write('</index>\n')
 
   out.write('</index>\n')
 
   out.write('</vertexgroups>\n')
 
   out.write('</vertexgroups>\n')
 +
</python>
 
code getested
 
code getested
  
Zeile 408: Zeile 439:
 
Jede der bisherigen exportfunktionen began mit folgender Schleife:
 
Jede der bisherigen exportfunktionen began mit folgender Schleife:
  
 +
<python>
 
   for face in msh.faces:
 
   for face in msh.faces:
 +
</python>
  
 
Sie arbeitet die Faces ohne Optimierung in der Reinfolge ab, wie Blender sie im Speicher hällt. Diese Schleife kann nun einfach ersetzt werden:
 
Sie arbeitet die Faces ohne Optimierung in der Reinfolge ab, wie Blender sie im Speicher hällt. Diese Schleife kann nun einfach ersetzt werden:

Version vom 18. Juni 2006, 14:23 Uhr

Hinweis: Dieser Artikel wird gerade Offline bearbeitet!

Bitte haben Sie etwas Geduld und nehmen Sie keine Änderungen vor, bis der Artikel hochgeladen wurde.

(weitere Artikel)
WIP Offline.jpg

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. <python>

 #!BPY
 """
 Name: 'DGL Wiki'
 Blender: 241
 Group: 'Export'
 Tooltip: 'DGL Wiki Exporter'
 """
 import Blender
 def write(filename):
   out = file(filename, 'w')
   obj = Blender.Object.GetSelected()[0]
   msh = obj.getData()
   #hier wird Code eingefügt, der die zu exportierende Daten schreibt
   out.write('</vbo>\n')
   out.close()
 Blender.Window.FileSelector(write, "Export")

</python> code getestet



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.

Es gibt zwei Möglichkeiten die Vertexdaten zu speichern: Interleaved oder getrennte Blöcke. Beim interleaved Format verliert man die Flexibilität und die Daten werden noch unübersichtlicher. Ein Geschwindigkeitsgewinn ist auch nicht zu erwarten.

In einigen Codestücken wird auffallen, das die Convertierung von Quads nach Triangles immer wieder vorgenommen wird. In einem komplettem Exporten macht es durchaus Sinn, alle Daten erst zu sammel, dann zu sortieren, umwandeln und erst zum Schluss zu schreiben. Dabei wäre es allerdings nicht mehr möglich einzelne Beispiele zu liefern.

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:

<python>

 out.write('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<vbo>\n')

</python>

Die letzte Zeile vor dem schließen der Datei sollte das VBO tag wieder schließen:

<python>

 out.write('</vbo>\n')

</python>

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:

<python>

 out.write('<name>%s</name>\n' % (msh.name))

</python>

Anzahl der Dreiecke

Wenn nur Dreiecke vorhanden sind kann man so die Anzahl der Dreiecke speichern:

<python>

 out.write('<triangles>%i</triangles>\n' % (len(mesh.faces))) 

</python>

Mit folgendem Code kann man berücksichtigen, wie viele Triangles entstehen wenn die Quads gesplittet werden:

<python>

count = 0
for face in msh.faces:
     count += len (face.v)-2
out.write('<triangles>%i</triangles>\n' % (count))

</python>

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:

<python>

 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')

</python>

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:

<python>

 out.write('<vertices>\n')
 for face in msh.faces:
   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')
   else:
     d1 =(msh.verts[face.v[0].index].co.x - msh.verts[face.v[2].index].co.x) ** 2 
     d1+=(msh.verts[face.v[0].index].co.y - msh.verts[face.v[2].index].co.y) ** 2 
     d1+=(msh.verts[face.v[0].index].co.z - msh.verts[face.v[2].index].co.z) ** 2 
     d2 =(msh.verts[face.v[1].index].co.x - msh.verts[face.v[3].index].co.x) ** 2 
     d2+=(msh.verts[face.v[1].index].co.y - msh.verts[face.v[3].index].co.y) ** 2 
     d2+=(msh.verts[face.v[1].index].co.z - msh.verts[face.v[3].index].co.z) ** 2 
     if (d1<d2):
       out.write( ' %f %f %f' % (msh.verts[face.v[0].index].co.x, msh.verts[face.v[0].index].co.y, msh.verts[face.v[0].index].co.z))
       out.write( ' %f %f %f' % (msh.verts[face.v[1].index].co.x, msh.verts[face.v[1].index].co.y, msh.verts[face.v[1].index].co.z))
       out.write( ' %f %f %f' % (msh.verts[face.v[2].index].co.x, msh.verts[face.v[2].index].co.y, msh.verts[face.v[2].index].co.z))
       out.write('\n')
       out.write( ' %f %f %f' % (msh.verts[face.v[0].index].co.x, msh.verts[face.v[0].index].co.y, msh.verts[face.v[0].index].co.z))
       out.write( ' %f %f %f' % (msh.verts[face.v[2].index].co.x, msh.verts[face.v[2].index].co.y, msh.verts[face.v[2].index].co.z))
       out.write( ' %f %f %f' % (msh.verts[face.v[3].index].co.x, msh.verts[face.v[3].index].co.y, msh.verts[face.v[3].index].co.z))
       out.write('\n')
     else:
       out.write( ' %f %f %f' % (msh.verts[face.v[0].index].co.x, msh.verts[face.v[0].index].co.y, msh.verts[face.v[0].index].co.z))
       out.write( ' %f %f %f' % (msh.verts[face.v[1].index].co.x, msh.verts[face.v[1].index].co.y, msh.verts[face.v[1].index].co.z))
       out.write( ' %f %f %f' % (msh.verts[face.v[3].index].co.x, msh.verts[face.v[3].index].co.y, msh.verts[face.v[3].index].co.z))
       out.write('\n')
       out.write( ' %f %f %f' % (msh.verts[face.v[1].index].co.x, msh.verts[face.v[1].index].co.y, msh.verts[face.v[1].index].co.z))
       out.write( ' %f %f %f' % (msh.verts[face.v[2].index].co.x, msh.verts[face.v[2].index].co.y, msh.verts[face.v[2].index].co.z))
       out.write( ' %f %f %f' % (msh.verts[face.v[3].index].co.x, msh.verts[face.v[3].index].co.y, msh.verts[face.v[3].index].co.z))
       out.write('\n')
 out.write('</vertices>\n')

</python> code getested

Textur-Koordinaten

Kaum schwerer als die Vertexdaten sind die Texturkoordinaten zu exportieren. Da die Texturkoordinaten innerhalb der Faces gespeichert werden. Ist der Code abgesehen von der Quadkonvertierung sogar etwas leichter. Wieder erst der einfache Code:

<python>

 out.write('<texturecoords>\n')
 for face in msh.faces:
   out.write( ' %f %f %f %f %f %f\n' % (face.uv[0][0],face.uv[0][1],face.uv[1][0],face.uv[1][1],face.uv[2][0],face.uv[2][1]) )
 out.write('</texturecoords>\n')

</python>

Um die konvertierung der Quads in Triangles auch in den Texturkoordinaten zu berücksichtigen wird der Code etwas umfangreicher:

<python>

 out.write('<texturecoords>\n')
 for face in msh.faces:
   #Ein einfaches Triangle 
   if (len(face.v) == 3):
     for vert in face.v:
       out.write( ' %f %f %f %f %f %f\n' % (face.uv[0][0],face.uv[0][1],face.uv[1][0],face.uv[1][1],face.uv[2][0],face.uv[2][1]) )
   #Quads     
   else:
     #ermitteln der kürzeren diagonale durch pytagoras ohne Wurzel
     d1 =(msh.verts[face.v[0].index].co.x - msh.verts[face.v[2].index].co.x) ** 2 
     d1+=(msh.verts[face.v[0].index].co.y - msh.verts[face.v[2].index].co.y) ** 2 
     d1+=(msh.verts[face.v[0].index].co.z - msh.verts[face.v[2].index].co.z) ** 2 
     d2 =(msh.verts[face.v[1].index].co.x - msh.verts[face.v[3].index].co.x) ** 2 
     d2+=(msh.verts[face.v[1].index].co.y - msh.verts[face.v[3].index].co.y) ** 2 
     d2+=(msh.verts[face.v[1].index].co.z - msh.verts[face.v[3].index].co.z) ** 2 
     if (d1<d2):
       out.write( ' %f %f %f %f %f %f\n' % (face.uv[0][0],face.uv[0][1],face.uv[1][0],face.uv[1][1],face.uv[2][0],face.uv[2][1]) )
       out.write( ' %f %f %f %f %f %f\n' % (face.uv[0][0],face.uv[0][1],face.uv[2][0],face.uv[2][1],face.uv[3][0],face.uv[3][1]) )
     else:
       out.write( ' %f %f %f %f %f %f\n' % (face.uv[0][0],face.uv[0][1],face.uv[1][0],face.uv[1][1],face.uv[3][0],face.uv[3][1]) )
       out.write( ' %f %f %f %f %f %f\n' % (face.uv[1][0],face.uv[1][1],face.uv[2][0],face.uv[2][1],face.uv[3][0],face.uv[3][1]) )
 out.write('</texturecoords>\n')

</python> code getestet

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:

<python>

 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')

</python>

Besser wird es, wenn Smooth/Solid berücksichtigt wird. <python>

 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 range(0,3):
         out.write( ' %f, %f, %f,' % (face.no.x, face.no.y, face.no.z))
      out.write('\n')
 out.write('</vertices>\n')

</python>

Nun bleibt noch der Fall in dem noch Triangles und Quads gemischt sind. Die Entscheidung wie das Quad gesplitted wird muss die gleiche sein, wie bei den Vertices. Der Code wird allerdings noch ein wenig länger, da noch mehr Fälle vorkommen können:

<python>

 out.write('<normals>\n')
 for face in msh.faces:
   if (face.smooth==1):
    #Ein einfaches Triangle 
    if (len(face.v) == 3):
      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')
    #Quads     
    else:
      #ermitteln der kürzeren diagonale durch pytagoras ohne Wurzel
      d1 =(msh.verts[face.v[0].index].co.x - msh.verts[face.v[2].index].co.x) ** 2 
      d1+=(msh.verts[face.v[0].index].co.y - msh.verts[face.v[2].index].co.y) ** 2 
      d1+=(msh.verts[face.v[0].index].co.z - msh.verts[face.v[2].index].co.z) ** 2 
      d2 =(msh.verts[face.v[1].index].co.x - msh.verts[face.v[3].index].co.x) ** 2 
      d2+=(msh.verts[face.v[1].index].co.y - msh.verts[face.v[3].index].co.y) ** 2 
      d2+=(msh.verts[face.v[1].index].co.z - msh.verts[face.v[3].index].co.z) ** 2 
      if (d1<d2):
        out.write( ' %f %f %f' % (msh.verts[face.v[0].index].no.x, msh.verts[face.v[0].index].no.y, msh.verts[face.v[0].index].no.z))
        out.write( ' %f %f %f' % (msh.verts[face.v[1].index].no.x, msh.verts[face.v[1].index].no.y, msh.verts[face.v[1].index].no.z))
        out.write( ' %f %f %f' % (msh.verts[face.v[2].index].no.x, msh.verts[face.v[2].index].no.y, msh.verts[face.v[2].index].no.z))
        out.write('\n')
        out.write( ' %f %f %f' % (msh.verts[face.v[0].index].no.x, msh.verts[face.v[0].index].no.y, msh.verts[face.v[0].index].no.z))
        out.write( ' %f %f %f' % (msh.verts[face.v[2].index].no.x, msh.verts[face.v[2].index].no.y, msh.verts[face.v[2].index].no.z))
        out.write( ' %f %f %f' % (msh.verts[face.v[3].index].no.x, msh.verts[face.v[3].index].no.y, msh.verts[face.v[3].index].no.z))
        out.write('\n')
      else:
        out.write( ' %f %f %f' % (msh.verts[face.v[0].index].no.x, msh.verts[face.v[0].index].no.y, msh.verts[face.v[0].index].no.z))
        out.write( ' %f %f %f' % (msh.verts[face.v[1].index].no.x, msh.verts[face.v[1].index].no.y, msh.verts[face.v[1].index].no.z))
        out.write( ' %f %f %f' % (msh.verts[face.v[3].index].no.x, msh.verts[face.v[3].index].no.y, msh.verts[face.v[3].index].no.z))
        out.write('\n')
        out.write( ' %f %f %f' % (msh.verts[face.v[1].index].no.x, msh.verts[face.v[1].index].no.y, msh.verts[face.v[1].index].no.z))
        out.write( ' %f %f %f' % (msh.verts[face.v[2].index].no.x, msh.verts[face.v[2].index].no.y, msh.verts[face.v[2].index].no.z))
        out.write( ' %f %f %f' % (msh.verts[face.v[3].index].no.x, msh.verts[face.v[3].index].no.y, msh.verts[face.v[3].index].no.z))
        out.write('\n')
   else:
     #Drei Normalvektoren für ein Solid Triangle
     for vert in range(0,3):
       out.write( ' %f, %f, %f,' % (face.no.x, face.no.y, face.no.z))
     out.write('\n')
     # Drei zusätzliche Normalvektoren für ein gesplitetes Solid Quad
     if (len(face.v)==4):
       for vert in range(0,3):
         out.write( ' %f, %f, %f,' % (face.no.x, face.no.y, face.no.z))
       out.write('\n')
 out.write('</normals>\n')

</python> code getested


Vertuxgruppen / Bones

Ohne Vertexgruppen und Bones lassen sich keine Animationen mit hilfe des Vertexshaders realisieren. Diesen Teil können alle überspringen, die keine Vertexshader zum Animieren benutzen wollen. Die Jenigen, die diese Daten im Vertexshader benötigen, traue ich auch zu durch diesen etwas umfangreicheren Code durchzusteigen.

Leider liegen die Daten in Blender in einer für Vertexshader total unbrauchbaren Form vor. In jeder Vertexgruppe, auf die man auch noch über Ihren Namen zugreifen muss, liegt eine Liste mit der Vertexnummer und der Gewichtung. Für einen Vertexshader benötigen wir pro Vertex einen Index, der die Vertexgruppe/Bone beschreibt und die Gewichtung. Außerdem müssen wir uns voher entscheiden, wie viele Bones wir maximal pro Vertex berücksichtigen.

Da das umformen der Daten etwas komplizierter ist erst mal ein wenig Code:

<python>

 out.write('<vertexgroups>\n')
   groups = msh.getVertGroupNames()
   for face in msh.faces:
     for vert in face.v:
        list = []
        count = 1   
        for group in groups:
           if (len(msh.getVertsFromGroup(group,0,[vert.index]))==1)
              list += [(msh.getVertsFromGroup(group,1,[vert.index])[0][1],count)]
           count ++
        list.sort()
        list.reverse()
        #liste auf minstesanzahl mit nulldaten auffüllen zuviel schadet nicht
        for vert in range(0,4):
           list += [(0.0, 0)]
        #Ausgabe Die Indizies könnten noch von den Gewichtungen getrennt werden...
        out.write( ' %f %f %f %f' % (list[0][0],list[1][0],list[2][0],list[3][0]))
        out.write( ' %i %i %i %i \n' % (list[0][1],list[1][1],list[2][1],list[3][1]))
 out.write('</vertexgroups>\n')

</python>

Bei Mangel an "attribute" Variablen macht es Sinn, die Gewichtung (muss kleiner als 1 sein) mit den Indizies zu addieren. Im Shader kann man die dann wieder herausrechnen. Da der Code nun schon recht unüberschau bar ist und es ein völliges Caos geben würden wenn noch die Quads in Triangles zerlegt werden, werden im nächstem Codesample die Vertexgruppen vorab zusammengesammlt:

<python>

 vertGroupData = []
 groups = msh.getVertGroupNames()
 for vert in msh.verts:
   list = []
   count = 1
   for group in groups:
     if (len(msh.getVertsFromGroup(group,0,[vert.index]))==1):
       list += [(msh.getVertsFromGroup(group,1,[vert.index])[0][1],count)]
     count += 1
     list.sort()
     list.reverse()
     list += [(0.0, 0),(0.0, 0),(0.0, 0),(0.0, 0)]
     vertGroupData += [(list[0],list[1],list[2],list[3])]

</python> code getested

Zu jedem Vertex liegen nun die vier Vertexgruppen mit der höchsten Gewichtung als Listen in vertGroupData. Das schreiben in eine Datei ist nun sehr ähnlich zu den vorigen Codesamples aufgrund der Datenmenge allerdings etwas länger:

<python>

 out.write('<verrtexgroups>\n')
 out.write('<weight>\n')
 for face in msh.faces:
   #Ein einfaches Triangle 
   if (len(face.v) == 3):
     for vert in face.v:
       out.write( ' %f %f' % (vertGroupData[vert.index][0][0],vertGroupData[vert.index][1][0]))
       out.write( ' %f %f' % (vertGroupData[vert.index][2][0],vertGroupData[vert.index][3][0]))
       #Quads     
   else:
     #ermitteln der kürzeren diagonale durch pytagoras ohne Wurzel
     d1 =(msh.verts[face.v[0].index].co.x - msh.verts[face.v[2].index].co.x) ** 2 
     d1+=(msh.verts[face.v[0].index].co.y - msh.verts[face.v[2].index].co.y) ** 2 
     d1+=(msh.verts[face.v[0].index].co.z - msh.verts[face.v[2].index].co.z) ** 2  
     d2 =(msh.verts[face.v[1].index].co.x - msh.verts[face.v[3].index].co.x) ** 2 
     d2+=(msh.verts[face.v[1].index].co.y - msh.verts[face.v[3].index].co.y) ** 2 
     d2+=(msh.verts[face.v[1].index].co.z - msh.verts[face.v[3].index].co.z) ** 2 
     if (d1<d2):
       out.write( ' %f %f' % (vertGroupData[face.v[0].index][0][0],vertGroupData[face.v[0].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[0].index][2][0],vertGroupData[face.v[0].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[1].index][0][0],vertGroupData[face.v[1].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[1].index][2][0],vertGroupData[face.v[1].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[2].index][0][0],vertGroupData[face.v[2].index][1][0]))
       out.write( ' %f %f\n' % (vertGroupData[face.v[2].index][2][0],vertGroupData[face.v[2].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[0].index][0][0],vertGroupData[face.v[0].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[0].index][2][0],vertGroupData[face.v[0].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[2].index][0][0],vertGroupData[face.v[2].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[2].index][2][0],vertGroupData[face.v[2].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[3].index][0][0],vertGroupData[face.v[3].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[3].index][2][0],vertGroupData[face.v[3].index][3][0]))
     else:
       out.write( ' %f %f' % (vertGroupData[face.v[0].index][0][0],vertGroupData[face.v[0].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[0].index][2][0],vertGroupData[face.v[0].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[1].index][0][0],vertGroupData[face.v[1].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[1].index][2][0],vertGroupData[face.v[1].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[3].index][0][0],vertGroupData[face.v[3].index][1][0]))
       out.write( ' %f %f\n' % (vertGroupData[face.v[3].index][2][0],vertGroupData[face.v[3].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[1].index][0][0],vertGroupData[face.v[1].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[1].index][2][0],vertGroupData[face.v[1].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[2].index][0][0],vertGroupData[face.v[2].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[2].index][2][0],vertGroupData[face.v[2].index][3][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[3].index][0][0],vertGroupData[face.v[3].index][1][0]))
       out.write( ' %f %f' % (vertGroupData[face.v[3].index][2][0],vertGroupData[face.v[3].index][3][0]))
   out.write('\n')
 out.write('</weight>\n')
 out.write('<index>\n')
 for face in msh.faces:
   #Ein einfaches Triangle 
   if (len(face.v) == 3):
     for vert in face.v:
       out.write( ' %i %i' % (vertGroupData[vert.index][0][1],vertGroupData[vert.index][1][1]))
       out.write( ' %i %i' % (vertGroupData[vert.index][2][1],vertGroupData[vert.index][3][1]))
      
   else:
     #ermitteln der kürzeren diagonale durch pytagoras ohne Wurzel
     d1 =(msh.verts[face.v[0].index].co.x - msh.verts[face.v[2].index].co.x) ** 2 
     d1+=(msh.verts[face.v[0].index].co.y - msh.verts[face.v[2].index].co.y) ** 2 
     d1+=(msh.verts[face.v[0].index].co.z - msh.verts[face.v[2].index].co.z) ** 2  
     d2 =(msh.verts[face.v[1].index].co.x - msh.verts[face.v[3].index].co.x) ** 2 
     d2+=(msh.verts[face.v[1].index].co.y - msh.verts[face.v[3].index].co.y) ** 2 
     d2+=(msh.verts[face.v[1].index].co.z - msh.verts[face.v[3].index].co.z) ** 2 
     if (d1<d2):
       out.write( ' %i %i' % (vertGroupData[face.v[0].index][0][1],vertGroupData[face.v[0].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[0].index][2][1],vertGroupData[face.v[0].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[1].index][0][1],vertGroupData[face.v[1].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[1].index][2][1],vertGroupData[face.v[1].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[2].index][0][1],vertGroupData[face.v[2].index][1][1]))
       out.write( ' %i %i\n' % (vertGroupData[face.v[2].index][2][1],vertGroupData[face.v[2].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[0].index][0][1],vertGroupData[face.v[0].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[0].index][2][1],vertGroupData[face.v[0].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[2].index][0][1],vertGroupData[face.v[2].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[2].index][2][1],vertGroupData[face.v[2].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[3].index][0][1],vertGroupData[face.v[3].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[3].index][2][1],vertGroupData[face.v[3].index][3][1]))
     else: 
       out.write( ' %i %i' % (vertGroupData[face.v[0].index][0][1],vertGroupData[face.v[0].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[0].index][2][1],vertGroupData[face.v[0].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[1].index][0][1],vertGroupData[face.v[1].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[1].index][2][1],vertGroupData[face.v[1].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[3].index][0][1],vertGroupData[face.v[3].index][1][1]))
       out.write( ' %i %i\n' % (vertGroupData[face.v[3].index][2][1],vertGroupData[face.v[3].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[1].index][0][1],vertGroupData[face.v[1].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[1].index][2][1],vertGroupData[face.v[1].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[2].index][0][1],vertGroupData[face.v[2].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[2].index][2][1],vertGroupData[face.v[2].index][3][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[3].index][0][1],vertGroupData[face.v[3].index][1][1]))
       out.write( ' %i %i' % (vertGroupData[face.v[3].index][2][1],vertGroupData[face.v[3].index][3][1]))
   out.write('\n')
 out.write('</index>\n')
 out.write('</vertexgroups>\n')

</python> code getested

Tangenvektor / TBN Matrix

Ohne die TBN Matrix lässt sich in den shadern kein sinvolles Bumpmapping durchführen. Dummerweise Sind die meisten vorhandenen oder mir bekannten Methoden zum berechnen der TBN Matrix aufgrund von Normalisierungen gerade einma noch für Dotprodukt Normalmapping zu gebrauchen. Paralaxmapping oder Reliefmapping sind mit solchen Matrix nicht mehr richtig darstellbar. Eine TBN Matrix besteht aus drei Vektoren, die den Texturespace aufspannen:

Normal Er steht immer Senkrecht zur Oberfläche. Eigendlich entspricht er genau dem Normalvektor wie wir ihn kennen. Allerdings gibt es eine Ausnahme: Beim Paralax und Reliefmapping repräsentiert seine Länge den Masstab für die dritte Texturkoordinate (Im Editor wäre sie W, auch wenn die Texturkoordinaten in OpenGL s, t, p und q sind), Wenn der Normalvektor eh im Pixelshader normalisiert wird, dann entspricht dieser Normalvektor dem schon bekanntem. Tangent Er liegt tangential auf der Textur und ist dort zur U Achse ausgerichtet. Wieder einmal kann man überlegen ob es nun beteutet Senktrecht zum Normal oder Parallel zu Fläche des Dreieckes. Wer keine Kopfschmerzen hat macht sich welche. Bitangent Häufig wird er auch Binormal genannt, jedoch beschreibt Bitangent die Funktion besser. Er ist wie der Tangent tangential zur V Achse der Textur ausgerichtet. Wer meint Bitangent lässt sich durch das Kreutzprodukt von Normal und Tangend berechnen, hat nur recht wenn die Textur keine Scherung enthällt.

Wenn ich es schaffen sollte einen brauchbaren Code zum erzeugen einer Brauchbaren TBN Matrix zu schreiben kommt er hier rein

Optimierung der Daten

Bisher haben wir uns Hauptsachlich damit beschäftig wie wir die Daten aus der Blenderstruktur auszulesen und in ein Vertexbufferobjekt kompatible daten unzuformen. Wen die Modelle größer werden oder komplette Level in Blender realisiert werden sollen macht es Sinn die Daten so umzusortieren, das Triangles mit ähnlichen Eigenschaften wie Ort oder Ausrichtung nah im Vertexbufferobjekt beieinander liegen.

Quad/Octree

Beim Octree werden nur die Teile gerender, bei denen voher bekannt ist, dass sie sichtbar sind, leider ist die Baumstruktur eines Octtrees auf dem erstem Blick völlig inkompatibel zu dem linearem Verlauf des Vertexbuffersobjektes.

Um die Faces in den Octree einzusortieren muss von jedem der Schwerpunkt berechnet werden. Dabei sollte ermittelt werden wie groß das größte Face ist und welche Ausmaße der Octtree hat.

Jede der bisherigen exportfunktionen began mit folgender Schleife:

<python>

 for face in msh.faces:

</python>

Sie arbeitet die Faces ohne Optimierung in der Reinfolge ab, wie Blender sie im Speicher hällt. Diese Schleife kann nun einfach ersetzt werden:

Vorsortierung nach Vertexgruppen/Bones

Es ist zwar noch kein Algoritmus zum sortieren vorhanden, jedoch ist es bei animierten Modellen sinnvoll möglichst ganze Bones auf einmal abzuarbeiten, da ein Bone ganze 12 Uniform Floats im Vertexshader verbraucht (9 für eine Rotationsmatrix und 3 für das Gelenk). Laut GLSL Spezifikation soll man minimal 512 davon haben, was maximal 40 Bones entspricht. Dummerweise scheint es so als wenn einige Grafikkarten nur 256 Uniform Floats zur Verfügung stellen, was zur Folge hat, das nur noch 20 Bones möglich wären. Eine primitive Optimierung wäre nur einen halbes Model zu speichen. und für die zweite hälfte das gleiche Mesh gespiegelt mit neuen Bones zu verwenden.

Externe Links

http://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro/Advanced_Tutorials/Python_Scripting/Export_scripts