BBGL3D - Software-Renderer

Kommentare anzeigen Worklog abonnieren

Worklogs BBGL3D - Software-Renderer

3 Punkte, viel Arbeit und etwas für die Augen

Sonntag, 18. Oktober 2009 von Blitzkrieg Bop
Wie bereits am Ende des letzten Beitrages angedeutet, sollte nach der anfänglichen Theorie ein wenig Praxis folgen. Im Vergleich zum hier bereits vorgestellten Konzept hat sich dabei nicht viel geändert. Einzig die Ordnerstruktur wurde leicht angepasst: der Ordner BBGL3D_Error wurde hinzugefügt. In der im Ordner befindlichen *.bb Datei werden Fehlermeldungen ausgewertet. Außerdem habe ich die Entscheidung getroffen, Default-Werte und das Koordinatensystem (3D/2D) aus OpenGL beizubehalten bzw. zu übernehmen. Im 3D-Raum betrifft dies vor allem die Z-Achse (umgekehrtes Vorzeichen); im 2D-Raum liegt bspw. der Punkt (0,0) nicht wie in BB oben links, sondern unten links. Dies wird ein wesentlicher Unterschied zu BB3D sein - eventuell kann man später aber auch zwischen den Einstellungen wechseln können.

Mein Ziel war es, den Renderer bis zu diesem Zeitpunkt soweit zu realisieren, dass ein einfaches Dreieck gerendert wird. Dabei kam es mir nur darauf an, dass man die drei Eckpunkte des Dreiecks später auf dem Bildschirm zu sehen bekommt. Das mag zunächst relativ einfach klingen - und das ist es im Prinzip auch - doch bereits die früheren Versionen des Renderers waren ziemlich durchstrukturiert und die einzelnen Funktionen streng geordnet. Ich hatte also die Wahl: entweder schreibe ich den Code unflexibel, dreckig und unübersichtlich zusammen, sodass ich schnell das richtige Ergebnis begutachten kann oder ich bleibe von Anfang an sauber und folge der Strukturierung. Da letzteres für mich eine große Priorität hat, habe ich mich für eine dementsprechende Arbeitsweise entschieden. Aus diesem Grund ist der Umfang der benötigten Funktionen und Dateien, selbst für ein solch kleines Beispiel, bereits sehr groß.

Im Sinne der besseren Übersicht habe ich ein Changelog erstellt, in dem man die wesentlichen Änderungen ([ADD], [CHANGE], [DELETE] usw.) überblicken kann. Da ich es erst heute erstellt habe, fasse ich in der ersten Version den Zeitraum vom letzten Worklogeintrag (6.10) bis heute (18.10) zusammen. Das Changelog ist aufgeteilt in drei Bereiche: als Erstes werden Änderungen in der Ordnerstruktur angegeben. Danach folgen Informationen zur Dateiverwaltung. Zum Schluss gibt es einen Überblick über alle Funktionen. Die Dateien und Funktionen sind der Übersicht halber in die zugehörigen Bereiche (BBGL3D_Error, BBGL3D_Functions usw.) unterteilt. Mit Leerzeilen werden bei der Aufzählung der Funktionen zusammenhängende Funktionsblöcke dargestellt (z.B. alle Funktionen, die etwas mit der Kamera zu tun haben). Das Changelog sieht im Moment so aus:
Code: [AUSKLAPPEN]
Datum: 6.10.2009 - 18.10.2009
Version: 0.2

ORDNER:
[ADD]   BBGL3D_Debug
[ADD]   BBGL3D_Error
[ADD]   BBGL3D_Functions
[ADD]   BBGL3D_Maths
[ADD]   BBGL3D_Misc
[ADD]   BBGL3D_Variables

DATEIEN:
 BBGL3D_Debug:
   -
 BBGL3D_Error:
   [ADD]   BBGL3D_ERROR_Evaluate.bb
   [ADD]   BBGL3D_ERROR_Include.bb
 BBGL3D_Functions:
   [ADD]   BBGL3D_FUNCTIONS_Camera.bb
   [ADD]   BBGL3D_FUNCTIONS_Include.bb
   [ADD]   BBGL3D_FUNCTIONS_Mesh.bb
   [ADD]   BBGL3D_FUNCTIONS_Misc.bb
   [ADD]   BBGL3D_FUNCTIONS_Render.bb
   [ADD]   BBGL3D_FUNCTIONS_Surface.bb
   [ADD]   BBGL3D_FUNCTIONS_Triangle.bb
   [ADD]   BBGL3D_FUNCTIONS_Vertex.bb
 BBGL3D_Maths:
   [ADD]   BBGL3D_MATHS_Include.bb
   [ADD]   BBGL3D_MATHS_Matrix.bb
   [ADD]   BBGL3D_MATHS_Misc.bb
   [ADD]   BBGL3D_MATHS_Vertex.bb
 BBGL3D_Misc:
   -
 BBGL3D_Variables:
   [ADD]   BBGL3D_VARIABLES_Camera.bb
   [ADD]   BBGL3D_VARIABLES_Error.bb
   [ADD]   BBGL3D_VARIABLES_Include.bb
   [ADD]   BBGL3D_VARIABLES_Matrix.bb
   [ADD]   BBGL3D_VARIABLES_Mesh.bb
   [ADD]   BBGL3D_VARIABLES_Misc.bb
   [ADD]   BBGL3D_VARIABLES_Surface.bb
   [ADD]   BBGL3D_VARIABLES_Triangle.bb
   [ADD]   BBGL3D_VARIABLES_Vector.bb
   [ADD]   BBGL3D_VARIABLES_Vertex.bb

