Archiv für die Kategorie ‘Engine’

Kleiner Entwicklungsrückblick

Montag, 15. November 2010

Manchmal finde ich mich selbst etwas frustriert, wenn ich darüber nachdenke, wie lange ich bereits an dem Projekt Cyber E-Razor rockt die Galaxis arbeite und wie lange ich vermutlich noch daran arbeiten werde. Nämlich sehr lange. Das liegt vor allem an zwei Gründen, da mache ich mir nix vor: Zum Einen ist das halt einfach extrem aufwändig, so ein Projekt umzusetzen — und das auch noch alleine. “Normalerweise” entwickeln hunderte von Menschen über viele Jahre an einem Spiel und bringen einen Arbeitsaufwand ein, den ich alleine in mein Projekt stecke. Das führt uns zu Punkt zwei, denn zum Anderen ist es so, dass ich nicht nur das eigentliche Spiel Cyber E-Razor rockt die Galaxis erfinde und erbaue, sondern alles auf dem Weg dorthin selbst programmiere. In den Augen einiger Leute dürfte das relativ dämlich wirken. Aber bei diesem Hobbyprojekt geht es — wie schon an der einen oder anderen Stelle angesprochen — ja gar nicht (nur) um das fertige Produkt, was am Ende (hoffentlich) dabei rauskommt. Sondern bei diesem Hobbyprojekt ist der Weg das eigentliche Ziel. Und wenn man sich diesen Weg mal vor Augen hält, ist der tatsächlich gar nicht so unproduktiv. Im Gegenteil: Eigentlich habe ich im Rahmen der Entwicklung von Cyber E-Razor rockt die Galaxis und der zu Grunde liegenden “Engine” viele kleine bis mittelgroße oder sogar große (Teil-)Projekte erarbeitet und teilweise bereits fertig abgeschlossen. Mit Frust mischt sich dann vielleicht doch ein wenig Stolz, wenn ich mir die folgende Liste anschaue:

  • Eine umfangreiche Mathebibliothek für die Rechnung mit und Transformation von Vektoren und Matrizen;
  • Eine eigene Programmiersprache inklusive Schnittstelle zu C++;
  • Eine Physik-Engine für die Interaktion von Starrkörpern;
  • Ein DirectX-Wrapper-Framework mit Ressourcenverwaltung und Zusatzfunktionalität;
  • Ein XML-System zum Lesen, Schreiben, Verarbeiten und Generieren von XML-Bäumen;
  • Ein umfangreiches konfigurierbares GUI-System.

Im Moment bin ich noch auf der Suche nach einem “neuen” Werkzeug zum Zählen der Code-Zeilen etc. Denn das vorherige Visual-Studio-Addin kann ich leider bis auf Weiteres nicht mehr benutzen. Es müssten mittlerweile ca. 150.000 Zeilen (gesamt) sein. Bis bald.

Bald geht es endlich mit dem World-Editor weiter…

Freitag, 24. September 2010

So, Freunde! Es ist zwar wahr, dass hier nicht gerade viel passiert und dies leider auch ein wenig die tatsächliche Entwicklung widerspiegelt, die im Hintergrund stattfindet. Jedoch bin ich nicht ganz untätig. Das geplante und bereits schon einmal angesprochene XML-System ist fertig und bereits integriert: GUI-Elemente müssen jetzt nicht immer zwangsläufig im eigentlichen Code programmiert werden, sondern lassen sich nun über eine Spezifikation in XML definieren. Lediglich für die Logik muss dann noch ausführbarer Programmcode her, der aber gerne alternativ zu C++ auch in Ernie-Script verfasst werden kann.

