www.rolandk.de
- Aktuelle Themen zu .Net -
Achtung: Hier handelt es sich um meine alte Seite.
Die aktuelle ist unter folgendem Link erreichbar: www.rolandk.de/wp/
Home Tutorials WPF 3D Chapter 2 - Building objects in code




















































Chapter 2 - Building objects in code
Samstag, den 03. Juli 2010 um 13:24 Uhr

 

 

Allgemeines

Aufbauend auf das letzte Kapitel möchte ich hier erklären, wie genau 3D-Objekte für WPF im Code erzeugt werden können. Jedes 3D-Objekt muss aus Dreiecken, sog. Polygonen aufgebaut werden. Folgende Bilder verdeutlichen dies (links werden nur die Ränder der Dreiecke gezeichnet, rechts die komplette Szene).

 

 

Was bedeutet das jetzt für den Programmierer? Ganz einfach, damit ein 3D-Objekt gezeichnet werden kann, müssen alle Dreiecke irgendwo gespeichert werden. Um das zu erreichen, werden in einem MeshGeometry3D Objekt zunächst alle Eckpunkte definiert. Beim Dreieck aus dem letzten Kapitel sah das beispielsweise so aus:

  1. //Create all needex vertices
  2. MeshGeometry3D triangleMesh = new MeshGeometry3D();
  3.  
  4. //Create all needex vertices
  5. triangleMesh.Positions.Add(new Point3D(-5, 0, -5));
  6. triangleMesh.Positions.Add(new Point3D(5, 0, 0));
  7. triangleMesh.Positions.Add(new Point3D(0, 0, 5));

Als nächstes muss dem MeshGeometry3D Objekt gesagt werden, wie diese Eckpunkte (Vertices genannt) verbunden sind, sprich, welche zu welchen Dreiecken gehören. Die gängige Methode dafür ist es, eine Liste von Index-Werten anzulegen. Jeder Index zeigt dabei auf einen Vertex (Eckpunkt) und jedes Dreierpaar bildet ein Dreieck. Somit gehören die ersten drei Indices zum ersten Dreieck, die nächsten drei zum zweiten Dreieck und so weiter. Der nächste Quellcodeausschnitt definiert das Dreieck aus dem vorherigen Kapitel:

  1. //Build the triangle
  2. triangleMesh.TriangleIndices.Add(0);
  3. triangleMesh.TriangleIndices.Add(2);
  4. triangleMesh.TriangleIndices.Add(1);

Rein theoretisch würden die bisherigen Daten schon reichen, doch damit man später auch Farbe sieht oder besser noch primitive Lichteffekte nutzen kann, muss jeder Vertex noch wissen, wo „außerhalb“ und wo „innerhalb“ des Objektes ist. Man sagt auch gerne, jeder Vertex muss wissen, in welche Richtung er schaut. Erreichen kann man das über sog. Normals, 3D-Vektoren mit der Länge 1. Folgender Ausschnitt legt für jeden vorher erzeugten Vertex einen Normal an, welcher nach oben (in Richtung der Y-Achse) zeigt:

  1. //Set normals for all vertices
  2. Vector3D normal = new Vector3D(0, 1, 0);
  3. triangleMesh.Normals.Add(normal);
  4. triangleMesh.Normals.Add(normal);
  5. triangleMesh.Normals.Add(normal);

Die Liste Normals bezieht sich übrigens direkt auf die vorherige Liste Positions. Das heißt, dass der Normal bei Index 0 zu dem Vertex gehört, welcher auf Index 0 der Liste Positions steht.
Als nächstes geht es darum, der generierten 3D-Geometrie ein Material zuzuweisen. Dies geschieht nicht mehr im MeshGeometry3D Objekt, sondern in einer GeometryModel3D Instanz. Grund dafür liegt in der Trennung zwischen der Geometrie eines Models und dem darauf dargestellten Material. Eine Kiste kann schließlich aus Holz und aus Metall bestehen, auch wenn die geometrische Form an sich dieselbe ist. Nachfolgender Programmcode zeigt die Erzeugung eines GeometryModel3D Objekts:

  1. //Build the model object
  2. Material material = new DiffuseMaterial(new SolidColorBrush(Colors.Blue));
  3. GeometryModel3D triangleModel = new GeometryModel3D(
  4. triangleMesh,
  5. material);

Damit ist das 3D-Modell erstellt. Als nächstes muss dieses 3D-Model über ein ModelVisual3D in die 3D-Welt eingefügt werden:

  1. //Build the visual object
  2. ModelVisual3D model = new ModelVisual3D();
  3. model.Content = triangleModel;
  4.  
  5. //Add the object to the viewport
  6. this.m_viewport3D.Children.Add(model);

Wie schon weiter oben geschrieben ist das Ergebnis das Dreieck aus dem vorherigen Kapitel.

 

Koordinaten

Eine interessante Frage, die man sich an der Stelle natürlich auch noch stellen kann: Wie funktioniert das eigentlich mit dem Koordinatensystem in WPF? Grundsätzlich unterscheidet man im 3D-Grafik-Bereich zwischen dem linkshändigen und dem rechtshändigen Koordinatensystem. Folgende Grafik zeigt kurz die Koordinatenachsen dieser beiden Systeme:

 

 

