EINFÜHRUNG

Original Author: Damiano Vitulli

Translation by: Click here

Zunächst ist es notwendig, das Basiskonzept zu erklären: was ist eine 3D-Engine? Die Antwort ist ziemlich einfach:

eine 3D-Engine ist eine Gesamtheit aus Strukturen, Funktionen und Algorithmen, die benutzt wird, um nach vielen Berechnungen und Transformationen, 3D-Objekte auf einem 2D-Bildschirm darzustellen.

Die Hauptbestandteile einer 3D-Engine sind:

  1. Die Aufnahme der Objektdaten in Strukturen.
  2. Die Transformationen zur Positionierung der Objekte in der Welt.
  3. Das Rendering auf den 2D-Bildschirm

Deshalb sind wir hier ... und sollten uns daher jetzt an die Arbeit machen!

In dieser Lektion werden wir lernen, wie man die Haupstrukturen definiert, die benötigt werden, um ein 3D-Objekt abzubilden.

DIE ECKEN

Angenommen Du hast ein Objekt und willst es auf einem 2D-Bildschirm darstellen. Um dies zu tun, ist es notwendig Informationen über dessen Struktur zu erhalten. Wie können wir das tun? Zunächst definieren wir einige Schlüsselstellen: die Ecken des Objekts. Jede Ecke besteht aus drei Koordinaten, x, y und z. Jede Koordinate muß durch eine FLOAT- oder eine DOUBLE-Variable ausgedrückt werden, da wir immer die beste Auflösung benötigen, um die Szene zu rendern. Diese Lösung wird in jeder 3D-Engine verwendet, ist aber nicht frei von Fehlern, sonder büßt vielmehr Präzision ein, wenn der numerische Wert zu hoch ist. Diese Tatsache beschränkt den Raum, in dem die Simulation arbeiten kann. In einem Weltraum-Simulator sollte es uns möglich sein, uns durch unendliche Räume zu bewegen, daher werden wir uns letztendlich mit diesem Problem befassen. Um eine Ecke in C zu definieren, verwenden wir eine aus drei Variablen x, y und z zusammengesetzte Struktur.

typedef struct {
   float x,y,z;
} vertex_type;

Es ist sehr wichtig, immer daran zu denken, daß alle Berechnungen, die sich mit der Positionierung und Rotation des Objekts befassen, auf die Ecken angewendet werden, da dies die Einheiten sind, die die grundlegende Struktur bestimmen. Die zweitwichtigste Struktur ist das Polygon.

DIE POLYGONE

Die Polygone sind die Flächen des Objektes und setzen sich aus N Ecken zusammen. In den meisten 3D-Engines bestehen Polygone aus 3 Ecken und wir übernehmen diese Konvention daher ebenfalls. Unter Verwendung unserer vertex_type-Struktur, können wir eine Polygon-Struktur definieren, die drei Ecken enthält:

typedef struct {
   vertex_type a,b,c;
} polygon_type;

Wir werden weiterhin eine polygon_type Array-Variable deklarieren, die mit all den Polygonen gefüllt wird, die ein Objekt ausmachen.

#define MAX_POLYGONS 2000
polygon_type polygon[MAX_POLYGONS]; 

Aber ... aufgepaßt! Unsere Definition ordnet jedem Polygon 3 Ecken zu und diese Ecken werden nicht mit den anderen Polygonen geteilt. Überhaupt, wenn wir eine Weile darüber nachdenken, werden wir einsehen, daß sich jedes Polygon eines Objektes tatsächlich seine Seiten und auch seine Ecken mit anderen Polygonen teilt. Wir haben daher einen Fehler begangen! Nun, es ist nicht wirklich ein Fehler, aber wir haben die reale Anzahl von Ecken in der Szene beträchtlich erhöht, weit über das hinaus, was notwendig ist. Wir haben schon gesagt, daß die Engine die Ecken nutzen wird, um die meisten ihrer Berechnungen auszuführen. Daher sollten wir wirklich eine andere Methode finden, die Polygone zu definieren. Wir könnten eine Liste von Ecken erstellen, die alle Ecken des gesamten Objektes enthält. Dann werden wir, um die Polygone zu definieren, eine Art Referenz-Schema benutzen, das auf die Ecken dieser Liste "zeigt". Wir deklarieren nun eine Array-Variable vom Typ vertex_type die MAX_VERTICES Ecken enthält.

#define MAX_VERTICES 2000
vertex_type vertex[MAX_VERTICES];

Die Polygon-Struktur wird nicht mehr die Ecken enthalten, sondern nur 3 Zahlen, die auf die 3 Elemente der Ecken-Liste zeigen. Auf diese Art und Weise können mehrere Polygone auf dieselbe Ecke verweisen. Das optimiert das Design der Engine sehr stark.

typedef struct {
   int a,b,c;
} polygon_type;

DAS OBJEKT

An dieser Stelle räumen wir ein bißchen auf und organisieren die vorangegangenen Definitionen in einer Struktur, die wir obj_type nennen.

typedef struct {
   vertex_type vertex[MAX_VERTICES];
   polygon_type polygon[MAX_POLYGONS];
} obj_type,*obj_type_ptr;

Das ist nur eine grundlegende Definition. In der Zukunft werden wir mehr Felder hinzufügen, die die Position, Rotation und den Zustand des Objektes identifizieren.

An dieser Stelle können wir die Objekt-Variable deklarieren und die Ecken-Liste füllen:

obj_type obj;
obj.vertex[0].x=0;  obj.vertex[0].y=0;  obj.vertex[0].z=0; //vertex v0
obj.vertex[1].x=1;  obj.vertex[1].y=0;  obj.vertex[1].z=0; //vertex v1
obj.vertex[2].x=1;  obj.vertex[2].y=0;  obj.vertex[2].z=1; //vertex v2
obj.vertex[3].x=0;  obj.vertex[3].y=0;  obj.vertex[3].z=1; //vertex v3
obj.vertex[4].x=0;  obj.vertex[4].y=1;  obj.vertex[4].z=0; //vertex v4
obj.vertex[5].x=1;  obj.vertex[5].y=1;  obj.vertex[5].z=0; //vertex v5
obj.vertex[6].x=1;  obj.vertex[6].y=1;  obj.vertex[6].z=1; //vertex v6
obj.vertex[7].x=0;  obj.vertex[7].y=1;  obj.vertex[7].z=1; //vertex v7

Das Problem besteht jetzt darin, unseren Kubus in Dreiecke zu unterteilen. Die Lösung ist einfach: jede Fläche des Kubus ist ein Quadrat, das aus zwei benachbarten Dreiecken zusammengesetzt ist. Unser Kubus besteht daher aus 12 Polygonen (Dreiecken) und 8 Ecken.

Tut 3dengine cube.png

Die Polygon-Liste muß folgendermaßen ausgefüllt werden:

obj.polygon[0].a=0;  obj.polygon[0].b=1;  obj.polygon[0].c=4;  //polygon v0,v1,v4
obj.polygon[1].a=1;  obj.polygon[1].b=5;  obj.polygon[1].c=4;  //polygon v1,v5,v4
obj.polygon[2].a=1;  obj.polygon[2].b=2;  obj.polygon[2].c=5;  //polygon v1,v2,v5
obj.polygon[3].a=2;  obj.polygon[3].b=6;  obj.polygon[3].c=5;  //polygon v2,v6,v5
obj.polygon[4].a=2;  obj.polygon[4].b=3;  obj.polygon[4].c=6;  //polygon v2,v3,v6
obj.polygon[5].a=3;  obj.polygon[5].b=7;  obj.polygon[5].c=6;  //polygon v3,v7,v6
obj.polygon[6].a=3;  obj.polygon[6].b=0;  obj.polygon[6].c=7;  //polygon v3,v0,v7
obj.polygon[7].a=0;  obj.polygon[7].b=4;  obj.polygon[7].c=7;  //polygon v0,v4,v7
obj.polygon[8].a=4;  obj.polygon[8].b=5;  obj.polygon[8].c=7;  //polygon v4,v5,v7
obj.polygon[9].a=5;  obj.polygon[9].b=6;  obj.polygon[9].c=7;  //polygon v5,v6,v7
obj.polygon[10].a=3; obj.polygon[10].b=2; obj.polygon[10].c=0; //polygon v3,v2,v0
obj.polygon[11].a=2; obj.polygon[11].b=1; obj.polygon[11].c=0; //polygon v2,v1,v0

Du mußt daran denken, daß es, um die Polygone sauber zu definieren, notwendig ist, immer dieselbe Richtung (im oder gegen den Uhrzeigersinn) für alle Polygone der Szene zu verwenden. Im nächsten Tutorial werden wir sehen, wie die Richtung verwendet wird, um zu kontrollieren ob das Polygon sichtbar ist oder nicht. Widme der Art und Weise Polygone zu definieren, daher viel Aufmerksamkeit, oder viele von ihnen werden nicht sichtbar sein. Wir werden die gegen-den-Uhrzeigersinn-Methode verwenden (das erste Polygon zum Beispiel ist definiert durch v0,v1,v4 or v1,v4,v0 or v4,v0,v1 und nicht v1,v0,v4 or v0,v4,v1 oder v4,v1,v0).

EIN ANDERER, ELEGANTERER WEG UNSER OBJEKT ZU DEFINIEREN

In C/C++ können wir die obj_type-Struktur auf folgende, elegantere Art und Weise, ausfüllen:

obj_type cube = 
{
    {
        -10,-10, 10, //vertex v0
         10,-10, 10, //vertex v1
         10,-10,-10, //vertex v2
        -10,-10,-10, //vertex v3
        -10, 10, 10, //vertex v4
         10, 10, 10, //vertex v5
         10, 10,-10, //vertex v6 
        -10, 10,-10  //vertex v7
    },  
    {
        0, 1, 4, //polygon v0,v1,v4
        1, 5, 4, //polygon v1,v5,v4
        1, 2, 5, //polygon v1,v2,v5
        2, 6, 5, //polygon v2,v6,v5
        2, 3, 6, //polygon v2,v3,v6
        3, 7, 6, //polygon v3,v7,v6
        3, 0, 7, //polygon v3,v0,v7
        0, 4, 7, //polygon v0,v4,v7
        4, 5, 7, //polygon v4,v5,v7
        5, 6, 7, //polygon v5,v6,v7
        3, 2, 0, //polygon v3,v2,v0
        2, 1, 0, //polygon v2,v1,v0
    }
};

Du kannst auch sehen, daß wir die Ecken-Koordinaten von 0,1 nach 10,-10 abänderten. Das geschah mit Absicht, denn das Zentrum unseres Objektes muß sich bei 0,0 befinden (weshalb werden wir im nächsten Tutorial verstehen). Unser Objekt ist auch 20 Mal größer (um die Koordinaten einfach verwalten zu können).

SCHLUSSFOLGERUNG

Das ist alles für diese Lektion. In der nächsten werden wir mit der Verwendung von Open GL beginnen, um unseren Kubus auf dem Bildschirm darzustellen.

SOURCE CODE

The Source Code of this lesson can be downloaded from the Tutorials Main Page