Ansonsten beschäftige ich mich gerade mit einer kleinen Zusatzkomponente, die es unterstützt, mehrere kleinere Grafikdateien zu laden und diese möglichst “platzsparend angeordnet” in eine große Textur zu packen. Den Grund hierfür erkläre ich euch natürlich: Fakt ist, dass ein ständiger Wechsel des “States” der Grafikkarte enormen negativen Einfluss auf die Performance nimmt. Das bedeutet, je seltener der Zustand der Grafikkarte geändert wird — z.B. durch das Wechseln der aktuellen Textur oder Setzen von Shader-Optionen —, desto schneller läuft das gesamte Spiel. Also bietet es sich an, häufig verwendete Pixelgrafiken gebündelt in eine Textur bzw. ein paar wenige Texturen zu packen, weil somit die aktuell gesetzte Textur nicht unbedingt gewechelt werden muss, wenn verschiedene Grafiken dargestellt werden sollen. Gut, gar kein Problem: Ich nehme mir ein Bildbearbeitungsprogramm und klatsche die Grafiken einfach neben- und untereinander in diese Textur in Form einer größeren Pixelgrafik. Blöd wird es erst, wenn plötzlich unvorhergesehen viele neue Grafiken hinzukommen. Jedesmal muss die Textur manuell bearbeitet oder vielleicht sogar eine weitere Textur angelegt werden. Das muss ja nicht nur über ein beliebiges Bildbearbeitungsprogramm mühselig aktuell gehalten werden, sondern entsprechende Texturen und die Koordinaten der einzelnen Bilderchen innerhalb der Texturen müssen im Programmcode gepflegt und verwaltet werden. Der Trick ist also, dies alles zu automatisieren.

Insbesondere für den World-Editor, der ja seit ca. einem Jahr die eigentliche Hauptbaustelle darstellt, werden viele kleine Bilderchen benötigt, die die verschiedensten Bearbeitungs- und Einstellungsmöglichkeiten repräsentieren — vergleichbar mit den Toolbars und Shortcut-Leisten üblicher Computerprogramme, wie sie wohl so gut wie jeder kennt. Wenn es jetzt bald losgeht, dass ich hier zack-auf-zack neue Features dem World-Editor hinzufügen werde, dann muss ich mich so nicht mehr um eine geeignete Organisation der kleinen Grafiken kümmern. Das passiert alles automatisch. Die Grafiken bleiben dann sogar in ihren Originaldateien erhalten und sind leicht austauschbar und vor allem erweiterbar.

Hierfür muss ich jetzt noch ein paar Zeilen Code schreiben. Ganz trivial ist die Lösung des Problems übrigens nicht. Das Stichwort hierzu ist “Bin-Packing” bzw. “2D-Bin-Packing”, wofür es eine überschaubare Anzahl an ganz vernünftigen Ansätzen, aber leider keine “Killer-Lösung” gibt. Das Problem ist nämlich NP-hart und selbst die Branchen, in denen die Kohle steckt, haben mit dem Finden einer jeweils geeigneten Lösung zu kämpfen — z.B. um ein möglichst optimales Design eines Mikrochips zu realisieren oder einen LKW möglichst “geeignet” zu beladen, wofür jeweils Bin-Packing-Algorithmen angewendet werden. Ich bediene mich jedoch einer relativ einfachen Variante des 2D-Bin-Packings. Entsprechende Funktionalität kann ich ja bei Bedarf später noch optimieren.

TIGHT GANG!!

Cyber lernt laufen

Freitag, 24. Juli 2009

Juhu, heute gibt es ein neues Video plus einen neuen Download. Denn Cyber hat endlich laufen gelernt. Das bedeutet, dass ich nun ein Animationssystem für die 3D-Modelle gebaut habe. Zum Testen habe ich mich an einer kleinen Animation von Cyber versucht, wie er läuft — und zwar sehr easy und entspannt und überaus cool, wie alle finden. Diese Animation basiert komplett auf ganzrationalen sowie trigonometrischen Funktionen. Üblicherweise werden solche Animationen zwar durch Key-Frames realisiert: Das konkrete Aussehen des 3D-Modells wird für bestimmte Zeitpunkte der Animation durch Drehen und Verschieben der einzelnen Joints (Gelenke des “Skeletts” im 3D-Modell) modelliert. Das Aussehen der dazwischen liegenden Zeitpunkte wird dann per Interpolation berechnet. Diesen Key-Frame-basierten Ansatz wird mein Animationssystem später vielleicht auch noch unterstützen. Aber bislang werden die Rotationen und Verschiebungen der Joints durch Funktionen abgebildet (Stichwort Forward Kinematics). Die Resultate sehen so nämlich zumindest zum Teil viel schöner aus als die einer (linearen) Interpolation. Zieht euch die erste Testanimation rein:

Get the Flash Player to see the wordTube Media Player.

Cyber ist jawohl die coolste Sau, oder?

Als nächstes werde ich wohl kleinere “Baustellen” der Engine ausbessern und noch eine auf Ernie-Script-basierende Debugging-Konsole  einbauen, wie man sie sonst eher von Ego-Shootern kennt. (Der Begriff Engine gefällt mir ja eigentlich nicht. Aber wie will man das sonst nennen? Framework? Ist doch auch kacke, zumal es ja mehr als “nur” ein Framework ist. Naja, egal.) Aber so eine Konsole ist überaus nützlich. Sie enthält zum Beispiel auch sämtliche Einträge, die in die Log-Datei geschrieben werden. Wie dem auch sei — dann ist der allgemeine Teil auch hoffentlich bald abgeschlossen, so dass ich mit den ersten “Cyber-spezifischen Features” anfangen kann. Bor, da liegt aber auch noch ein ordentlicher Batzen vor mir, sage ich euch! Schubidu.

Ernie-Script (II/II)

Dienstag, 07. Juli 2009

Heute gibt es wieder einen Download. Dabei handelt es sich um eine Testanwendung zum Ausprobieren der neu entwickelten Skriptsprache Ernie-Script: Hier bin ich! Yeah, Baby! Ernie-Script vereint mehrere Eigenschaften verschiedener Sprachen. Im Allgemeinen folgt die Sprache der C++-Syntax. Zuweisungen ähneln aber eher denen, wie sie mit dem :=-Operator in Pascal zu finden sind. Die Konkatenation von Strings über zwei Punkte (..) mag der ein oder andere von Lua kennen. Um nur ein paar Aspekte zu nennen.

Beim Start der Anwendung öffnet sich ein Windows-Konsolen-Fenster mit etwas Text und einer Eingabeaufforderung. Wenn man jetzt zum Beispiel who; eintippt, wird der gesamte Inhalt des globalen Namensraums ausgegeben:

Allriggedy Right und Tiggedy Tight! Da sind ja bereits zwei Objekte std und math vom Typ Table. Bei denen handelt es sich um vordefinierte Funktionsbibliotheken (auf C++-Basis). Die Anweisung std.out( "Hello World!" ); ruft nun zum Beispiel die Funktion out der Bibliothek std auf. Bei dieser Anweisung handelt es sich dann auch schon um ein vollwertiges Hello-World-Skript. Neben der soeben angesprochenen Funktion beinhaltet jene “Standardbibliothek” bislang keine weiteren Features von Bedeutung. Die bisherigen Funktionen der Mathebibliothek sind die folgenden:

abs ceil floor exp log
log10 fmod sin cos tan
sinh cosh tanh asin acos
atan atan2 sqrt pow

Es besteht natürlich auch die Möglichkeit, eigene, auf Ernie-Script basierende Funktionen zu definieren. Im folgenden Beispiel wird eine Funktion zur Berechnung der Quadratwurzel definiert, dessen entscheidenden Code wir bereits bei der Vorstellung des Syntaxbaums und des Builders kennen gelernt haben:

Das Skript zur Definition von my_sqrt erstreckt sich über mehrere Zeilen. Hierfür mussten am Zeilenende jeweils drei Punkte eingefügt werden, sofern das Skriptende noch nicht erreicht war. Das ist nicht Ernie-Script-spezifisch sondern ein Feature der Testanwendung, bei der es sich schließlich nur um eine einfache Konsolenanwendung und nicht um eine volle IDE mit komfortablem Texteditor handelt. (Aus Sicht von Ernie-Script existieren Zeilenumbrüche oder Sachen dieser Art sowieso nicht.) Ein kleiner Test der Funktion my_sqrt zeigt ihr korrektes Verhalten (zumindest für die Zahl 2, hahaha).

Da die vordefinierten Bibliotheken nix weiter als normale Ernie-Script-Objekte vom Typ Table sind, können sie auch nachträglich noch geändert werden. So wird im folgenden Beispiel der Mathebibliothek eine weitere Funktion round hinzugefügt:

Geil, wie Ernie-Script nun mal ist, sind rekursive Funktionsaufrufe natürlich auch kein Problem. Als Beispiel bietet sich hierfür natürlich die Fibonacci-Funktion an, die auch direkt getestet wird. Geil:

Die Skriptsprache hat an einigen Stellen noch ein paar Macken. Insbesondere sind einige Fehlermeldungen noch nicht intuitiv genug auf entsprechende Fehler (Ernies) abgestimmt. Ferner ist der Sprachumfang noch deutlich erweiterbar. Wie ich bereits angesprochen hatte, gibt es noch keine Möglichkeit zur expliziten Deklaration von lokalen bzw. globalen Variablen — Bitoperationen sind ebenfalls noch nicht möglich. Über eine Erweiterung der möglichen Typen (bislang Void, Float, Integer, Boolean, Character, String, Table, Function und Reference) habe ich natürlich auch schon nachgedacht.

