Welkom! Dit is het derde deel van onze serie over 3D grafische engines. Als je dit tot ver in de serie hebt gemaakt, zul je blij zijn te weten dat dit stuk veel lichter zal zijn in het wiskundige aspect van 3D-engines, en in plaats daarvan meer aandacht zal besteden aan meer praktische dingen - in het bijzonder het toevoegen van een camera en een eenvoudig weergavesysteem.
Tip: Als je de eerste twee delen nog niet hebt gelezen, raad ik je ten zeerste aan dit te doen voordat je doorgaat.
U kunt ook extra hulp krijgen bij Envato Studio, waar u kunt kiezen uit een breed scala aan hoogwaardige 3D-ontwerp- en modelleringservices van ervaren providers.
3D-ontwerp- en modelleringsdiensten op Envato StudioLaten we eerst eens kijken naar de klassen die we tot nu toe hebben gemaakt:
Puntklasse variabelen: num tuple [3]; // (x, y, z) Operators: Point AddVectorToPoint (Vector); Point SubtractVectorFromPoint (Vector); Vector SubtractPointFromPoint (punt); Null SetPointToPoint (punt); // verplaats punt naar gespecificeerd punt Functies: drawPoint; // teken een punt op de positie tuple Vector Class Variables: num tuple [3]; // (x, y, z) Operators: Vector AddVectorToVector (Vector); Vector SubtractVectorFromVector (Vector); Vector RotateXY (graden); Vector RotateYZ (graden); Vector RotateXZ (graden); Vectorschaal (s0, s1, s2); // params: schalen langs elke as
Alleen al het gebruik van deze twee klassen is tot nu toe een beetje rommel gebleken en het tekenen van elk mogelijk punt kan het geheugen van je systeem vrij snel leegmaken. Om deze problemen op te lossen introduceren we een nieuwe klasse in onze game-engine: de camera.
Onze camera wordt waar al onze rendering gebeurt, exclusief; het gaat ruiming al onze objecten op het scherm, en het gaat ook een lijst van al onze punten beheren.
Maar voordat we dit allemaal kunnen bereiken, moeten we eerst een beetje praten over ruimen.
Schillen is per definitie de selectie van objecten uit een grotere groep objecten. Voor onze game-engine is de kleine selectie die we maken de punten die we naar het scherm willen trekken. De grotere groep objecten zal elk punt zijn dat bestaat.
Als u dit doet, wordt de afvoer van uw motor in het geheugen van een systeem drastisch beperkt door alleen te kijken wat een speler daadwerkelijk kan zien, in plaats van een hele wereld aan punten. In onze engine gaan we dit doen door parameters in te stellen voor a bekijk ruimte.
Onze kijkruimte wordt gedefinieerd op alle drie de traditionele assen: x, y en z. De x-definitie zal bestaan uit alles tussen de linker- en rechtergrenzen van het venster, de y-definitie zal bestaan uit alles tussen de boven- en ondergrenzen van het venster en de z-definitie zal tussen 0
(waar de camera is ingesteld) en de kijkafstand van onze speler (voor onze demonstratie gebruiken we een willekeurige waarde van 100
).
Voordat we een punt tekenen, gaat onze cameraklasse controleren of dat punt in onze kijkruimte ligt. Als dat zo is, wordt het punt getrokken; anders zal het niet.
Met dat basiskennis van ruiming kunnen we afleiden dat onze klas er tot nu toe zo uitziet:
Cameraklasse Vars: int minX, maxX; // minimum- en maximumgrenzen van X int MinY, maxY; // minimum- en maximumgrenzen van Y int minZ, maxZ; // minimum- en maximumgrenzen van Z
We gaan onze camera ook alle rendering voor onze engine laten afhandelen. Afhankelijk van de engine ziet u dat de renderers vaak worden gescheiden van de camerasystemen. Dit wordt meestal gedaan om de systemen mooi ingekapseld te houden, omdat - afhankelijk van de omvang van uw motor - de twee nogal rommelig kunnen worden als ze bij elkaar worden gehouden. Voor onze doeleinden zal het echter eenvoudiger zijn om ze als één te behandelen.
Eerst willen we een functie die extern kan worden aangeroepen door de klas die de scène zal tekenen. Deze functie doorloopt alle punten die er zijn, vergelijkt ze met de ruimingsparameters van de camera en tekent ze indien van toepassing.
Tip: Als u uw camerasysteem van uw renderer wilt scheiden, kunt u eenvoudig een renderer
klasse, laat het camerasysteem de punten halen, sla degenen op die in een array moeten worden getekend en verzend vervolgens die array naar de trek()
functie van uw renderer.
Het laatste stukje van onze cameraklasse wordt het puntbeheersysteem. Afhankelijk van de programmeertaal die u gebruikt, kan dit gewoon een eenvoudige array zijn van alle objecten die kunnen worden getekend (we zullen meer verwerken dan alleen punten in latere delen). Als alternatief moet u mogelijk de standaard object bovenliggende klasse van de taal gebruiken. Als je super ongelukkig bent, moet je je eigen object-ouderklasse maken en elke tekenbare klasse (tot nu toe alleen punten) een kind van die klasse zijn.
Nadat je dat in de klas hebt toegevoegd, ziet een basisoverzicht van onze camera er als volgt uit:
Cameraklasse Vars: int minX, maxX; // minimum- en maximumgrenzen van X int MinY, maxY; // minimum- en maximumgrenzen van Y int minZ, maxZ; // minimum- en maximumgrenzen van Z-array objectsInWorld; // een array van alle bestaande objecten Functies: null drawScene (); // tekent alle benodigde objecten op het scherm, retourneert niets
Laten we met deze toevoegingen iets verbeteren aan het programma dat we de vorige keer hebben gemaakt.
We gaan een eenvoudig puntentekeningprogramma maken, met het voorbeeldprogramma dat we de vorige keer als vertrekpunt hebben gemaakt.
In deze iteratie van het programma gaan we het gebruik van onze nieuwe cameraklasse toevoegen. Wanneer de D toets wordt ingedrukt, zal het programma het scherm opnieuw tekenen zonder te ruimen, en wordt het aantal objecten weergegeven dat in de rechterbovenhoek van het scherm werd weergegeven. Wanneer de C toets wordt ingedrukt, het programma hertekent het scherm met ruimen, en toont ook het aantal gerenderde objecten.
Laten we de code eens bekijken:
main // setup voor je favoriete grafische API hier // setup voor toetsenbordinvoer (is mogelijk niet nodig) var camera = new Camera (); // maak een instantie van de cameraklasse camera.objectenInWorld [100]; // maak 100 objectruimten binnen de array van de camera // stel de camera in voor de beeldruimte camera.minX = 0; camera.maxX = schermbreedte; camera.minY = 0; camera.maxY = schermhoogte; camera.minZ = 0; camera.maxZ = 100; voor (int x = 0; x < camera.objectsInWorld.length; x++) //Set its location to a random point on the screen camera.objectsInWorld[x].tuple = [random(-200,1000), random(-200,1000), random(-100,200)); function redrawScreenWithoutCulling() //this function clears the screen and then draws all of the points ClearTheScreen(); //use your Graphics API's clear screen function for(int x = 0; x < camera.objectsInWorld.length; x++) camera.objectsInWorld[x].drawPoint(); //draw the current point to the screen while(esc != pressed) // the main loop if(key('d') == pressed) redrawScreenWithoutCulling(); if(key('c') == pressed) camera.drawScene(); if(key('a') == pressed) Point origin = new Point(0,0,0); Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++) //store the current vector address for the point, and set the point tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added camera.objectsInWorld[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(0.5,0.5,0.5)); if(key('s') == pressed) Point origin = new Point(0,0,0); //create the space's origin as a point Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++) //store the current vector address for the point, and set the point tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added camera.objectsInWorld[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location camera.objectsInWorld[x].addVectorToPoint(tempVector.scale(2.0,2.0,2.0)); if(key('r') == pressed) Point origin = new Point(0,0,0); //create the space's origin as a point Vector tempVector; for(int x = 0; x < camera.objectsInWorld.length; x++) //store the current vector address for the point, and set the point tempVector = camera.objectsInWorld[x].subtractPointFromPoint(origin); //reset the point so that the scaled vector can be added camera.objectsInWorld[x].setPointToPoint(origin); //scale the vector and set the point to its new, scaled location camera.objectsInWorld[x].addVectorToPoint(tempVector.rotateXY(15));
Nu kun je uit de eerste hand de kracht van het ruimen zien! Houd er rekening mee dat als u de voorbeeldcode bekijkt, sommige dingen iets anders worden gedaan om de demo's webvriendelijker te maken. (Je kunt hier mijn eenvoudige demo bekijken.)
Met een camera en rendering-systeem onder je riem kun je technisch zeggen dat je een 3D-game-engine hebt gemaakt! Het is misschien nog niet zo indrukwekkend, maar het is onderweg.
In ons volgende artikel zullen we kijken naar het toevoegen van een aantal geometrische vormen aan onze engine (namelijk lijnsegmenten en cirkels), en we zullen praten over de algoritmen die kunnen worden gebruikt om hun vergelijkingen op de pixels van een scherm aan te passen.