FUNKTIONEN:
 BBGL3D_Debug:
   -
 BBGL3D_Error:
   [ADD]   BBGL3D_ERROR_GetError(ErrorIndex%)
 BBGL3D_Functions:
   [ADD]   BBGL3D_CreateCamera()
   [ADD]   BBGL3D_CameraViewport(CameraIndex%, X%, Y%, Width%, Height%)
   [ADD]   BBGL3D_CameraRange(CameraIndex%, zNear#, zFar#)
   [ADD]   BBGL3D_CameraDepthRange(CameraIndex%, dNear#, dFar#)
   [ADD]   BBGL3D_CameraFOVY(CameraIndex%, FOVY#)
   [ADD]   BBGL3D_CameraAspect(CameraIndex%, Aspect#)

   [ADD]   BBGL3D_CreateMesh()

   [ADD]   BBGL3D_GetFPS()
   [ADD]   BBGL3D_Free()

   [ADD]   BBGL3D_RenderWorld(CameraIndex%)

   [ADD]   BBGL3D_CreateSurface(MeshIndex%)
   [ADD]   BBGL3D_CountSurfaces(MeshIndex%)

   [ADD]   BBGL3D_AddTriangle(SurfaceIndex%, Vertex0Index%, Vertex1Index%, Vertex2Index%)
   [ADD]   BBGL3D_CountTriangles(SurfaceIndex%)

   [ADD]   BBGL3D_AddVertex(X#, Y#, Z#, W# = 1.0)
   [ADD]   BBGL3D_VertexCoords(VertexIndex%, X#, Y#, Z#)
 BBGL3D_Maths:
   [ADD]   BBGL3D_MATHS_MATRIX_LoadIdentiy(Matrix.TBBGL3D_Matrix)
   [ADD]   BBGL3D_MATHS_MATRIX_Perspective(Matrix.TBBGL3D_Matrix, FOVY#, Aspect#, zNear#, zFar#)
   [ADD]   BBGL3D_MATHS_MATRIX_VectorMultiply(Matrix.TBBGL3D_Matrix, srcVector.TBBGL3D_Vector4D, destVector.TBBGL3D_Vector4D)
   
   [ADD]   BBGL3D_MATHS_MISC_Clamp#(Value#, Min#, Max#)

   [ADD]   BBGL3D_MATHS_VERTEX_ComputeEyeCoords(Vertex.TBBGL3D_Vertex)
   [ADD]   BBGL3D_MATHS_VERTEX_ComputeClippingCoords(Projection.TBBGL3D_Matrix, Vertex.TBBGL3D_Vertex)
   [ADD]   BBGL3D_MATHS_VERTEX_ComputeNormalizedDeviceCoords(Vertex.TBBGL3D_Vertex)
   [ADD]   BBGL3D_MATHS_VERTEX_ComputeScreenCoords(Camera.TBBGL3D_Camera, Vertex.TBBGL3D_Vertex)
 BBGL3D_Misc:
   -
 BBGL3D_Variables:
   -

Neben den Hinweisen auf die internen Implementierungen, dürften vor allem die Funktionen in der Rubrik BBGL3D_Functions interessant sein. Das sind die Funktionen, mit denen der Benutzer später direkt arbeiten wird. Alles andere sind interne Funktionen, die dem Benutzer im Grunde genommen egal sein können.

Man erkennt bereits die starke Anlehnung an den BB3D-Syntax. Dabei sind einige Funktionen natürlich (leicht) vereinfacht bzw. syntaktisch nicht 100% identisch. Außerdem erkennt man, dass es zusätzliche Funktionen geben wird, die BB3D von Haus aus nicht zur Verfügung stellt.

Die beiden Funktionen BBGL3D_CameraFOVY(CameraIndex%, FOVY#) und BBGL3D_CameraRange(CameraIndex%, zNear#, zFar#) müssen bei der Initialisierung einer Kamera in jedem Fall angegeben werden, da die zugehörige OpenGL Funktion (gluPerspective) diese Parameter erwartet und somit keine Default-Werte existieren.

Es wird also deutlich, was ich bereits in einem vorherigen Eintrag erläutert habe: das Projekt ist ein Kompromiss zwischen dem BB3D-Syntax und den zusätzlichen Möglichkeiten von OpenGL.

In Aktion sieht das Ganze dann wie folgt aus. Das Resultat (die drei Punkte) sind auch zu erkennen. Außerdem sieht man, wie leicht BBGL3D einzubinden ist:

user posted image

Zum Vergleich wird die Szene mit Basic4GL gerendert (das werde ich bei späteren Tests auch immer wieder machen):

user posted image

Man sieht: die Ergebnisse sind identisch (zum Glück Wink). Damit scheinen die ersten Strukturen, Berechnungen und der Ablauf der Pipeline einigermaßen vernünftig implementiert worden zu sein Smile .

Als nächstes wird es unter anderem darum gehen, dass Fehlersystem zu integrieren. Das ist bisher nicht geschehen, sollte aber dringend getan werden, damit der Benutzer auch mitbekommt, wenn er falsche Parameter übergibt.

Gruß,
Blitzkrieg Bop

Matrizen und Vektoren

Dienstag, 6. Oktober 2009 von Blitzkrieg Bop
Hallo!

Im vorletzten Beitrag habe ich bereits die Projektions-Matrix und ihre Rolle innerhalb von OpenGL erläutert. Matrizen spielen jedoch nicht nur bei der Projektion eine wichtige Rolle - darum soll sich der heutige Eintrag mit ein paar weiteren, wichtigen Matrizen auseinandersetzen.

Der Ausgangspunkt für die Überlegung weiterer Matrizen, ist die Frage, inwiefern man Objekte (in den meisten Fällen Meshes) manipulieren bzw. transformieren will. Dabei existieren vor allem drei Standardtransformationen: Skalierung, Rotation und Translation (Verschiebung).

Jede einzelne Transformation lässt sich mit Hilfe einer geeigneten 4x4 Matrix beschreiben. Mathematisch gesehen handelt es sich bei den Matrizen um sogenannte affine Abbildungen. Um die ganze Thematik komplett verstehen zu können, ist einiges an Mathematik notwendig - aus diesem Grund werde ich nicht näher auf mathematische Einzelheiten eingehen, sondern einfach die Matrizen angeben. Zu jeder Transformation habe ich den zugehörigen (kurzen) Artikel auf http://wiki.delphigl.com/ verlinkt. Dort sind auch Abbildungen vorhanden, welche die jeweilige Operation veranschaulichen.

Skalierung:
user posted image
(Quelle und Link zum Artikel: http://wiki.delphigl.com/index.php/glScale)

Rotation:
Code: [AUSKLAPPEN]
x²(1-c)+c    xy(1-c)-zs   xz(1-c)+ys   0
                                                           
yx(1-c)+zs   y²(1-c)+c    yz(1-c)-xs   0

xz(1-c)-ys   yz(1-c)+xs   z²(1-c)+c    0

    0            0            0        1

Wobei gilt:
Code: [AUSKLAPPEN]
c = cos(angle)
s = sin(angle)
length(x,y,z) = 1 (Falls nicht, normalisiert OpenGL den Vector vorher.)

(Quelle und Link zum Artikel: http://wiki.delphigl.com/index.php/glRotate)

Translation:
Code: [AUSKLAPPEN]
| 1  0  0  x |
| 0  1  0  y |
| 0  0  1  z |
| 0  0  0  1 |

(Quelle und Link zum Artikel: http://wiki.delphigl.com/index.php/glTranslate)

Anmerkung: DirectX sieht Vektoren als Zeilenvektoren an. Bei OpenGL ist es anders: hier liegen Vektoren in Form von Spalten vor. Insgesamt ist also nicht festgelegt, wie Matrizen bzw. Vektoren zu interpretieren sind. Man kann es prinzipiell machen, wie man will. Nur sollte man aufpassen, dass man von Anfang an ein klares System implementiert und sich auch konsequent daran hält. Vektoren sind in meinem Renderer ebenfalls Spaltenvektoren.

Wie werden diese Matrizen nun angewendet? Das ist relativ einfach: Soll ein Objekt (in diesem Fall ein Mesh) transformiert werden, so spielen die zugehörigen Matrizen und die Vertices (Ortsvektoren) des Meshes eine Rolle. Vereinfacht ausgedrückt: die alten Werte werden angepasst, indem die (alten) Vektoren mit der jeweiligen Matrix multipliziert werden.

Das ist allerdings nur eine sehr einfache Beschreibung des mathematischen Prozesses. In Wirklichkeit ist es so, dass die drei Matrizen (Skalierung, Rotation und Translation) zunächst zu einer "gesamten" Matrix verschmolzen werden. Diese Matrix wird als Modelviewmatrix bezeichnet. Die Matrix entsteht durch Multiplikation der anderen Matrizen. Es gilt:
Code: [AUSKLAPPEN]
Modelview = Translation * Rotation * Skalierung

Die Reihenfolge ist sehr wichtig (Matrizenmultiplikation ist nicht kommutativ). Folgender Link erklärt mit vielen Bildern, warum die Reihenfolge wichtig ist.

Die Modelviewmatrix kann dann mit den Vertices des Meshes verrechnet werden. Im weiteren Verlauf kann man sich zu jedem Zeitpunkt in einem anderen Raum befinden: die Koordinaten eines Vertex sind zunächst Objektkoordinaten (object space). Multipliziert man die Modelviewmatrix mit den Objektkoordinaten erhält man Augpunktkoordinaten (eye space). Die bereits vorgestellte Projektionsmatrix wird dann mit den Augpunktkoordinaten multipliziert: man erhält Clipping Koordinaten (clipping space). Von dort ist es nur ein kleiner Schritt hin zu den normalisierten Device Koordinaten. (NDC space) (X, Y und Z werden jeweils durch die W-Koordinate (siehe unten) dividiert). Nun werden die Viewport-Informationen verarbeitet, sodass man schließlich die Bildschirmkoordinaten (screen space) erhält (dieser Absatz lässt sich auch gut in den OpenGL FAQs nachlesen: http://www.opengl.org/resource...ations.htm Punkt 9.011 "How are coordinates transformed? ... ").

Anmerkung: Für die Skalierung und Rotation würden normalerweise 3x3 Matrizen ausreichen. Bei der Translation ist es nicht ganz so einfach: diese ist im R^3 nicht linear; kann aber im R^4 linear dargestellt werden. Daher werden die sogenannten homogenen Koordinaten eingeführt, sodass es sich bei der Translations-Matrix schließlich um eine 4x4 Matrix handelt. Dadurch, dass die Matrizen der Standardtransformationen miteinander multipliziert werden, müssen auch die Matrizen der Rotation und Skalierung neu dimensioniert werden (4x4). Wie oben bereits erläutert, spielt die Multiplikation von Matrizen und Vektoren eine wesentliche Rolle. Da Vektoren als Spaltenvektoren aufgefasst werden, sieht eine Matrix-Vektor-Multiplikation wie folgt aus:
Code: [AUSKLAPPEN]
neuerVektor = Matrix * alterVektor

"alterVektor" muss dabei genauso viele Zeilen, wie "Matrix" Spalten besitzt, enthalten. Und da ein (Spalten-)Vektor sowieso ein Spezialfall einer Matrix ist, werden auch hier die homogenen Koordinaten eingeführt. Genau genommen lässt sich ein Vertex also nicht anhand von drei Koordinaten (X, Y und Z) definieren, sondern auch noch durch eine weitere vierte (diese wird üblicherweise mit W bezeichnet). In den meisten Fällen ist W jedoch gleich 1, wird vom Benutzer innerhalb der Anwendung sehr selten verändert und spielt daher in der Regel kaum eine Rolle.

So, ich denke das war erstmal der letzte größere Theorieteil. Das bisherige Wissen reicht aus, um den ersten Grundstein des Codes zu legen.

Gruß,
Blitzkrieg Bop

Planung und Strukturen

Donnerstag, 1. Oktober 2009 von Blitzkrieg Bop
Zur Abwechslung soll sich im heutigen Eintrag nicht alles direkt um OpenGL drehen. Ich möchte ein wenig die Strukturen erläutern, nach denen BBGL3D aufgebaut sein wird.

Gerade bei etwas komplexeren Projekten ist eine durchdachte Planung unverzichtbar. Im BBGL3D-Projektordner wird es so sein, dass insgesamt fünf verschiedene Ordner existieren. Jeder Ordner beinhaltet die zugehörigen *.bb-Dateien, welche über ein Include-System leicht zu integrieren sind (jede Datei einzeln zu inkludieren, wäre im Sinne des Benutzers nicht sehr elegant). Die Ordner lauten bisher:

- BBGL3D_Variables: hier werden sämtliche Variablen/Strukturen definiert.
- BBGL3D_Maths: sämtliche Mathe-Funktionen.
- BBGL3D_Functions: hier befinden sich alle Funktionen, die direkt vom Benutzer verwendet werden.
- BBGL3D_Debug: ein temporärer Ordner, zum systematischen debuggen.
- BBGL3D_Misc: ein paar zusätzliche Funktionen (z.B. FPS-Counter).

Dabei sind die Bezeichnungen der einzelnen *.bb-Dateien durch feste Präfixe vorbestimmt. Das gleiche Prinzip trifft auf sämtliche Funktionen zu. Zur besseren Vorstellung ein paar Beispiele:

- Die Definition eines Vertex befindet sich in der Datei BBGL3D_VARIABLES_Vertex.bb, welche sich im Ordner BBGL3D_Variables befindet.

- Will man zwei Matrizen miteinander multiplizieren, so lautet die zugehörige Funktion BBGL3D_MATHS_MATRIX_Multiply; sie befindet sich in der Datei BBGL3D_MATHS_Matrix.bb im Ordner BBGL3D_Maths.

- Will der Benutzer eine Szene rendern, so wird die notwendige Funktion BBGL3D_RenderWorld heißen; sie befindet sich in der Datei BBGL3D_FUNCTIONS_Render.bb im Ordner BBGL3D_Functions.

Bisher hat sich dieses Verfahren ganz gut geschlagen. Es kann natürlich sein, dass ich noch ein paar Veränderungen einbaue, aber fürs Erste bleibt es wohl erstmal so.

Zum Abschluss noch ein paar Worte zur internen Struktur, also der Verwaltung von Vertices, Triangles usw.: ich werde versuchen, die Hierarchie von BB3D nachzuempfinden. Das ist insofern nützlich, als das man ein klares Muster hat, an dem man sich orientieren kann. Außerdem ist damit der Bezug zu BB3D hergestellt.

Nach der vielen Theorie am Anfang, folgen allmählich die ersten Näherungen hin zum konkreten Code Smile .

Bis zum nächsten Mal!

Gruß,
Blitzkrieg Bop

Betrachtung von 3D-Szenen

Dienstag, 29. September 2009 von Blitzkrieg Bop
Moin!

Nachdem es im letzten Worklogeintrag vor allem um Vertices und deren Verarbeitung innerhalb der Rendering-Pipeline ging, soll dieses Mal ein Blick auf die grundsätzliche Betrachtung von 3D-Szenen geworfen werden.

Möchte man 3D-Szenen darstellen, stellt sich automatisch die Frage, wie man die Wahrnehmung dieser Szenen realisieren kann. Dabei hat sich das Prinzip der "Kamera" durchgesetzt. Viele kennen dieses Verfahren sicherlich von BB3D: es wird eine Kamera erstellt, ggf. zugehörige Attribute (Position, Winkel usw.) angepasst, und schon lässt sich die aktuelle Szene durch diese Kamera betrachten (man spricht auch von einer Projektion der Szene).

Bei OpenGL verhält es sich ein wenig anders: im Grunde genommen existiert hier keine Kamera (insbesondere keine bewegliche). Man geht immer davon aus, dass eine gegebene Szene vom Ursprung (Koordinaten: X = Y = Z = 0; Blickrichtung: negative Z-Achse) aus betrachtet wird.

In BB3D wäre es ein leichtes, eine solche Kamera zu manipulieren. Man könnte z.B. die Position anpassen, indem man einfach den PositionEntity Befehl auf die Kamera anwendet. Sowas ist, wie bereits angedeutet, in OpenGL nicht möglich - zumindest nicht direkt. Um einen solchen Effekt ebenfalls umsetzten zu können, muss ein wenig getrickst werden. Soll beispielsweise die Kamera (auch wenn es, wie oben bereits angedeutet, im Grunde keine Kamera in OpenGL gibt, wird dennoch oftmals der Begriff der Kamera verwendet) um zwei Einheiten nach oben versetzt werden, lässt sich ein analoger Effekt dadurch erreichen, indem alle relevanten Objekte zwei Einheiten nach unten verschoben werden. Dieses Verfahren lässt sich auf alle relevanten Kamera-Attribute übertragen, sodass sich theoretisch ein flexibles Kamerasystem "vortäuschen" lässt (inwiefern ein solches Kamerasystem innerhalb von BBGL3D implementiert werden wird, ist noch offen).

Die tatsächliche Realisierung der jeweiligen Kamera erfolgt durch eine sogenannte Projektions-Matrix, in der alle relevanten Daten "verarbeitet" werden. OpenGL unterstützt orthogonale und perspektivische Projektion. Auch dies wird vielen von BB3D her bekannt sein: dort lässt sich mit Hilfe des Befehls CameraProjMode die Art der jeweiligen Projektion auswählen (orthogonal bzw. perspektivisch).

Die beiden Arten der Projektionen unterscheiden sich im Wesentlichen im Hinblick auf Tiefenhinweise: so erscheinen bei der orthogonalen Projektion weit entfernte Objekte genauso groß, wie nahegelegene. Im Gegensatz dazu steht die perspektivische Projektion, bei der weit entfernte Objekte kleiner wahrzunehmen sind, als nahe. Somit entspricht die perspektivische Projektion unserer natürlichen Wahrnehmung des 3D-Raumes und ist damit in den meisten (reinen) 3D-Anwendungen von größerer Bedeutung, als die orthogonale Alternative, welche vor allem bei 2D-Menüs usw. Verwendung findet (aus diesem Grund ist innerhalb von BBGL3D bisher nur die perspektivische Projektion integriert. Das wird vermutlich auch erstmal so bleiben).

Der ein oder andere wird sich vielleicht fragen, wo genau der Unterschied zwischen den Projektionssystemen liegt. Schließlich kann dieser kaum lediglich anhand der Werteverarbeitung innerhalb der jeweiligen Projektions-Matrix erfolgen! Dabei wurde bisher eine wichtige Vokabel verschwiegen: der Sichtbereich einer 3D-Szene. Je nachdem, wie dieser Bereich "organisiert" wird, lassen sich die gewünschten Effekte, wie z.B. der korrekte Tiefenhinweis bei der perspektivischen Projektion, realisieren. Dabei lässt sich der Sichtbereich bei orthogonaler Projektion durch einen Quader repräsentieren:
user posted image
(Quelle: http://www.glprogramming.com/red/chapter03.html)

Bei der perspektivischen Projektion handelt es sich um ein Frustum (Kegelstumpf):
user posted image
(Quelle: http://www.glprogramming.com/red/chapter03.html)

Man erkennt anhand der Bilder viele relevante Attribute der Kamera: dabei existieren stets sechs Ebenen (near-, far-, top-, bottom-, left- und right-plane), die eine sehr charakteristische Eigenschaft der Kamera sind. Relevant ist dies vor allem bei der Prüfung, ob ein Objekt überhaupt im sichtbaren Bereich der Kamera liegt (Frustum Culling). Bei der perspektivischen Projektion ist weitehrin der fovy (Field of View-Winkel (in Grad) entlang der Y-Achse) und der aspect, also das Verhältnis von Breite zu Höhe (dies sind oftmals die nötigen Viewport-Informationen), relevant:
user posted image
(Quelle: http://www.glprogramming.com/red/chapter03.html)

Mathematisch lassen sich die Matrizen der jeweiligen Projektionstypen wie folgt definieren:

Orthogonal:
user posted image
(Quelle: http://wiki.delphigl.com/index.php/glOrtho)


Perspektivisch:
user posted image
(Quelle: http://wiki.delphigl.com/index.php/gluPerspective)

Als letztes noch der Hinweis auf das zu Grunde liegende Koordinatensystem: im 3D-Raum wird oftmals zwischen dem left- und right-handed Koordinatensystem unterschieden. Zum Vergleich eine Abbildung:
user posted image
(Quelle: http://jccc-mpg.wikidot.com/ba...e-geometry)

Die Systeme unterscheiden sich also lediglich durch das Vorzeichen entlang der Z-Achse. BB3D, bzw. DirectX, verwendet das left-handed System - OpenGL die right-handed Variante. Letzteres ist bisher in BBGL3D implementiert; eventuell wird es eine Möglichkeit geben, die Systeme bei Bedarf umschalten zu können.

Damit endet der kurze Einblick in das Thema der Betrachtung von 3D-Szenen. Leider kann ich hier kaum näher ins Detail gehen, da der Umfang enorm wäre. Auch wird dies wohl nicht das einzige Thema bleiben, bei dem höchstens ein grober Überblick vermittelt werden kann - ich hoffe, dass dies einigermaßen gelungen ist. Wenn jemand weiteres Interesse hat, so findet man im Internet zu jedem einzelnen Thema mehr oder weniger viele Information (gerade wenn man die Fachbegriffe mit angibt).

Gruß,
Blitzkrieg Bop

Die OpenGL Rendering-Pipeline

Mittwoch, 23. September 2009 von Blitzkrieg Bop
Okay, wie bereits im ersten Eintrag angedeutet, dreht sich bei BBGL3D alles um die OpenGL Rendering-Pipeline. Aus diesem Grund möchte ich den Pipeline-Ablauf kurz skizzieren:

Zur Erinnerung: das Ziel ist es, Grafiken darzustellen. Diese Grafiken müssen in irgendeiner Form beschrieben und repräsentiert werden, sodass auch Manipulationen leicht möglich sind. Üblicherweise geschieht dies mit Hilfe von Vektoren. Oftmals wird der Begriff eines Vektors indirekt benutzt; man spricht eher von sogenannten Vertices (Mehrzahl von Vertex).
Ein Vertex beschreibt einen Punkt im Raum (viele werden es sicher von BB3D kennen: dort lässt sich mit Hilfe des Befehls "AddVertex" ein solcher Punkt definieren. In der weiteren Betrachtung wird immer vom 3D-Raum ausgegangen).

Hat man nicht nur einen Vertex, sondern mehrere Vertices, so lassen sich damit wunderbar Grafiken beschreiben. Dabei stellt jeder Vertex den Eckpunkt eines "Grafik-Objektes" dar. Bei BB3D verhält es sich beispielsweise so, dass ein Mesh, also ein Gitterobjekt, existiert. Dieses besteht aus sogenannten Surfaces (Oberflächen). Diese wiederum bestehen aus Triangles (Dreiecken). Diese bestehen jeweils aus drei Vertices. Somit sind Vertices der Grundstein jedes Grafik-Objektes und dementsprechend außerordentlich wichtig. Dabei kann jeder Vertex als Vektor (genauer: Ortsvektor) interpretiert werden, womit die mathematische Grundlage zur Beschreibung von 3D-Grafiken gegeben ist.

Mit diesem Wissen kann die Rendering-Pipeline anhand der drei wesentlichen Phasen erläutert werden:

(I) Vertexprozessor
Gegeben ist eine Menge an Vertices. Diese wird nun an den Vertexprozessor weitergeleitet und dort verarbeitet. Hier finden unter anderem alle notwendigen Transformationen (Rotation, Skalierung und Translation), sowie Lichtberechnungen usw. statt.
Da das zugrunde liegende 3D-Objekt ein beliebiges Polygon sein kann, müssen die vorhandenen Vertices dem Prozess der sogenannten Tesselierung unterzogen werden. Dabei wird das ursprüngliche, durch die Vertices definierte, Polygon in primitive Flächen (z.B. Dreiecke, Vierecke und Linien) zerlegt, da man mit diesen Flächen besser arbeiten kann. Grafikkarten sind auf einen Spezialfall der Tesselierung, die sogenannte Triangulierung, spezialisiert. Somit wird prinzipiell jedes Polygon (ab der Dimension eines Vierecks), im Sinne der Hardware, in Dreiecke zerlegt (Anmerkung: der Software-Renderer wird vermutlich auf die Tesselierung verzichten, da Dreiecke als absolute Grundlage angesehen werden (genau wie bei BB3D)).
Zu diesem Zeitpunkt wird jeder Vertex durch einen Ortsvektor und den zugehörigen Koordinaten (X, Y, Z) im 3D-Raum beschrieben. Das Ziel muss es sein, diesen 3D-Punkt auf die 2D-Ebene des Monitors zu transformieren. Genau das passiert in einem der letzten Schritte innerhalb des Vertexprozessors. Hier finden auch erste Tests statt, ob bestimmte Vertices überhaupt im Sichtbereich der Kamera liegen: ist dies nicht der Fall, so können die betroffenen Vertices ignoriert und die Performance unter Umständen enorm gesteigert werden.

(II) Scan Conversion (Rasterung)
Nachdem nun alle notwendigen (Eck-)Punkte berechnet und auf die 2D-Ebene projiziert wurden, findet der Prozess der Rasterung statt. Dabei geht es darum, "alle anderen notwendigen Punkte" zu bestimmen. Man darf nicht vergessen: die durch den Vertexprozessor bestimmten Punkte waren ursprünglich Vertices, also Eckpunkte eines Gitter-Objektes! Angenommen es soll eine Linie dargestellt werden: diese Linie besitzt genau zwei Eckpunkte (Vertices). Natürlich interessiert man sich nicht nur für diese beiden Eckpunkte, sondern auch für die Punkte zwischen ihnen, sodass (später) graphisch auch wirklich eine Linie zu erkennen ist. Um diese Art von Punkten geht es bei der Scan Conversion. Um bei dem Beispiel zu bleiben: anhand der gegebenen (und durch den Vertexprozessor auf die 2D-Ebene projizierten) Eckpunkte der Linie, lassen sich nun alle anderen Punkte durch Interpolation bestimmen. Diese Punkte werden Fragmente genannt.

(III) Fragmentprozessor
Im Prinzip hat man nun alles was man braucht: alle notwendigen Fragmente, die es darzustellen gilt, sind bekannt. Ähnlich wie beim Vertexprozessor gibt es aber auch hier noch einen Haufen an durchzuführenden Operationen, wie z.B. Texturzugriffe, Nebel usw. Es geht also im Grunde darum, die finalen Farben der Fragmente festzulegen.
Sind diese Operationen abgeschlossen, finden abschließend ein paar Tests statt. Dabei soll festgestellt werden, ob das jeweilige Fragment tatsächlich gezeichnet werden darf, oder nicht. Einer dieser Tests ist der Depth-Test: da jedes Fragment bei der vorherigen Interpolation auch einen Z-Wert erhalten hat, lassen sich somit mögliche Überdeckungen von unterschiedlichen Fragmenten leicht feststellen, sodass Fragmente ggf. verworfen werden können.
Erst wenn alle Tests bestanden sind, kann das Fragment gezeichnet werden.

So, das war der sehr grobe Entwurf der Rendering-Pipeline. Ich habe diverse Fachbegriffe und Vertiefungen weggelassen, da es absolut den Rahmen sprengen würde. Außerdem werden einige Themen später detaillierter behandelt. Auch auf Vertex-, Geometry- und Fragment-Shader gehe ich nicht weiter ein, da sie an dieser Stelle keine Rolle spielen (nur als kleine Anmerkung: in Wirklichkeit repräsentiert der hier beschriebene Pipeline-Ablauf eine sogenannte Fixed-Function-Pipeline. Dieser Begriff soll deutlich machen, dass der Programmierer keinen Einfluss auf den Ablauf hat und dementsprechend auch keine Manipulationen stattfinden können. Dieser Aspekt hat sich jedoch im Laufe der Zeit gewandelt, so besteht mittlerweile die Möglichkeit, den Ablauf (abgesehen von der Scan Conversion) durch geeignete Shader-Programme zu manipulieren. Die zugehörigen Berechnungen werden dann direkt auf der GPU (Shader-Einheiten) ausgeführt, wodurch viel Zeit einzusparen ist. Bei der aktuellen Hardware arbeitet man im Prinzip vollständig mit Shadern. Die hier beschriebene Fixed-Function-Pipeline wird also einfach in Form eines Shaders realisiert).

Tut mir ja fast schon Leid, so viel geschrieben zu haben. Hoffentlich fühlt sich keiner direkt erschlagen Confused . Aber ein wenig Theorie ist bei dieser Thematik leider notwendig bzw. ziemlich hilfreich. Vielleicht hat ja noch jemand was dabei gelernt!

Bevor ich es vergesse: vielen Dank für die ersten Kommentare. Das kann wirklich sehr motivieren Cool .

In diesem Sinne, bis zum nächsten Mal Smile .

Gruß,
Blitzkrieg Bop

Hey! Ho! Let's Go

Montag, 21. September 2009 von Blitzkrieg Bop
Hallo,
hiermit möchte ich euch mein Projekt BBGL3D vorstellen Smile .

Bei BBGL3D handelt es sich um einen Software-Renderer, welcher auf den Spezifikationen (Ablauf/Mathematik) der Grafik-API OpenGL basiert. Der Name des Projektes setzt sich aus den Worten Blitz Basic, OpenGL und einem hinzugefügten 3D zusammen.

Das Projekt ist mittlerweile ziemlich alt und schon relativ weit fortgeschritten, aber ich möchte es nochmal komplett neu aufsetzen und dabei die Gelegenheit nutzen, hier ein paar Schritte festzuhalten.

Für diejenigen, die mit den Begriffen (noch) nicht so viel anfangen können, fasse ich kurz ein paar Informationen zusammen:

Question Was ist ein Software-Renderer?
Da kann ich am besten auf den kurzen Wiki-Eintrag verweisen, in dem viele wesentliche Aspekte genannt werden. Im Grunde geht es darum, Grafikberechnungen komplett durch die CPU durchführen zu lassen. Die Grafikkarte spielt in diesem Prozess also nur eine untergeordnete Rolle, da sie lediglich die von der CPU berechneten Daten an den Monitor leitet.

Question Nichts für Frames-Fetischisten!
Somit wird schnell deutlich, dass dies kein Projekt für Frames-Fetischisten ist. Je nachdem, um welche Szene es sich handelt, müssen hunderte, wenn nicht sogar tausende Pixel in Bezug auf Farbe, Position, usw., individuell berechnet werden. Die Frames sind dann unter Umständen sehr schnell im Keller.

Question Was ist OpenGL?
Auch hier verweise ich auf den zugehörigen Wikipedia-Artikel. Kurz gesagt: OpenGL stellt eine Schnittstelle zur Grafikhardware dar. Mit Hilfe von OpenGL lassen sich 2D/3D-Grafiken relativ leicht rendern. Dabei ist die Verarbeitung der Daten (OpenGL Rendering-Pipeline (siehe z.B.: http://www.cs.kent.edu/~farrel...l/pipe.gif)) von wesentlicher Bedeutung, da sich der Software-Renderer an dieserm Ablauf und den zugehörigen Umsetzungen orientiert.

Question Wie ist der Software-Renderer strukturiert?
Ich versuche einen Kompromiss zwischen dem BB3D-Syntax und den zusätzlichen Möglichkeiten von OpenGL zu finden. Wie das genau aussehen wird, kann man dann am besten anhand der ersten Beispielen sehen, sobald diese fertig sind.

Question Was wird möglich sein? Wie weit führt das Projekt?
Das ist leider nicht so leicht zu beantworten. Ich weiß aus der bisherigen Erfahrung, dass es einige Probleme (z.B. Rechengenauigkeit) geben wird, die nur schwer (mit BB) zu umgehen sind. Man wird sehen, inwiefern man diese Probleme beheben kann. Ein weiterer Aspekt ist die Umsetzung der einzelnen Prozesse der Rendering-Pipeline: sicher ist es unmöglich auch nur ansatzweise den kompletten OpenGL-Ablauf/Umfang zu implementieren. Das wäre ein gigantisches Unterfangen. Es wird also ganz klare Grenzen geben. Wo diese liegen werden, kann ich im Moment noch nicht genau sagen.

Question An wen richtet sich das Projekt?
In erster Linie richtet sich das Projekt an diejenigen, die einen Blick hinter die 3D-Kulisse werfen wollen. Gerade in Zeiten von hochauflösenden und immer detaillierteren (3D-)Grafiken, ist so eine Betrachtung ganz interessant, denke ich. Zumindest hat es mich seit Beginn des Projektes (und auch weiterhin) sehr fasziniert Smile.

Das Projekt wird vollständig mit Hilfe von Blitz Basic 3D realisiert. Da jedoch keinerlei 3D-Befehle genutzt werden, sollte es auch BB2D und B+ kompatibel sein.

Question Warum wird das Projekt in Blitz Basic umgesetzt?
Im Grunde ist es nicht sehr schlau, ein solches Projekt in Blitz Basic umzusetzen. Schließlich muss man auf viele nützliche Strukturen verzichten, die man von anderen Hochsprachen kennt. Allerdings ist das Projekt von Anfang an ein Blitz Basic Projekt gewesen. Sowohl die Sprache, als auch das Projekt bedeuten mir viel. Ich möchte es daher unbedingt in Blitz Basic zu Ende bringen (vielleicht wird es das letzte größere Projekt in dieser Sprache sein – man wird es sehen). Außerdem ist es auch ein gewisser Reiz, mit den "paar Möglichkeiten", die einem Blitz Basic bietet, ein solches Projekt aufzubauen.

Das war es auch schon - der erste Eintrag ist fertig. Ich hoffe der zweite folgt bald, denn leider lässt sich nicht sagen, wie kontinuierlich ich an dem Projekt in Zukunft arbeiten kann. Gerade wenn das Stuidum bald weitergeht, wird es von der Zeit her sicher wieder sehr eng.

Vielen Dank fürs Lesen!

Gruß,
Blitzkrieg Bop

P.S.: An dieser Stelle möchte ich dem User Vertex sehr danken, da er "damals", zu Beginn des Projektes, sehr Hilfsbereit war und mir somit den Einstieg in die Thematik wesentlich erleichtert hat. Danke, Alter! Wink