Bis die Tage!

Ernie-Script (I/II)

Montag, 29. Juni 2009

In der letzten Zeit habe ich mich darum bemüht, das Scripting-System mehr oder weniger fertigzustellen. Es funktioniert auch schon ganz hervorragend und die Sprache trägt den Namen Ernie-Script. Hahaha! Und zwar bekommt der Skriptschreiber keine Errors und Warnings an den Kopf geballert, sondern Ernies und Warnies, wenn er es nicht rafft, korrekt zu coden. Wie findet ihr das? Ich find’s geil! Ein ganz dicker Dank geht da an meinen Kollegen Milan “Marek” Karow, der die ausschlaggebende Idee hatte.

Die Sprache umfasst einige bekannte Schlüsselwörter mit vermutlich erwartungskonformer Bedeutung:

break continue do else elseif
false for function if repeat
return true until void while

Dann gibt es noch einige zusätzliche schlüsselwortartige Features wie zum Beispiel typeof, cast oder who. Was jedoch noch fehlt, ist die Möglichkeit, in einem Block lokale Variablen anzulegen, ohne globale Variablen zu überschreiben. Oder entsprechendes Gegenstück: Wenn auf globale Variablen zugegriffen werden soll, müssen diese entsprechend deklariert werden. Der Grund dafür, dass ich das noch nicht eingebaut habe, ist nämlich eben der, dass ich mich bislang noch nicht entscheiden konnte, welche dieser beiden Alternativen die geilere ist.

Neben den Schlüsselwörtern gibt es noch einige Operatoren, die ihr folgender Tabelle entnehmen könnt (nach Prioritäten sortiert):

Symbol Name Beispiel
. table entry selection obj.member
[] table entry selection obj[ expr ]
() function call foo( bar )

! not !expr
- unary minus -expr
+ unary plus +expr
& address of &obj
* dereference *ref

* multiply expr * expr
/ divide expr / expr
% modulo expr % expr

+ add expr + expr
- subtract expr – expr

< less than expr < expr
<= less than or equal expr <= expr
> greater than expr > expr
>= greater than or equal expr >= expr

== equal expr == expr
!= not equal expr != expr

&& logical and expr && expr

|| logical inclusive or expr || expr

^ logical exclusive or expr ^ expr

:= single assignment lvalue := expr

Habe ich was vergessen? Ich weiß es nicht. Achso: Bitweise and-, or- und xor-Operatoren werden bislang noch nicht unterstützt.

Für die meisten ist das jetzt vielleicht relativ uninteressant, aber in den näxten Tagen werde ich einen Download bereitstellen. Dann könnt ihr euch eine Testanwendung runterladen und selbst etwas mit Ernie-Script herumspielen.

Bis die Tage.

Shader- und Rendering-System (II/II)

Freitag, 19. Juni 2009

Na endlich, es ist soweit: Der zweite Teil über Shader- und Rendering-System ist fertig. Das hat nun aber wirklich lange auf sich warten lassen. Das lag vor allem daran, dass ich noch ein paar Probleme mit dem finalen Comic-Shader hatte. Zum Einen wollte ich zusätzlich zur Beleuchtung noch Schatteneffekte einbauen, aber das sieht bisher nicht wirklich toll aus. Insofern habe ich es jetzt doch erstmal rausgelassen. Zum Anderen werden beim Mischen der verschiedenen Material- und Beleuchtungsfarben auch die Alphawerte entsprechend gemischt. Das führte dazu, dass unter bestimmten Umständen der Alphawert gewisse Pixel transparent erscheinen ließ. Ein solches Resultat könnt ihr im folgenden Screenshot sehen:

Fehler in Shader

Um dem Problem entgegenzuwirken, muss entweder das “Alpha-Blending” komplett abgeschalten werden. Das führt jedoch dazu, dass keine transparenten Gegenstände mehr angezeigt werden können, bzw. solche Gegenstände würden halt einfach nicht transparent sondern komplett opak dargestellt (opak ist das Gegenteil von transparent). Oder aber im Shader wird der Fall abgefangen, dass sich die gemischten Farbinformationen auf die Alphawerte auswirken. Das kann jedoch — je nach Hardware — enorme Leistungseinbußen bedeuten. Dennoch ist die zweite Alternative die “bessere”, erlaubt sie schließlich weiterhin den Einsatz von transparenten Gegenständen.