In DirectX Anwendungen wird häufig das linkshändige System angewendet, in OpenGL dagegen vermehrt das rechtshändige. Unterm Strich liegt es aber meist am Entwickler, welches verwendet wird. In WPF wird das rechtshändige System verwendet.

 

Komplexe Objekte bauen

Das alles hört sich ja bis jetzt ganz gut und einfach an, bei komplexeren Objekten wie Kugeln, Quadern und Kegeln kann das manuelle Erzeugen aber schnell kompliziert werden. Wie man externe Objekte aus Dateien laden kann, beschreibe ich in Kapitel 4. Um komplexere Objekte direkt im Quellcode zu erzeugen gibt es leider keine Hilfestellungen in den Standard .Net Klassen. Dafür findet man aber im Internet viele kleine Beispiele, wie man etwa Kugeln und ähnliche Objekte erstellen kann. Bei AvalonSnake (Siehe Showcase auf meiner Seite) habe ich mir eine kleine Hilfsklasse geschrieben, welche für das Bauen von Objekten zuständig ist. Folgende Methode erleichtert beispielsweise das Erzeugen eines einfachen Rechtecks:

  1. private void BuildRectangle(
  2. MeshGeometry3D geometry,
  3. Point3D a, Point3D b, Point3D c, Point3D d,
  4. Vector3D normal)
  5. {
  6. int baseIndex = geometry.Positions.Count;
  7.  
  8. //Add vertices
  9. geometry.Positions.Add(a);
  10. geometry.Positions.Add(b);
  11. geometry.Positions.Add(c);
  12. geometry.Positions.Add(d);
  13.  
  14. //Add normals
  15. geometry.Normals.Add(normal);
  16. geometry.Normals.Add(normal);
  17. geometry.Normals.Add(normal);
  18. geometry.Normals.Add(normal);
  19.  
  20. //Add indices
  21. geometry.TriangleIndices.Add(baseIndex + 0);
  22. geometry.TriangleIndices.Add(baseIndex + 2);
  23. geometry.TriangleIndices.Add(baseIndex + 1);
  24. geometry.TriangleIndices.Add(baseIndex + 2);
  25. geometry.TriangleIndices.Add(baseIndex + 0);
  26. geometry.TriangleIndices.Add(baseIndex + 3);
  27. }

Was hier passiert ist schnell erklärt: An die übergebene Geometrie (MeshGeometry3D Objekt) wird ein Rechteck angehängt. Das Rechteck wird durch die Punkte a, b, c und d definiert, wobei jedem Punkt der gleiche Normal zugewiesen wird. Mithilfe dieser Methode kann jetzt recht einfach ein kleiner Würfel erzeugt werden:

  1. //Create the mesh geometry
  2. MeshGeometry3D triangleMesh = new MeshGeometry3D();
  3.  
  4. Point3D a = new Point3D(-2, -2, -2);
  5. Point3D b = new Point3D(2, -2, -2);
  6. Point3D c = new Point3D(2, -2, 2);
  7. Point3D d = new Point3D(-2, -2, 2);
  8. Point3D e = new Point3D(-2, 2, -2);
  9. Point3D f = new Point3D(2, 2, -2);
  10. Point3D g = new Point3D(2, 2, 2);
  11. Point3D h = new Point3D(-2, 2, 2);
  12. BuildRectangle(triangleMesh, a, b, f, e, new Vector3D(0, 0, 1));
  13. BuildRectangle(triangleMesh, b, c, g, f, new Vector3D(1, 0, 0));
  14. BuildRectangle(triangleMesh, c, d, h, g, new Vector3D(0, 0, -1));
  15. BuildRectangle(triangleMesh, d, a, e, h, new Vector3D(-1, 0, 0));
  16. BuildRectangle(triangleMesh, e, f, g, h, new Vector3D(0, 1, 0));
  17. BuildRectangle(triangleMesh, a, d, c, b, new Vector3D(0, -1, 0));

Um sich die Koordinaten besser vorstellen zu können, hier noch Bilder eines Rechtecks und eines Quaders mit den Punkt-Bezeichnungen, so wie oben im Quellcode.

 

 

Weiter geht's...

Objekte im Code zusammenzubauen ist also kein wirklich großes Problem, nur bei komplexeren Objekten kann das schnell umständlich und zeitaufwendig werden. Diverse kleine Hilfsmethoden helfen aber dabei, den Programmcode übersichtlich zu halten. Das nächste Kapitel beschäftigt sich nicht mehr mit der Geometrie, sondern mit den Oberflächen, denn hier zeigt sich eine Stärke von WPF: Neben Farbverläufen und Texturen können auch beliebige Oberflächensteuerelemente auf die 3D-Objekte gezeichnet werden.

 

Quellen

 

Siehe auch...

/p

 

Kommentar hinzufügen

Ihr Name:
Kommentar: