INTRODUCTIE

Original Author: Damiano Vitulli

Translation by: Click here

Als eerste zullen we het basis principe van een 3d engine uitleggen. De beschrijving van een 3d engine is relatief simpel:

een 3d engine is het geheel van structuren, functies en algoritmes om na een heleboel berekeningen een 3d object op een 2d vlak te projecteren.

Een 3d engine is opgebouwd uit 3 belangrijke onderdelen:

  1. De gegevens van objecten omzetten naar structuren
  2. De transformaties om een object in een virtuele wereld te positioneren
  3. Het geheel naar een 2d vlak renderen

In deze les zal uitgelegd worden hoe je de belangrijkste structuren van een 3d object definieert.

DE VERTICES

Het uitgangspunt is een 3d object dat je op een 2d scherm wilt laten zien. Om dit voor elkaar te krijgen heb je informatie nodig over de structuur van het object. Hoe verkrijgen we deze informatie? Als eerste gaan we een aantal belangrijke punten van een object definiëren, de vertices. Elke vertex bestaat uit drie coördinaten, namelijk: x,y en z. Elk coördinaat moet gedeclareerd worden als een float of double omdat we altijd de beste resolutie nodig hebben om de scène te renderen. Deze oplossing wordt in elke 3d engine gebruikt maar brengt wel een aantal problemen met zich mee. Zo wordt het zelfs onnauwkeuriger wanneer de numerieke waarde te hoog wordt. Dit limiteert de ruimte waarin de simulatie gaat werken. Omdat we in een ‘space simulator’ door een oneindig heelal willen kunnen vliegen komen we dit probleem sowieso tegen.

Om een vertex in C te definiëren gebruiken we een structuur die bestaat uit de 3 variabele x,y en z.

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

Het is belangrijk om te weten dat alle calculaties, posities en rotaties, berekeningen zijn die toegepast worden op de vertices. Vertices zijn dan ook de belangrijkste structuren. Na de vertex komt het polygoon qua belangrijkheid.

HET POLYGOON

De polygonen zijn de vlakken van een object en bestaan uit N vertices. In de meeste 3d engines bestaan polygonen uit 3 vertices, zo ook in deze tutorial.

Door middel van onze vertex_type structuur kunnen we een polygoon structuur maken die uit 3 vertices bestaat:

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

We zullen ook een polygon_type array declareren. Deze variabele zal worden gevuld met al de polygonen waaruit het object bestaat.

#define MAX_POLYGONS 2000
polygon_type polygon[MAX_POLYGONS];

Dit brengt echter een groot nadeel met zich mee. De huidige definitie gebruikt 3 vertices per polygoon en deze worden niet gedeeld met andere polygonen. Dit is dus geen ideale situatie omdat we nu veel meer vertices opslaan dan nodig. Eerder vertelde ik al dat de engine de meeste calculaties op vertices zal uitvoeren. We zullen dan ook een andere methode moeten bedenken om een polygoon te definiëren. Wat we kunnen doen is een lijst van vertices maken en verwijzen naar een vertex uit die lijst. Dit doen we door een array te declareren van het type vertex_type en zal MAX_VERTICES vertices bevatten.

#define MAX_VERTICES 2000
vertex_type vertex[MAX_VERTICES];

Op deze manier zal de polygoon structuur geen vertices meer bevatten maar enkel 3 nummers die verwijzen naar 3 elementen uit de vertices lijst. Zo kunnen meerdere polygonen naar dezelfde vertex verwijzen.

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

HET OBJECT

We zullen nu de hiervoor gemaakte code in een structuur obj_type stoppen.

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

Dit is de basis definitie. In de toekomst zullen we meerdere velden maken om de positie, rotatie en status van het object vast te leggen.

Nu kunnen we een object variabele gaan declareren en de vertices invullen.

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

De vraag is nu hoe de vierkanten op te delen in driehoeken. Het antwoord is simpel: ieder vlak van de kubus is een vierkant en bestaat uit 2 aan elkaar grenzende driehoeken. De kubus zal dus bestaan uit 12 polygonen (driehoeken) en 8 vertices.

Tut 3dengine cube.png

De lijst van polygonen komt er dan als volgt uit te zien:

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

Om de polygonen (driehoeken) op de juiste manier te definiëren moet je wel altijd dezelfde richting gebruiken. Wij zullen tegen de klok in nummeren. Zo kunnen we het eerste polygoon als volgt definiëren: v0,v1,v4 of v1,v4,v0 of v4,v0,v1. Maar v1,v0,v4 of v0,v4,v1 of v4,v1,v0 is in ons geval fout.

In de volgende tutorial zullen we zien hoe de richting bepalend is of een polygoon zichtbaar is of niet. Het is dus belangrijk dat je alle polygonen op dezelfde manier nummert.

Een nettere manier om een object te definiëren

In C/C++ kunnen we de obj_type structuur op een nettere manier vullen:

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
    }
};

Zoals je kunt zien zijn de coördinaten veranderd van 0,1 naar 10,-10. Dit is expres gedaan zodat het middelpunt van het object coördinaat 0,0 heeft. In de volgende tutorial zal uitgelegd worden dat dit nodig is. Als laatste zie je dat het object ook 20x zo groot is geworden. Dat is enkel gedaan om de ‘makkelijke’ coördinaten.

CONCLUSIES

Dat was dat voor de eerste les. In de volgende tutorial zullen we beginnen met het gebruik van OpenGL om de kubus op het scherm te tonen.

SOURCE CODE

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