Ein entsprechendes korrektes Resultat sieht dann ungefähr so aus:

Comic-Style-Shader

Ist doch schon recht hübsch, oder? Damit ihr euch ein noch besseres Bild machen könnt, gibt es noch eine kleine Anwendung zum Runterladen, bei der ihr euch das Cyber-Modell und den Comic-Style-Shader auf eurem eigenen Rechner reinziehen könnt. Falls es Probleme bei der Darstellung geben sollte, sagt mir bitte Bescheid. Aber ihr solltet die Minimalvoraussetzungen bezüglich Hard- und Software beachten:

  • 3D-beschleunigte Grafikkarte mit Shader-Modell 2.0
  • DirectX 9.0c

Aber entsprechende Fehlermeldungen bei Nichtvorhandensein der Voraussetzungen sollten in jenem Fall in der Log-Datei “logger.html” zu finden sein, die beim Starten der Anwendung erzeugt wird.

Tja, so viele Worte habe ich jetzt ja eigentlich gar nicht zu den technischen Aspekten von Shader- und Rendering-System verloren. Aber Screenshots und Downloads sollten für die meisten von euch ohnehin viel interessanter sein, denke ich. Darüber hinaus habe ich im ersten Teil ja bereits den technischen Aufbau erläutert. Hinzugekommen ist jetzt hauptsächlich noch der Szenegraph, der die komplette darzustellende Szene in kleine “Knoten” zerlegt, um diese Knoten nach Geometrie- und Materialeigenschaften sortieren zu können. Dadurch werden die einzelnen 3D-Modelle größtenteils zerpflückt. Das macht aber nix, denn im fertigen Bild ist das nicht zu sehen. Aber durch das Zerpflücken der 3D-Modelle und Sortieren der so erzeugten Knoten nach ihren Geometrie- und Materialeigenschaften werden Zustandswechsel der Grafikhardware minimiert. Diese Zustandswechsel sind nämlich maßgeblich von eben diesen Eigenschaften abhängig. Dieser durchaus gängige Ansatz  der Minimierung von Zustandswechsel der Grafikhardware bringt dann derbste Leistungsvorteile mit sich.

Shader- und Rendering-System (I/II)

Montag, 30. März 2009

Puh! Das war was! Das Rendering-System ist nun auch fast fertig. Da es in letzter Zeit nicht allzu viel zu zeigen gab, möchte ich nun das Shader- und insbesondere das Rendering-System etwas genauer vorstellen. Schließlich habe ich mich ja genau damit in der letzten Zeit befasst. Damit der Rahmen hier nicht gesprengt wird und ich nicht alles Pulver auf einmal verschieße, mache ich mehrere Teile. Dieses ist der erste Teil. Ganz fertig ist das Rendering-System ja auch — wie gesagt — noch nicht, insofern ist eine Aufteilung ja ganz sinnvoll.

Struktur und Funktionalität des Rendering-Systems, was auf Teile des Shader-Systems zurückgreift, möchte ich anhand einer kleinen Grafik erklären:

Rendering-System

Kern des Rendering-Systems bilden Chain, Effect und Pass. Generell gibt es pro darzustellender Szene ein Chain-Objekt, dass die “Kette” an Effekten (Effect-Objekten) repräsentiert, die letztlich das Bild sukzessiv aufbauen. Ein Effekt kann dabei wiederum aus mehreren Pass-Objekten zusammengesetzt sein. Für einen Cel-Shading-Effekt (Cel = Contour Enhancing Lines) werden beispielsweise vielleicht drei Durchgänge (Passes) benötigt, was ich gleich noch genauer erläutern und anhand von Screenshots verdeutlichen werde. Pro Chain- und Effect-Objekt werden verschiedene Render-Targets zur Verfügung gestellt. Schließlich soll ja im Falle von mehreren Effekten bzw. mehreren Durchgängen eines Effekts nicht direkt in den Bildschirmspeicher, sondern in weiterverwertbare Render-Targets in Form von Texturen gezeichnet werden. Diese Render-Targets werden also entsprechend von den Chain- und Effect-Objekten verwaltet und den Pass-Objekten zur Verfügung gestellt. Ein Pass-Objekt greift dann auf ein Paar von Pixel- und Vertex-Shadern zurück (Shader-Combo), um die Geometrie der Szene letztlich zu transformieren und als Farb- und Tiefeninformationen in die Render-Targets zu zeichnen. Welche Eigenschaften der Geometrie zum Zeichnen mit auf den Weg gegeben werden, ist in der Vertex-Declaration gespeichert.

Um das noch ein bisschen zu verdeutlichen, kommt jetzt mein Cel-Shading-Beispiel:

Cyber E-Razor ist ja eine Comicfigur. Eine Comicfigur wird natürlich in gewissem Comicstil gezeichnet. So mit schwarzer Umrandung und wenigen (aber dafür umso markanteren) Farbabstufungen und so. Im Folgenden geht es darum, wie ich an diese schwarzen Konturlinien komme. Das endgültige Resultat zeige ich ein andermal. Im Moment geht es nur um die Konturlinien. Zur Extraktion der gewünschten Linien verfolge ich einen Ansatz von Mitchell et al., “Real-Time Image-Space Outlining for Non-Photorealistic Rendering”, den ich natürlich meinen Bedürfnissen angepasst habe. Dabei werden die Konturlinien aus drei “Informationen” der darzustellenden Objekte zusammengestellt: Die Farbe, der Normalenvektor (die Ausrichtung der Objektoberfläche) und die Tiefe (die Entfernung zur Kamera). Dafür wird die gesamte Szene erstmal in zwei Texturen gezeichnet: Eine soll die Farben enthalten. Die andere soll die Normalenvektoren in den drei Farbkanälen Rot, Grün und Blau sowie die Tiefenwerte im Alphakanal enthalten. Mit der zweiten Textur schlagen wir also zwei Fliegen auf einen Streich. Wie diese Texturen dann aussehen, ist in folgenden zwei Screenshots zu sehen:

Color-Map

Normal-Depth-Map

Ich habe zwar extra eine Perspektive gewählt, bei der Cyber seine heftige Gun Richtung Kamera streckt, damit die Tiefeninformationen möglichst stark zur Geltung kommen. Der rechte Screenshot ist aber trotzdem nicht so ganz aussagekräftig, daher hier noch mal zwei Screenshots, die die Normalenvektoren und die Tiefeninformationen getrennt voneinander beinhalten:

Normal-Map

Depth-Map

Sau geil! Die Normalenvektoren werden ja in den Rot-, Grün- und Blaukanälen der Textur gespeichert, insofern sieht Cyber auf dem linken Screenshot derbe bunt aus. Die XYZ-Komponenten der Normalenvektoren werden hierfür einfach als RGB-Farbwerte in die Textur geschrieben. Das passt ja ganz gut, sind ja schließlich jeweils drei Werte, XYZ oder halt RGB. Daher wird man aus der gegebenen Perspektive auch niemals die Farbe Blau sehen, weil die blauen Pixel alle in die andere Richtung “zeigen”, von der Kamera weg. Im rechten Screenshot sind die Tiefeninformationen gespeichert, je heller desto größer die Entfernung zur Kamera. Diese Informationen waren im oberen rechten Screenshot ja im Alphakanal der Textur gespeichert. Dort erscheinen die Farben daher etwas dunkler, weil sie wegen der “Transparenz des Alphakanals” mit dem schwarzen Hintergrund gemischt werden. Auch hier gilt wieder: je “heller” desto größer die Entfernung zur Kamera, wobei “hell” einen höheren Alphawert und demnach geringere Transparenz bedeutet.

Ballert man das alles nun zusammen und lässt noch einen weiteren entsprechenden Shader drüber laufen, kommt etwas Schönes bei raus. Nämlich Folgendes: Aus den drei Informationen, Farbe, Normalenvektor und Tiefe, bauen wir uns nun die Konturlinien. Und zwar soll an jeder Pixelposition des resultierenden Bildes eine Konturlinie gezeichnet werden, wenn die drei gegebenen Informationen relativ stark von den jeweiligen Werten benachbarter Pixel abweichen. Das bedeutet also:

  • Weicht die Farbe stark von den Farben benachbarter Pixel ab: Konturlinie zeichnen.
  • Hat der Normalenvektor einen weitaus anderen Winkel als die Normalenvektoren benachbarter Pixel: Konturlinie zeichnen.
  • Ist der Tiefenwert heftig näher an oder weiter entfernt von der Kamera als die Tiefenwerte benachbarter Pixel: Konturlinie zeichnen.
  • Ansonsten: Keine Konturlinie zeichnen.

Das führt dann als Resultat zu richtig schönen Konturlinien, die so ziemlich alle Eigenschaften besitzen, wie man sie von Comicbildern erwartet:

Color-Map

Sehr schön! Gut zu erkennen ist, dass einige Linien dicker sind als andere. Welche das sind, ist noch nicht völlig optimal, aber tendenziell sieht das schon ganz gut aus: Die tatsächliche Umrandung der Figur ist weitestgehend dick gezeichnet, während die Linien “innerhalb” der Figur (hach, dafür gibt es doch auch einen Begriff?) meistens dünner erscheinen.

Wie gesagt: Bisher handelt es sich hier nur um die Konturlinien. Für das fertige Bild können noch Beleuchtung und Schatteneffekte oder sonst was Verwendung finden. Die Konturlinien werden dann zum Schluss quasi über das eigentliche Bild drübergelegt, um den Comicstil zu realisieren.

Hui, jetzt brennen mir aber wieder die Finger! — Als näxtes werde ich das Rendering-System noch um die Funktionalität eines so genannten Szenegraphen erweitern, der u.a. für eine geeignete Sortierung der darzustellenden Objekte zuständig ist. Dabei sollen die verschiedenen Geometrieeigenschaften gruppiert und somit der Rendering-Prozess beschleunigt werden. Wenn das soweit ist, kommt der zweite Teil zur Vorstellung von Shader- und Rendering-System. Dann wird es natürlich wieder neue Screenshots geben. Tschau!

Erster einfacher Test-Shader

Dienstag, 27. Januar 2009

Wie ich bereits vermerken ließ, hatte ich auch diesen Monat nicht die meiste Zeit, das Projekt voranzutreiben. Das Shader-System ist nahezu fertig. Es ist lediglich noch ein bisschen Funktionalität geplant, die den Komfort bei der Verwendung des Shader-Systems steigern soll. Nebenbei habe ich das Fundament für das Rendering-System gelegt. Jedoch bin ich mir da noch nicht ganz sicher, wie ich das genau aufbauen soll. Ist aber auch nicht ganz so schlimm, denn das Rendering-System dient eigentlich rein der Steigerung des Komforts. Insbesondere verschiedene Render-Targets sollen darüber verwaltet und zur Verfügung gestellt werden. Für einfache Techniken (z.B. die simple Beleuchtung mit einem Phong-Shader) wird so eine Funktionalität gar nicht benötigt.

Das Shader-System habe ich bereits getestet. Im folgenden Video kommt hierfür ein Vertex-Shader zum Einsatz, der keine tollen Berechnungen vollzieht, sondern einfach nur die nötigen Matrixtransformationen durchführt und die korrekte Texel-Farbe der Textur extrahiert. Der verwendete Pixel-Shader ist noch primitiver; er gibt die (interpolierte) Farbe unverändert zurück. Das Resultat ist natürlich nicht besonders hübsch, aber zum Testen reicht es allemal.

Get the Flash Player to see the wordTube Media Player.

Ups, da habe ich aber lange gebraucht, um zu merken, dass sich der Mauszeiger mitten im Bild befindet. Einer rafft es nie! Witzig ist auch — was mir gerade erst auffällt —, dass das 3D-Modell spiegelverkehrt dargestellt wird (man beachte die Sieben auf Cybers Brust). Da findet anscheinend an irgendeiner Stelle ein ungeplanter Koordinatensystemwechsel statt. Das krich ich aber schon noch raus.

Erste Tests der SIMD-Optimierungen

Donnerstag, 20. November 2008

Bei der Nutzung von SIMD entsteht lediglich ein geringer Overhead, um die “breiten” 64- bzw. 128-Bit-Register vor den eigentlichen Operationen mit Werten zu füllen, und die Resultate aus den Registern zu extrahieren und zurück in den Speicher zu schreiben. Der Speedvorteil überwiegt den Overhead, selbst bei geringen Datenmengen. Nur ist die Frage, ob es sich lohnt, sich für geringe Datenmengen hinzusetzen und in Assembler zu programmieren, was ja doch schon einen gewissen Aufwand mit sich bringt.

Für größere Datenmengen macht sich der Speedvorteil tierisch bemerkbar und da kann es sich unter Umständen tatsächlich lohnen, mal ein bisschen Assembler zu programmieren. Mir persönlich macht es sogar Spaß, insofern würde ich es auch ohne den so hohen Zeitvorteil machen.

Den Unterschied zwischen geringen und großen Datenmengen sowie den zwischen einer regulären Implementierung in C++ und Implementierungen unter Verwendung von SIMD möchte ich anhand folgender Tabelle demonstrieren:

Tech. / # Vek. 1 10 100 1.000 10.000
C++ 100,0 % 100,0 % 100,0 % 100,0 % 100,0 %
SSE 103,9 % 99,9 % 49,8 % 26,8 % 24,6 %
3DNow! 115,4 % 97,2 % 48,4 % 26,0 % 24,0 %

Die Tabelle enthält die “Performance” (in Form des relativen Zeitaufwands) von drei verschiedenen Implementierungen einer Funktion “vec3trans”, die eine Menge von 3D-Vektoren mit einer 4×4-Matrix transformiert. Die Prozentwerte in der Tabelle stellen die jeweilis zur Ausführung der Funktion benötigte Zeit in Relation zur Referenzimplementierung in C++ dar. Es wurden pro Implementierung jeweils 1 Vektor, 10, 100, 1.000 und 10.000 Vektoren transformiert. Pro Implementierung und Anzahl Vektoren wurden die Ergebnisse mehrerer Durchläufe gemittelt, um unerwünschte äußere Einflüsse “wegzuglätten”.

Der relativ hohe Overhead in der ersten Spalte der Tabelle (Anzahl Vektoren 1) ist nicht auf den Einsatz von SIMD zurückzuführen. Vielmehr liegt es daran, dass die Transformationsmatrix vor den SIMD-Operationen transponiert wird, um dann die Transformation vieler Vektoren weiter zu beschleunigen. Das führt, wie man sieht, bei geringen Datenmengen leider zu einem Nachteil. Würde die Matrix nicht erst transponiert, wäre der Speedvorteil selbst bei einer geringen Datenmenge von einem Vektor bereits zu erkennen. Was bleibt, ist der bei höher werdender Datenmenge steigende (aber konvergierende) Speedvorteil. Und dieser ist doch schon beachtlich: Die SIMD-Implementierungen benötigen bei größeren Datenmengen nur ca. ein Viertel der Zeit im Vergleich zur Referenzimplementierung in C++. Wahnsinn!

Eine Menge von 10.000 3D-Vektoren entspricht übrigens einem schon recht großen 3D-Modell, wie es in Cyber E-Razor rockt die Galaxis garantiert nicht geben wird. Die bisherigen 3D-Modelle umfassen maximal ca. 1.500 Vertizes (Vertizes: Eckpunktvektoren des 3D-Gittermodells, grob gesagt). Selbst diese Modelle sollte ich für den Einsatz im Spiel noch optimieren, das heißt in diesem Fall, möglichst ohne großen optischen Qualitätsverlust weniger (als 1.000?) Vertizes verwenden.

SIMD-Optimierungen

Donnerstag, 13. November 2008

Ich habe nun für einige bestimmte Funktionen alternative, optimierte Implementierungen gebaut, die auf SIMD-Technologien basieren, insbesondere 3DNow! und “einfaches” SSE (bisher). Durch SIMD-Technologien (SIMD: Single Instruction Multiple Data) können nämlich die 64- (3DNow!) bzw. 128-Bit-Register (SSE) des Koprozessors genutzt werden, um durch paralleles Rechnen tierisch den Zeitvorteil zu verwirklichen. So lassen sich zeitkritische Operationen wie zum Beispiel Dividieren, Multiplizieren oder gar Wurzelziehen auf zwei (3DNow!) bzw. vier (SSE) Werten auf einen Schlag durchführen.

Als ein Beispiel seht ihr im Folgenden die SSE-Version der Funktion “vec3norm”, die eine Menge von 3D-Vektoren normalisiert (so dass als Resultat alle Vektoren die Einheitslänge 1 haben). Geil:

vec3norm_sse

Ist das herrlich! Assembler ist jawohl echt mal ne geile Sache, oder? Ich finde ja! Sieht optisch schon tierisch krass aus! Und geht natürlich speedmäßig volle Kanne ab! Gut, heutige C++-Compiler generieren dermaßen optimierten Code, dass es sich meistens nicht lohnt, selbst zu Assembler zu greifen. Aber der Einsatz von SIMD-Technologien sieht da schon wieder ganz anders aus.

Bisher habe ich noch keine ausgiebigen Tests durchgeführt, um den tatsächlichen Vorteil der SIMD-Implementierungen zu analysieren. Aber das werde ich wohl bald machen und die Ergebnisse dann hier an den Tag kloppen.