https://wiki.delphigl.com/api.php?action=feedcontributions&user=Flo&feedformat=atomDGL Wiki - Benutzerbeiträge [de]2024-03-29T11:08:40ZBenutzerbeiträgeMediaWiki 1.27.4https://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=24133DGL Wiki:Files2009-08-18T21:12:56Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_jvscript]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_2D&diff=24132Tutorial 2D2009-08-18T20:29:52Z<p>Flo: Datei Link hinzugefügt.</p>
<hr />
<div>=2D mit OpenGL - "Nicht jeder benötigt 3 Dimensionen"=<br />
<br />
==Einleitung==<br />
<br />
Bei der Erwähnung einer API wie OpenGL denken die meisten eigentlich eher an 3D, und sind der festen (aber sehr wohl falschen) Überzeugung eine solche API sei für reine 2D-Anwendung überdimensioniert oder gar ungeeignet. Das dies nicht der Fall ist möchte ich mit diesem (vor allem an Einsteiger gerichtet, denn die Könner wissen wohl was man mit der GL so alles machen kann) Tutorial zeigen und auch gleich mit mehreren praktischen Beispielen aufweisen das 2D mit OpenGL nicht nur möglich ist, sondern auch noch sehr viel einfacher (selbst mit der GDI ist 2D komplizierter) ist und dabei jede Menge Vorteile mit sich bringt.<br />
<br />
==Welche Vorteile bringt mir die GL für eine 2D-Anwendung?==<br />
<br />
Dies ist wohl das wichtigste Kapitel und sollte zugleich auch mit diversen Vorurteilen und Missverständnissen aufräumen. Denn gerade der 3D-Bereich ist es in dem seit Jahren fast monatlich neue Techniken entworfen werden und der dafür sorgt das v.&nbsp;a. Grafikkarten immer leistungsstärker werden, während der 2D-Bereich seit seligen VLB-Zeiten (= Vesa-Local-Bus, alte Haudegen kennen diese Grafikkartengeneration sicherlich noch) keine Innovationen mehr erlebt hat (und warum auch? Im 2D-Bereich reicht ein gutes Bild zusammen mit passabler Darstellungsgeschwindigkeit).<br />
<br />
Deshalb gibt es jetzt gleich mal die wichtigsten Punkte warum man denn gerade OpenGL (D3D würde hier auch zählen, aber das haben wir GL'ler ja nicht so gerne) für die 2D-Darstellung nutzen sollte:<br />
<br />
*'''Hardwarebeschleunigung'''<br />
:Moderne Grafikkarten können inzwischen über 200 Millionen Dreiecke pro Sekunde rendern und besitzen brachiale Füllraten jenseits der 2.000 M(Texel/Pixel)/s. Das bedeutet also das man selbst auf älteren Grafikkarten sehr komplexe 2D-Szenen mit Geschwindigkeiten jenseits der 100 FpS (= Frames per Seconds ~ Bilder pro Sekunde) darstellen kann, während man mit der GDI schon bei einfachen 2D-Grafiken Geschwindigkeitsprobleme bekommen würde.<br />
<br />
*'''"Kostenlose" Objektsortierung'''<br />
:Eine 3D-API braucht einen Tiefenpuffer um zu erkennen ob Fragmente verdeckt sind oder nicht und damit Overdraw zu vermeiden. Eine 2D-Anwendung kann diesen Tiefenpuffer aber auch nutzen, nämlich um Objekte zu sortieren. Man nutzt dann die Z-Koordinate der Objekte (= Tiefenkoordinate) quasi als Layer um zu kennzeichnen welches Objekt auf welcher "Höhe" liegt. Wenn man also z.&nbsp;B. einen 2D-Topdown-Shooter entwickelt bei dem der Spieler mit seinem Flugzeug über den Boden fliegt, dann nutzt man den Z-Puffer um die API (die das dann der Hardware überlässt) seine Objekte sortieren zu lassen. Das Flugzeug bekommt dann einen niedrigen Z-Wert (=oben) und Objekte auf dem Boden einen hohen Tiefenwert (=unten/hinten). Die Sortierung übernimmt dann die Grafikkarte und wir müssen uns darum keine Gedanken machen. Würden wir die Anwendung z.&nbsp;B. über die GDI realisieren, müssten wir diese Objekte selbst entsprechend ihrer Höhe sortieren.<br />
<br />
*'''Jede Menge hardwarebeschleunigte Spezialeffekt'''<br />
:Wie schon oben erwähnt haben im 3D-Bereich innerhalb der letzten Jahre sehr viele Innovationen stattgefunden. Warum sollte man diese also nicht auch für seine 2D-Anwendung nutzen? Klingt logisch und macht auch Sinn! So bietet OpenGL alle Arten von Effekten die auch in einer 2D-Anwendung nützlich sein können. Darunter solche Sachen wie den Alphatest (der dafür sorgt das maskierte Teile eines Objektes transparent sind), Blending und natürlich (auch wenn das jetzt für erfahrene GL'ler sehr trivial klingt) hardwarebeschleunigte Rotation und Skalierung; was zur Folge hat das man seine Objekte nicht für verschiedene Auflösungen in verschiedenen Größen erstellen muss. Für Fortgeschrittene gibt es dann natürlich noch solche Sachen wie Shader, mit denen man Teile der OpenGL-Pipeline durch eigene (kleine) Programme ersetzen kann (entweder in Assemblerform oder aber in der neuen GL-HLSL). Dadurch bietet sich dann ein quasi unendlich großes Spektrum an möglichen Effekten, und das wohlgemerkt alles hardwarebeschleunigt!<br />
<br />
*'''Plattformübergreifend'''<br />
:Auch ein großer Vorteil von OpenGL. Die Tatsache das die GL unter diversen Betriebssystemen verfügbar ist (im Gegensatz zu GDI oder gar DirectX) macht die eigenen Programme recht portabel (einschränkend ist hier halt nur die Verfügbarkeit der genutzten Programmiersprache auf dem passenden OS). Unterstützt werden alle größeren Betriebssysteme wie Windows, Linux, MacOS und Solaris.<br />
<br />
:Ganz nebenbei wurde vor kurzem mit [http://www.khronos.org/opengles/2_X/ OpenGL ES] ein mobiler Standard für OpenGL geschaffen, wodurch es dann auch möglich ist auf mobilen Geräten (Handys, PDAs, Handhelds) OpenGL zu nutzen. Und gerade dort sind 2D-Spiele (aufgrund der oft mangelnden Leistung der Geräte) ja noch stark verbreitet.<br />
<br />
So viel also zu den wichtigsten Vorteilen zur OpenGL unter 2D. Natürlich gibt es noch weiter Dinge die OpenGL für 2D-Anwendungen attraktiv machen, aber allein die oben genannten Gründe sollten jedermann überzeugt haben. Und alle die wirklich mal wissen wollen wie gut OpenGL denn für solche Anwendungen geeignet ist, sollten sich unbedingt eine neuere Version des MacOS ansehen, denn das benutzt OpenGL zur Darstellung seiner GUI.<br />
<br />
==Und welche Nachteile gibt es?==<br />
<br />
Nichts was der Mensch bisher erfunden hat (mal abgesehen von der Spaltung des Atoms ;) ) hat nur Vorteile. Genauso sieht es auch aus wenn man die OpenGL für seine 2D-Anwendung nutzen will. Welche genau das sind will ich hier grob auflisten.<br />
<br />
*'''Ohne 3D-Beschleuniger mit passenden Treibern geht nichts'''<br />
:Klingt logisch, oder? OpenGL ist eine 3D-API und da 2D nichts weiter als die (fast vollständige) Vernachlässigung der Z-Koordinate ist, kommen wir um einen 3D-Beschleuniger nicht herum, der dazu auch noch einen Treiber mitbringen muss der OpenGL unterstützt. Allerdings schritt der Fortschritt auf diesem Gebiet der IT-Technik in den letzten Jahren so rasant voran wie sonst nirgendwo, und wir werden nur sehr selten auf Rechner stoßen in denen Hardware agiert die keine 3D-Beschleunigung bietet. Ergänzend dazu sollte allerdings trotzdem immer der neuste Treiber installiert sein, denn besonders die in WindowsXP integrierten Grafikkartentreiber wurden ihrer OpenGL-Funktionalität entraubt (Man riecht hier förmlich die Konkurrenz zwischen D3D und der GL). Also ist dies im Endeffekt ein Nachteil der inzwischen kaum noch halt findet und in Zukunft total vernachlässigt werden kann.<br />
<br />
*'''Hardwarelimitationen'''<br />
:Einer der größten Nachteile einer jeden 3D-API die auf Hardwarebasis arbeitet sind die Limitationen die die Hardware mitbringt. Jeder Grafikkartentyp hat andere, was mitunter dazu führen kann das die selbstverfassten OpenGL-Anwendungen nicht auf allen Rechnern laufen. Da wir uns in diesem Tutorial (2D ist ja recht anspruchslos) allerdings in den Niederungen der OpenGL-Funktionalität bewegen, dürfte es hier kaum Probleme geben. Einzig die Tatsache das vor allem ältere 3D-Beschleuniger mit großen Texturen Probleme haben könnte hier und da Schwierigkeiten machen. Wer aber keine Texturen größer 1024x1024 Pixel nutzt und dazu noch sparend mit dem Speicher der Grafikkarte umgeht (nicht jede Grafikkarte hat 128 Mbyte Grafikspeicher oder gar mehr). Einige Leute werden sich übrigens evtl. dadurch verunsichert fühlen das ihnen jemand gesagt hat, man könnte unter OpenGL nur Texturen nutzen die der Dimension 2^n*2^n entsprechen. Das ist grundlegend korrekt, aber wir nutzen hier einen Texturenloader der [[gluBuildMipMaps]] benutzt um [[MipMaps]] (verschiedene Detailstufen) für unsere Texturen zu erstellen. Diese Funktion schluckt jede Größe (sofern diese kleiner oder gleich dem Hardwarelimit ist) und passt die Texturen dann entsprechend an eben genanntes Limit an, also müssen wir uns um diese so oft erwähnte Limitation keine Sorgen machen. Wer zu dem Thema Hardwarelimitation mehr wissen will, der sollte unbedingt mal auf [http://www.delphi3d.net/ Tom Nuydens Seite] vorbei schauen. Dort gibt es eine riesige Datenbank in der fast alle Grafikkarten mit ihren entsprechenden OpenGL-Fähigkeiten vertreten sind.<br />
<br />
*'''Filtering'''<br />
:Zugleich ein großer Vorteil, aber je nach Situation auch ein Nachteil. OpenGL filtert Texturen (sofern man das via GL_LINEAR so will) bilinear, was man auch tunlichst aktiviert lassen sollte (GL_NEAREST filtert nicht, sieht dann aber auch scheußlich blockig aus). Dadurch wirken Texturn meist etwas verschwommen. Ich für meinen Teil umgehe dies aber ganz einfach, denn in fast jedem Bildbearbeitungsprogramm gibt es eine Funktion mit der man ein Bild scharfzeichnen kann. Das sieht auf den ersten Blick dann zwar überzeichnet aus, aber wenn OpenGL das Bild dann als Textur bilinear filtert, heben sich Filtering und Scharfzeichnung gegenseitig fast auf. Das hat sich in meinen Anwendungen bisher bewährt und ist nicht wirklich viel Aufwand.<br />
<br />
Um dieses Kapitel hier abzuschließen sei noch gesagt das man ohne 3D-Beschleuniger nicht unbedingt auf OpenGL verzichten muss. Brian Paul hat mit [http://www.mesa3d.org/ Mesa3D] nämlich ein Projekt am laufen das OpenGL-DLLs zur Verfügung stellen die komplett über die CPU ablaufen. So kann man dann OpenGL-Anwendungen mit einer etwas schnelleren CPU trotz fehlendem 3D-Beschleuniger nutzen, oder auf Funktionen ausprobieren die von der (zu alten) Grafikkarte nicht unterstützt werden.<br />
<br />
==Die Grundlagen==<br />
<br />
Sollte sich der geneigte Leser nun also doch für die GL entschieden haben, so widmen wir uns dann jetzt den Grundlagen der 2D-Darstellung unter OpenGL. Viele Sachen die man bei einer 3D-Anwendung beachten muss, sind hier eigentlich zu vernachlässigen. Wer also schon mal eine kleine 3D-Anwendung unter OpenGL geschrieben hat wird hier sicherlich keine Problem bekommen. Da sich dieses Tutorial aber an blutige (mhh, lecker) Einsteiger richtet, versuche ich so genau und einfach wie möglich zu erklären was man machen muss und v.&nbsp;a. warum. Genau deshalb habe ich auch für einen Großteil der hier erwähnten Techniken im Download zu diesem Tutorial (siehe unsere Files-Sektion und dort unter VCL-Source) jeweils ein eigenes Beispielprogramm + Quellcode (und natürlich ausgiebigen Kommentaren) geschrieben. Wenn zu dem jeweiligen Kapitel ein solches im Download enthalten ist, dann steht das ''kursiv'' unter der Überschrift des Kapitels.<br />
<br />
==Die Projektion==<br />
<br />
Wie bekannt (sein sollte), besitzt OpenGL im Groben zwei wichtige Matrizen. Zum einen die Modellansichtsmatrix, in der man im Normalfall seine Szene (egal ob 2D oder 3D) rendert und (für dieses Kapitel wichtiger) die Projektionsmatrix. Diese Matrix lässt sich am besten mit der Linse einer Kamera vergleichen und legt fest wie die Objekte auf den Bildschirm projiziert werden (wer mitdenkt wird jetzt wissen warum diese Matrix so genannt wurde). In einer 3D-Anwendung setzen wir (meist über [[gluPerspective]]) eine Projektionsmatrix die dafür sorgt das unsere Objekte perspektivisch korrekt verzerrt werden (so wie es im echten Leben auch ist). Da Bilder aber mehr als tausend Worte sagen zeige ich das anhand der unteren Bildreihe, die einen Würfel an verschiedenen Positionen auf der X-Achse zeigt:<br />
<br />
[[Bild:Tutorial_2D_illustration_1.jpg|center]]<br />
<br />
Der Würfel wurde auf den beiden Bildausschnitten links und rechts jeweils um 40 Einheiten auf der X-Achse verschoben und man kann sehr gut sehen das die Seiten des Würfels perspektivisch verzerrt werden, also weiter entfernte Kanten kleiner erscheinen (wie im realen Leben, das kann man ja prima mit nem würfelähnlichem Objekt nachprüfen). Diese Art der Darstellung ist für 3D gut geeignet, aber für unseren Zweck nicht. Denn wir wollen ja das unser Objekt, egal an welcher Bildschirmposition es sich befindet, gleich aussieht.<br />
<br />
Dazu gibt es unter OpenGL den sog. orthogonalen Modus, der dafür sorgt das unser [[Frustum]] (Sichtkegel) nicht wie bei der 3D-Projektion kegelförmig ist (kleine Seite beim Betrachter, große Seite am Ende des Sichtfeldes), sondern wie eine Box aussieht. Für technisch interessierte hier der Vergleich zwischen dem 3D- und dem 2D-Frustum :<br />
<br />
[[Bild:Tutorial_2D_illustration_2.jpg|center]]<br />
<br />
Links sehen wir das Frustum (~Sichtbereich) für die orthogonale Projektion (also 2D) und rechts für die perspektivische Projektion (3D). In diesem Tutorial interessieren wir uns wie gesagt für ersteres Frustum, welches sich über die Funktion [[glOrtho]] erstellen lässt. Diese Funktion will von uns die Dimensionen haben die wir unserem Viewport geben wollen. Ich empfehle hier übrigens immer einen festen Wert der einer der gängigen Auflösungen (z.&nbsp;B. 640x480, 800x600) entspricht. Der feste Wert hat übrigens den Vorteil das unsere Anwendung von der vom Nutzer gewählten Bildschirmauflösung unabhängig ist. Wir müssen dann also nicht mehr umrechnen wo unser Objekt jetzt in der gewählten Auflösung wäre und wie groß es dort sein müsste. Dadurch das wir immer die selben virtuellen Koordinaten haben, überlassen wir der GL (bzw. der Grafikkarte) die Umrechnung. Wenn wir also eine virtuelle Auflösung von 640x480 an glOrtho übergeben, und ein Objekt zentriert bei 320x240 rendern, dann wird dieses egal in welcher Auflösung immer in der Mitte des Schirms gerendert. Die Umrechnung macht wie gesagt OpenGl (oder besser gesagt die Grafikkarte). Zusätzlich übergeben wir der Funktion dann noch die Z-Reichweite. Hier kann man beliebig wählen, und muss nicht wie in 3D darauf achten Z-Near und Z-Far so zu wählen das die Auflösung des Tiefenpuffers nicht unnötig verschwendet wird (z.&nbsp;B. mit einem Z-Near von 0.1 oder gar kleiner). Für Z-Near nehme ich gewöhnlich 0 und für Z-Far einen Wert der dafür sorgt das ich alle Objekte so sortieren kann das ihre Z-Position auf einen Integerwert fällt. Als kleines Codebeispiel könnte unsere Projektionsmatrix nun so aussehen:<br />
<br />
<source lang="pascal">glMatrixMode(GL_PROJECTION);<br />
glLoadIdentity;<br />
glViewport(0,0,ClientWidth,ClientHeight);<br />
glOrtho(0,640,0,480,0,128);</source><br />
<br />
Um den optischen Vergleich zur oben erwähnten 3D-Projektion zu zeigen, gibt es wieder ein Bild des Würfels, diesmal allerdings mit 2D-Projektion:<br />
<br />
[[Bild:Tutorial_2D_illustration_3.jpg|center]]<br />
<br />
Das sieht auf den ersten Blick zugegeben recht langweilig aus, stellt aber genau den selben Szenenverhalt dar wie die Ansicht ein paar Zeilen weiter oben. Diesmal allerdings mit der gerade besprochenen orthogonalen Ansicht, die als Grundlage für unsere 2D-Projektion dient.<br />
<br />
So viel also zur 2D-Projektion und hoffentlich hat das hier jeder verstanden. Die orthogonale Projektion ist ein essentieller Bestandteil einer jeden 2D-Anwendung unter OpenGL und sollte daher allen Interessierten ein Begriff sein. Falls das hier jemandem zu technisch war, im Forum werden weitergehende Fragen gerne beantwortet.<br />
<br />
Noch als kleiner Nachtrag: Wer sich mal die Parameter angesehen hat die glOrtho will, wird bemerkt haben das wir in obigem Quellcode (zumindest augescheinlich) Top mit Bottom verwechselt haben (sprich es sollte 0,640,480,0 statt 0,640,0,480) heißen. Das hat allerdings seine Richtigkeit, denn in OpenGL liegt der Ursprung des Koordinatensystems in der unteren linken Bildschirm(oder Fenster)ecke, wobei er bei Windows in der oberen Ecke liegt. Unter OpenGL liegt also quasi der "Boden" oben, genau umgekehrt wie unter Windows. Genau deshalb übergeben wir als "Oben" an glOrtho den eigentlichen Boden des Viewports. Das klingt verwirrend, aber ist im Endeffekt gar nicht so schwer zu behalten, besonders dann nicht wenn man sich folgende Illustration mal näher ansieht:<br />
<br />
[[Bild:Tutorial_2D_illustration_11.jpg|center]]<br />
<br />
Das sollte man immer in Hinterkopf behalten, und unter 3D ist es genauso. Während ein positiver Y-Wert Objekte in einem Windowsfenster nach unten verschiebt, geschieht unter OpenGL genau das Gegenteil. Wer sich mit dieser Tatsache nicht anfreunden kann, der kann auch gerne glOrtho dazu nutzen die 2D-Matrix von OpenGL an die Windowsgegebenheiten anzupassen:<br />
<br />
<source lang="pascal">glOrtho(0,640,480,0,0,128);</source><br />
<br />
Und schon verhält es sich unter OpenGL genauso wie unter Windows. Positive Y-Koordinaten zeigen nach unten. Allerdings muss man hier dann auch drauf achten die gerenderten Objekte an diese Gegebenheit anzupassen. Man muss diese also quasi auf den Kopf stellen, damit sie mit der neuen Matrix korrekt angezeigt werden. Das geht aber ganz leicht, indem man beim rendern von Quads oder anderen texturierten Primitiven ganz einfach die T-Texturkoordinaten vertauscht.<br />
<br />
==Darstellung der 2D-Objekte==<br />
<br />
Einige 2D-Interessierte haben sich sicherlich schon mal im Funktionsumfang von OpenGL umgesehen und bemerkt, dass es dort eigentlich gar keine Funktionen gibt um Dinge in 2D zu zeichnen. Auf den ersten Blick sieht das auch wirklich so aus, aber man darf halt nie vergessen dass OpenGL primär für den 3D-Bereich entworfen wurde und sich 2D-Sachen dann nur über 3D-Techniken realisieren lassen. So auch die Darstellung unserer 2D-Objekte, für die wir aus genau diesem Grund eine 3D-Technik anwenden müssen, nämlich das sog. Texturemapping (Den Begriff "Textur" gibt's übrigens auch im deutschen Sprachgebrauch, aber gängiger ist die korrekte Übersetzung "Oberfläche"). Unter OpenGL werden ja alle Objekte aus verschiedenen Primitiventypen zusammengesetzt (Dreiecke, Rechtecke, usw.) und diese Objekte kann man mit einer Textur belegen die dann auf dieser Oberfläche "angezeigt" wird. Diese Textur lädt man im Normalfall aus einer vorher erstellten Bilddatei unter Nutzung eines [[Texture_Loader|Texturenloaders]] (alternativ kann man den sich natürlich auch selbst schreiben), der diese Textur für die Grafikkarte vorbereitet (also z.&nbsp;B. ein BMP-Bild vom BGR-Format ins RGB-Format bringt) und dann auf dieser ablegt. Danach kann diese Textur an jeder Stelle im Programm auf eine Primitve "geklebt" werden, und genauso machen wir das auch in unserer 2D-Anwendung.<br />
<br />
Allerdings müssen wir keine komplexen Formen darstellen, da unsere Objekte ja nicht 3D sind, sondern (meistens) schon in einem anderen Programm erstellt (oder vorgerendert wurden) und als Bilddatei abgelegt wurde. Wir laden und stellen dann also nicht die 3D-Daten dieses Modells dar (was bei komplexen 2D-Objekten wohl eh zu viel wäre), sondern kleben diese schon fertige Bilddatei mittels einer Textur auf ein Rechteck (in der GL-Terminologie "Quad" genannt, vom Primitiventyp GL_QUADS). Hoffe mal das kam gut rüber, aber ich verdeutliche dass dann besser nochmal anhand einer kleinen "Bilderserie":<br />
<br />
[[Bild:Tutorial_2D_illustration_4.jpg|center]]<br />
<br />
Oben sei mal kategorisch der Vorgang geschildert um ein vorgerendertes 3D-Objekt als Textur in seine 2D-Anwendung zu bekommen. Rechts sieht man das 3D-Modell, das dann aus der gewünschten Ansicht (im obigen Falle von der Seite) im 3D-Modellierungsprogramm gerendert wird. Dieses Rendering speichert man dann in einem Format ab das der Texturenloader verarbeiten kann, lädt dies in seine OpenGL-Anwendung und stellt dies dann auf z.&nbsp;B. einem Quad dar (siehe letztes Bild). Natürlich spielt es keine Rolle ob man seine 2D-Objekte vorrendert oder diese von Hand zeichnet, wobei den meisten wohl Ersteres besser von der "Hand" geht.<br />
<br />
===Welches Bildformat ist das richtige?===<br />
<br />
Bevor wir nun weiter auf das Thema eingehen kümmern wir uns um die '''Frage nach dem richtigen Bildformat''' für unsere Texturen. Bildformate gibt's wie Sand am Meer, aber für unseren Zweck eignen sich nur sehr wenige (eigentlich nur ein einziges). Ich zähle die verbreitetsten Formate kurz auf und sag auch warum (oder warum nicht) und wofür man diese verwenden kann:<br />
<br />
*'''Joint Photographic Experts Group (*.jpg; *.jpeg)'''<br />
:Das in den Weiten des WWWs wohl verbreitetste Format ist für Texturen generell eher weniger zu empfehlen, und für 2D-Objekte erst recht nicht. Zum einen ist die in diesem Format genutzte Kompression verlustbehaftet (also verlieren unsere Texturen an Qualität) und außerdem hat dieses Format keine Möglichkeit Transparenzinformationen zu speichern. Diese benötigt man aber für 2D-Objekte, denn im Normalfall wollen wir den Hintergrund des Objektes ja durchsichtig machen (dazu gleich mehr). Also sollte dieses Format nur verwendet werden wenn wir etwas darstellen wollen das sehr viele Details enthält (dann fällt die verlustbehaftete Kompression nicht so stark auf) und keine transparenten Bereiche enthält.<br />
<br />
*'''Graphical Interchange Format (*.gif)'''<br />
:Direkt aus der Steinzeit des IT-Sektors kommt das (im Netz noch weit verbreitet) GIF-Format. Für unsere Zwecke ist es total unbrauchbar. Es ist eine Palettenformat, das maximal 256 verschiedene Farben unterstützt, allerdings inklusive Transparenzinformationen. Aber die maximal 256 Farben und die kaum vorhandene Kompression machen es für unseren Zweck nutzlos. Die Tatsache dass es Animationen unterstützt ist zwar im Internet für kleine animierte Sachen ganz toll, aber hilft uns auch nicht, denn dazu überwiegen die Nachteile zu stark<br />
<br />
*'''Portable Network Graphic (*.png)'''<br />
:Der Nachfolger des GIF-Formates. Eigentlich auch sehr gut für Texturen geeignet, denn neben einem Alphakanal (maximal 8 Bit) und 8 Bit pro Farbkanal unterstützt es auch verlustfreie Kompression (mit einem recht hohen Kompressionsfaktor). Nachteil ist allerdings der Aufbau des Formates, denn der Chunkaufbau macht das Laden recht schwer und bisher gibt es nur Loader die eine DLL-Datei mit sich schleppen. Alternativ kann man auf neuen Betriebssystemen jedoch via GDI+ auch PNG-Dateien direkt laden.<br />
<br />
*'''TARGA (*.tga)'''<br />
:Dieses Format werden sicherlich nicht viele Einsteiger kennen, allerdings ist dies '''''das perfekte Format für unsere Bedürfnisse bzw. Texturen im Allgemeinen'''''. Es kann nämlich bis zu 8 Bit pro Farbkanal (also das was man heute als 32-Bit Farbtiefe bezeichnet) speichern (= 24 Bit für Farben) und dazu noch einen Alphakanal (maximal 8 Bit). Der Alphakanal ist sehr nützlich, denn in ihm kann man die Transparenzinformationen eines Bildes ablegen. Auch viele kommerzielle Titel nutzen dieses Format (u.&nbsp;a. Quake3), und das aus gutem Grund. Kompression wird auch unterstützt, und zwar verlustfrei in Form einer LZW-Kompression. Alles in allem ist das momentan das geeignetste Format für Texturen, zumal so gut wie jedes Bildbearbeitungsprogramm damit umgehen kann. Nebenbei ist dies Format auch recht einfach aufgebaut und damit auch recht leicht einzulesen.<br />
<br />
*'''BITMAP (*.bmp)'''<br />
:Das BMP-Format dürfte sicherlich jedem ein Begriff sein, ist aber genauso wie GIF ein Relikt aus der Steinzeit. Es kann zwar genauso wie das TGA-Format neben den 8 Bits pro Farbkanal auch einen maximal 8 Bits großen Alphakanal anbieten, allerdings kommen damit nur recht wenige Bildbearbeitungprogramme klar. Ausserdem sind die Bilddaten hier im BGR-Format abgelegt, statt dem eher üblichem RGB-Format (Red, Green, Blue). Das ist zwar sehr leicht umzuwandeln, bzw. kann mit passender GL-Konstante auch direkt übergeben werden, aber trotzdem ist dieses Format in keinem Falle dem TARGA-Format vorzuziehen.<br />
<br />
*'''DirectDraw Surface (*.dds)'''<br />
:Ein sehr neues Format, das den Ursprung (wie am Namen zu erkennen) in Microsofts DirectX-Schnittstelle hat. Es ist ein recht modernes Format, das speziell für die Speicherung von Texturen entwickelt wurde. Allerdings ist das eher was für Fortgeschrittene, denn weder das Laden dieses Formates ist einfach, noch seine Speicherung (selbst teure Bildbearbeitungsprogramme brauchen ein passendes Plugin für DDS und bei der Erstellung des Formates muss man auf bestimmte Sachen achten). Aber ich wollte es hier trotzdem mal erwähnt haben, damit man sieht das es auch spezielle Formate für Texturen gibt, und wenn ihr euch eingearbeitet habt, dann könnt ihr im Forum mehr zu dem Format finden (Mars hat dort auch einen grundlegenden Loader gepostet), denn es ist recht interessant. Es unterstützt feste Kompressionsratios, Mip-Maps, 3D-Texturen, uvm.<br />
<br />
Die obige Liste dürfte also einen recht (groben) Überblick über die verbreiteten Bildformate geben, und für dieses Tutorial begnügen wir uns erstmal mit dem TARGA-Format. Das wurde übrigens bereits 1984 erfunden, ist aber trotzdem noch nicht veraltet, sorgt aber dafür das so ziemlich jedes Programm damit umgehen kann.<br />
<br />
Wer sich übrigens nicht auf einen fremden Texturenloader verlassen möchte, sondern sich selbst um das Einlesen der Bildformate kümmern will, der sollte mal eine Blick auf [http://www.wotsit.org wotsig.org] werfen, einer recht großen Bibliothek die es sich zur Aufgabe gemacht hat Spezifikationen für Dateiformate zu sammeln. Dort wird man zu jedem der oben gennannten Bildformate eine solche Spezifikation finden, anhand derer man dann selbst Laderoutinen schreiben kann.<br />
<br />
===Textur laden===<br />
<br />
Auch wenn es ein sehr simples Unterfangen ist eine Textur zu laden, werde ich hier trotzdem nochmal kurz darauf eingehen. Das Tutorial richtet sich ja an Einsteiger, und von daher kann es nicht schaden auch mal kurz zu zeigen wie man so eine Textur lädt. Nutzen tun wir dazu die ''Textures.pas'' (die im Original von Jan Horn stammt. Ein weitere guter Loader ist [[Glbitmap_loader|glBitmap.pas]]), die sich im Download des Beispiels für dieses Tutorial befindet. Der Loader kann JPG, BMP und TGA laden. Außerdem lädt er auch den Alphakanal aus einer TGA-Textur.<br />
<br />
Bevor wir die Textur laden können, benötigen wir eine Variable in der wir den Bezeichner der Textur speichern. OpenGL erstellt für alle Ressourcen eindeutige Bezeichner (Bezeichner hier in Form eines Integerwertes, also einer eindeutigen ID), so auch für Texturen. Dieser Bezeichner ist vom Typ glUInt (U=unsinged Int=Integer, also vorzeichenloser Ganzzahlwert, was in Delphi dem Variablentyp ''Cardinal'' entpricht). Deshalb deklarieren wir unseren Texturenbezeichner auch so:<br />
<br />
<source lang="pascal">var<br />
MyTex : glUInt;</source><br />
<br />
Wenn wir mehrere Texturen laden und verwalten wollen, bietet sich natürlich ein (dynamisches) ''array of glUInt'' an, aber das sind Delphigrundlagen die in diesem Tutorial nichts zu suchen haben.<br />
<br />
Das Laden der Textur geht nun dank der ''Textures.pas'' ganz einfach:<br />
<br />
<source lang="pascal">LoadTexture('MeineTextur.tga', MyTex, False);</source><br />
<br />
Die Parameter sollten recht logisch sein, der erste gibt den Dateinamen der Textur an, der zweite das Texturobjekt (in das die ID der Textur geschrieben wird) und der letzte Parameter gibt an ob die Textur aus einer dem Programm angehängten Ressource geladen werden soll. Sollte der Ladevorgang erfolgreich gewesen sein, so müsste sich in ''MyTex'' ein Wert > 0 befinden, nämlich der eindeutige Bezeichner dieses Texturenobjektes.<br />
<br />
===Textur anwenden===<br />
<br />
Wer sich schonmal ein wenig über OpenGL schlau gemacht hat, der wird wissen dass die GL eine Statemachine ist. Das trifft auch auf Texturen zu, denn wenn eine Textur gebunden wurde, wird sie solange auf alle Primitiven angewendet, bis entweder eine andere Textur gebunden wurde oder das Texturemapping über [[glDisable]] abgeschaltet wird. Das hat besonders dann den Vorteil, wenn man viele Objekte mit der gleichen Textur rendern muss, denn Texturenwechsel sind recht kostspielig. Von daher sollte man also bei vielen Objekten eine Sortierung nach Textur vornehmen, dann diese Textur binden und danach dann alle Objekte die diese Textur besitzen rendern.<br />
<br />
Doch bevor Texturen überhaupt angezeigt werden, müssen wir OpenGL erstmal mitteilen das es diese auch anzeigen soll. Dazu gibt es die Funktion '''glEnable''', der man mit der Konstante '''GL_TEXTURE_2D''' mitteilt das wir die 2D-Texturierung aktivieren wollen (1D oder 3D-Texturen benötigen wir ja in diesem Tutorial nicht):<br />
<br />
<source lang="pascal">glEnable(GL_TEXTURE_2D);</source><br />
<br />
Unglaublich einfach, oder? So schnell kann das dank einer gut durchdachten API wie OpenGL gehen. Jetzt wo wir der API erstmal gesagt haben dass wir gerne Texturen sehen möchten, müssen wir auch noch sagen welche Textur als nächstes auf unserer Primitiven gezeigt werden soll. Dazu bindet (~ aktiviert) man das passende Texturobjekt:<br />
<br />
<source lang="pascal">glBindTexture(GL_TEXTURE_2D, MyTex);</source><br />
<br />
Von nun an werden alle folgenden Primitiven solange mit der hinter ''MyTex'' abgelegten Textur gerendert, bis das Texturemapping entweder deaktiviert wird oder wir eine andere Textur binden.<br />
<br />
===Transparenz===<br />
<br />
Eine wichtige Sache die es noch zu klären gibt ist Transparenz. Oben habe ich ja gesagt das wir unsere Objekte auf Quads kleben (über eine Textur), aber unsere vorgefertigten Objekte nur selten auch genau die Form eines Quads haben. Der Panzer auf der oben gezeigten Textur wird z.&nbsp;B. von sehr viel schwarz umgeben, das wir da natürlich nicht sehen wollen. Aber auch um die Transparenz brauchen wir uns unter OpenGL keine Sorgen zu machen, denn dafür gibt es den sog. Alphakanal der Textur, der angibt welche Teile einer Textur später transparent (oder besser gesagt gar nicht, aber dazu gleich mehr) gezeigt werden sollen. Aus diesem Grund haben wir uns mit dem TGA-Format auch ein Format gewählt das diesen Kanal direkt im Bild speichern kann, sodass wir diesen nicht extra erstellen oder aus einer seperaten Bilddatei laden müssen.<br />
<br />
Wie man den Alphakanal nun in die Textur bekommt hängt davon ab wie man seine Textur erstellt. Wer seine Objekte von Hand malt, der muss den Alphakanal im Bildbearbeitungsprogramm selbst erstellen. Wer seine Objekte allerdings vorrendert, der kann diese Arbeit im Normalfall von der 3D-Software erledigen lassen, die Alphainformationen direkt mitexportieren kann. Als kleiner Hinweis sei übrigens gesagt das man beim Rendering des Objektes im 3D-Programm die Kantenglättung deaktivieren muss, da man sonst an den Rändern Artefakte hat (die logischerweise Teile der Hintergrundfarbe enthalten) die dann in der OpenGL-Anwendung zu unschönen Effekten führen. Um das zu verbildlichen hier nochmal unsere Panzertextur, allerdings begleitet vom (im Bildformat gespeichertem) Alphakanal:<br />
<br />
[[Bild:Tutorial_2D_illustration_5.jpg|center]]<br />
<br />
Links also unsere Textur und rechts der Alphakanal. Den sieht man normalerweise nicht, aber fast jedes Bildbearbeitungsprogramm gibt einem die Möglichkeit sich diesen anzeigen zu lassen. Wie zu sehen befinden sich in unserem Falle nur zwei Werte im Alphakanal. Und zwar Schwarz (= 0) für komplett transparent und Weiß (= 1) für komplett Sichtbar.<br />
<br />
Unter OpenGL nutzen wir jetzt für die Transparenz den [[GlAlphaFunc|Alphatest]]. Transparenz ließe sich auch über Blending realisieren, allerdings hat Blending den Nachteil das man dann die transparenten Objekte nach Tiefe sortieren müsste, da Blending im Framepuffer abläuft und nicht wie der Alphatest auf Fragmentbasis, wo wir uns dann keine Sorge um die Reihenfolge unserer Objekte machen müssen. Wollen wir nun also den Alphakanal der Textur nutzen (natürlich muss dieser vorher im Texturenloader geladen werden, sonst geht's nicht) müssen wir vor dem rendern des mit der Objekttextur belegten Quads den Alphatest aktiveren und festlegen wie der Test auszusehen hat :<br />
<br />
<source lang="pascal">glEnable(GL_ALPHA_TEST);<br />
glAlphaFunc(GL_GREATER, 0.1);</source><br />
<br />
Zuerst aktiveren wir also den Alphaest (dazu gibt es die Konstante '''GL_ALPHA_TEST'''), bevor wir dann der GL mittels [[GlAlphaFunc|glAphaFunc]] sagen wie der Test aussehen soll. Der erste Parameter ('''GL_GREATER''') gibt an, das nur Fragmente (also Teile der Textur) gerendert werden sollen deren Alphawert größer ist als der im zweiten Wert angegebene (0.1). Wie auch Farbwerte wird der Alphawert unter OpenGL "geclampt", also in eine bestimmte Reichweite gebracht, nämlich 0 (= Schwarz) bis 1 (= Weiß). Die 0,1 (statt der 0) als Vergleichswert nehmen wir quasi aus Toleranz. Wenn wir das obige also getan haben, dürften wir auf unserem Quad (sofern der Alphakanal korrekt erstellt und geladen wurde) also nur noch das eigentliche Objekt sehen, und der Hintergrund müsste an den transparenten (Alpha <= 0,1) zu sehen sein:<br />
<br />
[[Bild:Tutorial_2D_illustration_6.jpg|center]]<br />
<br />
Links sehen wir unsere Bohne ohne aktiven Alphatest, was zur Folge hat das der eigentlich transparente (schwarze) Teil der Objekttextur den Hintergrund überdeckt. Rechts wuede der Alphatest aktiviert und wir sehen nur den Teil unseres Objektes den wir auch sehen wollen.<br />
<br />
===Das Objekt anzeigen===<br />
<br />
Nachdem wir nun unsere Textur mit passendem Alphakanal geladen haben und den Alphatest auch aktiviert haben, müssen wir schlussendlich noch unser(e) Objekt(e) rendern. Wie schon mehrfach gesagt nutzen wir dazu die GL_QUADS-Primitive. Bei diesem Primitiventyp beschreiben wir mit vier Eckpunkten ein Rechteck (das von der Grafikkarte dann in zwei Dreiecke zerlegt wird), wobei jeder Eckpunkt auch eine Texturkoordinate zugewiesen bekommt. Diese Koordinate gibt an, aus welchem Teil der Textur dieser Eckpunkt seine Bilddaten beziehen soll, und sie wird über das gesamte Quad hinweg interpoliert. Also haben wir in der genauen Mitte des Quads als interpolierte Texturkoordinate das genaue Mittel der übergebenen Texturkoordinaten. Um diese Interpolation müssen wir uns allerdings keine Sorgen machen, das macht die Grafikkarte.<br />
<br />
Und in Sachen Texturkoordinaten sind wir auch schnell fertig, denn für den Anfang haben wir pro Textur immer nur ein Objekt (später zeige ich dann wie man mehrere Objekt in eine Textur packt) und müssen dementsprechend auch nur eine 1 (= Ende der Textur) bzw. 0 (= Anfang der Textur vergeben. Wenn man sich das bildlich vorstellt, sieht das dann so aus:<br />
<br />
[[Bild:Tutorial_2D_illustration_7.jpg|center]]<br />
<br />
Da wir nur mit 2D-Texturen arbeiten, müssen wir pro Eckpunkt auch nur zwei Koordinaten angeben. Und zwar einmal in X-Richtung auf der Textur (unter OpenGL auch S-Richtung genannt, oft auch mit "U" betitelt) und in Y-Richtung (unter OpenGL T-Richtung, oft auch "V" genannt). Wenn S=0 und T=0, bedeutet das also das dieser Eckpunkt seinen [[Texel|Texel]] (wie Pixel, bloß im Bezug auf Texturen) aus der oberen linken Ecke unserer Textur (X=0/Y=0) bezieht, während S=1 und T=1 dafür sorgt das der Eckpunkt sich den Texel in der untersten rechten Ecke der Textur schnappt. Wie bereits oben erwähnt müssen wir uns um den Raum zwischen den vier Eckpunkten nicht kümmern, das wird ja von der Hardware linear interpoliert.<br />
<br />
Der Quellcode zu obigem Beispiel sieht dann so aus:<br />
<br />
<source lang="pascal">glBegin(GL_QUADS);<br />
glTexCoord2f(0,0); glVertex3f(-Breite/2, -Höhe/2, -Tiefe);<br />
glTexCoord2f(1,0); glVertex3f(+Breite/2, -Höhe/2, -Tiefe);<br />
glTexCoord2f(1,1); glVertex3f(+Breite/2, +Höhe/2, -Tiefe);<br />
glTexCoord2f(0,1); glVertex3f(-Breite/2, +Höhe/2, -Tiefe);<br />
glEnd;</source><br />
<br />
Wer obigen Text aufmerksam gelesen hat, sollte eigentlich problemlos verstehen was der Quellcode denn bewirkt. Wer damit Problem hat, der sollte sich das obige Kapitel nochmal unbedingt sorgfältig durchlesen, denn das ist eine sehr wichtige Sache.<br />
<br />
Soviel also zu den Grundlagen von 2D unter OpenGL. Wer nämlich hier angelangt ist, sollte zumindest 2D-Objekte unter OpenGL anzeigen können. Evtl. ist es hier angebracht das Tutorial einige Minuten ruhen zu lassen und ein wenig mit dem Erlerntem herumzuprobieren. Danach geht's nämlich mit etwas fortgeschritteneren (aus Sicht des Einsteigers) Themen weiter.<br />
<br />
===Die Rolle des Tiefenpuffers===<br />
<br />
Trotz der Tatsache dass wir in 2D die dritte Dimension vernachlässigen, bedeutet dies nicht das wir den Tiefenpuffer nicht doch nutzen können. Und zwar nutzen wir diesen in 2D zur hardwarebeschleunigten Sortierung unserer Objekte. In 2D-Anwendungen kann es ja genauso vorkommen das ein Objekt unter ("hinter") einem anderen liegt und da wäre es ziemlich dumm diese selbst zu sortieren, wo OpenGL uns doch einen Tiefenpuffer anbietet der dies für uns macht.<br />
<br />
Genau deshalb haben wir mittels glOrtho auch die Reichweite für unseren Tiefenpuffer angegeben. Haben wir dann auch noch den Tiefentest mittels {{INLINE_CODE|glEnable(GL_DEPTH_TEST)}} und dem passenden Tiefentest via {{INLINE_CODE|[[glDepthFunc]](GL_LESS ''oder'' GL_EQUAL)}} aktiviert, so können wir über die Z-Koordinate unserer Objekte angeben wo die nun genau liegen. Ein Objekt dessen Z-Koordinate also näher an Z-Near ist, wird dann über einem an gleicher Stelle befindlichem Objekt mit einer Z-Koordinate näher an Z-Far gerendert, und zwar egal welches dieser Objekte wir als erstes an die GL übergeben haben.<br />
<br />
===Backface Culling===<br />
<br />
Auch diese von OpenGL angebotene Funktionalität sollte hier nicht verschwiegen werden. Denn in OpenGL bestehen Flächen immer aus einer Vorder- und Rückseite (wie im echten Leben, ein Blatt Papier hat ja auch zwei Seiten, egal wie dünn es ist), was spätestens dann Sinn macht wenn man bedenkt das man sich in einer 3D-Umgebung ja komplett frei bewegen kann. Doch in unserer 2D-Welt sehen wir immer nur eine Seite unserer Objekte, egal was wir anstellen. Und genau deshalb sollten wir OpenGLs [[Backface Culling]] (zu Deutsch heisst das wörtlich "Rückseiten Ausschluß", aber solche Fachbegriffe deutsch man auch besser nicht ein) aktivieren, denn ist dies nicht der Fall, so werden alle Berechnungen immer für beide Seiten eines Polygons ausgeführt. Und dabei spielt es keine Rolle welche der Seiten sichtbar ist oder nicht. Wir aktivieren also das Backfaceculling und sagen OpenGL dass die Rückseiten der Primitiven nicht dargestellt werden soll (letzteres könnte man sich sparen, da das die Voreinstellung ist, allerdings sollte man lieber auf Nummer sicher gehen):<br />
<br />
<source lang="pascal">glEnable(GL_CULL_FACE);<br />
glCullFace(GL_BACK);</source><br />
<br />
Solltet ihr jetzt übrigens eure Objekte nicht mehr sehen, dann habt ihr die Eckpunkte eurer Primitiven in der falschen Reihenfolge (Standard ist für OpenGL '''GL_CCW''' ('''C'''ounter'''C'''lock'''W'''ise, gegen den Uhrzeigersinn) angegeben. Ich empfehle dann entweder einen Blick ins Reedbook oder in eine der Beispielanwendungen, wo es eine Funktion namens ''DrawQuad'' gibt, die ein korrektes Quad rendert. Bei modernen Grafikkarten sollte das Backface Culling zwar nur recht wenig Performance bringen, aber es wäre trotzdem Performanceverschwendung dieses nette Feature einfach ungenutzt zu lassen.<br />
<br />
==Animation==<br />
Bisher können wir zwar unsere 2D-Objekte mittels texturierter Quads auf dem Bildschirm darstellen, aber das ist natürlich noch recht statisch; und was wäre ein Computerspiel ohne Animationen? Und genau darum kümmern wir uns jetzt. Aber zuerst kurz zum Unterschied "Animation in 3D" und "Animation in 2D". In 3D ist es normalerweise so, dass man ein 3D-Modell lädt und dann die einzelnen Teile des Modells animiert, sei dies über die GL-Befehle oder Animationsdaten im 3D-Format (Bones, Keyframes). Das bedeutet also das man in 3D quasi unendlich viele Freiheiten hat, denn man kann ja z.&nbsp;B. den Turm eines Panzers über [[glRotate]]f in jeden beliebigen Winkel bringen. In 2D ist das anders, denn da sind ja alle Grafiken vorgefertigt und werden nur abwechselnd auf unsere Quads geklebt. Würden wir dort also den Turm eines vorgefertigten Panzers rotieren lassen wollen, so müssten wir jeden Animationsframe vorfertigen und als Textur laden. Man muss sich also vorher Gedanken drüber machen ob 2D denn im Endeffekt wirklich weniger Aufwand bedeutet. Aber diese Entscheidung muss jeder für sich selbst fällen, weshalb wir uns nun zwei verschiedenen Animationsmöglichkeiten widmen werden.<br />
<br />
===Animation über Einzeltexturen===<br />
''(Projektdatei : openGL2D_demo1)''<br />
[[Bild:Tutorial_2D_illustration_8.jpg|center]]<br />
<br />
Die wohl offensichtlichste (und auch einfachste) Art ein 2D-Objekt über Texturen zu animieren, ist über in einzelnen Texturen abgelegte Frames. Besonders aus einem 3D-Animationsprogramm (3D Studio, Maya) geht das besonders einfach. Denn diese Programme können eine Animation als Einzelbilder auf die Platte rendern. Dadurch hat man dann für eine Animation mit 60 Bildern 60 Texturen die dann einfach in ein Array geladen werden:<br />
<br />
<source lang="pascal">var<br />
Frame : array of glUInt;<br />
<br />
SetLength(Frame, NumFrames);<br />
for i := 0 to NumFrames-1 do<br />
LoadTexture('animframe'+IntToStr(i)+'.tga', Frame[i], False);</source><br />
<br />
Das zu animierende Objekt verpasst man dabei mit einem Fließkommawert der den aktuell anzuzeigenden Frame darstellt, und erhöht diesen dann über die Zeit. Beim Rendern des Objektes nutzt man diesen Wert gerundeten (Indizes müssen ja Ganzzahlwerte sein) als Index in das Texturenarray:<br />
<br />
<source lang="pascal">glBindTexture(GL_TEXTURE_2D,Frame[Round(CurrentFrame)]);<br />
...<br />
CurrentFrame := CurrentFrame + 0.025 * TimeFactor;<br />
if CurrentFrame > Length(Frame) then<br />
CurrentFrame := 0;</source><br />
<br />
Der Vorteil dieser Methode ist das man diese Animationen direkt aus seinem 3D-Programm heraus speichern kann und das die Animationen quasi unendlich lange (wobei sowohl Speicherplatz als auch Speicherausbau der Grafikkarte hier limitierende Faktoren sind) sein können. Nachteil ist der hohe Speicherverbrauch, sowohl auf der Platte als auch im Grafikspeicher, sowie die Tatsache das man dann recht oft die Textur wechseln muss.<br />
<br />
===Animation in einer einzigen Textur===<br />
''(Projektdatei: openGL2D_demo2)''<br />
<br />
Diese Art der Animation ist wie oben schon angedeutet recht speichersparend und vermindert auch die Zahl der Texturenwechsel. Allerdings ist sie auch aufwendiger zu implementieren und unterliegt einigen Einschränkungen die man bei der Erstellung der Animationstextur berücksichtigen sollte. Statt also jeden Frame der Animation in einer eigenen Datei (und damit später auch Textur, obwohl sich das natürlich auch kombinieren lässt) abzulegen, wird versucht bei dieser Animationsart alle Frames in einem Gitter auf einer Textur anzuordnen.<br />
<br />
Wenn man nicht noch mit der Erstellung komplexer Texturkoordinaten rumfuchteln will, dann sollte man drauf achten dass das gewählte Gitter so hoch wie breit ist und die Texturen an diesem Gitter ausgerichtet sind. So habe ich bei dieser Beispieltextur (bevor sie für das Tutorial etwas verkleinert wurde) die Frames in einem Raster von 256x256 Pixeln untergebracht.<br />
<br />
[[Bild:Explosion_det.jpg|center]]<br />
<br />
Zu sehen sind hier alle Animationsframes einer Explosionstextur aus der Beispielanwendung. Diese wurd mit einem entsprechenden Tool generiert, aber kaum ein 3D-Porgramm kann Animationen direkt in eine einzelne Textur exportieren. Hier muss man sich dann mit passenden Plug-Ins oder Drittprogrammen aushelfen.<br />
<br />
Das "Abspielen" dieser Animation ist nun recht einfach, denn wir erinnern uns ja daran das die Texturkoordinaten in OpenGL immer im Bereich 0 bis 1 liegen, unabhängig (es gibt Ausnahmen, aber interessiert hier nicht) von der Größe unserer Textur. Also müssen wir im Endeffekt nur wissen wie viele Spalten und Zeilen unsere Animationstextur enthält um für jeden Frame die passenden Texturkoordinaten errechnen zu können. Um es konkret zu machen nehmen wir obiges Beispiel. Es besteht aus drei Reihen und drei Spalten, wobei die Animationen von links nach rechts durchlaufen. Also bedeutet dies das unser erster Animationsframe bei S=0 / T=0 beginnt und bei S=1/4 / T=1/4 Ende, Frame 2 beginnt dann bei S=1/4 / T=0 und endet bei S=2/3 / T=1/4, usw. Dies zu errechnen sollte als keine Probleme bereiten, genauso wie das Rendern.<br />
<br />
Doch dürfen wir die Nachteile dieser Animationsart auch nicht verschweigen. Das es oft schwierig ist (wenn man die Objekte und Animationen sowieso von Hand zeichnet dann natürlich nicht) aus den Animationsframes eine einzelne Textur zu machen habe ich bereits gesagt, aber man muss auch wie immer auf die Hardwarelimitationen achten. Denn wenn man viele Animationen in einer Textur unterbringen will, wird diese oft recht groß und selbst auf modernen Karten sollte die Textur nicht größer als 2048x2048 Pixel sein. Wenn wir also ein Objekt der Größe 256x256 haben, bekommen wir im besten Falle (2048 wird auf älteren Karten entweder nicht machbar sein, deren Grafikkartenspeicher fast ganz aufbrauchen, oder zu langsam sein) 64 Animationsframes auf eine Textur. Alles darüber müsste man dann in eine weitere Textur auslagern, was den Verwaltungsaufwand stark erhöhen würde. Ausserdem muss man drauf achten, dass sich die Animationsframes nicht direkt berühren, also das zwischen einem sichtbarer Objektteil in Frame N mindestens ein Pixel Abstand zum sichtbaren Objektteil in Frame N+1 besteht. Denn dadurch das die Textur von OpenGL gefiltert wird, verlaufen Pixel die direkt aneinander liegen ineinander; wodurch dann unschöne Effekte entstehen. Ein weitere (allerdings nicht so gravierender) Nachteil ist die Tatsache dass man solche Animationen nicht über Texturenwiederholung mehrfach auf ein Objekt legen kann. Wenn man bei der erstgenannten Animationsart z.&nbsp;B. statt S=1/T=1 S=2/T=2 nutzt, dann wird dann Animation insgesamt viermal auf dem Quad wiederholt. Da man hier aber über die Texturkoordinaten quasi einen Frame aus der Textur herauspickt ist das nicht möglich, was im Normalfall aber kaum Gewicht haben sollte.<br />
<br />
===Rotation und Skalierung===<br />
''(Projektdatei: openGL2D_demo3)''<br />
<br />
Wenn man alles selbst zeichnet (also z.&nbsp;B. mit der GDI arbeitet), dann hat man für die Rotation (oder Skalierung) eines Objektes im Normalfall nur zwei Möglichkeiten: Entweder man schreibt sich eigene Routinen die den Pixelsalat rotieren (was meist langsam ist, solange man es nicht in Assembler schreibt) oder man erstellt Bilddateien auf denen die Objekte schon vorrotiert sind. Erstere Methode ist wie gesagt langsam und sieht hässlich aus (es sei denn man filtert auch noch selbst) und die zweite Methode schränkt einen auf die vorberechneten Drehwinkel ein.<br />
<br />
Alle gerade genannten Probleme kann man unter OpenGL dank der hardwarebeschleunigung ad acta legen. Nicht nur dass man dank Hardware unendlich schnell rotieren kann, diese Rotation auch noch frei ist, nein, man bekommt sogar das Filtering dank Hardware umsonst:<br />
<br />
[[Bild:Tutorial_2D_illustration_10.jpg|center]]<br />
<br />
Links ist das um 15° gegenüber dem Ausgangsbild gedrehte Raumschiff ohne Filtering zu sehen. Gut ist hier die dadurch entstehende "Körnung" erkennbar, die in Bewegung noch sehr viel unschöner aussieht. Rechts sieht man das gleiche, diesmal allerdings mit Filtering. Der Unterschied dürfte direkt auffallen, und besonders auf hellen Hintergründen ist er noch frapierender. Natürlich sollte man die Grundlagen von OpenGL beherrschen um die Rotation korrekt anwenden zu können, denn folgender Code:<br />
<br />
<source lang="pascal">glRotatef(45, 0,0,1);<br />
glTranslatef(320, 240, 0);<br />
DrawQuad(0,0,0, 256,256);</source><br />
<br />
Bewirkt etwas total anderes als folgender Code :<br />
<br />
<source lang="pascal">glTranslatef(320, 240, 0);<br />
glRotatef(45, 0,0,1);<br />
DrawQuad(0,0,0, 256,256);</source><br />
<br />
Wer sich die OpenGL-Grundlagen bereits angeeignet hat, der wird sicher schnell erkannt haben wo der Unterschied liegt. Denn in OpenGL bewegt man Objekte nicht direkt, sondern immer nur den Ursprung der Matrix. Ersterer Code rotiert also unsere Matrix um 45° und verschiebt diese dann um 320 Einheiten auf der X- und 240 auf der Y-Achse, was dazu führt das unser Objekt in einem recht großen Kreis um den Ursprung rotiert wird. Der zweite Codeschnippsel hingegen verschiebt unser Objekt an den Punkt 320/240 (in unserem Falle genau die Bildschirmmitte) und rotiert dann dort unser Objekt um die eigene Achse. Das ist auch der Rotationscode den wir im Normalfall nutzen um ein Objekt um sich selbst rotieren zu lassen. In unserer 2D-Anwendung müssen wir auch prinzipiell nur um die Z-Achse rotieren, denn die ragt in OpenGL "in" den Bildschirm hinein. Um das nachzuvollziehen einfach mal den rechten Arm grade ausstrecken und "um" den ausgestreckten Zeigefinger rotieren lassen. Euer Arm stellt dann in diesem Falle die Z-Achse dar.<br />
<br />
Das Skalieren eines Objektes gestaltet sich noch leichter, denn hier muss man nichts weiter machen als vor dem Rendern des Quads ein glScalef aufzurufen und diesem dann den Skalierungsfaktor (1=keine Skalierung) pro Achse zu übergeben. Da wir hier allerdings nur in 2D arbeiten sollten wir die Z-Skalierung immer bei 1 belassen:<br />
<br />
<source lang="pascal">glScalef(2,2,1);<br />
DrawQuad(0,0,0, 256,256);</source><br />
<br />
Obiger Quellcode zeichnet unser Objekt in der doppelten Größe (also statt 256x256 Einheiten 512x512 Einheiten groß). Über die Skalierung kann man schön in 2D einen Tiefeneffekt realisieren, zum Beispiel ein Raumschiff das mit einer Skalierung von 0,5 /0,5 / 1 auf einem Träger startet und dann zu Beginn des Levels langsam in Richtung 1 / 1 / 1 skaliert wird. Dadurch wird das Schiff größer und es entsteht beim Betrachter der Eindruck, das Schiff würde hinaufsteigen.<br />
<br />
==Scrollen und zoomen==<br />
''(Projektdatei: openGL2D_demo4)''<br />
<br />
Selbst viele kommerzielle 3D-Spiele benötigen oft einen 2D-Teil um solche Sachen wie Übersichtskarten (man will ja gerne wissen wo man hin will) oder evtl. Credits und ähnliche größere 2D-Anzeigen zu tätigen, die oft den ganzen Bildschirm einnehmen oder gar größer sind. Besonders bei solchen Sachen wie z.&nbsp;B. einer großen Übersichtskarte (sei es nun die Strategiekarte für einen Schlachtplatz im Zweiten Weltkrieg, oder die Übersichtskarte eines Fantasyreiches) sind die Vorteile von OpenGL (wie an dem im Download enthaltenem Beispiel zu erkennen) gut ersichtlich. Das Scrollen wird durch einen einfachen Aufruf an [[glTranslate]]f erledigt (um die außerhalb des sichtbaren Bereichs liegenden Teile der Karte muss man sich dabei keine Sorgen machen), während man das vergrößern der Karte (~ zoomen) mit einem einfachem [[glScale]]f vollbringen kann.<br />
<br />
Und dank der Hardwarebeschleunigung wird diese Karte schneller angezeigt als man dies von Hand (oder über andere 2D-APIs je tun) könnte. Auf einer halbwegs modernen Grafikkarte sollten locker an die 1000 FpS drin sein.<br />
<br />
Nachdem man die Übersichtskarte geladen hat, gestaltet sich das Rendern selbiger als sehr einfach:<br />
<br />
<source lang="pascal">glBindTexture(GL_TEXTURE_2D, MapTex);<br />
glTranslatef(MapPos.x, MapPos.y, 0);<br />
glScalef(MapScale, MapScale, MapScale);<br />
DrawQuad(0,0,0, MapSize.x,MapSize.y);</source><br />
<br />
Sieht schockierend einfach aus. Mehr braucht es nicht um eine Übersichtskarte zu scrollen und zu zoomen. Allerdings sollte man wie bereits einige Male erwähnt wurde auf die Hardwarelimiationen achten. Wenn die Übersichtskarte also allzu groß sein sollte, dann kann man diese ja ohne Probleme unterteilen und als mehrere Quads rendern. Außer der etwas abgeänderten Positionierung der einzelnen Kartenteile ändert sich aber am Grundprinzip nichts.<br />
<br />
==Tiling (Kachelung)==<br />
''(Projektdatei: openGL2D_demo5)''<br />
<br />
Die oben genannte Technik mag zwar gut genug für statische Übersichtskarten sein, aber wenn man eine dynamische Karte aus der Vogelperspektive realisieren will, so kommt man mit einer riesigen vorgefertigten Textur nicht weit. Dafür gibt es dann aber eine Technik namens "Tiling", was zu Deutsch so viel wie "Kachelung" heißt. Hier hat man ähnlich einem Schachbrett ein zweidimensionales Spielfeld das aus unterschiedlichen Kacheln besteht die als Texturen vorliegen. In einem zweidimensionalem Array wird dann für jedes Feld gespeichert welche Textur zu ihm gehört. Diese Technik ist immer noch recht weit verbreitet (zumindest im Hobbybereich) und für 2D-Spiele aus der Vogelperspektive recht gut geeignet, wobei hier der Großteil der Arbeit eher beim Grafiker als beim Programmierer liegt, denn da die Teils nur Vierecke darstellen müssen Übergänge von z.&nbsp;B. einem Terraintyp zum anderen in die Textur gezeichnet werden. <br />
<br />
Das Rendern eines solchen Spielfeldes gestaltet sich dabei sehr einfach:<br />
<br />
<source lang="pascal">for x := 0 to MapWidth-1 do<br />
for y := 0 to MapHeight-1 do<br />
begin<br />
glBindTexture(GL_TEXTURE_2D, MapTexture[Map[x,y]]);<br />
DrawQuad(MapPos.x+x*32, MapPos.y+y*32, 0, 32,32);<br />
end;</source><br />
<br />
Allerdings ist obige Methode alles andere als optimiert. Das erste (und größte) Problem ist die Tatsache das unabhängig von aktuellen Betrachtungsposition immer die komplette Karte gerendert wird. Bei großen Karten (z.&nbsp;B. 128x128 Kacheln) gehen dann selbst moderne Karten in die Knie. Die Kacheln außerhalb des Sichtbereiches werden zwar von der Grafikkarte nicht gerendert (da sie außerhalb des Viewports liegen), aber müssen dennoch in jedem Frame über den Bus gesendet werden, genauso wie die Texturenwechsel in jedem Frame stattfinden müssen. Das erste Problem lässt sich recht schnell lösen, in dem wir einfach prüfen ob die aktuell zu rendernde Kachel auch im momentan sichtbarem Bereich liegt:<br />
<br />
<source lang="pascal">glScalef(MapScale, MapScale, MapScale);<br />
for x := 0 to High(Map) do<br />
for y := 0 to High(Map[x]) do<br />
if (MapPos.x + x*32 >= -16*MapScale) and (MapPos.x + x*32 <= SizeX/MapScale+16) and<br />
(MapPos.y + y*32 >= -16*MapScale) and (MapPos.y + y*32 <= SizeY/MapScale+16) then<br />
begin<br />
glBindTexture(GL_TEXTURE_2D, MapTex[Map[x,y]]);<br />
DrawQuad(MapPos.x+x*32, MapPos.y+y*32, 0, 32,32);<br />
end;</source><br />
<br />
In der If-Abfrage, die wir vor dem Rendern einer jeden Kachel machen, prüfen wir jetzt ganz einfach ob die aktuelle Kachel im Sichtfeld liegt. Da wir eine dynamische Karte haben und die Vorteile von OpenGL nutzen wollen, müssen wir bei dieser Abfrage natürlich auch den Zoomfaktor (''MapScale'') und das Scrolling (''MapPos'') mit einbeziehen.<br />
<br />
Dank dieser offensichtlichen Optimierung dürften wir jetzt einen starken Performancegewinn von mehreren hundert Prozent sehen, denn sehr viele Texturenwechsel fallen weg und es wird sehr viel weniger Geometrie über den Bus gesendet. Aber natürlich sind wir noch nicht am Ende der Fahnenstange angelangt, denn wie gesagt sind Texturenwechsel recht performancelastig. Also liegt es nahe diese zu optimieren:<br />
<br />
<source lang="pascal">for t := 0 to High(MapTex) do<br />
begin<br />
glBindTexture(GL_TEXTURE_2D, MapTex[t]);<br />
for x := 0 to High(Map) do<br />
for y := 0 to High(Map[x]) do<br />
if Map[x,y] = t then<br />
if (MapPos.x + x*32 >= -16*MapScale) and (MapPos.x + x*32 <= SizeX/MapScale+16) and<br />
(MapPos.y + y*32 >= -16*MapScale) and (MapPos.y + y*32 <= SizeY/MapScale+16) then<br />
DrawQuad(MapPos.x+x*32, MapPos.y+y*32, 0, 32,32);<br />
end;</source><br />
<br />
Wie zu sehen haben wir unsere Schleife etwas umgebaut. Wir gehen jetzt in der Hauptschleife durch alle unsere Kacheln (abgelegt in ''MapTex'') und dann für jede Kachel die komplette Karte in beide Dimensionen durch. So binden wir jede Texur nur einmal und rendern dann alle Kacheln die diese Textur nutzen. Das klingt erstmal so (zumindest vom Schleifenaufbau her) als würden wir hier Performance vernichten, denn schliesslich gehen wir jetzt nicht einmal die komplette Karte durch, sondern so viele Male wie wir Kacheltexturen haben. Allerdings ist es meistens so das der CPU-Overhead durch die zusätzlichen Schleifendurchläuft weniger kostet als uns die gesparten Texturenwechsel eingebracht haben. Im Normalfall (es gibt natürlich Situationen wo der CPU-Overhead zu groß wird, z.&nbsp;B. bei wirklich riesigen Karten) sollten wir auch durch diese Optimierung einen Performancezuwachs im zweistelligen Prozentbereich erkennen.<br />
<br />
So viel also zum Thema Kachelung. Besonders für kleinere Projekte ist diese Technik ein guter Start und vor allem ist diese Technik leicht umzusetzen. Ist natürlich nicht mehr zeitgemäß, aber bei den meisten Hobbyprojekten geht es ja eher um den Inhalt und nicht nur um das Äußere. <br />
<br />
==Spezialeffekte==<br />
<br />
Nach diesem langen Marsch sind wir jetzt so ziemlich mit allen im 2D-Bereich verwendeten Techniken durch und können uns nun einem interessanterem Kapitel zuwenden; den Spezialeffekten. Hier ist der eigenen Kreativität natürlich keine (oder kaum, je nach Hardware) Grenze gesetzt, weshalb ich nur einige häufig unter 2D genutzte Effekte erwähnen möchte.<br />
<br />
===Beleuchtung ("Per-Pixel")===<br />
''(Projektdatei: openGL2D_demo6)''<br />
<br />
Beleuchtung ist ja (auch wenn man das nicht so wahrnimmt, da es ja im echten Leben selbstverständlich ist) eine Sache die man in keinem Spiel vernachlässigen sollte. Ohne korrekte Beleuchtung geht viel Atmosphäre flöten, aber besonders in einer 3D-Umgebung ist korrektes Per-Pixel-Licht (die OpenGL-Beleuchtung arbeitet auf Vertexbasis) oft schwer zu implementieren (was dank Shader aber leichter geworden ist). In einer 2D-Umgebung ist das aber zum Glück sehr viel einfacher, denn dort haben wir z.&nbsp;B. im Falle einer 2D-Karte ja keine dritte Dimension um die wir uns kümmern müssten, müssen unsere Lichtquellen also nicht auf die Szene draufprojizieren.<br />
<br />
In 2D geht das also ganz einfach und wir machen die Beleuchtung bequem über eine Lichttextur in der die Intensität der Lichtquelle abgelegt ist. Für die Demo habe ich dazu einen einfachen radialen Verlauf gewählt, aber der Fantasie sind da keine Grenzen gesetzt:<br />
<br />
[[Bild:Tutorial_2D_illustration_12.jpg|center]]<br />
<br />
Farbinformationen speichern wir nicht in der Textur, denn wir wollen ja dynamisch bleiben und realisieren die Färbung des Lichtes später in unserem Programm über einen [[glColor]]3f Aufruf. Bevor wir allerdings loslegen, sollten noch ein paar Kleinigkeiten im Bezug auf diese (einfache Form der) Beleuchtung geklärt werden. Damit wir unsere Szene mit einer solchen Textur "beleuchten" können, nutzen wir additives Blending. Das bedeutet also das wir zur Farbe im Framepuffer (der unsere Karte zeigt) den Farbwert in unserer Lichttextur addieren. Dort wo unsere Textur also komplett weiß ist, haben wir auf der Karte die volle Lichtfarbe. Blending hat aber einen Nachteil, der in 3D oft sehr viel Kopfzerbrechen macht, aber in 2D leicht gelöst werden kann: Blending arbeitet "auf" dem Framepuffer, was also bedeutet das OpenGL in diesem Falle nicht für uns sortiert. Wenn wir dann also eine Lichttextur rendern die nahe am Betrachter ist und dahinter (im Raum) eine zweite Lichttextur rendern die weiter entfernt ist, werden wir diese nicht sehen, da im Framepuffer bereits die erste Lichttextur "liegt" (sprich dort der Z-Wert der ersten Textur abgelegt wurde und die Fragmente der zweiten Textur deshalb aufgrund des Z-Tests verworfen werden). Das ist natürlich nicht korrekt, aber in 2D lässt sich dieses Problem mit dem passenden Tiefentest lösen:<br />
<br />
<source lang="pascal">glDepthFunc(GL_ALWAYS);</source><br />
<br />
Normalerweise sollte man diese Tiefenfunktion erst setzen bevor man die Lichtquellen zeichnet, sofern man vorher den Tiefenpuffer nutzen will um die Objekte auf seiner Karte in der Höhe zu sortieren. Denn wie der Name vermuten lässt bewirkt diese Form des Tiefentests das Fragmente (="Pixel auf Probe") immer rasterisiert werden, egal ob sie jetzt durch ein bereits im Frampuffer liegendes Fragment verdeckt werden würden oder nicht. Wenn wir mit diesem Tiefentest nun also zwei Lichtquellen übereinander rendern würden, werden diese korrekt angezeigt. Aber wie immer soll euch dieser etwas technisch klingende Text nicht aus dem Konzept bringen, weshalb ich dann hier folgende zwei Bilder reden lasse:<br />
<br />
[[Bild:Tutorial_2D_illustration_13.jpg|center]]<br />
<br />
Links sehen wir zwei sich überlappende Lichtquellen mit einem normalerweise verwendetem Tiefentest ('''GL_LEQUAL''' oder '''GL_LESS'''). Dass die grüne Lichtquelle die zweite verdeckt ist reiner Zufall und liegt wohl daran das diese als erstes gerendert wurde. Wie oben gesagt (vereinfacht) steht dann im Z-Puffer drin das an dieser Stelle (des grünen Lichtes) bereits ein Objekt im Z-Puffer liegt. Danach wird dann irgendwann die zweite (blaue) Lichtquelle gerendert, aber die Stellen an denen bereits steht dass dort im Z-Puffer ein Fragment liegt werden nicht mehr gerendert, aufgrund des Tiefentests. Das zweite Bild ist hingegen korrekt, einzig der Tiefentest wurde auf '''GL_ALWAYS''' umgestellt. Dann werden die mit der Lichttextur belegten Quads immer gezeichnet, egal ob sich an der Z-Position bereits ein Fragment befindet oder nicht, und bei Lichtquellen ist das reichlich egal welche zuerst gerendert wird. Denn ob ich jetzt Blau mit Grün addiere, oder Grün mit Blau ist egal, denn beides ergibt im Ende Türkis. Das dürfte dann hoffentlich verstanden worden sein, wenn nicht dann einfach mal das Beispielprogramm öffnen und den Tiefetest wie hier angesprochen ändern; Probieren geht je bekannter weise meist über Studieren.<br />
<br />
Abschließend auch noch kurz zum verwendeten Blendmodus:<br />
<br />
<source lang="pascal">glBlendFunc(GL_ONE, GL_ONE);</source><br />
<br />
Wer sich schon mal ein wenig mit dem Thema Blending beschäftigt hat, wird schnell erkennen was hier abläuft. Sowohl der Quellfaktor als auch der Zielfaktor stehen auf '''GL_ONE''', was bedeutet dass bei unserer Blendoperation die bereits im Framepuffer befindliche Farbe mit der Farbe unseres Lichtes addiert wird (additives Blending genannt). Bei einer grünen Lichtquelle haben wir in der Mitte also RGB = 0/1/0 und wenn wir das dann auf einen Pixel legen würden der halb grau ist (RGB = 0,5/0,5/0,5) hätten wir als Ergebnis RGB = 0,5/1/0,5 (1+0,5 ist eigentlich 1,5 , aber OpenGL zwängt Farbwerte in den Bereich 0..1).<br />
<br />
===Texturenverläufe===<br />
''(Projektdatei: openGL2D_demo7)''<br />
<br />
Im Kapitel "Kachelung" habe ich erwähnt, dass es eigentlich Aufgabe des Grafikers ist die Überläufe zwischen zwei unterschiedlichen Kacheln zu erstellen. Allerdings lässt sich das auch mit OpenGL bewerkstelligen, und zwar dank Blending und des Alphakanals. Dazu rendern wir zuerst ein Quad mit der ersten Kachel, und zwar dort wo diese Kachel komplett erscheinen soll mit vollem Alpha (vierter Parameter von [[glColor]]4f = 1) und dort wo später die andere Kachel komplett erscheinen soll mit Alpha = 0. Danach rendern wir an exakt der gleichen Stelle die zweite Kachel, müssen aber natürlich darauf achten das wir den richtigen Tiefentest aktiviert haben damit diese auch sichtbar ist ('''GL_ALWAYS''' oder '''GL_LEQUAL'''), allerdings mit genau umgekehrten Alphawerten und aktiviertem Blending. Fürs Blending nutzen wir als Quellfaktor '''GL_SRC_ALPHA''' und als Zielfaktor dann in logischer Konsequenz '''GL_DST_ALPHA'''. Dadurch wird dann im finalen Ergebnis dort wo die erste Kachel eine Alpha von 1 hat ihr kompletter Farbwert übernommen und dort wo die zweite Kachel einen Alpha von 1 hat deren voller Farbwert. Dazwischen wird in gewohnter Weise interpoliert:<br />
<br />
<source lang="pascal">glDisable(GL_BLEND);<br />
glBindTexture(GL_TEXTURE_2D, TileA);<br />
glBegin(gl_Quads);<br />
glColor4f(1,1,1,1); glTexCoord2f(0,0); glVertex3f(-128, -128, 0);<br />
glColor4f(1,1,1,0); glTexCoord2f(1,0); glVertex3f( 128, -128, 0);<br />
glColor4f(1,1,1,0); glTexCoord2f(1,1); glvertex3f( 128, 128, 0);<br />
glColor4f(1,1,1,1); glTexCoord2f(0,1); glvertex3f(-128, 128, 0);<br />
glEnd;<br />
<br />
glEnable(GL_BLEND);<br />
glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);<br />
glBindTexture(GL_TEXTURE_2D, TileB);<br />
glBegin(gl_Quads);<br />
glColor4f(1,1,1,0); glTexCoord2f(0,0); glVertex3f(-128, -128, 0);<br />
glColor4f(1,1,1,1); glTexCoord2f(1,0); glVertex3f( 128, -128, 0);<br />
glColor4f(1,1,1,1); glTexCoord2f(1,1); glVertex3f( 128, 128, 0);<br />
glColor4f(1,1,1,0); glTexCoord2f(0,1); glVertex3f(-128, 128, 0);<br />
glEnd;</source><br />
<br />
Allerdings wird das bei komplexeren Kachelübergängen recht aufwendig und endet in jeder Menge Overdraw, da man dann viele Übergänge rendern muss. Aber wer sich mal näher damit beschäftigen will, sollte einen Blick auf das "Verblendet"-Tutorial (Einsteiger) von Phobeus werfen, in dem auch diese Technik hier näher besprochen wird.<br />
<br />
===Schatten===<br />
''(Projektdatei : openGL2D_demo8)''<br />
<br />
Im 3D-Bereich sind Schatten eigentlich immer noch die "Königsdisziplin" für jeden Programmierer, besonders wenn diese volumetrisch und vor allem auch korrekt sein sollen. Meist muss man dazu die Szene analysieren, Umrisse von 3D-Objekten generieren und auch noch diverse Spezialfälle beachten (Stichwort "Betrachter im Schattenvolumen"). In unserer heilen 2D-Welt geht das aber schon wie bei der Beleuchtung sehr viel einfacher und hat mit dreidimensionalen Schatten rein gar nichts gemeinsam.<br />
<br />
Hier gehen wir nämlich ganz einfach hin und täuschen Schatten vor indem wir unser Objekt (das dazu natürlich einen Alphakanal besitzen muss) mittels {{INLINE_CODE|glColor3f(0, 0, 0)}} schwarz machen und dann unter dem eigentlichen Objekt leicht versetzt (die Richtung des Versatzes kann man je nach Lichteinfall verändern) rendern. Über [[glScalef]] passen wir dann noch die Größe des Schattens an um einfach vermitteln zu können wie hoch sich unser Objekt befindet. Wenn wir also den Schatten eines Spielers rendern, dann kann der genauso groß sein wie der Spieler selbst, aber bei einem Flugzeug das hoch über dem Boden fliegt sollte der Schatten schon etwas verkleinert werden um einen Eindruck von der Flughöhe zu vermitteln.<br />
<br />
Rein programmiertechnisch sind Schatten in 2D (auch wenn diese keineswegs physikalisch korrekt sind) also genau das Gegenteil von 3D-Schatten: Einfach und ohne jegliche Hindernisse:<br />
<br />
<source lang="pascal">glPushMatrix;<br />
glBindTexture(GL_TEXTURE_2D, ShipTex);<br />
glEnable(GL_ALPHA_TEST);<br />
glAlphaFunc(GL_GREATER, 0.1);<br />
glScalef(0.75, 0.75, 0.75);<br />
glColor3f(0, 0, 0);<br />
DrawQuad(60,-60,1, 256,192);<br />
glPopMatrix;<br />
<br />
glPushMatrix;<br />
glColor3f(1, 1, 1);<br />
glBindTexture(GL_TEXTURE_2D, ShipTex);<br />
glEnable(GL_ALPHA_TEST);<br />
glAlphaFunc(GL_GREATER, 0.1);<br />
DrawQuad(0,0,0, 256,192);<br />
glPopMatrix;</source><br />
<br />
Im ersten Block rendern wir unser Objekt etwas verkleinert und nach unten/rechts versetzt, natürlich komplett schwarz. Danach müssen wir nur noch unser Objekt drüberrendern (in gewohnter Weise) und fertig ist unser Schatten:<br />
<br />
[[Bild:Tutorial_2D_illustration_14.jpg|center]]<br />
<br />
==Schlusswort==<br />
<br />
Das war es also zum Thema 2D in OpenGL. Ich hoffe stark das dieser doch recht ausführliche Bericht so ziemlich alle Bereiche abgedeckt hat und damit auch häufig gestellte Fragen beantwortet. Natürlich war das nicht alles was in diesem Bereich machbar ist, allerdings kann ich hier schlecht auf jeden Effekt eingehen, denn sonst könnte ich das Tutorial gleich als ein einige hundert Seiten starkes Buch verkaufen.<br />
<br />
Doch eines sei noch am Schluss gesagt: 2D ist zwar programmiertechnisch fast immer (besonders bei in 3D komplexen Sachen wie Beleuchtung und Schatten) sehr viel einfacher und man kommt dann auch meist schneller ans Ziel, allerdings ist hier die Erstellung des Inhalts meist aufwendiger. Während z.&nbsp;B. ein 3D-Terrain zwar von der Programmierung her aufwendiger ist, muss man dort am Ende nur eine Terraintextur draufkleben und fertig ist die Sache. In 2D muss man dies hingegen über Kacheln lösen und dann für jeden Übergang verschiedene Kacheln in einem Bildbearbeitungsprogramm erstellen. Oder zum Beispiel das in unserem letzten Beispiel verwendete Raumschiff; wenn man das in 3D über ein 3D-Modell animiert (Keyframes), dann ist es kaum Aufwand was am Modell zu verändern (andere Textur, Form ändern), aber in 2D liegen diese Animationen vorberechnet auf der Platte, und sobald man dann was am Raumschiff ändert, müssen auch alle Animationen geändert werden.<br />
<br />
Wie so oft im Leben muss man also abwägen was jetzt für die eigene Anwendung richtig ist. Essentiell reicht es sich die Frage ''"Ist es für mich aufwendiger in die 3D-Programmierung einzusteigen, oder es ist es aufwendiger den Content für meine 2D-Anwendung zu erstellen"'' zu beantworten, aber das kann ich euch nicht abnehmen, da müsst ihr selbst drauf antworten können. Außerdem sei noch gesagt das die moderne Generation der Computerspieler inzwischen 3D gewohnt ist, was dazu führt das 2D-Anwendungen dann spielerisch sehr überzeugen müssen.<br />
<br />
In dem Sinne also viel Spaß in der zweiten Dimension (die auch Spaß machen) kann. Und vergesst nicht: Ich WILL Feedback zu diesem Tutorial und außerdem will das DGL-Team sehen was ihr so mit unseren Tutorials auf die Beine stellt. Wenn dabei also was halbwegs brauchbares raus gekommen ist, lasst es uns wissen!<br />
<br />
Euer<br />
:'''Sascha Willems''' (webmaster AT delphigl.de)<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_opengl2d_vcl|text=Delphi-VCL-Quelltexte und Windows-Binaries zum Tutorial}}<br />
<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial Lektion 8]]|[[Tutorial_Matrix2]]}}<br />
<br />
[[Kategorie:Tutorial|2D]]</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_opengl2d_vcl&diff=24131Archiv:tut opengl2d vcl2009-08-18T20:29:33Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|autor=Sascha Willems|beschreibung=Delphi-VCL-Quelltexte und Windows-Binaries zum Tutorial 2D.}}“</p>
<hr />
<div>{{Archiv|autor=[[Benutzer:Sascha_Willems|Sascha Willems]]|beschreibung=Delphi-VCL-Quelltexte und Windows-Binaries zum [[Tutorial_2D|Tutorial 2D]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=24130DGL Wiki:Files2009-08-18T19:53:45Z<p>Flo: /* Fehlende Datei Artikel */</p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]<br />
* [[Archiv:tut_jvscript]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23663DGL Wiki:Files2009-05-17T18:24:48Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]<br />
* [[Archiv:tut_jvscript]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23662DGL Wiki:Files2009-05-17T15:47:45Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:jvscript]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_stereo_delphi_vcl&diff=23661Archiv:tut stereo delphi vcl2009-05-17T15:46:30Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-VCL-Quelltext zum Tutorial StereoSehen.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial StereoSehen]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_StereoSehen&diff=23660Tutorial StereoSehen2009-05-17T15:45:19Z<p>Flo: </p>
<hr />
<div>=Stereo einmal sehen statt hören=<br />
[[Bild:Tutorial_Stereo_anaglyph.jpg|center]]<br />
==Einleitung==<br />
Viele Tiere haben mit dem Menschen eins gemein: Zwei Augen. Dies kann unterschiedliche Vorteile haben, etwa in zwei Richtungen gleichzeitig sehen zu können, oder, was viel häufiger ist, um räumlich sehen zu können. Die Welt aus zwei leicht unterschiedlichen Blickwinkeln zu sehen ermöglicht es dem Gehirn Abstände und Geschwindigkeiten besser einzuschärtzen, als mit einem Auge. Das es uns auch mit einem Auge gelingt, erkennt man dran, dass das in begrenztem Umfang auch bei Fotos gelingt und wenn wir mit einem geschlossenem Auge durch ein Zimmer laufen trotzdem nicht überall anstossen. Das funktioniert sogar so gut, dass Menschen, die nur stark eingeschränkt oder gar nicht räumlich sehen können, dies gar nicht so selten erst bei Routineuntersuchungen von Augenärzten oder im Verlauf ihrer Musterung erfahren.<br />
<br />
An einer Stelle ist es jedoch egal, ob wir räumlich sehen können oder nicht - beim Spielen und Arbeiten am Computer. Aber 3D Spiele wollen doch gerade eines: uns die Welt möglichst realistisch vor Augen führen. Leider hat der Computer meist nur einen Monitor, wir aber zwei Augen... Und damit Good Bye räumliches Sehen? Nein, nein, so einfach dürfen wir uns nicht geschlagen geben. Tatsächlich fallen mir gleich eine Reihe von Möglichkeiten ein: Stereogramme, wie sie eine Zeit lang modern waren, und in vielen Büchern vorkommen: langes Stieren auf stylisch eingefärbte Buchseiten. Bei 3D-Spielen waren eine Zeit lang 3D-Shutterbrillen beliebt, bei denen jeweils ein Auge abgedunkelt wird, auf dem Monitor für das Auge ein Bild angezeigt und dann gewechselt wird. Zeigt man für jedes Auge ein leicht versetztes Bild an und wechselt die Bilder häufig genug, entsteht für den Betrachter ein 3-Dimensionales Bild der Szenerie. Leider benötigt man für diese Technik schnell schaltende Röhrenmonitore - mit aktuellen TFT Monitoren ist dies, wegen ihrer langsamen Schaltgeschwindigkeiten nicht möglich und statt eines 3D-Bildes entsteht irgend ein wenig aussagekräftiges Geschmiere.<br />
<br />
Das ist noch immer kein Grund, entmutigt zu sein: In Kinofilmen wird noch heute oft die Technik der Anaglyphe eingesetzt. Dabei muss der Betrachter eine 3D-Brille aufsetzen, die vor jedes Auge einen anderen Farbfilter setzt, etwa rot und grün. Bei der Filmaufnahme werden dann zwei überkreuzt filmende Kameras verwendet.<br />
<br />
Der 3D Effekt stellt sich bei den meisten Menschen schnell ein, bei Anderen dauert es ein wenig oder erfordert etwas Übung. Den Effekt gar nicht geniessen können üblicherweise nur stark Schielende, Einäugige und Blinde. Eine wesentliche Einschränkung bei diesem Verfahren ist jedoch, dass das entstehende Bild meist einfarbig, ähnlich einem s/w Film, ist. Der entstehende 3D Effekt gleicht dieses Manko meiner Ansicht nach auf alle Fälle aus - und da wir möglichst Niemanden ausschliessen möchten, unsere Programme zu bestaunen, werden wir unsere Programme sowohl für "normale" Grafikausgabe als auch für 3D-Brillen vorbereiten.<br />
<br />
==Die 3D-Brille==<br />
Jeder der weitermachen möchte, sollte sich spätestens hier eine 3D-Brille besorgen. Diese gibts z.B. beim Optiker und sollte möglichst einen roten und einen grünen Filter besitzen. Zur Not tuts auch rot-blau, ist jedoch wegen den stark auseinanderliegenden Wellenlängen des roten und blauen Lichtes weniger gut geeignet. Einigen Büchern zu optischen Täuschungen liegen ebenfalls Brillen bei. Solltet ihr keine passende, vorgefertigte Brille finden, könnt ihr sie auch mit etwas Pappe oder Karton und Farbfolien aus dem Bastelladen selbst bauen - oder ganz nobel: mit Filtern aus dem Fotoladen statt ordinären Farbfolien. <br />
[[Bild:Tutorial_Stereo_brille.png|center]]<br />
<br />
==Projektionen==<br />
Bei der üblichen, perspektivischen Projektion ist die Sache einfach: Man hat ein Auge, einen Öffnungswinkel für die Kamera und die Entfernung für die nahe Clipping-Ebene: <br />
[[Bild:Tutorial_Stereo_perspektivisch.png|center]]<br />
<br />
Beim Stereo-Sehen wird die Sache ein Wenig komplizierter. Da man mit Beiden Augen auf die Projektionsebene Bildschirm schaut, ist diese für beide Augen identisch, jedoch sind die Blickkegel nicht mehr gerade, sondern schief:<br />
[[Bild:Tutorial_Stereo_stereo.png|center]]<br />
<br />
Zu allem überfluss können wir unsere Augen auch noch auf eine bestimmte Entfernung ausrichten, d.h. die Ebene, auf die die Augen eingestellt sind, muss nicht der Entfernung der nahen Clippling-Plane entsprechend - das Problem können wir jedoch ganz einfach mithilfe der Strahlensätze lösen.<br />
<br />
<source lang="pascal">...<br />
var<br />
zNear, zFar, Oeffnungswinkel, Augenabstand, Zielweite : Single;<br />
...<br />
const<br />
PBufSize = 1024; //Seitenläne des PBuffers<br />
var<br />
SVerh, ROeffnung : Single;<br />
Breitenhaelfte, NeardZielweite : Single;<br />
left, right, top, bottom : Single;<br />
<br />
procedure CalcValues;<br />
begin<br />
ROeffnung := DegToRad(Oeffnungswinkel / 2); //Halber Öffnungswinkel in RAD<br />
Breitenhaelfte := zNear * Tan(ROeffnung); //Halbe Breite der Proj. Ebene<br />
NeardZielweite := zNear / Zielweite; <br />
end;<br />
<br />
begin<br />
if Opt.Stereo then //Stereo Modus<br />
begin<br />
glViewport(0, 0, ClientWidth, ClientHeight);<br />
//Projektionsmatrix resetten<br />
glMatrixMode(GL_PROJECTION);<br />
glLoadIdentity();<br />
<br />
SVerh := ClientWidth/ClientHeight; //Breite zu Höhe<br />
CalcValues;<br />
<br />
//Ränder der Projektionsebene für glFrustum<br />
left := - SVerh * Breitenhaelfte - 0.5 *Augenabstand*NeardZielweite;<br />
right := SVerh * Breitenhaelfte - 0.5 *Augenabstand*NeardZielweite;<br />
top := Breitenhaelfte;<br />
bottom := -Breitenhaelfte;<br />
glFrustum(left, right, bottom, top, zNear, zFar);<br />
<br />
glMatrixMode(GL_MODELVIEW);<br />
glLoadIdentity;<br />
<br />
//PBuffer erzeugen und aktivieren, wenn noch nicht geschehen<br />
if not Assigned(PBuffer) then<br />
begin<br />
PBuffer := TPixelBuffer.Create(PBufSize, PBufSize, DC, RC, Self);<br />
wglShareLists(RC, PBuffer.RC);<br />
PBuffer.Enable;<br />
InitGl;<br />
end<br />
else<br />
PBuffer.Enable;<br />
<br />
glViewport(0, 0, PBufSize, PBufSize);<br />
//Projektionsmatrix resetten<br />
glMatrixMode(GL_PROJECTION);<br />
glLoadIdentity();<br />
<br />
//Diesmal das Frustum in die andere Richtung schiefstellen(+ statt -)<br />
left := - SVerh * Breitenhaelfte + 0.5 *Augenabstand*NeardZielweite;<br />
right := SVerh * Breitenhaelfte + 0.5 *Augenabstand*NeardZielweite;<br />
top := Breitenhaelfte;<br />
bottom := -Breitenhaelfte;<br />
glFrustum(left, right, bottom, top, zNear, zFar);<br />
<br />
glMatrixMode(GL_MODELVIEW);<br />
glLoadIdentity;<br />
<br />
PBuffer.Disable;<br />
end<br />
else<br />
begin<br />
//Normale Ansicht<br />
glViewport(0, 0, ClientWidth, ClientHeight);<br />
//Projektionsmatrix resetten<br />
glMatrixMode(GL_PROJECTION);<br />
glLoadIdentity();<br />
//Perspektivische Darstellung<br />
SVerh := ClientWidth/ClientHeight;<br />
CalcValues;<br />
//Hier wird nichts verschoben<br />
left := - SVerh * Breitenhaelfte;<br />
right := SVerh * Breitenhaelfte;<br />
top := Breitenhaelfte;<br />
bottom := -Breitenhaelfte;<br />
glFrustum(left, right, bottom, top, zNear, zFar);<br />
<br />
glMatrixMode(GL_MODELVIEW);<br />
glLoadIdentity;<br />
<br />
end;<br />
end;<br />
...</source><br />
<br />
<br />
==Und Rendern?==<br />
Nun müssen wir nur noch zwei Bilder anzeigen. Die einfachste Möglichkeit dürfte sein, ein Bild für das linke Auge in einen PBuffer oder sonstige Textur zu rendern, dann das Bild für das rechte Auge in den Hintergrundpuffer. Der Inhalt des PBuffers wird dann mittels Blending auch in den Hintergrundpuffer gezeichnet und voila. Wir haben zwei verschiedene Bilder auf dem Schirm. Zu beachten ist noch, dass die Farben mittels [[glColorMask]] beim Rendern maskiert werden müssen - wir wollen ja für die beiden Augen unterschiedliche Farben anzeigen. <br />
<br />
<source lang="pascal">procedure TStereoForm.Render;<br />
procedure RenderScene;<br />
begin<br />
//kein glLoadIdentity am Anfang! Das muss vorher gemacht worden sein -<br />
//für die Augen muss ja bereits verschoben worden sein. Also Achtung.<br />
//Brav mit Push und Pop Matrix arbeiten.<br />
...<br />
end;<br />
<br />
var<br />
Error : glUInt;<br />
begin<br />
if (Opt.Stereo) and (Assigned(PBuffer)) then<br />
begin<br />
//Stereo Modus<br />
PBuffer.Enable;<br />
glClearColor(0.0, 0.0, 0.0, 0.0);<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);<br />
<br />
glLoadIdentity;<br />
glTranslatef(Augenabstand/2, 0, 0);<br />
<br />
//linkes Auge bekommt grün und blau - das sollte über Optionen wählbar sein<br />
glColorMask(false, true, true, true);<br />
RenderScene;<br />
PBuffer.Disable;<br />
<br />
glClearColor(0.0, 0.0, 0.0, 0.0);<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);<br />
<br />
glLoadIdentity;<br />
glTranslatef(-Augenabstand/2, 0, 0);<br />
<br />
//rechtes Auge bekommt nuir rot<br />
glColorMask(true, false, false, true);<br />
RenderScene;<br />
<br />
//Und zusammenblenden<br />
glColorMask(true, true, true, true);<br />
glDepthFunc(GL_ALWAYS);<br />
glMatrixMode(GL_PROJECTION);<br />
glPushMatrix;<br />
glLoadIdentity;<br />
glOrtho(0, 1, 1, 0, -1.0, 1.0);<br />
<br />
glMatrixMode(GL_MODELVIEW);<br />
glLoadIdentity;<br />
<br />
glDisable(GL_LIGHTING);<br />
glEnable(GL_TEXTURE_2D);<br />
PBuffer.Bind;<br />
glEnable(GL_BLEND);<br />
glBlendFunc(GL_ONE, GL_ONE);<br />
glBegin(GL_QUADS);<br />
glTexCoord2f(1, 0); glVertex2f(1,1);<br />
glTexCoord2f(1, 1); glVertex2f(1,0);<br />
glTexCoord2f(0, 1); glVertex2f(0,0);<br />
glTexCoord2f(0, 0); glVertex2f(0,1);<br />
glEnd();<br />
glDisable(GL_BLEND);<br />
<br />
glEnable(GL_LIGHTING);<br />
glDisable(GL_TEXTURE_2D);<br />
PBuffer.Release;<br />
<br />
glMatrixMode(GL_PROJECTION);<br />
glPopMatrix;<br />
glMatrixMode(GL_MODELVIEW);<br />
glDepthFunc(GL_LESS);<br />
end<br />
else<br />
begin<br />
//"Mono" Modus<br />
glClearColor(0.0, 0.0, 0.0, 0.0);<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);<br />
<br />
glLoadIdentity;<br />
RenderScene;<br />
end;<br />
...<br />
end;</source><br />
<br />
<br />
==Und 3D-Shutterbrillen?==<br />
Wer eine 3D-Sutterbrille und entsprechendes Equipment zuhause hat, kann seine Anwendungen auch für diese anpassen. Einige Grafikkarten haben bereits passende Treiber, die die Ausgabe der Programme automatisch anpassen. Wer das nicht will, sollte sich einmal mit Stereo-OpenGl auseinandersetzen. Bei der dglOpenGl genügt es, den Rendering Context mit der Option opStereo zu erstellen. Mit [[glDrawBuffer]]('''GL_BACK_LEFT'''/'''GL_BACK_RIGHT''') kann dann für jedes Auge in einen anderen Puffer gerendert werden.<br />
<br />
==Abschluss==<br />
Ich hoffe euch hat dieser kleine Exkurs gefallen. Vielleicht läuft einem ja mal das eine oder andere Programm über den Weg, wo man die Wahl zwischen Tiefensehen und normaler Ansicht hat - mich würde es jedenfalls freuen, zumal es ja nicht wirklich schwer ist, seine Programme entsprechend anzupassen und es doch ein wesentlich anderes Gefühl der Szene erzeugt, als üblicherweise vor dem Computer. Ich bin jedenfalls gespannt, erwarte mir aber nicht allzuviel - bislang war das Feedback und eure Demos ja sehr bescheiden. Wo ich da nur immer die Motivation hernehme, euch mit ein paar neuen Ideen zu versorgen? ;-D<br />
<br />
<br />
<br />
Euer Delphic ('''Nico Michaelis''')<br />
<br />
<br />
<br />
<br />
==Kleine Anmerkung am Rande==<br />
Nach Fertigstellung des Tutorials wurde ich darauf aufmerksam gemacht, daß man um den Einsatz des P-Buffers herumkommt - was Geschwindigkeit und Kompatibilität erhöht. Man muss nur zwischen den glColorMask Befehlen einmal den Tiefenpuffer leeren. In etwa läuft das ganze dann wie folgt:<br />
<br />
private void RenderMono()<br />
{<br />
RenderSettings rs = new RenderSettings();<br />
rs.Mono(RenderPanel.ClientRectangle.Width, RenderPanel.ClientRectangle.Height);<br />
<br />
GL.Clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);<br />
//Szene Rendern<br />
}<br />
<br />
private void RenderStereo()<br />
{<br />
GL.Clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);<br />
<br />
RenderSettings rs = new RenderSettings();<br />
rs.Stereo(true, RenderPanel.ClientRectangle.Width, RenderPanel.ClientRectangle.Height);<br />
GL.ColorMask(1, 0, 0,1);<br />
//Szene Rendern<br />
<br />
rs.Stereo(false, RenderPanel.ClientRectangle.Width, RenderPanel.ClientRectangle.Height);<br />
GL.Clear(GL.DEPTH_BUFFER_BIT);<br />
GL.ColorMask(0, 1, 1,1);<br />
//Szene Rendern<br />
<br />
<br />
if (GL.GetError() != GL.NO_ERROR)<br />
{<br />
}<br />
}<br />
<br />
private void Render()<br />
{<br />
if (rc != null)<br />
{<br />
GL.PushAttrib(GL.ALL_ATTRIB_BITS);<br />
GL.ClearColor(0.0f, 0f, 0f, 1f);<br />
if (stereo)<br />
RenderStereo();<br />
else<br />
RenderMono();<br />
rc.SwapBuffers();<br />
GL.PopAttrib();<br />
}<br />
}<br />
<br />
Die rs.stereo(true/false, ...) Befehle laden die benötigten Matrizen für das linke bzw. rechte Auge.<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_stereo_delphi_vcl|text=Delphi-VCL-Quelltext zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_StencilSpiegel]]|[[Tutorial_Alphamasking]]}}<br />
<br />
[[Kategorie:Tutorial|StereoSehen]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Lektion_4&diff=23659Tutorial Lektion 42009-05-17T15:40:40Z<p>Flo: /* Dateien */</p>
<hr />
<div>=Texturen, Tapeten und Ihre Tücken=<br />
<br />
==Vorwort==<br />
Hi Leute,<br />
kaum zu glauben aber wahr. Dieses Tutorial wird ausnahmsweise mal etwas mehr Erholung sein. Zumindest am Anfang. Was wir bisher erreicht haben ist ja alles schön, nett, praktisch und auch wichtig als Grundlage aber wenn wir aus dem Fenster sehen hören wir die Vöglein zwitschern. Hö? Wir hören was sehend? Es ist tiefste Nacht? Es muss Frühling sein ^__^.<br><br />
Und was macht man da normalweise? Wir kramen herum und machen einen großen Frühjahrsputz... die Schränke abziehen und alles schön sauber und ordentlich machen. Hach... es dürstet mich richtig danach... :D (means... *würg*).<br><br />
Da unsere Tutorials bisher Gott sei Dank keine dreckige Sache waren werden wir das mit dem Saubermachen einfach mal wegfallen lassen und uns nur mit einem Tapetenwechsel begnügen. In der Tat werden wir ab jetzt unseren Tutorials mehr grafisches Gewicht zumuten. Endlich haben die blauen Dreiecke ein Ende! Ich wünsche Euch viel Spaß ;).<br />
<br />
==Crash-Kurs im Handwerk des Tapezierens==<br />
<br />
===Ich verstehe nur "Renovierung"?===<br />
Ich finde es immer wieder erschreckend Leute im Internet zu treffen, die nicht wissen was eine Textur ist... ich meine, kennen keinen Kafka, keine Quantenphysik und wissen nicht einmal wo man die Systemsteuerung findet. Wie soll man solch einem Menschen erklären, was eine Textur ist?<br />
Nun, am Besten fangen wir mit einem möglichst praktischen Beispiel an. Texturen sind wie... Tapeten. Wir blicken nun zu unserer linken Seite. Der Autor erwartet nun ein DirectMind-Uplink zum Empfangen der visuellen Bildsignale. Dort haben wir eine schöne Wand... quadratisch in ihrer Form. Wollen wir diese mit einer Tapete verzieren, gehen wir in den nächsten Baumarkt, suchen uns eine hübsche aus und bringen diese an der Wand an. Natürlich haben wir eine Große geholt, und fangen nicht mit kleinen Streifen an.<br><br />
Wenn dann alles geklappt hat stehen wir vor dieser Wand und begutachten die Tapete in voller Pracht direkt vor uns. Wir haben die Wand texturiert. Streng genommen machen wir auch bei OpenGL nichts anderes, als ein Bild zu laden und dieses auf eine Fläche zu kleben. Wir werden uns jedoch auf Dauer nicht damit begnügen immer nur quadratische Flächen zu texturieren, sondern durchaus auch mal Dreiecke oder andere Vielecke. Und auch hat man uns Werkzeuge in die Hand gegeben um diese Textur noch nachträglich an der Wand zu verschieben ohne dass wir sie abnehmen müssen. Ist doch super. Es lebe die virtuelle Welt!<br />
<br />
===Tapezieren leicht gemacht!===<br />
Okay, wir haben lange genug um den heißen Brei herumgeredet und sollten uns nun endlich auf die Arbeit stürzen. Jeder von uns sollte in der Lage sein, ein Quadrat in OpenGL genau vor unserer Nase zu erzeugen.<br />
<br />
[[Bild:Tutimg_lektion4_nonetex.png|thumb|256px|left|Leeres Quadrat]]<br />
Spätestens nun sollte die gleiche Frage aufkommen, wie bei jedem, der zum ersten Mal versucht, eine Tapete an die Wand zu bringen: "Wie rum muss ich das Ding da befestigen?". Immerhin müssen wir uns nicht um den Leim kümmern, das erledigt unsere Grafikkarte bzw. OpenGL für uns ;).<br />
<br />
Wer noch nie 3D programmiert hat wird im ersten Moment vielleicht fälschlicherweise denken, dass man die Position der Textur per Weltkoordinaten definiert. Das hätte allerdings fatale Folgen sobald sich das Objekt im Raum bewegt. Wir müssten die Position jedes mal neu berechnen. Um eben dieses Problem zu umgehen, geht man in der 3D-Programmierung einen anderen Weg. Man vergibt einfach für jede Ecke eines Objektes eine Koordinate der Textur. OpenGL errechnet dann aus diesen Texturkoordinaten das Stück der Textur, das dann über das gesamt Objekt projiziert wird.<br />
<br />
Die Rede ist hierbei vom so genannten UV-Mapping, welches vor allem bei Anfängern ein leichtes Gefühl des Unbehagens auslösen sollte. Es ist jedoch nur halb so wild, wenn man es halbwegs verstanden hat.<br />
<br />
Betrachten wir gleich mal das Bild rechts und versuchen, uns in die Problematik hineinzuversetzen. Da Texturen unterschiedliche Größen haben können wurden so genannte Texturkoordinaten eingeführt. Es ist total egal wie groß eine Textur ist, da sie nur einen Wertebereich von 0 bis 1 haben kann. Das heißt, wenn eine Textur 256x256 groß ist und eine andere 512x512, so haben beide eine maximale Größe von 1. Wir brauchen also das UV-Mapping nicht verändern, selbst wenn ein Objekt eine neue Textur mit anderer Größe erhält.<br />
<br />
[[Bild:Tutimg_lektion4_texuv_02.png|thumb|256px|right|Textur und leere Quadratfläche. Eingezeichnet ist das Standardmapping]]<br />
Auf diesem Bild sehen wir zum einen die Textur, die wir verwenden wollen, zum anderen das Objekt, auf das "geklebt" werden soll hintereinander. Natürlich sieht das in der Realität nicht so aus (die Textur würde das gesamte Objekt überdecken). Ich denke aber diese Darstellung erleichtert das Verstehen ;).<br />
<br />
Die obere linke Ecke der Textur trägt die Texturkoordinaten von (0 / 0) (d.h. u = 0 und v = 0), die untere linke Ecke (0 / 1), die untere rechte Ecke (1 / 1) und schließlich die obere rechte Ecke die Koordinaten (1 / 0). Das heißt alles was wir machen müssen um auf unser Objekt eine Textur zu kleben ist dem jeweiligen Eckpunkt unseres Quadrates die entsprechende Texturkoordinate zuzuweisen. Wobei in diesem Sinne korrekt in Anführungszeichnen stehen sollte. Es gibt kein falsches UV-Mapping. Man kann tolle Sachen mit diesen Textur-Koordinaten machen und so z.&nbsp;B. auch eine Textur auf einem Objekt spiegeln. Dafür müssten wir in unserem Beispiel nur die linken und rechten UV-Mapping vertauschen und z.&nbsp;B. für den zweiten Punkt die Koordinaten von (0 / 1) setzen und dafür beim dritten (1 / 1). Genauso würden wir auch die unteren vertauschen. Die UV-Koordinaten, wie sie oben angegeben sind bewirken nur, dass die Textur, so wie sie in der Datei vorkommt auch auf das Objekt geklebt wird. Selbstverständlich ist es auch möglich eine Textur gekachelt aufzukleben, nämlich indem Ihr Texturkoordinaten > 1 vergebt. Ebenso ist es möglich nur Teile einer Textur zu verwenden. Spielt ruhig ein wenig damit herum und schaut Euch an was passiert! Auf einige tolle Spielereien kommen wir zum Schluss noch mal zurück :).<br />
<br />
Sicherlich brennt es einigen von Euch nun bereits unter den Finger und Ihr fragt "Wie kann ich denn die UV-Koordinaten den Eckpunkten der Fläche zuweisen?". Nun, zunächst funktioniert das ähnlich wie bei allen Dingen unter OpenGL. Wir setzen eine UV-Koordinate und solange wir nichts verändern werden alle Punkte mit diesen Koordinaten versehen bis wir andere Instruktionen geben. In unserem Fall müssen wir dies natürlich nach jedem Punkt machen. Die Funktion, die dafür verwendet wird heißt [[glTexCoord]]. Um also eine Textur auf unserem Quad zu projizieren, benötigen wir folgenden Code:<br />
<br />
<source lang="pascal">glBegin(GL_QUADS);<br />
glTexCoord2f(0,0); glVertex3f(-1,1,0); //lo<br />
glTexCoord2f(0,1); glVertex3f(-1,-1,0); //lu<br />
glTexCoord2f(1,1); glVertex3f(1,-1,0); //ru<br />
glTexCoord2f(1,0); glVertex3f(1,1,0); //ro<br />
glEnd;</source><br />
<br />
Das klappt doch wunderbar oder? Damit wir die Textur aber auch wirklich (bzw. überhaupt) genießen können müssen wir Texturing mit Hilfe von '''glEnable''' und dem Token '''GL_TEXTURE_2D''' aktivieren. In unserem Fall wo das gesamte Projekt(ein Quad) texturiert werden soll, könnt ihr den Aufruf direkt in eure GL-Initialisierung schreiben, ansonsten gehört selbiger direkt vor die zu texturierenden Flächen in die Renderschleife.<br />
glEnable(GL_TEXTURE_2D);<br />
Mit Hilfe von glDisable und demselben Token ist es dann auch möglich Objekte zu Zeichnen, die über keine Texturen verfügen. Andernfalls würde nämlich die zuletzt gesetzte Textur und die Texturkoordinaten des letzten glTexCoord-Aufrufs verwendet werden. <br />
<br />
Doch eine Kleinigkeit habe ich Euch nun doch noch verschwiegen! Wir müssen natürlich noch die richtige Textur setzen damit OpenGL überhaupt weiß, was auf diesem Quad gezeichnet werden soll. Dies ist an sich noch relativ einfach, allerdings müssen wir diese auch noch in den Speicher bekommen, damit sie überhaupt zur Verfügung steht. Nun wird's theoretisch ;).<br />
<br />
===Tapeten besorgen===<br />
Ich werde jetzt nicht näher darauf eingehen, wie man Texturen erstellt. Vielleicht gibt's ja einige Photoshopexperten unter Euch, die Lust haben einige Ihrer Tricks den anderen in Form eines Tutorials zu zeigen.<br />
<br />
Zunächst einmal müssen wir uns die eigentlichen Bilddaten besorgen. Wir werden das jetzt in diesem Tutorial mit [[SDL]] machen es gibt jedoch auch die Möglichkeit die Daten manuell zu laden das könnt Ihr hier nachlesen:<br />
* [[TGA Bilder laden]]<br />
Es gibt allerdings auch Texturloader die Euch die nächsten Kapitel abnehmen und das alles für Euch machen. glBitmap ist so ein Loader. Mehr dazu erfahrt Ihr in dem [[Glbitmap_loader|glBitmap]]-Artikel.<br />
<br />
Bei SDL rufen wir nur [[IMG_Load]] auf und prüfen dann ob das Laden erfolgreich war. Hierbei sei erwähnt, dass es unter Linux zu Problemen führen kann, wenn ein Programm nicht aus einer Konsole heraus gestartet wurde. In diesem Fall sind die Pfade zu den Texturen nämlich falsch gesetzt und das Laden würde fehlschlagen. Abhilfe schafft man durch das Verwenden von absoluten Pfaden.<br />
<br />
<source lang="pascal">uses SDL, SDL_Image;<br />
<br />
var <br />
tex : PSDL_Surface;<br />
begin<br />
tex := IMG_Load(PChar(ExtractFileDir(paramStr(0))+'/wiki.jpg'));<br />
if assigned(tex) then<br />
begin</source><br />
<br />
Das war es dann auch schon... Wir haben die Textur im Speicher. Doch was nun?<br />
<br />
===Texturen richtig zubereitet===<br />
Nachdem sich unsere Textur nun im Speicher des Computers befindet, geht es darum daraus auch eine richtige Textur zu machen, damit wir diese in OpenGL anzeigen können. Bisher befinden sich ja nur die Rohdaten im Speicher. Hierfür teilen wir OpenGL mit, dass wir eine neue Textur erzeugen wollen:<br />
<br />
<source lang="pascal">glGenTextures(1, @TexID);</source><br />
<br />
TexID ist in diesem Fall ein gluInt, kann aber genauso gut ein Array davon sein, um mehrere Texturen zu erzeugen. Genau dafür wird dann auch der erste Parameter verwendet, der OpenGL mitteilt wieviele Texturen in dieses Array geschrieben werden sollen. In unserem Fall ist dies eben nur ein Element. Aber was ist ist das für ein Wert in TexID? OpenGL verwaltet die Texturen anhand eindeutiger Namen. [[glGenTextures]] ermittelt einen oder mehrere bisher ungenutzte Namen und schreibt diese in TexID. Durch TexID können wir unsere Textur ab sofort also eindeutig identifizieren.<br />
<br />
<source lang="pascal">glBindTexture(GL_TEXTURE_2D, TexID);</source><br />
<br />
Wir teilen OpenGL mit, dass sich von nun an alle Änderungen und Anweisungen, die sich auf Texturen beziehen auf die Textur TexID beziehen.<br><br />
Die folgenden beiden Zeilen sind zwar nicht wirklich nötig, um eine Textur zu erzeugen aber glaubt mir, sie werden ansonsten potthässlich aussehen. Wir werden in einem anderen Tutorial näher auf dessen Bedeutung eingehen, nämlich den so genannten Texturfiltern. Die momentane Einstellung ist leicht rechenlastig jedoch auch von recht guter Qualität. Ihr werdet anfangs keine Probleme mit der Geschwindigkeit bekommen ;).<br />
<br />
<source lang="pascal">glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);<br />
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);</source><br />
<br />
Zu guter Letzt müssen wir die Bildinformationen irgendwie an OpenGL übergeben. Dies übernimmt die Funktion [[glTexImage2D]]:<br />
<br />
<source lang="pascal">glTexImage2D(GL_TEXTURE_2D, 0, 3, tex^.w, tex^.h,0, GL_RGB, GL_UNSIGNED_BYTE, tex^.pixels);</source><br />
<br />
Der erste Parameter steht für den Typ der Textur. Die Dimension des Typs muss hier mit der des Befehls übereinstimmen (glTexImage2D erlaubt also nur GL_TEXTURE_2D). Der zweite Parameter gibt die Nummer des Level of Detail (LoD) an. Für den Anfang reicht hier der Level 0. Der dritte Parameter gibt an, wie viele Farbkomponenten in dem Bild enthalten sind (1-4). Die zwei folgenden Parameter übermitteln OpenGL die Breite und die Höhe des Bildes. Der sechste Parameter gibt die Breite des Rahmens an. Im siebenten Parameter wird das Format verlangt, in welcher Reihenfolge die einzelnen Farbkomponenten gespeichert sind. Der Typ, der einzelnen Farbwerte muss im 8. Parameter angegeben werden. Letztendlich müssen im 9. Parameter nur noch die Bildpunkte selbst übergeben werden.<br><br />
Mit Hilfe dieser Funktion sollten nur Texturen der Größe 2^n x 2^n erzeugt werden. Andernfalls werdet Ihr die Textur nicht in Ihrer vollen Schönheit, d.h. überhaupt nicht betrachten können. Es gibt jedoch Möglichkeiten Texturen zu laden, die nicht die Größe 2^n entsprechen. Die Funktion [[gluBuild2DMipmaps]] bietet hier beispielsweise eine Alternative.<br><br />
Das war es auch schon. Wer mehr über die einzelnen Parameter und Befehle wissen will ist herzlich eingeladen in unserem Wiki umherzustöbern und sein Wissen zu erweitern um später selbst vielleicht einmal ein paar Artikel im Wiki zu veröffentlichen.<br />
<br />
Die Daten im Arbeitsspeicher brauchen wir nun nicht mehr. Bei SDL geben wir sie so frei:<br />
<br />
<source lang="pascal">SDL_FreeSurface(tex);</source><br />
<br />
Fassen wir das ganze bei SDL nochmal zusammen:<br />
<source lang="pascal">var<br />
tex : PSDL_Surface;<br />
begin<br />
tex := IMG_Load('./wiki.jpg');<br />
if assigned(tex) then<br />
begin <br />
glGenTextures(1, @TexID);<br />
glBindTexture(GL_TEXTURE_2D, TexID);<br />
<br />
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);<br />
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);<br />
<br />
// Achtung! Einige Bildformate erwarten statt GL_RGB, GL_BGR. Diese Konstante fehlt in den Standard-Headern<br />
glTexImage2D(GL_TEXTURE_2D, 0, 3, tex^.w, tex^.h,0, GL_RGB, GL_UNSIGNED_BYTE, tex^.pixels);<br />
<br />
SDL_FreeSurface(tex);<br />
end;</source><br />
<br />
Nun kommt aber bitte nicht auf die Idee die Textur in euerer Hauptschleife wieder und wieder neu zu laden. Es reicht die Textur einmal zu laden und von da an steht sie einem solange zur Verfügung bis man gedenkt sie wieder aus dem Grafikkartenspeicher zu entfernen.<br><br />
Das übernimmt die Funktion [[glDeleteTextures]]. glDeleteTextures funktioniert ähnlich wie glGenTextures, nur dass die Texturen entfernt werden. Der erste Parameter gibt die Anzahl der zu löschenden Texturen an, während der zweite Parameter den Namen der Textur bzw. ein Array der Namen mehrerer Texturen verlangt.<br><br />
Das ist doch für den Anfang nicht schlecht. Ihr solltet nun in der Lage sein, zumindest einfache Objekte zu texturieren. Das ist eigentlich das gesamte Grundprinzip. Natürlich gestaltet es sich schwieriger ein komplexeres Objekt mit UV-Koordinaten zu versehen als ein Quad. An der Technik selbst ändert sich aber nur wenig. <br />
<br />
An dieser Stelle noch eine Anmerkung zu den Texturformaten. Man sollte immer darauf achten, dass die verwendeten Texturen als Kantenlänge eine 2er-Potenz besitzen. Also z.B. 64x128, 256x1024 oder 32x32. Dies liegt daran, dass ältere Grafikkarten nur diese Formate unterstützen. Erst die neueren Generationen können auch sogenannte ''non power of two'' texturen darstellen. <br><br />
Falls ihr also einmal nur ein weißes Viereck seht, wo eigentlich eine Textur sein müßte, dann prüft ob ihr auch tatsächlich ein korrektes Format verwendet.<br />
<br />
Wir werden nun einige Spielereien zeigen, die man mit Texturen machen kann damit Ihr ein Gefühl dafür bekommt, wie man ein Problem elegant umschiffen kann!<br />
<br />
==Die Rückkehr der Matrizen==<br />
===Texturen===<br />
Tja... und da ich ein Sadist bin [Anm. des Lektors: Ooooh ja!] werden wir uns nun nochmals den Matrizen zuwenden! Dachtet Ihr etwa, Ihr seid die Dinger schon wieder los? Ich habe Euch im letzten Tutorial angedroht, dass es nicht nur eine Matrix für die "Welt" gibt, sondern auch noch weitere. Unter anderem eben auch für Texturen.<br />
<br />
Die Texturmatrix funktioniert streng genommen genauso wie auch die Worldmatrix. Solange wir sie aktiv haben, wird sie von jedem Matrixbefehl berücksichtigt! Einziger wirklicher Unterschied ist, dass sie nicht die Position oder die Form eines Objektes beeinflusst, sondern nur das Rendern der Textur selbst. Hö? Was meint der Kerl bloß damit?! Nun... stellt Euch vor, Ihr habt ein Quad und wollt darauf eine Textur bewegen, so dass es aussieht, als würde sie sich von rechts nach links bewegen! Was wir jedoch nicht wollen ist, dass sich das Objekt bewegt, sondern nur, das was darauf zu sehen ist. Stellt Euch vor, Ihr schaut aus einem Fenster und seht einen wolkigen Himmel, der Wind bläst die Wolken von einer Himmelsrichtung zur anderen. Ihr könntet so z.B. das Fenster als Quad nehmen und dann die Textur darauf zeichnen und glaubt mir, die Illusion würde auffliegen, sobald sich das Fenster mit der Textur bewegen soll. Nein, stattdessen bewegen wir nur die Textur auf dem Quad und zwar ohne das UV-Mapping anzutasten. Wir beeinflussen einfach die Art, wie die Textur auf das Quad projiziert werden soll. Dafür dient die Texturmatrix.<br />
<br />
Stellen wir uns mal vor, dass wir unser Quad zeichnen und eine Variable X haben, die wir bei jedem Rendervorgang leicht erhöhen. Dies soll die Bewegung darstellen. Alles was wir nun tun müssen ist, die Texturmatrix entsprechend anzupassen.<br />
<br />
<source lang="pascal">glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glTranslatef(x,0,0);<br />
glMatrixMode(GL_MODELVIEW);<br />
</source><br />
<br />
Das ist bereits der ganze Spuk! Wir teilen OpenGL durch glMatrixMode mit, dass sich ab sofort alle Veränderungen der Matrix nur noch auf die Texturmatrix beziehen sollen. Danach setzen wir diese sofort auf ihren Standardwert zurück. Der Grund hierfür sollte Euch von der Worldmatrix noch in Erinnerung sein. Anschließend ändern wir die Position der Textur auf dem Quad. Würden wir X jedes Mal um 1 Einheit erhöhen, so würde diese Operation ohne Effekt bleiben, da wir die Textur immer um ihre ganze Größe nach links projizieren würden. Würden wir X z.&nbsp;B. bei jedem Vorgang um 0.01 erhöhen, so würde die Textur sich langsam von rechts nach links bewegen. Ich denke, Ihr könnt bereits erahnen, welche Parameter dafür verwendet werden, um eine Textur von unten nach oben zu bewegen ;).<br />
<br />
Wichtig ist auf jeden Fall, dass Ihr anschließend mit glMatrixMode wieder die Worldmatrix aktiviert, da sonst alle weiteren Matrixmanipulationen auf die Texturmatrix angewandt werden würden. Denkt nicht, dass die Texturmatrix damit deaktiviert wird! Ab sofort wird auf das Objekt sowohl die World- als auch die Texturmatrix angewendet. Seht Ihr? Das Ganze ist doch gar nicht ganz so schlimm. Es versteht sich auch von selbst, dass Ihr glRotate und glScale ebenfalls darauf anwenden könnt, natürlich auch nach den gleichen Regeln auf die Worldmatrix. Experimentiert am Besten auch etwas mit diesen Einstellungen herum!<br />
<br />
==Wir tapezieren unsere Welt mal anders==<br />
===Am Fließband===<br />
Eigentlich sollte an dieser Stelle bereits Schluss sein. Ich wurde allerdings während des Schreibens des Tutorials nach einer Sache gefragt und möchte die Chance nutzen, diese Frage zu beantworten und gleichzeitig das angewandte Wissen der vorhergehenden Lektion etwas zu vertiefen. <br />
<br />
Wir haben folgende Problematik: Wir brauchen eine animierte Textur auf einem Objekt. Dies könnte z.&nbsp;B. eine bewegte Lavamasse sein oder irgendwas Glibbriges, was am Boden wabbelt. Oder eben in unserem Fall eine Folge von Zahlen, die wie ein Countdown aufgelistet werden. Sicherlich könnte nun jemand von Euch auf die Idee kommen, viele einzelne Texturen zu laden und diese in einem Array zu speichern. Dies mag auch durchaus sinnvoll sein nicht jedoch, wenn es sich um kleine Bilder handelt (bei uns z.&nbsp;B. 32x32 Pixel).<br />
<br />
Auch bei Bitmap-Fonts würde man nie auf die Idee kommen, für jeden Buchstaben eine einzelne Textur zu verwenden, sondern vielmehr eine Textur mit allen Buchstaben darauf erstellen, da dies u.a. den Ladevorgang erheblich beschleunigt! Klingt einleuchtend oder? Aber wie sollen wir dem Programm mitteilen, welcher Teil der Textur auf welche "Ecke" geklebt werden soll? Nun... auch hierbei heißt des Lösungs Rätsel [Anm. des Lektors: Er macht's schon wieder. Herrlich...] UV-Mapping!<br />
<br />
[[Bild:Tutimg_lektion4_numbers.gif]]<br />
<br />
Dies ist unsere Textur! Sie hat eine Gesamtlänge von 256x32 Bildpunkten und wie man leicht sehen kann, soll sie aus 8 Teilstücken bestehen. Die V-Koordinate können wir in diesem Fall getrost vernachlässigen, weil sie in diesem Fall immer konstant sein wird, weil wir die ganze Höhe der Textur verwenden wollen. Sie wäre nur dann interessant, wenn wir z.&nbsp;B. noch eine zweite Reihe darunter setzen würden. Ich denke jedoch, dass jemand der das gleich folgende verstanden hat, sofort eine Lösung für diese "Problematik" finden wird ;).<br />
<br />
[[Bild:Tutimg_lektion4_numbers2.gif]]<br />
<br />
Wichtig ist, dass wir uns bewusst werden, dass eine Textur beim UV-Mapping immer von 0 bis 1 reicht. Um nun die einzelnen Bilder aus einer Textur auf ein Objekt zu setzen, müssen wir nichts anderes machen, als zu errechnen, an welcher Stelle ein Bild anfängt und wo es aufhört. Nur zur Kontrolle, damit es auch jeder begreift: Würden wir nur die erste Hälfte der Textur auf ein Objekt kleben, so müsste unsere U-Koordinate von 0 bis 0.5 reichen. Die zweite Hälfte hingegen von 0.5 - 1.0. Soweit klingt es doch noch alles logisch oder?<br />
<br />
Genauso müssen wir auch vorgehen, wenn wir einzelne Bilder auf einem Quad abbilden wollen. In unserem Fall müsste die U-Koordinate von 0 bis 1/8 reichen. Das zweite Bildchen hingegen von 1/8 bis 2/8 etc. D.&nbsp;h. wir wissen, dass jedes unserer Bilder 1/8 "Einheiten" lang ist! Und somit haben wir ja bereits eine Lösung für unser Problem. Um das ganze dynamisch auszudrücken: Wir brauchen nur die Größe der Textur durch die Anzahl der Bilder zu teilen. Bevor jemand einen Denkfehler macht: Es ist hierbei ganz egal, wie groß die Textur wirklich ist (hier 256x32 Pixel). Dank OpenGL errechnen wir das UV-Mapping ja in absoluten Größen.<br />
Nun der Code:<br />
<br />
<source lang="pascal">PicLength:= 1 / PicCount;<br />
PicPos:=Round(Pic)*PicLength;<br />
glBegin(GL_QUADS);<br />
glTexCoord2f(PicPos, 1); glVertex3f(-1,-1,0);<br />
glTexCoord2f(PicPos + PicLength, 1); glVertex3f(1,-1,0);<br />
glTexCoord2f(PicPos + PicLength, 0); glVertex3f(1,1,0);<br />
glTexCoord2f(PicPos, 0); glVertex3f(-1,1,0);<br />
glEnd;<br />
<br />
Pic:=Pic + MovementValue;<br />
</source><br />
<br />
Pic ist in diesem Fall eine Variable vom Typ Single, die langsam erhöht wird. Wir runden den Wert hier, so dass beim Erreichen eines jeden ganzzahligen Wertes das nächste Bild angezeigt wird. Wir multiplizieren die Nummer des anzuzeigenden Bildes mit der Breite eines Bildes, um die Anfangsposition zu erhalten und addieren dann noch eine volle Bildbreite dazu, um die Endposition zu erhalten. Hört sich gewaltig gefährlich an, liegt aber mehr an meinem mangelnden Ausdruck als an der Schwierigkeit dieses Problems ;).<br />
<br />
Im Sample werdet Ihr noch sehen, was passiert, wenn man den Wert nicht rundet. Man erhält in diesem Fall einen "flüssigen" Bildübergang. Letztendlich gibt es viele Möglichkeiten, solche Ideen zu implementieren. Nehmt dies einfach als kleineren Gedankenschub und vor allem: Werdet Euch bewusst, was genau dort passiert! Eine Menge toller Dinge lassen sich mit einem guten UV-Mapping erzielen.<br />
Wer noch etwas umherexperimentieren will kann gern versuchen selbes Ziel mit Hilfe der Texturenmatrix zu erreichen. Die Lösung ist verblüffend einfach.<br />
<br />
===Terraforming mal anders===<br />
Relativ lange habe ich nach einem guten Beispiel für folgende Problematik gesucht: Ich wollte Euch die UV-Koordinaten etwas näher bringen und Euch zeigen, wofür man sie einsetzen kann. Irgendwie wollte mir nichts Interessantes aus meinem Kopf entspringen bis ich irgendwann in einigen alten Programmen von mir rumgewühlt habe und einen alten Terrainrenderer von mir fand. Schon war die Idee da! Wir schreiben ein kleines Programm, das eine ganz simple, unoptimierte Landschaft rendern wird. Das hört sich sicherlich Anfangs relativ gewaltig an, mit etwas Verständnis für die oberen Probleme sollte dies jedoch kein Problem für Euch sein.<br />
<br />
Zuvor allerdings ein paar Gedankenspiele. Zunächst widmen wir uns kurz der Texturiering. Wie würde die simpelste Landschaft aussehen, die wir uns vorstellen können? Richtig! Sie wäre ein einfaches, flach liegendes Quadrat mit einer Textur überzogen, der Wand aus unserem ersten Versuch sehr ähnlich! Dies hat jedoch einen klitzekleinen Nachteil: Wir könnten keine Höhenstufen einbauen und ohne die wäre die Landschaft nur halb so realistisch. Denn wir wollen versuchen, folgende Szene zu zaubern:<br />
<br />
[[Bild:Tutimg_lektion4_landscape.gif|thumb|256px|none|eine Lanschaft]]<br />
Um allerdings Höhen einzubinden, müssen wir die Landschaft in viele kleinen Quads unterteilen, die natürlich an Ihren Eckpunkten unterschiedliche Höhen haben, sich jedoch jeweils einige Punkte teilen. Es versteht sich von selbst, dass diese die gleiche Höhe haben müssen, damit die Landschaft auch durchgängig ist und nicht irgendwelche mysteriösen Löcher darauf erscheinen ;).<br />
Auf folgendem Screenshot kann man dies deutlich erkennen:<br />
<br />
[[Bild:Tutimg_lektion4_landwire.gif|thumb|256px|left|Gitter Ansicht der Landschaft]]<br />
Technisch gesehen ist das Ganze einfach zu realisieren, da wir nur begreifen müssen, dass alle Quads nebeneinander liegen und sich - bis auf die äußeren alle einen Eckpunkt teilen. Nun müssen wir beim Rendern jeden Eckpunkt nur noch vom angrenzenden Quad lesen und fertig ist die Landschaft. Ich will da nicht näher drauf eingehen, weil es sich hier nicht um ein Tutorial zur Landschaftsgestaltung handelt. Der Code sollte sich eigentlich von selbst erklären.<br />
<br />
Vielmehr sollten wir uns einer anderen Problematik widmen! Nämlich wie zur Hölle texturieren wir die Landschaft so, dass sie nicht zur Marke "augenfeindlich" gehört?<br />
<br />
[[Bild:Tutimg_lektion4_landscapeerror.gif|thumb|256px|right|wiederkehrende Landschaft]]<br />
Wenn Euer erster Gedanke dabei "Boah, cool!" ist, dann gibt's eins auf die Finger! Das wollen wir doch nicht... wie sieht den die Landschaft aus. Eine immer wiederkehrende Landschaft -> man erkennt sofort, dass wir hierbei auf jedes Quad die gleiche Textur geklebt haben. Doch wie schaffen wir es nun, dass wir eine Textur über alle Quads ziehen, so dass die ganze Landschaft mit einer Textur überzogen ist anstatt nur über ein einzelnes Feld?<br />
Nun, des Lösungs Geheimnis [Anm. des Lektors: Ich liebe ihn dafür! Andere lösen Rätsel. Er geheimnist Lösungen] sind eben unsere UV-Koordinaten und einige pfiffige Köpfe unter Euch sollten bereits einen ersten Verdacht haben. Denn die erste Idee sollte es sein, den linken unteren Punkt eine UV-Koordinate von (0/0) zu geben und der rechten oberen (1/1).<br />
Alles was wir nun also machen müssen, ist, uns die entsprechenden UV-Koordinaten für die Quads dazwischen auszurechnen und sie dann den Punkten zuzuweisen. Dafür benötigen wir zunächst die Breite eines Quads. Mit normaler Logik lässt sich folgende Aussage aufstellen.<br />
<source lang="pascal">qw:=1 / XCount<br />
qh:=1 / YCount;<br />
</source><br />
Ein Quad benötigt die Länge von 1 durch die Anzahl der Quads in einer Reihe oder Spalte. Genauso verhält es sich auch mit der Höhe. Nun brauchen wir beim Rendern nur noch dem jeweiligen Quad in einer For-Schleife die entsprechende Koordinate zuzuweisen:<br />
<source lang="pascal">U:=1 / XCount;<br />
V:=1 / YCount;<br />
<br />
for y:=0 to YCount-1 do<br />
begin<br />
glPushMatrix;<br />
for x:=0 to XCount-1 do<br />
begin<br />
glBegin(GL_QUADS);<br />
glTexCoord2f(U*x, V*(y+1)); <br />
glVertex3f(0, Map[x,y+1], 0);<br />
glTexCoord2f(U*x, V*y); <br />
glVertex3f(0, Map[x,y], 1);<br />
glTexCoord2f(U*(x+1), V*y); <br />
glVertex3f(1, Map[x+1,y], 1);<br />
glTexCoord2f(U*(x+1), V*(y+1)); <br />
glVertex3f(1, Map[x+1,y+1], 0);<br />
glEnd;<br />
glTranslatef(1,0,0);<br />
end;<br />
glPopMatrix;<br />
glTranslatef(0,0,-1);<br />
end;<br />
</source><br />
Das sieht nach einem herben Stück Arbeit aus oder? Aber wenn Ihr Euch das ganze mal aufzeichnet und im Kopf durchspielt, wird der Groschen fallen. Denkt mal etwas darüber nach! Wenn es dann doch noch Probleme mit dem Verständnis geben sollte, wird das nächste Kapitel hoffentlich jedes Missverständnis aus dem Wege räumen ;).<br />
<br />
==Nachwort==<br />
Okay... ich hoffe Ihr fühlt Euch nach der Abarbeitung dieses Tutorials genauso wie ich, nachdem ich es für Euch geschrieben habe... nämlich elend. Irgendwie wurde das immer mehr und ich sagte mir immer wieder "nein, dass ist nicht genug. Das muss genauer und anschaulicher werden". Dies ist auch der Grund dafür, warum dieses eines der größten Tutorials mit den meisten Bildern usw geworden ist. Erst sollte es noch etwas mehr werden und dann in mehrere Tutorials aufgespaltet werden, allerdings glaube ich mit diesem Tutorial einen guten Mittelweg zwischen Information und Totlabern gefunden zu haben. Lasst es mich wissen, wie Ihr dazu steht!<br><br />
Und bevor die Kritiker gleich wieder alle aus Ihren Löchern kommen und mir sagen, dass einige Bereich einfach als gegeben angenommen werden; denen sei nur gesagt, dass wir u.&nbsp;a. auch versuchen Rücksicht auf Leute zu nehmen, die noch nie 3D programmiert haben und vielleicht Eurem Wissen nicht standhalten können. Daher halte ich es hier für wichtiger, Grundlagen wie UV-Koordinaten und den Einsatz von Texturen zu erklären, als ihnen tausende von Zeilen um die Ohren zu hauen, wie sie die Bytes von der Festplatte in den Arbeitsspeicher laden können. Um jedoch keine Wissenslöcher offen zu lassen: Ihr könnt Euch sicher sein, dass spezielle Tutorials folgen werden, die sich speziell mit dem Laden verschiedener Formate beschäftigen und die genaue Interna (was im Hintergrund abläuft) zu erklären versuchen. Auch das Alpha Blending wird hier mehr zu "Show-Zwecken" verwendet und wird zusammen mit dem Z-Buffer genauer erklärt werden! <br><br />
Also bloß keine falsche Hektik! Ich weiß ... einige von Euch sind recht ungeduldig, aber ständiges Nachfragen und Drängeln führt zu nichts. Versucht Fragen lieber ins Forum zu setzen, damit dort ein wenig Leben reinkommt. Denn wenn es dort belebter wird, kommen auch schneller neue Leute und vielleicht sind ja auch welche dabei, die dann das DGL-Team entlasten können. Also nutzt bitte das Forum, anstatt andauernd per ICQ oder Mail irgendwelche Fragen zu stellen! Die Antwort wird auch nicht viel länger auf sich warten lassen. Im Forum jedoch ist alles dokumentiert und auch anderen zugänglich, so dass ich nicht die gleiche Frage bis zu 5 mal am Tag beantworten muss. Sorry aber das geht einem auf die Nerven und vor allem auf die Zeit ;). Schließlich sind wir keine Maschinen sondern Menschen, die auch ein privates Leben haben ^__-.<br />
<br />
Glaubt uns, wir investieren einen sehr großen Teil unserer Zeit in dieses Projekt und solch ein Tutorial lässt sich nicht binnen weniger Tage anfertigen. Jeder der so etwas bereits einmal gemacht hat, wird wissen was ich meine. Es muss ein Konzept her, es müssen Samples geschrieben, Screenshots gemacht werden und ein halbwegs verständlicher Text her. Und nichtsdestotrotz soll es am Ende auch noch passen, einem möglichst großen Publikum Wissen vermitteln, am Besten von Schreib- und Sprachfehlern befreit und in einem halbwegs ansehnlichen HTML-Dokument präsentiert werden. Ein langer Weg ;).<br />
Okay, in diesem Sinne, bis bald ;)!<br />
btw: Vielen Dank an Magellan für die Bereitstellung und Integration seiner "besonderen Lernleistung".<br />
<br />
Euer<br><br />
'''Phobeus'''<br />
<br />
== Dateien == <br />
* Der aktuellste Beispiel-Quelltext befindet sich im DGLSDK 2006.1 für {{ArchivLink|file=dglsdk_win32_2006_1|text=Windows}} und {{ArchivLink|file=dglsdk_linux_2006_1|text=Linux}}<br />
* {{ArchivLink|file=tut_lektion_4_delphi_api|text=Alter Delphi-API-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_4_delphi_vcl|text=Alter Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_4_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial Lektion 3]]|[[Tutorial Lektion 5]]}}<br />
<br />
[[Kategorie:Tutorial|Lektion4]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Lektion_3&diff=23658Tutorial Lektion 32009-05-17T15:40:29Z<p>Flo: /* Dateien */</p>
<hr />
<div>= Eine Welt des Grauens =<br />
== Vorwort ==<br />
Liebe Leser,<br />
nachdem Ihr hoffentlich den kleinen Schock des letzten Tutorials alle gut überstanden habt, freue ich mich, Euch hier wieder begrüßen zu können. Wer dachte, dass die letzte Lektion bereits schwer war, der sollte dringend Urlaub nehmen. Seid gewarnt! Bevor ich jetzt richtig loslege, solltet Ihr einigermaßen entspannt sein. Wenn dies einer der Tage ist, an denen Ihr nur noch durch eine Kippe oder die Flasche Coke am Leben gehalten werdet, ordne ich erstmal eine kleine Zwangspause an ;).<br />
<br />
Der Stoff der jetzt kommt ist sicherlich nicht gerade die leichteste Lektüre. Wer Mathematik studiert, hat Vorteile, wer einigermaßen logisch denken kann auch. Ein Fachidiot, wie ich, hat nur eine Chance... probieren, testen und lernen :). Und weil ich weiß, wie schwer es eventuell sein kann, werde ich nun einfach mal versuchen, ganz simpel anzufangen und das Ganze mit vielen Bildern so gut wie nur irgend möglich zu illustrieren. <br />
<br />
Und wenn Ihr nun Angst habt, dass Ihr das nicht packt, weil ich hier so eine Panik verbreite, dann solltet Ihr mich sehen, wie ich hier verzweifelt vor meinen Tasten hänge und mich frage, wie ich das alles bloß in Worte fassen soll :). Aber was soll es? Ran an den Kram!!! Heulen könnt Ihr anschließend im Forum ;).<br />
<br />
== Das Grauen hat einen Namen ==<br />
=== "Kinofilm" vs. "Bahnhof" ===<br />
Wer kennt nicht "Matrix" und hätte gedacht, dass es davon nicht nur eine, sondern unendlich viele gibt die so genannten "Matrizen". Vor allem am Anfang werden diese Dinger Euch das Leben erschweren und Ihr werdet leichte Neigungen tief in Euch verspüren, dass am besten Euer ganzes Projekt sich nur im Zentrum Eurer Welt abspielt (und dies ist nicht im wahrsten Sinne des Wortes gemeint).<br />
<br />
Wer mit Matrizen umgehen kann, beherrscht das Wichtigste an der 3D-Programmierung. Wer nicht zu den Genies zählt, sollte nicht sofort aufgeben, sondern sich viele Beispiele ansehen und viel mit diesen herumspielen.<br />
<br />
Wie auch immer... Ihr solltest wissen, dass es bei jedem Rendervorgang mehrere Matrizen gibt, die ganz drastisch das Aussehen der Szene bestimmen. Es gibt in OpenGL drei Bereiche, in denen Matrizen eingesetzt werden. Die so genannte World- oder Modelviewmatrix, die Texturenmatrix und die Perspektivenmatrix. Die zweifellos wichtigste dieser Matrizen ist die Worldmatrix, da sie die Position Eures "Zeichenstiftes" beschreibt und somit Objekte positioniert. Die Texturenmatrix funktioniert fast genauso wie die Worldmatrix nur definiert sie die Ausrichtung der Texturen. Wir werden später auf sie zurückkommen. Die Perspektivenmatrix definiert wesentliche Eigenschaften des Blickfeldes des Betrachters.<br />
<br />
Der Befehl [[glMatrixMode]] erlaubt eine Änderung der aktuellen Matrix. Mit Hilfe der Konstanten GL_MODELVIEW, GL_TEXTURE oder GL_PROJECTION kann man die Matrix bestimmen. Die Namen der Konstanten sind soweit hoffentlich selbst erklärend. Von nun an bewirken alle Matrixoperationen wie beispielsweise [[glLoadIdentity]] oder [[glTranslate|glTranslate*]] eine Veränderung der aktuell gesetzten Matrix.<br />
<br />
=== Die Perspektivenmatrix ===<br />
Wie bereits erwähnt beschreibt die Perspektivenmatrix das aktuelle Sichtfeld. Zum Setzen der Perspektivenmatrix verwendet man in der Regel Befehle wie [[glFrustum]], [[gluPerspective]], [[glOrtho]] oder [[gluOrtho2D]].<br />
<br />
gluPerspective lässt den Raum beispielsweise dreidimensional erscheinen, indem sich die Größe von Objekten mit zunehmender Entfernung vom Betrachter verringert. Diese Verringerung ist abhängig von dem im ersten Parameter übergebenen Winkel. <br />
<br />
glOrtho hingegen ist phantastisch für ein 2D-Blickfeld geeignet, denn diese Art der Projektion enthält keine Tiefe mehr.<br />
<br />
Genauere Informationen zu den einzelnen Funktionen könnt ihr unserem OpenGL-Wiki entnehmen.<br />
<br />
Auf eine Sache möchte ich jedoch noch eingehen: Einige dieser Funktionen verlangen die Parameter "znear" und "zfar". Diese Parameter beschreiben die zwei Schnittflächen, die das Blickfeld vor dem Betrachter begrenzen. "znear" definiert die Entfernung der Nearclippingplane vom Betrachter. Alle Objekte, die sich vor dieser Ebene befinden sind nicht sichtbar. "zfar" beschreibt die Entfernung der Farclippingplane vom Betrachter. Alle Objekte, die sich hinter dieser befinden werden weggeschnitten. Wenn also wieder einmal nur die Hälfte Eurer Szene auf dem Bildschirm sichtbar ist, versucht die Schnittflächen entsprechend anzupassen!<br />
<br />
== Die Worldmatrix ==<br />
=== D3D-Verrat ===<br />
Wenn Ihr ebenfalls zu den D3D-Verrätern gehört (wie z.&nbsp;B. ich *g*), dann solltet Ihr Euch möglichst von eurer Denkweise trennen: Jede Positionierung der Kamera erfolgt über ein Drehen und Bewegen der Szene. Das ist vor allem am Anfang ein wenig gewöhnungsbedürftig ("Die Welt dreht sich! Nicht Du!").<br />
<br />
Vielmehr wird jede Manipulation direkt an die Worldmatrix übergeben. Das heißt, wenn Ihr glTranslate* aufruft, so ist diese Manipulation bereits bei Euch in die Worldmatrix eingetragen, alle weiteren Objekte werden mit dieser Matrix transformiert. Wer zweimal glTranslate*(2, 0, 0) aufruft wird feststellen, dass glTranslate* nicht zwei Matrizen zurückliefert, sondern nur eine, die dann auf (4, 0, 0) verweist. Am Anfang solltet Ihr also sehr darauf achten, es wird eine Weile dauern bis Ihr dies fest in Eurem Herzen tragt. Nicht sofort aufgeben :)!<br />
<br />
== Einfach, Kompakt und Funktional ==<br />
Neben dem Befehl glTranslate*, den wir im letzten Tutorial lieben gelernt haben, gibt es noch weitere Befehle, die sich auf die einzelnen Matrizen auswirken und auf die ich in dem folgenden Abschnitt eingehen möchte.<br />
<br />
=== Dreh- und Angelpunkt des Geschehens ===<br />
Zunächst beschäftigen wir uns mit der Rotation. Wir versuchen ein Objekt zu erzeugen, welches um 90° um seine eigene Achse gedreht wurde. Das ist nicht sonderlich schwer: <source lang="pascal">glRotatef(90,0,1,0);</source><br />
Ein Objekt auf das wir diese Matrix anwenden, wird um 90° um seine Y-Achse gedreht sein. Daraus schließen wir, dass der erste Parameter den Winkel im Gradmaß um den wir rotieren möchten definiert und die 3 folgenden Parameter den Vektor beschreiben um den die Rotation durchgeführt werden soll. Rotationen bergen tatsächlich eine Schwierigkeit, denn es ist nicht immer einfach ein Objekt um die Eigene Achse zu rotieren. Verschiebt Ihr euer Objekt zuerst und beginnt dann die Rotation, so rotiert das Objekt um die eigene Achse.<br />
<br />
Ruft Ihr jedoch zuerst glRotate* und anschließend glTranslate* auf, so unterscheidet sich erzielte Ergebnis von dem vorherigen.<br />
<br />
Um diesen Problemen aus dem Weg zu gehen so müsst Ihr Euch vorstellen, dass Ihr den Rotationspunkt im Moment des Aufrufs von glRotate* setzt. Verschiebt Ihr Euer Objekt anschließend so bleibt der Rotationspunkt derselbe und das Objekt beginnt den Rotationspunkt auf einer Bahn zu umkreisen. So gesehen gibt es keinen wirklichen Unterschied und die Rotation um die eigene Achse ist ein Ausnahmefall, denn der Rotationspunkt liegt dann in dem von uns erwarteten Mittelpunkt des Objektes.<br />
<br />
=== Eine Frage der Größe ===<br />
Nun ja... was kann man denn noch alles in einer 3D-Welt machen? Ihr habt gelernt, wie man Objekte bewegt und diese durch Rotationen ausrichten kann. Das ist doch schon eine Menge zum Spielen oder? Nun, bei einer Transformation kann für uns noch eine Sache sehr wichtig werden, nämlich die Möglichkeit, zu bestimmen in welcher Größe ein Objekt dargestellt werden kann.<br />
<br />
Sicher würde es Sinn machen ein benötigtes Objekt gleich in der richtigen Größe in die Anwendung zu laden. Manchmal ist es aber von Vorteil, wenn man die Größe eines Objektes ohne großen Aufwand verändern kann oder aber mehrere Objekte desselben Typs mit unterschiedlicher Größe darzustellen.<br />
<br />
Eine Größenveränderung ist mit Hilfe des Befehls [[glScale|glScale*]] möglich.<br />
<source lang="pascal">glScalef(1,1,1);</source><br />
In diesem Fall würden wir das Objekt so zeichnen, wie wir es auch angegeben haben. Das heißt in seiner Originalgröße. Sicherlich ahnst Du bereits, was die Parameter aussagen... sie stehen für das Verhältnis der einzelnen Seiten nach dem Muster X, Y und Z. Würden wir als ersten Parameter statt einer 1 eine 2 einsetzen, würde unser Objekt in seiner räumlichen Ausdehnung bezogen auf der X-Achse doppelt so groß sein. Würden wir stattdessen 0.5 angeben, wäre es nur noch halb so groß.<br />
<br />
In Wirklichkeit bewirkt glScale* jedoch keine Veränderung der Größe sondern eine Verzerrung unseres Koordinatensystems. Daher wirkt jeder Aufruf von glScale* auch auf Befehle wie glTranslate*. Aus Sicht der Matrizen lässt sich dieses Phänomen auch recht leicht erklären. In Wirklichkeit erzeugt jeder Aufruf von glRotate*, glTranslate* oder glScale* eine eigene Matrix, die dann mit der aktuellen Matrix (z.B. der Modelviewmatrix) multipliziert wird. Wurde in der Modelviewmatrix bereits ein glScale* verewigt, so wirkt sich dieses bei der Multiplikation der Matrizen auf die Transformationsmatrix aus.<br />
<br />
Ich denke ich muss nicht näher darauf eingehen, dass es keine gute Idee ist das Koordinatensystem mit einem Aufruf von glScalef(0, 0, 0) zu zerstören. Was würde es auch für einen Sinn ergeben, das Koordinatensystem in einem Punkt unterzubringen? <br><br />
'''''Hinweis''': Es ist bereits gefährlich nur eine Achse auf 0 zu skalieren, denn dadurch wird die aktuelle Matrix beschädigt ([http://www.math.unizh.ch/fachverein/forum/detail.jsp?FORUM=120 singulär]) und Befehle wie [[gluProject]] und [[gluUnProject]] funktionieren nicht mehr.''<br />
<br />
Ich hoffe ich habe Euch nun nicht den letzten Funken Hoffnung OpenGL zu verstehen geraubt. Ich kann Euch versichern, dass dieses Verständnis mit der Zeit heranreifen wird.<br />
[[bild:Tutimg_lektion3_skalierung.gif|256px|right]]<br />
glScale* ist aber aufgrund seiner phantastischen Eigenschaften nicht nur in der Lage die Größe von Objekten zu verändern sondern es ist auch möglich Objekte zu spiegeln indem man negative Werte an glScale* übergibt. Somit kann sich z.&nbsp;B. ein D3D-Umsteiger das leben vereinfachen indem er mit einem Aufruf von glScalef(1, 1, -1) sicherstellt, dass die Z-Achse in den Bildschirm hinein positiv verläuft.<br />
<br />
Jeder, der die Funktion der Modelviewmatrix begreifen möchte, dem möchte ich das Programm Matrixcontrol von Lithander (siehe Anhang) ans Herz legen. Es erlaubt Euch bestimmte Änderungen an der Matrix direkt nachzuvollziehen und wenn man mit dem Programm ein wenig umherspielt wird selbst dem letzten ziemlich schnell ein Licht aufgehen. Wenn nicht, dann hilft nur fleißiges Programmieren und Anwenden und irgendwann klatscht es dann laut, wenn die eigene Handfläche auf der Stirn zum stehen kommt ;).<br />
<br />
== Virtuelle Gedächtnisse ==<br />
=== Vom Pushen und Poppen ===<br />
Wer diesen Titel ließt und sich nun zwangsläufig daran erinnert, was er bereits alles in seinem Leben konsumiert hat oder neben wem er am nächsten Morgen aufgewacht ist, sei entwarnt! Wir reden hier von etwas ganz anderem...<br />
<br />
Wie wir bereits gelernt haben, verändert jeder Aufruf von glTranslate*, glRotate* oder glScale* die Worldmatrix und wirkt sich unmittelbar auf andere Objekte aus. Dies kann durchaus sehr erwünscht sein. Wenn wir allerdings in einer großen Welt verschiedene Objekte positionieren, kann dies schnell zum Fluch werden. Natürlich können wir dem entgegenwirken und zum Beispiel jedes Mal glLoadIdentity aufrufen. Dies hat jedoch den Nachteil, dass die World- Matrix dann eben wieder leer ist und wir z.B. die Camera erst wieder setzen müssen, damit die Objekte an die richtige Stelle transformiert werden.<br />
<br />
Eine weitaus elegantere Lösung ist die Verwendung des "Matrixstacks"! Jeder Aufruf von [[glPushMatrix]] bewirkt, dass die momentane Matrix auf den [[Stack]] geschrieben wird. Alle Manipulationen, die wir dann durchführen, werden wie gewohnt vollzogen. Ist alles gezeichnet, rufen wir mit [[glPopMatrix]], die letzte Matrix vom Stack wieder herunter und haben im Prinzip den letzten Zustand vor unseren Veränderungen rekonstruiert und können dann wieder anfangen, auf dieser Matrix aufzubauen. Natürlich lassen sich auch mehre Matrizen auf den Stack pushen! Dies kann reichlich Zeit sparen, wenn man bedenkt, dass man ansonsten andauernd die Matrix der Szene neu setzen muss.<br />
<br />
Um das ganze etwas anschaulicher zu machen, verwenden wir ein einfaches Beispiel. Wir werden zwei Dreiecke jeweils rechts und links vom Ursprung zeichnen. Nachdem wir das linke Objekt gezeichnet haben, werden wir nicht glLoadIdentity einsetzen oder das zweite Objekt entsprechend nach rechts transformieren, sondern eben den Matrixstack zu Hilfe nehmen.<br />
<source lang="pascal">glLoadIdentity();<br />
glTranslatef(0,0,-10);<br />
glPushMatrix();<br />
glTranslatef(-2,0,0);<br />
glBegin(GL_TRIANGLES);<br />
glColor3f(1,0,0); glVertex3f(-1,-1, 0);<br />
glColor3f(0,0,1); glVertex3f( 1,-1, 0);<br />
glColor3f(0,1,0); glVertex3f( 0, 1, 0);<br />
glEnd();<br />
glPopMatrix();<br />
<br />
glTranslatef(2,0,0);<br />
glBegin(GL_TRIANGLES);<br />
glColor3f(1,0,0); glVertex3f(-1,-1, 0);<br />
glColor3f(0,0,1); glVertex3f( 1,-1, 0);<br />
glColor3f(0,1,0); glVertex3f( 0, 1, 0);<br />
glEnd();</source><br />
Wie Ihr seht, setzen wir am Anfang praktisch die Distanz zum Objekt, speichern die Matrix und zeichnen das erste Objekt. Wir setzen dann die Matrix wieder zurück, so dass sie nur noch den Aufruf von glTranslatef(0, 0, -10) beschreibt und transformieren das zweite Objekt... fertig. Man kann auch ohne diese Matrixstacks leben sie können einem aber das Leben auch sehr erleichtern.<br />
<br />
== I wanna all! ==<br />
Wer glaubt, dass es das bereits war der irrt. Es gibt noch weitere Möglichkeiten auf die Matrix Einfluss zu nehmen. Der Befehl [[glLoadMatrix|glLoadMatrix*]] erlaubt es eine beliebige 4x4 Matrix zu laden und die aktuelle Matrix durch die geladene komplett zu ersetzen.<br />
<br />
Der Befehl [[glMultMatrix|glMultMatrix*]] multipliziert die übergebene 4x4-Matrix mit der aktuellen Matrix. Mit diesem Befehl könnte man beispielsweise eigene glScale*- und glRotate*-Befehle schreiben, obgleich sich auch hier über den Sinn streiten lässt. Eine sinnvollere Anwendungsmöglichkeit wird in einem späteren Tutorial vorgestellt werden.<br />
<br />
Der Befehl [[glGet|glGet*]] mit den Tokens GL_MODELVIEW_MATRIX, GL_TEXTURE_MATRIX und GL_PROJECTION_MATRIX ermöglicht euch die einzelnen Matrizen anzufordern.<br />
<br />
== Weltenformeln ==<br />
=== Hier kommt die Sonne... ===<br />
Genug der Theorie! Es wird langsam Zeit für etwas Praxis...<br />
<br />
Nun, um ehrlich zu sein, habe ich an der folgenden Idee ein wenig Zeit benötigt, um ein einigermaßen gut anschauliches Beispiel zu finden... ich will hier nicht weiter darauf eingehen, was ich gerade gehört habe, als mir die Sonne aufging :-D.<br />
<br />
Wie auch immer, man kann komplexere Matrixtransformationen sehr leicht mit unseren Sonnensystem beschreiben (Hey, wenn jetzt jemand denkt, dass ich spinne...). Um das Ganze etwas übersichtlicher zu gestalten, werden wir unser Prinzip auf Sonne, Mond und Erde beschränken.<br />
<br />
Wie funktioniert unser Sonnensystem? (Warnung, der Autor betritt mal wieder seine ausgeprägte Fantasywelt! [Anm. d. Lektors: Die richtige Sprache dafür hat er ja schon]).<br />
<br />
Die Sonne steht im Mittelpunkt und sollte sich in unserem Beispiel nicht selbst bewegen. Sie ist einfach nur im Mittelpunkt und vegetiert dort vor sich hin! Nun gibt es einen kleinen blauen Planeten, namens Erde (wieso er im Sample ein Dreieck ist, sei Eurer Kreativität überlassen...), der zum einen um seine eigene Achse rotiert, viel wichtiger jedoch, sich auch noch um die Sonne dreht. Nun werden sich vor allem die Neulinge unter Euch sadistisch freuen, das Fenster schließen, ihr Delphi starten und drauf los proggen! Das ist doch alles kein Ding, der gerade mal frisch aufgeschnappte Sinus-Satz lässt sich prima mit einbringen! Und schon berechnet man mit Hilfe von Sinus, die Position auf der Kreisbahn und positioniert den Planeten mit glTranslate* an seine Position. Und oh Wunder es klappt. HALT! Wer es nicht gemerkt hat, da war ne Portion Ironie dabei. Bitte Weiterlesen ^__^!<br />
<br />
Denn spätestens wenn wir folgende Ergänzung zum Besten geben, werden die ersten Augen wässrig werden, weil die meisten Hirne einem Informationskoller unterliegen. Meines stieg ja schon beim einfachen Sinus aus :-D. Ab und an kann man nämlich nachts weißgräuliche Flecken am Himmel beobachten! Wer annimmt, dass es sich hierbei um eine geschickt platzierte, in Echtzeit berechnete, Textur handelt, ist schief gewickelt! Es handelt sich dabei nämlich um ein Decal, namens Mond. Oha... so schlimm war es schon lange nicht mehr. Dieser bewegt sich in unserem Beispiel auch nicht entlang der Erdachse, sondern auf einer schiefen Bahn, damit man auf der unteren Hemissphäre den Mond auch am Tage noch sehen kann ^___^ (*selbstgefälliges, göttliches Grinsen*). Dies lässt sich nur sehr schwer mit Sinus beschreiben. Bevor wir uns nun jedoch mit einem Block bewaffnen oder den nächsten Mathematik-Professor entführen, damit er die Mathematik für uns übernimmt, greifen wir lieber zu den Sternen und nehmen ein einfacheres Verfahren! Les Matrices!<br />
<br />
=== Und es dreht sich doch! ===<br />
Die Vorgehensweise ist eigentlich relativ simpel. Wir Zeichnen unsere Sonne im Mittelpunkt unseres Universums. Mit dem Aufruf von glRotate* setzen wir den Rotationspunkt an die Position unseres "Zeichenstiftes". In unserem Fall ist das noch immer die Position der Sonne. <br />
<br />
Nun verschieben wir die Erde nach links und die erste Hürde ist genommen. Jetzt müssen wir nur noch den Mond setzen und das funktioniert genauso einfach wie zuvor mit der Erde.<br />
<br />
Unser Zeichenstift befindet sich nun an der Position der Erde und daher rufen wir glRotate* auf. Nun müssen wir den Zeichenstift nur noch ein kleines Stück neben der Erde positionieren und den Mond zeichnen. Voila! Es ist vollbracht!<br />
<br />
Wenn man genauer darüber nachdenkt erkennt man sehr schnell wie elegant man dieses verhältnismäßig komplizierte Problem gelöst hat.<br />
<br />
Wer nun auf Wolke Sieben schwebt und glaubt er könnte sich nun an den nächsten DOOMTitel werfen, der sollte das Programm zuvor noch so erweitern, dass die Sonne, die Erde und auch der Mond sich zusätzlich um die eigene Achse drehen.<br />
<br />
== [[Timebased Movement]] ==<br />
Wer bereits versucht hat eines der ersten selbst geschriebenen OpenGL-Programme voller Stolz seinem Kumpel vorzuführen wird festgestellt haben, dass die Bewegungen auf dem anderen Rechner schneller oder langsamer abgelaufen sind als es auf dem eigenen Rechner der Fall war.<br />
<br />
Dieses Phänomen lässt sich sehr einfach erklären. Nehmen wir an wir bewegen ein Dreieck von links nach rechts um den Wert 1 über den Bildschirm. Eine alte Krücke wie mein PC schafft vielleicht 50 FPS. Das bedeutet das Objekt wird in einer Sekunde genau 50mal um eine Einheit nach rechts verschoben. Das bedeutet insgesamt also um 50 Einheiten in einer Sekunde. Wenn das Programm nun aber auf einem High-End-System läuft werden wir die Szene... sagen wir... 500mal aktualisieren können. Folglich bewegt sich auf diesem Rechner das Dreieck in einer Sekunde um 500 Einheiten.<br />
<br />
Das einzige was wir von unserem Dreieck erhaschen können ist ein blitzartiges Zucken auf dem Bildschirm, welches wir schnell als optische Täuschung abtun würden. Wie kann man diesem Problem nun aber entgegen wirken?<br />
<br />
Nehmen wir an wir kennen die Zeit, die wir zum Zeichnen eines Frames benötigen und ermitteln anhand dieses Wertes einen Zeitfaktor, den wir in die Bewegung mit einfließen lassen.<br />
<source lang="pascal">NewPosition := OldPosition + Movement * TimeFactor;</source><br />
Handelt es sich um einen schnellen Rechner so ist der Zeitfaktor entsprechend niedrig und die Bewegung pro Frame verringert sich. Ist der Rechner hingegen langsam so besitzen wir einen hohen Zeitfaktor. Folglich legt das Dreieck auf dem schnelleren Rechner pro Frame weniger Weg zurück als auf dem langsamen. Insgesamt jedoch legen die beiden Dreiecke im gleichen Zeitabstand (z.&nbsp;B. in einer Sekunde) denselben Weg zurück.<br />
<br />
Um den Zeitfaktor zu ermitteln muss man lediglich die Zeit für einen Schleifendurchlauf ermitteln. Um eine möglichst hohe Genauigkeit zu erhalten sollte man die Zeit für den letzten Schleifendurchlauf ermitteln und im aktuellen Schleifendurchlauf verwenden.<br />
<br />
== Nachwort ==<br />
Wow... und wieder ein wenig Zeit zum Ausspannen und ein paar persönlichen Worten. Ich kann getrost nur eines sagen: Wenn Ihr nun denkt "das war alles ja sehr leicht" habt Ihr irgendetwas falsch gemacht und solltet das Tutorial noch einmal von Vorn durcharbeiten ;). Wenn Ihr jedoch leichte Kopfschmerzen verspürt (ob es nun die Matrizen sind oder der fiese Elementarbereich *sg*...) so habt Ihr alles richtig gemacht ;).<br />
<br />
Matrizen sind vor allem vielen Einsteigern sehr fremd. "Übung macht den Meister". Versucht Euch doch einfach mal die eine oder andere Aufgabe selbst zu stellen oder unser "Sonnensystem" selbst einmal nachzuschreiben. Wenn Ihr glaubt, dass das Ganze ganz nett geworden ist, dann schickt es mir doch einfach mal zu. Ich freue mich immer darüber zu sehen, was für Früchte meine Tutorials tragen. *lach* Und wenn sich plötzlich alles um die Erde dreht, dann habe ich a) das Gefühl, dass die Kenntnisse über das Sonnensystem nicht mehr ganz up to date sind oder b) ich einen ziemlich fatalen Fehler beim Erklären gemacht habe :->.<br />
<br />
Schreckt auch bitte nicht davor zurück, eine Frage diesbezüglich im Forum zu stellen. Es ist keine Schande, mit einer Matrix Probleme zu haben und vor allem die Erfahrenen unter uns, sollten nur zu gut wissen, was alles die Ursache dafür sein kann, wenn man plötzlich gar nichts mehr auf dem Screen sieht :). <br />
<br />
Das [[Tutorial Matrix2]] behandelt das Thema Matrizen, und vor allem wie man Sachen in OpenGL positioniert, noch genauer.<br />
<br />
Wir selbst werden uns in den folgenden Kapiteln noch etwas intensiver mit den Matrizen beschäftigen und unter anderem die eine oder andere interessante Spielerei zeigen!<br />
<br />
'''Euer'''<br><br />
'''Phobeus'''<br />
<br />
== Dateien ==<br />
* Der aktuellste Beispiel-Quelltext befindet sich im DGLSDK 2006.1 für {{ArchivLink|file=dglsdk_win32_2006_1|text=Windows}} und {{ArchivLink|file=dglsdk_linux_2006_1|text=Linux}}<br />
* {{ArchivLink|file=tut_lektion_3_delphi_api|text=Alter Delphi-API-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_3_delphi_vcl|text=Alter Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_3_exe|text=Windows-Binary zum Tutorial}}<br />
* [http://www.pixelpracht.net Pixelpracht] - Hier könnt Ihr das Programm "Matrix Control" finden<br />
* [http://www.phobeus.de/hosting/shared/pixelpracht/downloads/mc_v02.zip Matrix Control]<br />
<br />
{{TUTORIAL_NAVIGATION | [[Tutorial Lektion 2]] | [[Tutorial Lektion 4]]}}<br />
[[Kategorie:Tutorial|Lektion3]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Lektion_2&diff=23657Tutorial Lektion 22009-05-17T15:40:20Z<p>Flo: /* Dateien */</p>
<hr />
<div>= Entdeckung einer neuen Welt =<br />
== Vorwort ==<br />
Ich möchte Euch an dieser Stelle bei DGL herzlich willkommen heißen. Vermutlich wird dies eines der ersten Tutorials sein, das Ihr als Einsteiger lesen werdet. Wahrscheinlich werdet Ihr dann auch noch nicht lange bei uns sein und Euch nicht vorstellen können, dass all die Schreiber auf unserer Seite keine Gurus, sondern ganz normale Menschen sind.<br />
<br />
Man kann uns also jeder Zeit im Forum "anfassen", uns Fragen stellen, Vorschläge unterbreiten oder einfach nur einmal kurz mit einem Lob ermutigen weitere Texte zu verfassen ;-).<br />
<br />
Ich wünsche Euch an dieser Stelle viel Erfolg bei dem Einstieg in die Thematik OpenGL und ermahne Euch noch einmal, dass Ihr Eure Ziele nicht zu weit steckt. Wirklich Spaß beginnt OpenGL nämlich erst dann zu machen, wenn man stets kleinere Erfolge feiern kann.<br />
== Höhere Mächte - Matrizen und ihre Folgen ==<br />
=== Saubere Arbeit ===<br />
Bevor wir direkt beginnen, etwas zu zeichnen, müssen wir erst einmal das Bild löschen, denn wie in der vorherigen Lektion beschrieben zeichnen wir die Szene jeden Schleifendurchlauf neu. Das Löschen der Puffer, in dem die Bildinformationen enthalten sind, übernimmt die Funktion [[glClear]]. Die Farbinformationen unser [[Fragment|Fragmente]] (Fragmente sind vergleichbar mit den [[Pixel|Pixeln]] auf dem Bildschirm, besitzen aber weitere Informationen wie z.&nbsp;B. Tiefenwerte. Bei der Ausgabe des Bildes werden diese Fragmente in Pixel umgewandelt) sind in dem so genannten [[Farbpuffer]] (Colorbuffer) gespeichert. Aus diesem Grund übergeben wir an die Funktion [[glClear]] die Konstante '''GL_COLOR_BUFFER_BIT'''. Die oben bereits angesprochenen Tiefenwerte stehen im [[Tiefenpuffer]]. Diese Daten werden benutzt, um zu entscheiden, welche Pixel durch andere verdeckt werden. Deshalb sollten wir den Tiefenpuffer auch mit löschen. Die Konstante dafür heißt '''GL_DEPTH_BUFFER_BIT'''.<br />
<br />
<source lang="pascal">//Farbbuffer und Tiefenpuffer entleeren<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);</source><br />
<br />
[[bild:Tutimg_lektion2_puffereffekt.gif|right]]<br />
Mit Hilfe des Befehls [[glClearColor]] können wir festlegen, welche Farbinformationen mit dem Aufruf von glClear in den Colorbuffer geschrieben werden sollen. Wenn wir es also genau nehmen, so löscht glClear den Colorbuffer nicht, sondern es schreibt ihn mit den definierten Werten voll. Standardmäßig erhalten die Fragmente eine schwarze Farbe. (Der Vollständigkeit halber sei erwähnt, dass der Tiefenpuffer immer mit Nullen überschrieben wird.)<br />
<br />
Es wird also nicht bei jedem Zeichenvorgang (Rendervorgang) der Bildspeicher gelöscht, sondern der Programmierer entscheidet, wann dies geschehen soll. Theoretisch könnt Ihr auch die Szene wiedergeben und dann löschen, bevor sie ausgegeben wird... wer's mag :).<br />
<br />
Wer sich entscheidet, den Colorbuffer nicht zu löschen, der kann so auch einige nette Effekte erzeugen. Den Cheatern unter euch sollte der erreichte Effekt bekannt vorkommen, wenn man beispielsweise bei Counter-Strike durch die Wand gelaufen ist. Der Grund für das Verwischen ist in beiden Fällen derselbe: Das neue Bild wird gezeichnet, ohne dass das alte Bild aus dem Puffer entfernt wurde.<br />
<br />
=== Und die Welt ist doch keine Scheibe... ===<br />
[[bild:Tutimg_lektion2_koordinatensystem.jpg|256px|right]]<br />
Nachdem wir nun wieder Ordnung im Speicher geschafft haben, können wir zum interessanten Teil kommen: "Wie darf man sich einen 3D-Raum vorstellen?" Nun... warum schaut Ihr Euch nicht einmal in Eurem Zimmer um?<br />
<br />
Versuchen wir doch mal, ein Beispiel direkt aus dem Leben gegriffen zu nehmen und stellen uns unsere Umgebung als Koordinaten-System vor (Igitt!). Der Monitor, der hoffentlich direkt vor uns steht, ist in diesem Fall unser Ursprung, d.h. er liegt an dem Punkt (0, 0, 0).<br />
<br />
Blicken wir seitwärts neben den Monitor, so sehen wir eine Linie (wenn Ihr es nicht tun solltest, macht Euch keine Sorgen ... solche Anfälle hat der Autor häufiger *g*), die von links nach rechts verläuft. Es handelt sich hierbei um die X-Achse, die links vom Monitor im negativen Bereich verläuft, rechts davon in den positiven.<br />
<br />
Nun blicken wir einmal nach oben und einmal nach unten und schon erspähen wir die Y-Achse, die oberhalb des Monitors positiv verläuft, unterhalb negativ. Wunderbar! Wenn Ihr bereits in 2D gearbeitet habt, solltet Ihr an dieser Stelle ein dümmliches Grinsen auf Eurem Gesichte haben und das wohltuende Gefühl, dass Euch das doch alles bereits irgendwie bekannt vorkommt. Doch zu unserem Entsetzen können wir uns ja auch noch von unserem Bildschirm wegbewegen oder auch dichter heran (alle Kurzsichtigen unter uns sollten das nicht so persönlich nehmen, auch sie leben in einem 3D-Raum ^__-).<br />
<br />
Dieses Phänomen ist im 3D-Raum typisch, jedoch nicht unerklärlich, da wir uns auf der Z-Achse bewegen. Rollen wir mit dem Stuhl vom Monitor weg, so gelangen wir in den positiven Bereich der Z-Achse, bewegen wir uns drauf zu, gelangen wir in den negativen Bereich. Jeder, der bereits in D3D programmiert hat, sollte sich schleunigst einprägen, dass das ein großer Unterschied zwischen D3D und OpenGL ist. Das kann sonst zu einigen wirklich fiesen Fehlern führen, wenn man es nicht weiß... *fg*.<br />
<br />
Soweit so gut! Klingt bisher hoffentlich immer noch nicht so kompliziert. Nur keine Sorge, das Niveau versuchen wir zu halten ;).<br />
<br />
Speziell wenn Ihr bereits 2D-Spiele programmiert habt, solltet Ihr Euch sehr schnell von folgenden Gedanken trennen: Die Koordinaten, die Ihr seht und angebt, entsprechen in der Regel nicht den Pixeln des Bildschirmes, sondern so genannten Weltkoordinaten. Eine Definition für diese Weltkoordinaten gibt es nicht, denn wie groß diese sind, ist dem Programmierer überlassen. Ob Ihr euer Objekt eine Einheit vor Euch und 0,1 Einheit rechts von Euch oder aber 100 Einheiten vor euch und 10 Einheiten neben Euch positioniert ist egal. Die euch zur Verfügung stehende Welt ist grenzenlos ;). Ihr könnt Eure Szene theoretisch unendlich klein oder aber unendlich groß darstellen. Das Einzige, was euch wirklich daran hindert, ist die Größe und Auflösung der Euch zur Verfügung stehenden Typen wie Integer oder Single ;). Der eigentliche Größeneindruck eines Objektes entsteht durch die Geschwindigkeit mit der sich der Betrachter und andere Objekte in der Welt bewegen.<br />
<br />
=== Der erste Kontakt ===<br />
Um nun etwas mit OpenGL rendern zu können, müssen wir uns bewusst werden, was die Worldmatrix ist. In dieser Matrix wird festgehalten, wie und wo ein Objekt gezeichnet wird. Das hört sich vielleicht zunächst recht merkwürdig an, lässt sich aber leicht veranschaulichen.<br />
<br />
Stellen wir uns vor, die Worldmatrix zeigt auf einen Punkt, an dem ein Objekt gezeichnet werden soll (ganz übel... wir werden später detaillierter darauf eingehen. Sollte es jemand also nach der folgenden Erläuterung nicht verstanden haben, kann er mich selbstverständlich persönlich per Mail zur "Rechenschaft" ziehen oder aber er wirft einen Blick in unser OpenGLWiki :)).<br />
<br />
Diesen Punkt setzen wir nun am Anfang in den Mittelpunkt unseres Koordinatensystems. Um oberes Beispiel aufzugreifen: Den Bildschirm. Dies geschieht mit dem Befehl [[glLoadIdentity]] und entspricht einem Reset der Worldmatrix. Theoretisch können wir nun an dieser Stelle etwas zeichnen. Dies würde allerdings dazu führen, dass das Objekt sehr groß oder gar nicht zu sehen ist, weil wir uns eben auch an diesen Stellen befinden. Gehen wir doch einmal 1,5 Einheiten nach links und 6 Einheiten nach hinten!<br />
<br />
Hierfür sollte man sich bewusst werden, dass wir in OpenGL praktisch an einen Stuhl gefesselt sind, d.h. wir können uns gar nicht bewegen. Das soll uns aber nicht davon abhalten. Wir brauchen ja nur die ganze Welt so zu bewegen, dass es für uns aussieht, als ob wir uns bewegen. Denn wenn sich alles außer uns nach links bewegt, haben wir den Eindruck, wir wären nach rechts gewandert, right?<br />
<br />
Nun aber zu der Bewegung unseres ersten Objektes:<br />
<source lang="pascal">glTranslatef(-1.5,0,0);<br />
glTranslatef(0, 0,-6);</source><br />
Dies hat bewirkt, dass sich unser Zeichenstift 1,5 Einheiten nach links (negativer Bereich der X-Achse) und anschließend 6 Einheiten nach hinten bewegt hat. Ich habe diesen Fall absichtlich in zwei Schritten gefasst, um es zu verdeutlichen. Sicherlich wäre es einfacher, alles mit nur einem Aufruf von [[glTranslate|glTranslate*]] zu machen:<br />
<source lang="pascal">glTranslatef(-1.5, 0,-6);</source><br />
Man beachte, dass in diesem Fall die 6 Schritte nach hinten notwendig sind um ein bisschen Distanz zum Objekt zu bekommen und nicht direkt in ihm zu stehen!<br />
<br />
Fertig! Schon haben wir dort unseren "Zeichenstift" positioniert, der nun auf weitere Zeichenkommandos von uns wartet. Das mag sicherlich alles ein wenig verwirrend klingen... probiert es am Besten aus und spielt mit den Parametern und erkennt, was gemeint ist :).<br />
== Von Sichtungen... ==<br />
=== Die Büchse der Pandora ===<br />
Nun sind wir aber auch alle scharf darauf, endlich etwas zu rendern und auf dem Bildschirm auszugeben. Dies geschieht z.&nbsp;B. mit folgenden Zeilen:<br />
<source lang="pascal">glBegin(GL_TRIANGLES);<br />
glVertex3f(-1,-1, 0); <br />
glVertex3f( 1,-1, 0);<br />
glVertex3f( 0, 1, 0);<br />
glEnd;</source><br />
Wir erkennen hierbei eindeutig eine Art Block. Gerade wir Pascaler sollten diese ja lieben :->. Wir teilen OpenGL mit Hilfe von [[glBegin]] mit, dass wir ein Objekt zeichnen wollen. In diesem Fall übergeben wir den Parameter '''GL_TRIANGLE''', der OpenGL angibt, dass folgende Punkte als ein Dreieck interpretiert werden sollen.<br />
<br />
Anschließend folgt ein dreifacher Aufruf von [[glVertex|glVertex3f]]. Jeder Aufruf erzeugt nun einen Punkt (Vertex) in unserem 3D-Raum. Da wir OpenGL bei glBegin mitgeteilt haben, dass diese Punkte zu einem Dreieck zusammengefügt werden sollen, wird das auch so getan ;).<br />
<br />
Wie wir auf dem Bild erkennen können, wurde unser Dreieck wie erwartet nach links verschoben abgebildet und auch mit einem leichten Abstand zur Kamera, nämlich 6 Einheiten entlang der Z-Achse.<br />
=== Guter Stoff! ===<br />
Nun... ein wenig trostlos sieht unser Dreieck nun doch aus, oder? Ich habe übrigens damals bei D3D rund eine Woche benötigt, bis ich ein schwarzes Dreieck hatte. Wir haben immerhin schon ein weißes... aber wir gehen nun einen Schritt weiter und werden das Dreieck schön bunt einfärben.<br />
<br />
Bevor nun irgendjemand anfängt und Pixel für Pixel den Bildschirm nach zu pinseln oder gar sein PaintShop bereits offen hat, um die ersten Texturen zu erstellen, sei gestoppt! Es gibt für einfache Einfärbungen in OpenGL eine bessere Methode. Wir definieren einfach für jeden Eckpunkt eine Farbe und OpenGL wird dann sogar eigenständig die Farbverläufe dafür erstellen. Jeder der D3D kennt, wird diese Vorgehensweise bekannt vorkommen und er wird beginnen, verzweifelt nach dem FVF zu suchen, um es korrekt zu definieren... Wenn Ihr einen Vertex zeichnet, so rendert OpenGL diesen automatisch "weiß" (Ah huch! Deswegen ist unser Dreieck auch weiß???). Alles was wir nun machen müssen, ist OpenGL mitzuteilen, dass es eine andere Farbe einsetzen soll. Und zwar wird OpenGL diese Farbe für alle folgenden Eckpunkte nutzen, so lange bis von uns ein anderes Kommando kommt.<br />
<br />
Der mysteriöse Befehl, um den ich nun schon die ganze Zeit herumschwafle ist [[glColor|glColor*]]:<br />
<source lang="pascal">// Alle folgenden Eckpunkte werden rot gefärbt<br />
glColor3f(1,0,0);</source><br />
Wobei jeder Parameter einen Farbwert repräsentiert und zwar nach dem Muster RGB. Es wird ein Wert zwischen 1 und 0 erwartet, wobei eine Eins "volle Farbsättigung" bedeutet. Das heißt in unserem Fall haben wir als Farbe "rot" gesetzt.<br />
<br />
Und weil wir schließlich die Welt ein wenig bunter machen und nicht nur das weiße Dreieck rot färben wollten, werden wir nach jedem Eckpunkt eine neue Farbe setzen:<br />
<source lang="pascal">glBegin(GL_TRIANGLES);<br />
glColor3f(1, 0, 0); glVertex3f(-1,-1, 0); <br />
glColor3f(0, 0, 1); glVertex3f( 1,-1, 0);<br />
glColor3f(0, 1, 0); glVertex3f( 0, 1, 0);<br />
glEnd;</source><br />
Und dies ist dann unser farbenfrohes Ergebnis. Beeindruckend, wenn man bedenkt, wie wenig Aufwand letztendlich dahinter steckt, oder?<br />
<br />
[[bild:Tutimg_lektion2_dreieck.gif]]<br />
<br />
== Nachwort ==<br />
Wer meine Tutorials kennt weiß, dass zum Abschluß noch immer ein wenig Geblubber von mir kommt (man beachte diese entzückende Wortwahl von meiner einer... hoffe, der Lektor findet es auch amüsant...). (Anm. des Lektors: Und wie!) Schauen wir doch mal stolz auf das zurück, was wir heute erreicht haben! Wir haben OpenGL initialisiert, ein erstes Dreieck auf den Bildschirm gezaubert und dieses in den Farbtopf gesteckt! Das solltet Ihr als ein historisches Ereignis ansehen. Als ich damals mit D3D angefangen habe, brauchte ich, um es mir selbst zu erarbeiten, ca. eine Woche (Das ist jetzt der Moment, in dem Ihr Eure Hände heben solltest und ein lautes "Call me God" aus Euch herauskommen sollte, so dass zumindest Eure unmittelbaren Mitmenschen denken, dass Ihr einen Dachschaden habt!!! Das gehört einfach mit dazu ^__- )<br />
<br />
Wie immer solltet Ihr Euch nun hinsetzen und Euch ein wenig mit den Parametern vertraut machen. Speziell bei glTranslate* solltet Ihr ein wenig mit den Parametern experimentieren, damit Ihr seht, was es mit den Matrizen auf sich hat und wie diese funktionieren. Wenn Ihr dann auch denkt, dass Ihr damit vertraut seid, versucht ein wenig mit den einzelnen Vertexpositionen zu experimentieren und verändert diese. Wenn auch das klar ist, setzt Euch in die Ecke und warte sehnsüchtig auf mehr von uns :D! (Anm: Die "Ecke" ist nicht die Kneipe nebenan... :)<br />
<br />
Im nächsten Kapitel werden wir dann Matrizen-Hardcore machen ... legt also schon mal Euer Aspirin parat, es wird witzig werden *grunz* :).<br />
<br />
Und wie immer freuen wir uns sehr über Feedback. Schreibt uns doch einfach ein paar Worte in unser [http://www.delphigl.com/forum/viewforum.php?f=8 Feedback-Forum]. Sagt, was Ihr gut und was hingegen Ihr als schlecht empfunden habt! Auch freuen wir uns immer über Ideen für weitere Tutorials, teilt uns also bitte Eure Ideen mit ;)!<br />
<br />
Okay... ich wünsche Euch einen angenehmen Tag / Nacht!<br />
<br />
'''Euer'''<br><br />
'''Phobeus'''<br />
<br />
== Dateien == <br />
* Der aktuellste Beispiel-Quelltext befindet sich im DGLSDK 2006.1 für {{ArchivLink|file=dglsdk_win32_2006_1|text=Windows}} und {{ArchivLink|file=dglsdk_linux_2006_1|text=Linux}}<br />
* {{ArchivLink|file=tut_lektion_2_delphi_api|text=Alter Delphi-API-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_2_delphi_vcl|text=Alter Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_2_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION | [[Tutorial Lektion 1]] | [[Tutorial Lektion 3]]}}<br />
<br />
[[Kategorie:Tutorial|Lektion2]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23656DGL Wiki:Files2009-05-17T15:33:12Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:jvscript]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:stereo_vcl]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_terrainclod_exe&diff=23655Archiv:tut terrainclod exe2009-05-17T15:32:42Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Windows-Binary zum Tutorial Terrain3.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Windows-Binary zum [[Tutorial Terrain3]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_terrainclod_delphi_vcl&diff=23654Archiv:tut terrainclod delphi vcl2009-05-17T15:32:13Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-VCL-Quelltext zum Tutorial Terrain3.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial Terrain3]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Terrain3&diff=23653Tutorial Terrain32009-05-17T15:31:14Z<p>Flo: </p>
<hr />
<div>=Continuous Level of Detail fur Heightmaps=<br />
<br />
==Einführung==<br />
<br />
Hi, <br />
<br />
Ich habe in meinem ersten Heightmap Tutorial im Nachwort erzahlt, dass ich vielleicht ein Tutorial uber Level of Detail (LOD) fur Heightmaps schreibe. Ich will hiermit mein Versprechen einlosen, will euch aber auch gleich warnen, es ist nicht so leicht, wie man eigentlich denken konnte, denn viele Probleme stellen sich einem in den Weg, z.B. Cracks im [[Mesh]]. <br />
<br />
Erkundigt man sich unter Spieleentwicklern nach LOD fur Heightmaps, wird man in den meisten Fallen wohl auf den Begriff ROAMing stosen. Dieser wird z.B. in einem meiner Lieblingsgames Tribes 2 verwendet. Mich hat dieser Algorithmus nicht wirklich angesprochen und ich habe weitergesucht. Nach einiger Zeit bin ich auf den Algorithmus aufmerksam geworden, der im Spiel Aquanox sein Konnen zeigt. Genauer gesagt bin ich uber das [http://www.vterrain.org Virtual Terrain Project] darauf gestosen. Das Original Script von [http://www9.cs.fau.de/Persons/Roettger Stefan Rottger] findet sich direkt auf seiner Homepage unter Terrain Rendering: [http://wwwvis.informatik.uni-stuttgart.de/~roettger/data/Papers/TERRAIN.PDF Real-Time Generation of Continuous Levels of Detail for Height Fields] Ich kann euch wirklich warmstens empfehlen, dieses Script sich einmal genauer anzuschauen - sehr interessant. In diesem Tutorial werde ich das Schriftstuck etwas genauer beleuchten und ein paar Hilfestellungen dazu geben, in der Hoffnung, dass ihr es mir gleich tut und diesen Algorithmus selbst einmal nachbaut.<br />
<br />
==Quadtree==<br />
<br />
Der Ganze Algorithmus basiert auf einem Quadtree. Dieser wird durch eine Boolean Matrix(fur die Delpher, die immer noch nicht wissen, was eine Matrix ist: Wir reden uber ein 2 Dimensionales '''Array[0..n,0..n] of Boolean''') reprasentiert, so kann man sich eine Menge Pointer(und damit Speicherplatz) und viel Rechenarbeit sparen. Wir gehen davon aus, dass die Matrix eine Grose von (2n+1)x(2n+1) hat. Idealerweise hat die verwendete Heightmap die gleichen Ausmase. Die Hauptnode sitzt in der Mitte der Matrix, die 4 Kinder... Bevor ich in Erklarungsnote komme, hier einfach ein Bild, das den Sachverhalt verdeutlichen soll:<br />
<br />
[[Bild:Tutorial_Terrain3_quadmatrix.gif|center]]<br />
<br />
Ok, wir wissen nun, wie die Matrix aufgebaut ist, wie die Mutter Kind Verhaltnisse des Quadtrees sind, etc., nur ist noch immer nicht klar, wozu diese Matrix. Wie bereits gesagt, handelt es sich um eine Boolean Matrix. Die Werte, die die Matrix annehmen kann, sind True oder False :-). Diese sagen aus, ob eine Node gesetzt ist und damit gezeichnet wird, oder eben nicht. Unsere Ziele sind also zum einen die Matrix rendern und zum andern die Matrix berechnen<br />
<br />
==Rendern der Heightmap==<br />
<br />
Unsere Landschaft wird gerendert, indem wir den Quadtree rekursiv entlanggehen und immer wenn ein Endpunkt des Baums erreicht wird, wird ein kompletter oder teile eines Dreieck Fans gezeichnet. Diese Fans sind besonders gut dazu geeignet, ohne Locher und Cracks im Mesh ein Heighfield darzustellen:<br />
<br />
[[Bild:Tutorial_Terrain3_setmatrix.gif]]<br />
[[Bild:Tutorial_Terrain3_triangulation.gif]] <br />
<br />
Das linke Bild stellt eine Beispiel Matrix dar, das rechte die dazu passende Triangulation. Ihr konnt gerne nach Stellen suchen, an denen die Moglichkeit besteht, dass dort ein Crack ist. Ihr werdet keine finden. Zuerst wird einmal davon ausgegangen, dass der Levelunterschied im Quadtree zweier benachbarter Blocke niemals mehr als 1 betragt. Diese Bedingung wird beim Berechnen der Matrix abgesichert, dazu spater mehr. Das Zentrum eines Fans, ist immer auch das Zentrum eines Quadtree Blattes. Vier Vertieces bilden die Ecken des Blocks und weitere 4 bilden jeweils die Mittelpunkte der Seiten des Blattes:<br />
<br />
[[Bild:Tutorial_Terrain3_fan.gif]]<br />
<br />
Es werden also hochstens 8 Dreiecke gezeichnet. Hat eine Seite keinen benachbarten Block gleichen Levels, so wird das Vertex in der Mitte der Seite einfach ubersprungen, was zur obigen Beispiel Triangulation fuhrt.<br />
<br />
<br />
==Erzeugen der Matrix==<br />
<br />
LOD besagt, dass Gegenstande in groser Entfernung mit einer niedrigeren Auflosung angezeigt werden, als nahe Objekte. Daneben werden auch bei groserer Entfernung bestimmte Teile eines Meshes mit hoherer Auflosung angezeigt, deren Oberflache besonders uneben ist, wodurch die Bildqualitat stark erhoht wird.<br />
<br />
<br />
===Oberflachenfehler===<br />
<br />
Fehler? Fehler?!? Will ich euch jetzt verarschen, ware doch dumm Fehler zu machen... Nein, die gemeinten Fehler besagen ausschlieslich, wie verfalscht eine Stelle in der Landschaft aussehen wurde, wenn man beim Hinuntergehen des Baumes an der aktuellen Stelle aufhort und zu rendern beginnt. Je hoher dieser Wert, desto wichtiger ist es, den Baum noch tiefer hinabzuschreiten.<br />
<br />
[[Bild:Tutorial_Terrain3_dhvalues.jpg|center]]<br />
Um diese '''d2''' genannten Werte fur ein Quadree Blatt zu erhalten, errechnet man zuerst die Erhebungsunterschiede an den Mitten der 4 Seiten und an den 2 Diagonalen(Achtung: das sind unterschiedliche Werte). Der '''d2''' Wert errechnet sich daraus so:<br />
<br />
'''d2=(1/d)*maxi=1..6|dhi|'''<br />
<br />
In Delphi konnte das ganze dann so aussehen:<br />
<br />
<source lang="pascal"><br />
procedure CalcD2Value(X, Z, Size : Longint);<br />
var<br />
HSize : LongInt; //Hälfte der eigenen Seitenlänge<br />
I : LongInt;<br />
EdgeHeights : Array[1..4] of Single; //Höhe der Ecken<br />
ExpectedHeight : Single; //Erwartete höhe zw 2 Punkten<br />
RealHeight : Single; //Tatsächliche Höhe<br />
<br />
DHValues : Array[1..6] of Single; //DHWerte<br />
<br />
const<br />
//Ort der Ecken des Quads<br />
Edges : Array[1..4] of Array[0..1] of ShortInt = ((+1, +1),(+1, -1),(-1,-1),(-1,+1));<br />
//Jeweils die 2 Ecken, zwischen denen ein DHWert berechnet wird<br />
DHLines : Array[1..6] of Array[0..1] of Byte =<br />
((1,2),(2,3),(3,4),(4,1),(1,3),(2,4));<br />
<br />
//Positionen der DHWerte selber<br />
DHPositions : Array[1..6] of Array[0..1] of ShortInt =<br />
((1,0),(0,-1),(-1,0),(0,1),(0,0),(0,0));<br />
<br />
begin<br />
//Höhen der 4 Ecken<br />
for I := 1 to 4 do<br />
EdgeHeights[I] := GetHeightMapHeight(X + Size * Edges[i,0],<br />
Z + Size * Edges[i,1]);<br />
<br />
//DHWerte berechen<br />
for i := 1 to 6 do<br />
begin<br />
//Erwartete Höhe zwischen 2 Punkten<br />
ExpectedHeight := (EdgeHeights[DHLines[i,0]] + EdgeHeights[DHLines[i,1]])/2;<br />
//Tatsächlich vorliegende Höhe<br />
RealHeight := GetHeightMapHeight(<br />
X + DHPositions[i,0] * Size,<br />
Z + DHPositions[i,1] * Size);<br />
//DH Wert ist |absoluter Fehler|<br />
DHValues[I] := Abs(ExpectedHeight - RealHeight)<br />
end;<br />
<br />
//D2Value finden und setzen<br />
QuadMatrix[X,Z].D2Value := Trunc(1/(2*Size) * Max(DHValues));<br />
<br />
//D2Values für die 4 Kinder<br />
if Size > 1 then<br />
begin<br />
HSize := Size div 2;<br />
CalcD2Value(X + HSize, Z + HSize, HSize);<br />
CalcD2Value(X + HSize, Z - HSize, HSize);<br />
CalcD2Value(X - HSize, Z + HSize, HSize);<br />
CalcD2Value(X - HSize, Z - HSize, HSize)<br />
end<br />
end; (*CalcD2Values*)<br />
...<br />
CalcD2Value((QuadTreeLength-1) div 2, (QuadTreeLength-1) div 2,<br />
(QuadTreeLength-1) div 2);<br />
...<br />
</source><br />
<br />
In diesem Beispiel steht '''Size''' immer fur die Halfte der Seitenlange einer Quadtreenode. Man denke hier am Besten an den Radius eines Kreises. Die '''d2''' Werte werden auf Bytegrose komprimiert, um wertvollen Speicher einzusparen.<br />
<br />
<br />
===Matrix erzeugen===<br />
<br />
Jetzt wollen wir uns daran machen, die furs Zeichnen unbedingt notige Matrix zu erzeugen. Gehen wir einmal davon aus, dass die Matrix bereits sauber entleert ist und damit kein Node als gesetzt gilt. Wir laufen den Quadtree wieder rekursiv hinab und entscheiden an jeder Node nach folgender Formel, ob weiter unterteilt werden muss oder nicht:<br />
<br />
'''f=l/(d*C*max(c*d2, 1)'''<br />
<br />
Ist der Wert von '''f''' kleiner 1, dann wird weiter unterteilt. Es gilt: '''C''' ist eine feste Konstante. Je hoher sie ist, desto hoher ist die Landschaft insgesamt aufgelost. '''l''' ist die Entfernung des Node Mittelpunkts zur Kamera und klein '''c''' kontrolliert die Auflosung in Bereichen, die einen hohen Oberflachenfehler aufweisen.<br />
<br />
<br />
===Schweizer Kase===<br />
[[Bild:Tutorial_Terrain3_kaese.jpg|center]]<br />
Das bisherige Ergebnis ist doch mehr ernuchternd als erfreulich. Es sieht alles mehr nach Schweizer Kase aus. Viele Locher, aber sonst nicht viel zu bieten. In einem Spiel kann man so ganz schnell dem Spieler den Spass verderben, wir sollten also etwas dagegen unternehmen.<br />
<br />
Um dieses Ziel zu erreichen, mussen wir ganz einfach die '''d2''' Werte so verandern, dass Nebeneinanderliegende Blocke keinen Levelunterschied groser als 1 aufweisen (Bedingung für das Rendern der Matrix, siehe oben). Zuerst mussen also die '''d2''' Werte einmal berechnet worden sein. Nun gehen wir den Quadtree wieder soweit herunter, bis wir genau 1 level vor dem niedrigsten sind und setzen den '''d2''' Wert der Node neu:<br />
<br />
<source lang="pascal"><br />
QuadMatrix[X,Z].D2Value := Max(QuadMatrix[X,Z].D2Value, <br />
(d2Werte der benachbarten Nodes ein Level tiefer) * K)<br />
K = C / (2 * (C - 1)); C groser 2<br />
</source><br />
<br />
<source lang="pascal"><br />
procedure DoCrackAvoid(X, Z, Size, SizeToCheck : Longint);<br />
const<br />
Neighbours : Array[0..8] of Array[0..1] of ShortInt =<br />
((+0,+0), (+3,+1),(+3,-1),(+1,-3),(-1,-3),(-3,-1),(-3,+1),(-1,+3),(+1,+3));<br />
var<br />
HSize : LongInt; //1/4 der Seitenlänge<br />
I : LongInt;<br />
D2Values : Array[0..8] of Byte;<br />
<br />
begin<br />
Assert(Size > 1);<br />
//Der Baum wird rekursiv abgehandlet(nicht unbedingt die<br />
//beste Idee. Wer Lust hat, darf sich gerne daran versuchen<br />
//es einmal ohne Rekursivität zu probieren!)<br />
<br />
HSize := Size div 2;<br />
//Wir haben noch nicht das gewünschte Level erreicht<br />
if Size > SizeToCheck then<br />
begin<br />
DoCrackAvoid(X + HSize, Z + HSize, HSize,SizeToCheck);<br />
DoCrackAvoid(X + HSize, Z - HSize, HSize,SizeToCheck);<br />
DoCrackAvoid(X - HSize, Z + HSize, HSize,SizeToCheck);<br />
DoCrackAvoid(X - HSize, Z - HSize, HSize,SizeToCheck)<br />
end<br />
else<br />
begin<br />
Assert(SizeToCheck = Size);<br />
D2Values[0] := QuadMatrix[X,Z].D2Value;<br />
for i := 1 to 8 do<br />
//Nachbarnodes holen....<br />
D2Values[i] := GetNode(X + HSize * Neighbours[i][0],<br />
Z + HSize * Neighbours[i][1]) * K;<br />
<br />
QuadMatrix[X,Z].D2Value := MaxByte(D2Values)<br />
end<br />
end; (*DoCrackAvoid*)<br />
</source><br />
<br />
Diesen Vorgang wiederholen wir, wobei wir immer ein Level niedriger gehen, bis wir schlieslich das Level 1 erreichen. Wenn wir nun unsere Matrix berechnen, ist unsere Bedingung erfullt und wir erhalten ein Mesh ohne Locher:<br />
<br />
[[Bild:Tutorial_Terrain3_result.jpg|center]]<br />
<br />
[[Bild:Tutorial_Terrain3_wireframe.jpg|center]]<br />
<br />
Wer glaubt hier ein paar Locher finden zu konnen liegt falsch. Die Stellen die schwarz erscheinen, werden durch das OpenGl Licht nur so unglucklich getroffen, dass sie fast schwarz erscheinen. Ist euer Monitor richtig eingestellt, sollte dagegen alles passen. Siehe dazu auch: [[Farbraum]]{{excIcon}}. Die "Kamera" befindet sich übrigens links unten im Bild, dort wo ein rotes Quad sichtbar ist.<br />
<br />
'''Anmerkung:''' Nach Aussage von Stefan Röttger ist diese Methode um Löcher zu entfernen nicht optimal. Es genügt bei jedem Vater Knoten alle Kinder in die Berechnung einzubeziehen. Die Nachbarn müssen nicht einbezogen werden. Genaueres findet sich in diesem [http://www.delphigl.com/forum/viewtopic.php?p=48761#48761 Forumsbeitrag].<br />
<br />
==Nachwort==<br />
<br />
Wie gehts weiter?<br />
Um die ganze Sache nun noch ein bischen weiter zu verbessern, sollte man uberlegen, ob man die Landschaft nicht vielleicht texturiert. Hier bietet sich sicher das [[Tutorial_Terrain2|2. Highmap Tutorial]] als guter Anfang an. Auch konnte man das "Popping" unterbinden, das auftritt, wenn man sich einer Stelle nahert und plotzlich ein paar neue Dreiecke angezeigt werden. Das ganze ist bekannt unter dem Begriff Geomorphing. <br />
<br />
'''Nico Michaelis''' aka DelphiC<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_terrainclod_delphi_vcl|text=Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_terrainclod_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_Terrain2]]|[[Tutorial_Skyboxen]]}}<br />
<br />
[[Kategorie:Tutorial|Tutorial3]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23652DGL Wiki:Files2009-05-17T15:20:49Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:jvscript]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:stereo_vcl]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:terrainclod_exe]]<br />
* [[Archiv:terrainclod_src_vcl]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_particle1_exe&diff=23651Archiv:tut particle1 exe2009-05-17T15:20:17Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Windows-Binary zum Tutorial Partikel1.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Windows-Binary zum [[Tutorial Partikel1]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_particle_delphi_api&diff=23650Archiv:tut particle delphi api2009-05-17T15:19:59Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-API-Quelltext zum Tutorial Partikel1.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-API-Quelltext zum [[Tutorial Partikel1]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Partikel1&diff=23649Tutorial Partikel12009-05-17T15:09:30Z<p>Flo: </p>
<hr />
<div>=Partikel Systeme I=<br />
<br />
==Übersicht==<br />
Dies ist das erste von zwei Tutorials zum Thema Partikel Systeme. Zuerst werde ich den Begriff Partikel Systeme und deren Funktionsweise erklären.Anhand des beiliegenden Beispiel-Programms erläutere ich die Programmierung eines einfachen Partikel-Systems. Dieses Tutorial wird hauptsächlich auf die verwendeten Konzepte eingehen, die Umsetzung ist dem Beispiel zu entnehmen. Es empfiehlt sich deshalb, das Gelesene sofort anhand des Source-Codes nachzuvollziehen.<br />
<br />
Das zweite Tutorial baut auf dem ersten auf und erweitert das System um Effekte wie Gravitation oder Luftwiderstand. Außerdem werde ich noch ein paar Probleme und deren Lösung ansprechen, damit der eigenen Partikel-Engine nichts mehr im Wege steht!<br />
<br />
==Von Chaos, Ordnung und Explosionen==<br />
Wer sich mit Grafikprogrammierung beschäftigt, hat meist das Ziel, Szenen oder Grafiken zu erschaffen, die realen Vorbildern möglichst exakt nachempfunden sind. In Computerspielen zum Beispiel ist die Atmosphäre und damit auch der Unterhaltungswert abhängig von Detailgrad und Stimmigkeit der simulierten virtuellen Welt.<br />
<br />
Es ist also notwendig Methoden zu finden, um reale Objekte und Phänomene möglichst genau nachzubilden. Während Dinge wie Personen oder Gebäude von festlegbarer Form sind und sich sehr gut aus Polygonen und Texturen nachbilden lassen, dürfte auch der beste Modeller bei Effekten wie Feuer, Dampf oder Wasser das Handtuch werfen. Sieht man sich zum Beispiel eine Explosion genauer an, so lassen sich keine festen Formen erkennen, die man nachbauen könnte. Stattdessen fliegen hier Funken, Feuerzungen blähen sich auf und fallen zusammen, Teile fliegen auseinander und Rauch steigt auf. Tausende kleiner Partikel verhalten sich scheinbar chaotisch und bilden doch im Zusammenspiel ein stimmiges Bild.<br />
<br />
Um solche Effekte nachbilden zu können, braucht man ein anderes Konzept, nämlich das der Partikel-Systeme. So komplex eine Explosion auch aussehen mag, das Verhalten der einzelnen Komponenten lässt sich durch physikalische Gesetze erklären. Sind die Zusammenhänge erst einmal erkannt, so lassen sie sich auch auf virtuelle Systeme übertragen. Ein Partikel-System besteht also aus einer Reihe von Regeln, die das Verhalten von unzähligen dynamischen Partikeln beeinflussen. Um ein möglichst natürliches, chaotisches Resultat zu erhalten, werden zusätzlich zufällige Elemente eingeflochten.<br />
<br />
Das Ergebnis sind chaotische, dynamische Gebilde, die trotzdem kontrollierbar bleiben. Durch die Kombination verschiedenster Regeln/Effekte mit variierten Parametern wird der Kreativität kaum Grenzen gesetzt.<br />
<br />
==Am Anfang war nur Staub==<br />
Nachdem jetzt klar ist, was Partikel Systeme sind und wofür sie gebraucht werden, zeige ich an einem einfachen Beispiel, woraus solch ein System aufgebaut sein muss und wie es sich implementieren lässt.<br />
<br />
Eins jedoch noch vorweg: Ich werde hier keine ultraschnelle und flexible Partikel-Engine mit dutzenden von Effekt-Templates und Schießmichtot-Features entwickeln. Am Ende der Tutorials sollte der Grundstein für die Entwicklung einer echten Engine gelegt sein, aber diese Arbeit nehme ich euch nicht ab! ;)<br />
<br />
Das allerwichtigste an einem Partikel System sind natürlich die Partikel (siehe Unit pfxCore). Deshalb sollten wir uns zunächst Gedanken darüber machen, welche Attribute diese ausmachen.Zuerst fallen einem da die physikalischen Grundeigenschaften ein, die jeder Körper besitzt: Position, Geschwindigkeit (und Richtung), Masse, Dichte (des Materials) und natürlich eine bestimmte Größe. Eigenschaften wie Masse oder Dichte scheinen auf den ersten Blick unwichtig zu sein, sind aber für bestimmte Effekte wie Gravitation oder Wind unverzichtbar.Außerdem ordnen wir jedem Partikel eine individuelle Lebensspanne zu, die jeden Frame nach unten korrigieren wird. Von ähnlicher Bedeutung ist das Alter, das in Millisekunden angegeben wird.<br />
<br />
Zuletzt spendieren wir unseren Partikeln noch eine Farbe, die Zeiten der Schwarz-Weiß Effekte sind schließlich vorbei!Nun dürfte auch die Implementation keine Fragen mehr aufwerfen - na ja, fasst keine! Man könnte sich fragen, ob man lieber ein Partikel-Record oder eine Partikel-Klasse macht, aber wenn man bedenkt, dass eine Klassen-Instanz wesentlich mehr Speicher belegt als ein schlichtes Record und wir mit hunderten von Partikel hantieren wollen, sollte die Entscheidung klar sein.<br />
<br />
Ein Partikel-Typ könnte also folgendermaßen definiert sein:<br />
<br />
<source lang="pascal">TPfxParticle = record<br />
Position : TPfxVector;<br />
Velocity : TPfxVector;<br />
Density : single;<br />
Mass : single;<br />
Size : single;<br />
Color : TPfxColor;<br />
LiveSpan : integer;<br />
Age : integer;<br />
end;</source><br />
<br />
Die Typen TPfxVector und TPfxColor müssen wir natürlich auch noch definieren.TPfxVektor enthält eine x, y und z Koordinate und eignet sich damit prächtig zum speichern von Orts- oder Richtungsvektoren.<br />
<br />
TPfxColor definiert beliebige Farben, indem es die Farbintensität der Grundfarben angibt. Üblicherweise verwendet man dazu drei Byte, so dass jede Grundfarbe eine Intensität von 0 bis 255 haben kann. OpenGL benutzt jedoch Fließkommazahlen (Single), bei denen die Intensität zwischen 0 und 1 angegeben wird.<br />
<br />
<source lang="pascal">TPfxVector = record<br />
x : single;<br />
y : single;<br />
z : single;<br />
end;<br />
<br />
TPfxColor = record<br />
r : single;<br />
g : single;<br />
b : single;<br />
end;</source><br />
<br />
==Container auf - Partikel rein - Container zu==<br />
Nun haben wir festgelegt, wie ein Partikel auszusehen hat, aber ein Partikel allein macht noch keinen eindrucksvollen Effekt. Für etwas Aufwendiges wie z.B. eine Explosion sollten es schon einige hundert Partikel sein mit gänzlich verschiedenen Eigenschaften. So müssen sich Rauch-Partikel anders verhalten als ihre Kollegen bei den sprühenden Funken, oder züngelnden Flammen.<br />
<br />
Um da nicht die Übersicht zu verlieren, fassen wir gleiche Partikel-Typen zu Gruppen zusammen, die über eine Container-Klasse verwaltet werden.Unsere Container-Klasse hat ein Interface, über das sich Partikel hinzufügen und löschen lassen. Diese Partikel werden in einem dynamischen Array gespeichert, das dem Benutzer Zugriff auf die Attribute aller Partikel gewährt. Außerdem aktualisiert die Container-Klasse die Partikel in Bezug auf Position und Alter und gibt bei abgelaufener Lebensspanne den Speicher frei.<br />
<br />
Ein einfaches Container Interface könnte z.B. so aussehen:<br />
<br />
<source lang="pascal">TPfxContainer = class(TObject)<br />
protected<br />
FnumParticles : word;<br />
function GetSize : word;<br />
public<br />
Particles : array of TPfxParticle;<br />
property numParticles : word read FnumParticles;<br />
property Size : word read GetSize;<br />
constructor Create(aSize : word); overload;<br />
function Add(var aParticle : TPfxParticle) : integer;<br />
procedure Delete(aIndex : integer);<br />
procedure Advance(aTime : integer);<br />
function Clean : word;<br />
procedure Clear;<br />
end;</source><br />
<br />
Wie gesagt, dieses Interface ist einfach - und genauso einfach ist die Implementation. Ein Blick auf den Quellcode in der Unit pfxCore sollte die meisten Fragen beantworten. Die Speicherverwaltung ist zwar effektiv, aber noch lange nicht perfekt. Wenn die Instanz eine Containers erstellt wird, muss die Anzahl der zur Verfügung stehenden Plätze mit angegeben werden. Danach ändert sich daran nichts mehr, denn das Zuweisen und Freigeben von Speicher ist sehr rechenintensiv.<br />
<br />
numParticles gibt an, wie viele Plätze belegt sind. Dabei ist es egal, ob es sich um aktive Partikel handelt oder "tote", bei denen LiveSpan bereits negativ ist. Für ein neues Partikel wird numParticels einfach um eins erhöht und dient gleichzeitig als Index für das dynamische Array.<br />
<br />
Und was machen wir, wenn alle Plätze belegt sind (numParticles = Size-1)? Wir rufen die Funktion Clean auf, die das gesamte Array einmal durch geht und in sich selbst kopiert, dabei aber Partikel mit negativer LiveSpan außen vor lässt. Die Anzahl der kopierten Partikel ist damit auch die Anzahl der belegten Plätze und wird in numParticles geschrieben.<br />
<br />
Die Funktion Advance geht alle Partikel im Array durch und aktualisiert sie. aTime gibt dabei die Zeit seit der letzten Aktualisierung an - dadurch wird die Bewegung von der Framerate unabhängig. Die Position wird anhand der alten Position und des Bewegungsvektors neu festgelegt und die Lebensspanne wird um aTime gesenkt, während das Alter entsprechend erhöht wird.<br />
<br />
Wie bereits erwähnt, sind Partikel mit LiveSpan kleiner eins nicht mehr aktiv (und werden damit nicht mehr gerendert), auch wenn sie noch bewegt werden. Um ein Partikel zu löschen, reicht es demnach, LiveSpan auf Null zu setzen.<br />
<br />
==Klappe und Action!==<br />
Es wird Zeit für etwas Farbe auf dem Bildschirm, und deshalb werden wir uns jetzt einen kleinen Beispiel-Effekt basteln, der Feuer nachempfunden ist.Dazu erstellen wir in der Unit PfxImp eine Effekt-Klasse, um im praktischen Einsatz möglichst wenig Aufwand mit dem Effekt zu haben. Diese sollte über eine Methode zum Rendern des Effekts und eine für die Aktualisierung verfügen. Außerdem brauchen wir einen Container für die Partikel, eine Partikel-Schablone, eine Textur und eine Hilfsvariable, die die Emission steuert.<br />
<br />
<source lang="pascal">TExampleFX = class(Tobject)<br />
protected<br />
Container : TPfxContainer;<br />
Particle : TPfxParticle;<br />
EmissionTime : integer;<br />
FireTex : GluInt;<br />
public<br />
constructor Create;<br />
destructor Destroy; override;<br />
procedure Render;<br />
procedure Advance(aTime : integer);<br />
end;</source><br />
<br />
Zu Constructor und Destructor gibt es nicht viel zu sagen interessant ist vor allem die Methode Advance, die das eigentliche Herz des Effekts ist. Sie fügt neue Partikel in den Container ein und sorgt dafür, dass der Inhalt des Containers per Advance-Methode aktualisiert wird. Da Anzahl und Eigenschaften der erstellten Partikel nicht von der Framerate abhängen sollen, brauchen wir wieder die Zeit seit der letzten Aktualisierung. Advance macht zunächst einmal nichts anderes, als die Zeit seit der letzten Aktualisierung (aTime) zu der Zeit seit der letzten Partikel Emission (EmissionTime) hinzuzuaddieren. Erst wenn ein bestimmtes Limit überschritten ist, werden Partikel erstellt. Dieses Limit ist übrigens über die Konstante EMISSION_RATE festgelegt. Ist es Zeit für eine Emission, so werden neue Partikel erstellt, und zwar so viele, wie der Wert von PARTICLES_PER_EMISSION vorschreibt.<br />
<br />
Um ein chaotisches Aussehen zu erreichen, werden die Attribute der zu erstellenden Partikel innerhalb bestimmter Grenzen vom Zufall ausgewählt und in der Schablone gespeichert. Da sich alle Partikel einer Emission von den Attributen ähnlich sein sollen - denn das sorgt für einen realistischeren Flammen-Effekt -, wird die Schablone nur einmal pro Emission generiert und dann für jedes Partikel der Emission leicht variiert. Mit der Add-Methode des Containers fügen wir eine Kopie der Schablone den Partikeln im Container hinzu.<br />
<br />
Da der Container die Partikel von nun an selbstständig verwaltet, brauchen wir uns nur noch um das Zeichnen der Partikel zu kümmern. Dies geschieht in der Prozedur Render. Wie ein Partikel gerendert wird, hängt natürlich vor allem davon ab, um was für ein Partikel es sich handelt. Die einfachste Möglichkeit einen Partikel zu zeichnen wäre, mit [[GlBegin#Beschreibung|GL_POINTS]] an die Position des Partikels einen Punkt zu setzen. Meistens bedient man sich jedoch des so genannten Billboards, wodurch sich wesentlich vielseitigere Effekte realisieren lassen. Jeder Partikel wird als Quad gerendert, das genau senkrecht zur Blickrichtung der Kamera positioniert ist. Durch den Einsatz unterschiedlicher Texturen, Farben, Größen, Formen und natürlich Blending lassen sich so mit relativ wenigen Partikeln eindrucksvolle und vielseitige Effekte schaffen.<br />
<br />
In unserem Bespiel Effekt werden alle Quads gleichgroß sein und dieselbe Textur (eine Feuerzunge in Schwarz-Weiß) haben - lediglich Farbe und Transparenz der Partikel unterscheiden sich. Da es sich bei Feuer im Wesentlichen um Licht handelt, nutzen wir, wie bei fast allen Lichteffekten, additives Blending, bei dem die Farbwerte des Quads zu den Farbwerten im Buffer addiert werden. Außerdem errechnen wir aus dem Alter der Partikel einen Alphawert, der die nötige Transparenz angibt.<br />
<br />
Bevor wir aber das erste Mal Rendern können, müssen gewisse Vorbereitungen getroffen werden. Natürlich muss Blending aktiviert und die richtige Blendfunktion gesetzt werden, aber auch der Tiefen-Puffer muss deaktiviert sein, denn wir wollen ja auch mehrere Quads übereinanderzeichnen.<br />
<br />
<source lang="pascal">glEnable(GL_BLEND);<br />
glDisable(GL_DEPTH_TEST);<br />
glBlendFunc(GL_SRC_ALPHA, GL_ONE);</source><br />
<br />
Jetzt können wir uns der eigentlichen Render-Methode der Effekt-Klasse zuwenden: In einer Schleife werden alle Partikel des Containers durchlaufen. Ist die Lebensspanne größer 0, so ist das Partikel noch aktiv und muss gerendert werden. Dazu wird als erstes die Farbstärke (sat von Saturation = Sättigung) berechnet. In den ersten 100ms steigt sat auf 1 an, um dann bis zum Ablauf der Lebensspanne wieder auf 0 zu sinken. Nun können wir die Farbe des aktuellen Partikels setzen. Als Alphawert setzen wir sat ein, wobei das Partikel transparenter wird, je kleiner sat ist.<br />
<br />
Bevor wir das Partikel rendern, müssen wir mit glTranslate die Modelview-Matrix auf die aktuelle Position des Partikels setzen. Davor sichern wir jedoch die Matrix, denn die Transformationen sind für jeden Partikel unterschiedlich. Nach der Transformation entfernen wir mit FXBillboardBegin alle Rotationen aus der Matrix d.h. wir ersetzen die ersten 3 Felder der ersten drei Zeilen durch eine 3x3 Einheitsmatrix. Das texturierte Quad, das wir nun zeichnen, steht damit parallel zum Bildschirm.<br />
<br />
Am Ende jeden Schleifendurchlaufs wird die im [[Stack]] gesicherte Matrix wieder hergestellt, die Matrix wird also nicht dauerhaft verändert.<br />
<br />
Jetzt brauchen wir die Partikel-Klasse nur noch irgendwo einzubauen. (Für die Demo habe ich das OpenGL-Template genommen) Nachdem die Klasseninstanz erstellt ist, müssen wir der Prozedur glDraw nur noch folgende Methoden-Aufrufe hinzufügen:<br />
<br />
<source lang="pascal">Effect.Advance(round(1000/FPS));<br />
glLoadIdentity;<br />
glTranslate(0,-1,-3);<br />
Effect.Render;</source><br />
<br />
Und schon haben wir eine lustig flackernde Flamme auf unserem geliebten Bildschirm!<br />
<br />
[[Bild:Tutorial_Partikel1_fire.gif|center]]<br />
<br />
==Das Wort zum Sonntag==<br />
Tja, es ist Sonntag und ich habe mein erstes Tutorial für die DGL fertig! Ich hoffe, es hat dem ein oder anderen gefallen. Die Materie ist teilweise nicht ganz einfach, aber dafür umso spannender! ^^ Ich schlage vor, ihr nehmt das soeben Gelesene als Grundstock für eigene Versuche auf dem Gebiet der Partikel-Systeme. Wie eingangs erwähnt, werde ich im zweiten Tutorial das heute entwickelte System erweitern und auf besonders quälende Fragen und häufige Probleme eingehen. Das setzt natürlich voraus, dass ich von den Problemen und Fragen erfahre. Also her mit dem Feedback - sonst gibt's kein zweites Tut! *fg*<br />
<br />
Happy Coding!<br />
<br />
'''Thomas aka Lithander'''<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_particle_delphi_api|text=Delphi-API-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_particle1_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_Nebel]]|[[Tutorial_BumpMap]]}}<br />
[[Kategorie:Tutorial|Partikel1]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23648DGL Wiki:Files2009-05-17T15:01:08Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:jvscript]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:particle1_exe]]<br />
* [[Archiv:particle1_src_api]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:stereo_vcl]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:terrainclod_exe]]<br />
* [[Archiv:terrainclod_src_vcl]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_spiegelung_exe&diff=23647Archiv:tut spiegelung exe2009-05-17T15:00:33Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Windows-Binary zum Tutorial StencilSpiegel.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Windows-Binary zum [[Tutorial StencilSpiegel]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_spiegelung_delphi_vcl&diff=23646Archiv:tut spiegelung delphi vcl2009-05-17T15:00:14Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-VCL-Quelltext zum Tutorial StencilSpiegel.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial StencilSpiegel]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_StencilSpiegel&diff=23645Tutorial StencilSpiegel2009-05-17T14:59:16Z<p>Flo: </p>
<hr />
<div>=Realistische Spiegelungen - Spiel, Spaß und Spannung mit dem Stencil-Puffer=<br />
<br />
==Vorwort==<br />
Ave!<br />
<br />
Herzlich willkommen zu meinem ersten Tutorial für DGL. Einige Leser dürften schon die von mir auf meiner Seite veröffentlichen Tutorials kennen, von nun an werde ich jedoch verstärkt Tutorials für die DGL veröffentlichen.<br />
<br />
In dieser Lektion geht es um die realistische Darstellung von Spiegelungen mit Hilfe des [[Schablonenpuffer|Stencilpuffers]].<br><br />
Diese Technik findet langsam aber sicher Verbreitung in der Spieleindustrie. Das hat zum einen den Grund, das spiegelnde Flächen in der realen Welt sehr häufig vorkommen, und ihre Nachahmung einer 3D-Welt noch ein Stück mehr an Realitätsnähe verleiht. Der weitere Grund, warum realistische Spiegelungen erst seit kurzem verwendet werden, ist die Tatsache das die Szene mindestens zwei mal gerendert werden muß, und das die Grafikkarte einen [[Schablonenpuffer|Stencilpuffer]] mitbringen muß. Moderne 3D-Beschleuniger besitzen ihn natürlich, aber die damals so weit verbreitete Vodoo-Serie besaß diesen nicht.<br />
<br />
==Der Stencil-Puffer==<br />
<br />
Wie schon erwähnt ist der [[Schablonenpuffer|Stencilpuffer]] ein wichtigste Mittel um realistische Spiegelungen (und noch viele andere interessante Dinge) zu realisieren. Wie das Wort Stencil (=Schablone) schon andeutet, kann man in diesem Puffer eine Schablone ablegen, die später bestimmt welcher Bereich des Bildschirms überzeichnet, und welcher Bereich unberührt bleibt.<br><br />
Die Tiefe des [[Schablonenpuffer|Stencilpuffers]] ist je nach Hardware unterschiedlich, inzwischen kann man aber auf jeder aktuelleren Karte von mindestens 8-Bit für diesen Puffer (oft werden dann die 24-Bit für den Tiefenpuffer mit diesen kombiniert um ein DWORD zu erhalten) ausgehen.<br />
<br />
Ein praktisches Anwendungsbeispiel für die Nutzung dieses Puffers zur Begrenzung der zu überzeichnenden Fläche wäre z.B. die Cockpitansicht einer Flugsimulation. Zuerst wird der [[Schablonenpuffer|Stencilpuffer]] aktiviert, und dann wird das Cockpit in diesen Puffer "hineingezeichnet" und als Ausschlussmaske benutzt. Überall dort wo das Cockpit im [[Schablonenpuffer|Stencilpuffer]] einen Pixel hinterlassen hat wird später im [[Farbpuffer|Farbpuffer]] nicht gezeichnet.Dadurch muss das Cockpit also nur einmal gezeichnet werden. Die folgende Bildreihe verdeutlicht dies :<br />
<br />
<div align="center"><br />
{|{{Prettytable}}<br />
|width="25%" |[[Bild:Tutorial_Stencil_cockpit01.jpg|center]] <br />
|width="25%" |[[Bild:Tutorial_Stencil_cockpit02.jpg|center]]<br />
|width="25%" |[[Bild:Tutorial_Stencil_cockpit03.jpg|center]]<br />
|width="25%" |[[Bild:Tutorial_Stencil_cockpit04.jpg|center]]<br />
|-<br />
|align="center" | Cockpit-Textur (oder 3D-Modell)<br>(wird in den Stencilpuffer gezeichnet)<br />
|align="center" | Inhalt des Stencil-Puffers <br>(Schwarz : Stencil-Wert = 0)<br>(Weiß : Stencil-Wert = 1)<br />
|align="center" | Auf dem Bildschirm sichtbarer Teil der Szene <br />
|align="center" | Endergebnis<br />
|}<br />
</div><br />
<br />
Jetzt fragt ihr euch bestimmt, was dieses Cokpit-Beispiel mit unserer Spiegelung zu tun hat?<br />
Ganz einfach : Zum einen erleichtert es das Verständnis der Funktionsweise des [[Schablonenpuffer|Stencilpuffers]], was unbedingt notwendig ist, und zum anderen funktioniert die Sache mit dem Spiegel genau umgekehrt.<br><br />
Während der [[Schablonenpuffer|Stencilpuffer]] im Cockpit-Beispiel dazu genutzt wird, den Teil an dem sich das Cockpit befindet vor dem Überschreiben durch andere Farbwerte zu bewahren, wird der Puffer bei einer Spiegelung dazu genutzt, den Bereich ausserhalb des Spiegels vorm Überschreiben zu schützen. So wird also nur der Teil der Szene gezeichnet, die sich auch "im" Spiegel befindet.<br><br />
Wozu der [[Schablonenpuffer|Stencilpuffer]] also gut ist, lässt sich anhand der folgenden zwei Screenshots wohl am besten verdeutlichen. Während der Spiegel im linken Bild korrekt ist, und das Spiegelbild ausserhalb der Spieglfläche aufgrund des Stenciltests nicht gezeichnet wird, ist das Spiegelbild rechts falsch. Hier wurde der Stenciltest deaktiviert :<br />
<br />
<center> [[Bild:Tutorial_Stencil_Trooper04.jpg]] [[Bild:Tutorial_Stencil_Trooper03.jpg]] </center><br />
<br />
==Clipping Plane==<br />
Ein weiterer, für unseren Zweck notwendiger Begriff sind die [[Clipping Plane]]s (zu Deutsch : Schnittflächen). Das ist aber nichts mathematisch unmögliches, sondern eine ganz einfache Fläche, an der die Geometrie "abgeschnitten" wird. D.h., dass alles was auf der per Vorzeichen definierten Seite dieser Schnittfläche liegt einfach nicht gezeichnet wird.<br><br />
OpenGL bringt von Haus aus schonmal sechs solcher Clipping-Planes mit, die zusammen das Frustum ergeben, also der Quader, an dem die Geometrie aufgrund ihrer Sichtbarkeit "abgeschnitten" wird.<br />
<br />
Darüberhinaus kann man jedoch selbst noch zusätzliche Schnittflächen definieren. Laut OpenGL-Vorgaben muß jeder OpenGL-Treiber mindestens sechs zusätzliche Schnittflächen anbieten können. Für unseren Spiegel reicht jedoch eine.<br />
<br />
Eine Clippingplane wird mittels der Funktion {{INLINE_CODE|[[glClipPlane]](plane: TGLEnum; equation: PGLdouble)}}, wobei die Fläche mittles eines {{INLINE_CODE|array[0..3] of Double}} über ihre Gleichung ''Ax+By+Cz+D = 0'' definiert wird. Aktiviert bzw. deaktiviert wird eine Schnittfläche mittels der Konstante '''GL_CLIP_PLANE'''i, wobei i für die Nummer der Schnittfläche steht und zwischen 0 und 5 liegt.<br />
<br />
Das hört sich jetzt erstmal leicht trocken an, und dürfte ohne praktisches Beispiel auch nicht hängen bleiben. Deshalb hier zwei Screenshots aus dem Beispielprogramm zu diesem Tutorial, die verdeutlichen wozu die Schnittfläche benötigt wird. Der Spiegel liegt hier im Koordinatensystem am Ursprung (x:0,y:0,z:0) und "liegt" auf der Y-Achse.<br><br />
Definiert wird dieser Spiegel deshalb als {{INLINE_CODE|array[0..3] of Double = (0, 0, -1, 0)}}. Das Minus vor der Eins gibt also quasi an, auf welcher Seite der Spiegelfläche geclippt werden soll. Würde der Spielerraum also auf der anderen Seite liegen, wäre das Vorzeichen positiv.<br />
<br />
<center><br />
{|border=0<br />
|[[Bild:Tutorial_Stencil_Trooper01.jpg|framed|Ohne Clipping]] <br />
|[[Bild:Tutorial_Stencil_Trooper02.jpg|framed|Mit Clipping]] <br />
|-<br />
|}<br />
</center><br />
<br />
==Programmiertechnische Umsetzung==<br />
<br />
Nachdem wir nun mit der Begriffserklärung abgeschlossen haben und jedem klar sein sollte, was ein [[Schablonenpuffer|Stencilpuffers]] macht und wozu eine [[Clipping Plane]] gut ist, widmen wir uns nun der Implementation eines Spiegels in unser Programm. Der Quelltext zu diesem Thema ist zwar relativ kurz, bietet aber dennoch einiges an Erklärungsspielraum.<br><br />
''Deshalb : Nicht einfach nur abtippen, sondern auch sorgfältig meine mehr oder weniger ausführlichen Erklärungen mitlesen.''<br />
<br />
Die Renderprozedur sowohl für die normale Szene als auch für den Spiegelraum befindet sich im Beispielprogramm in der Prozedur ''DrawScene_Stencil''. Im folgenden Kapitel werden alle relevante Teile dieser Prozedur besprochen, unwichtige Grundlagen lasse ich jedoch weg. Jeder der sich mit Spiegelungen beschäftigen will, sollte sich mit den OpenGL-Grundlagen auskennen.<br />
<br />
===Definition der Schnittfläche===<br />
<br />
Zu allererst definieren wir die Schnittfläche an der das Spiegelbild abgeschnitten wird. Würden wir diesen Teil weglassen, dann würden spätestens beim Durchschreiten eines Spiegels (auch wenn das in einem Ego-Shooter nicht vorkommen sollte) hässlich falsche Spiegelbilder entstehen...und das wollen wir ja nicht, schliesslich soll unser Spiegel ja die reale Welt nachstellen. Wie die Schnittfläche definiert wird, haben wir ja oben bereits besprochen. Wichtig wäre hier vielleicht noch, dass als Datentyp unbedingt Double verwendet werden muß. Andere Datentypen (z.B. Single) führen zu unvorhersehbaren Effekten.<br />
<br />
const<br />
ClipPlane : array[0..3] of Double = (0, 0, -1, 0);<br />
<br />
===Den Stencil-Puffer löschen===<br />
Wie bekannt müssen (oder sollten) die verwendeten OpenGL-Puffer vor jedem neuen Zeichendurchgang gelöscht werden. Dies geschieht ja bekannterweise mit der Funktion [[glClear]]() und als Parameter oderverknüpft die zu löschenden Puffer. Neben den Bits für den [[Farbpuffer]] ('''GL_COLOR_BUFFER_BIT''') und dem [[Tiefenpuffer]] ('''GL_DEPTH_BUFFER_BIT''') kommt nun ein neues Puffer-Bit dazu, nämlich '''GL_STENCIL_BUFFER_BIT''', um den [[Schablonenpuffer|Stencilpuffer]] zu "löschen".<br />
<br />
Löschen habe ich deshalb in Anführungszeichen gesetzt, weil der [[Schablonenpuffer|Stencilpuffer]] nicht einfach wie z.B. der [[Farbpuffer]] geleert wird, sondern durch {{INLINE_CODE|glClear(GL_STENCIL_BUFFER_BIT)}} mit einem vorher definierten Wert gefüllt wird. Dieser Wert wird (am besten bei der OpenGL-Initialisierung) mittels {{INLINE_CODE|[[glClearStencil]](s:Integer)}} festgelegt. In unserem Falle übergeben wir als hier Parameter den Wert 0.<br />
<br />
// Löschen des Stencil-Puffers zum Beginn der Szene<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT or GL_STENCIL_BUFFER_BIT);<br />
<br />
<br />
===Den Stencil-Puffer vorbereiten und die Maske erstellen===<br />
<br />
Kommen wir nun also zum schwierigsten Teil dieses Tutorials. Die folgenden Zeilen aktiveren den [[Schablonenpuffer|Stencilpuffer]] und erstellen die Maske unseres Spiegels in diesem Puffer. Leider lassen sich die jetzt kommenden Befehle und Funktionen nicht ganz so einfach beschreiben.<br />
<br />
glColorMask(False, False, False, False);<br />
<br />
Mit der Funktion {{INLINE_CODE|[[glColorMask]](R,G,B,A : ByteBool)}} teilen wir OpenGL mit, welche Farbkomponenten auf den Bildschirm gezeichnet werden. In unserem Falle deaktivieren wir alle Farbkomponenten, es wird also nichts auf den Bildschirm gezeichnet.<br><br />
Dies ist insofern nötig, da wir jetzt erstmal unsere Maske in den [[Schablonenpuffer|Stencilpuffer]] zeichnen wollen, und dies kein Puffer ist in den man direkt hineinschreiben kann (wie das beim Farbpuffer der Fall ist). Wir müssen also den Weg über den Farbpuffer gehen.<br />
<br />
glEnable(GL_STENCIL_TEST);<br />
<br />
Zu diesem Befehl muss ich wohl kaum Worte verlieren. Kurz und schmerzlos : Er aktiviert den Stencil-Test und damit verbunden auch den [[Schablonenpuffer|Stencilpuffer]].<br />
<br />
[[glStencilFunc]](GL_ALWAYS, 1, 1);<br />
<br />
Mit diesem Befehl teilen wir OpenGL mit, welcher Test auf jeden gezeichneten Pixel angewendet werden soll. Der erste Parameter '''GL_ALWAYS''' gibt an, dass der Test immer erfolgreich ist. Der zweite Parameter gibt den Referenzwert für diesen Test an (den wir gleich noch brauchen werden). Wenn der Test z.B. '''GL_LESS''' wäre, dann würde das Pixelfragment den Test bestehen, wenn es kleiner als der im Referenzparameter angegebene Wert wäre.<br><br />
Der letzte Parameter gibt die Maske an, mit der unsere Referenzparameter beim Gelingen des Tests verunded wird. Wenn der Test also gelingt, wird eine 1 in den [[Schablonenpuffer|Stencilpuffer]] geschrieben (Test gelungen->Stencil-Wert = Refernzwert UND Maskenwert -> 1 UND 1 = 1).<br />
<br />
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);<br />
<br />
Nach obigem OpenGl-Vorschlaghammer kommt jetzt wieder etwas leichter verdauliche Kost. Mit der Funktion {{INLINE_CODE|[[glStencilOp]](fail, zfail, zpass: TGLEnum)}} teilen wir OpenGL mit, wie der Stencilwert verändert wird, wenn ein Fragment den Test besteht oder nicht besteht. Dabei unterscheidet OpenGL anhand der drei übergebenen Parameter drei verschiedene Szenarien :<br />
<br />
*Fail <br />
: Diese Funktion wird angewendet, wenn das Fragment den '''Stencil-Test nicht besteht'''<br />
*zFail <br />
: Wird angewendet, wenn das Fragment den '''Tiefentest nicht besteht'''<br />
*zPass <br />
: Wird angewendet, wenn das Fragment den '''Test besteht''' (Wichtigste Funktion!)<br />
<br />
Die ersten zwei Parameter sind in unserem Falle relativ uninteressant, weshalb wir OpenGL über '''GL_KEEP''' mitteilen dass der [[Schablonenpuffer|Stencilpuffer]] in diesen beiden Fällen nicht verändert wird.<br />
Der dritte Parameter ist jedoch sehr wichtig : Er gibt an, was OpenGL mit dem Stencil-Puffer macht, wenn das Fragment den Test besteht. In unserem Fall soll der Stencil-Pufferwert über '''GL_REPLACE''' mit dem veränderten Masken- und Referenzwert (=1) ersetzt werden.<br />
<br />
==Wer die Hoffnung jetzt noch nicht verloren hat, der hats fast geschafft. Das Schwerste liegt bereits hinter uns!==<br />
<br />
glDisable(GL_DEPTH_TEST);<br />
glDisable(GL_TEXTURE_2D);<br />
DrawMirror;<br />
<br />
Diese drei Zeilen sind ja schon fast selbsterklärend, aber aufgrund dessen was oben auf das Gehirn zukam auch eher zur Entspannung gedacht. Bevor wir mit ''DrawMirror'' unsere Spiegelfäche in den Stencil-Puffer zeichnen, deaktivieren wir den Tiefentest und das Texturemapping.<br />
Nun haben wir eine unsichtbare Maske unseres Spiegels im [[Schablonenpuffer|Stencilpuffer]]. Überall dort, wo unsere Spiegelfläche in den Stencilpuffer gezeichnet wurde, steht eine 1 (=Referenzwert und Maksenwert, da der Test bestanden wurde).<br />
Solange der Stenciltest jetzt also aktiv bleibt, wird nur dort gezeichnet wo unsere Spiegelfläche im Stencilpuffer eine 1 hinterlies.<br />
<br />
glEnable(GL_TEXTURE_2D);<br />
glEnable(GL_DEPTH_TEST);<br />
glColorMask(True, True, True, True);<br />
<br />
Da wir nun beginnen die Szene hinter dem Spiegel (also quasi das Spiegelbild) zu zeichnen, aktivieren wir sowohl das Texturemapping als auch den Tiefentest wieder.<br />
Und da wir natürlich auch was von unserem Spiegelbild sehen woll und dies in den Farbpuffer gelangen soll, teilen wir OpenGL mittels {{INLINE_CODE|[[glColorMask]]()}} mit, dass wieder alle Farbkomponenten in den Farbpuffer gelangen sollen.<br />
<br />
glStencilFunc(GL_EQUAL, 1, 1);<br />
<br />
Mit dieser Zeile teilen wie OpenGL mit, das nur dort gezeichnet werden soll, wo sich im [[Schablonenpuffer|Stencilpuffer]] eine 1 befindet (siehe oben). Dies ist das "Geheimnis" hinter realistisch aussehenden Reflektionen im Stencilpuffer!<br />
<br />
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);<br />
<br />
Nachdem wir unseren [[Schablonenpuffer|Stencilpuffer]] ja bereits mit einer unsichtbaren Spiegelmaske gefüllt haben, soll er von nun an nicht mehr verändert werden. In allen drei Testfällen wird der aktuelle Stencilwert deshalb also mittels '''GL_KEEP''' nicht mehr verändert.<br />
<br />
glEnable(GL_CLIP_PLANE0);<br />
glClipPlane(GL_CLIP_PLANE0, @ClipPlane);<br />
<br />
Bevor wir jetzt dazu übergehen unsere gespiegelte Szene zu zeichnen, aktivieren wir noch die Schnittfläche unseres Spiegels. Die Erklärung dazu gabs ja bereits zum Beginn dieses Tutorials.<br />
<br />
===Die gespiegelte Szene zeichnen===<br />
<br />
So. Nachdem die Stencilpuffer-Tortur überstanden ist und mindestens die Hälfte der Leser eines unnatürlichen Todes gestorben sind, darf ich die anderen beglückwünschen. Wir sind fast fertig!<br />
<br />
Nachdem unser [[Schablonenpuffer|Stencilpuffer]] also inzwischen die unsichtbare Spiegelfläche enthält und er auch schon für das Zeichnen der gespiegelten Szene vorbereitet wurde, müssen wir die Szene nur noch gespiegelt zeichnen. Nichts leichter als dass, denn mit [[glScale|glScalef]]() kann man eine Szene ja bekannterweise auf einfachste Art und Weise an einer Achse spiegeln.<br />
In unserem Falle müssen wir die Szene an der Z-Achse spiegeln, also der Achse an der unser Spiegel spiegelt. Das geht dann kurz und schmerzlos :<br />
<br />
glScalef(1,1,-1)<br />
<br />
Wie gesagt...einfacher gehts nimmer. Wenn wir unsere Szene z.b. auf dem Fussboden spiegeln wollten, dann würe ein einfaches {{INLINE_CODE|glScalef(1,-1,1)}} reichen. Dass sieht in der Praxis dann im Endergebnis so aus :<br />
<br />
{{Center| [[Bild: Tutorial_Stencil_Trooper05.jpg]]}}<br />
<br />
Anschliessend wird dann die Szene wie gewohnt gezeichnet. Wenn die Szene etwas komplexer ist, sollte man das Zeichnen in eine externe Prozedur auslagern, die man dann zweimal aufrufen muss.<br />
Nicht vergessen : Vor dem skalieren der Matrix sollte man diese mit [[glPushMatrix]] auf den Matrizenstack legen, und vor dem Zeichnen der normalen Szene wieder mit '''glPopMatrix''' wiederherstellen!<br />
<br />
glDisable(GL_CLIP_PLANE0);<br />
glDisable(GL_STENCIL_TEST);<br />
<br />
Nachdem wir unsere gespiegelte Szene gezeichnet haben, diese an der definierten Schnittfläche (wenn nötig) abgeschnitten wurde und aufgrund der Stencilmaske nur in die Spiegelfläche gezeichnet wurden, können sowohl die Schnittfläche als auch der Stenciltest wieder deaktiviert werden.<br />
<br />
===Den Spiegel zeichnen===<br />
<br />
<source lang="pascal">glEnable(GL_BLEND);<br />
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);<br />
glColor4f(1,1,1,0.45);<br />
MirrorTex.Bind;<br />
DrawMirror;<br />
glDisable(GL_BLEND);</source><br />
<br />
Nachdem wir nun unsere gespiegelte Szene hinter dem Spiegel gezeichnet haben, sollten wir noch unsere sichtbare Spiegelfläche zeichnen, um dem Spieler auch den Eindruck zu vermitteln, dass er vor einem Spiegel steht.Dazu benutze ich eine recht helle Glastextur, die ich mittels Alphablending über die gespiegelte Szene zeichne. Welchen Unterschied das macht, lässt sich auf folgenden beiden Screenshots (links ohne Spiegeltextur, rechts mit) sehr gut sehen : <br />
<br />
{{Center| [[Bild: Tutorial_Stencil_Trooper06.jpg]] [[Bild: Tutorial_Stencil_Trooper07.jpg]]}}<br />
<br />
<br />
===Und die Moral von der Geschicht...===<br />
...ohne die normale Szene dann zu rendern gehts nicht! Also nicht vergessen nach dem rendern des Spiegels auch noch die normale Spielszene zu zeichnen!<br />
<br />
<br />
==Nachwort==<br />
<br />
So...das war also mein erstes Tutorial für die DGL.Für mich wars halb so schlimm, während es für einige von euch wohl doch einem Vorschlaghammer auf den Hinterkopf gleich kam. Das macht aber nix, denn wenn irgendjemand etwas nicht verstanden hat, ich bin ja auch als Mod im Forum tätig und antworte gerne auf eure Fragen!<br />
<br />
Abschliessend sei noch zu sagen, dass diese Technik einen Nachteil hat : Der Raum hinter der Spiegelfläche ist tabu, da man sonst einen Teil des illusionären Spiegelraumes dahinter sehen würde.Dieser Nachteil lässt sich jedoch recht leicht mit einigen Sichtbarkeitstechniken umschiffen, und der Nutzung solcher Spiegelungen sollte damit keine Grenzen mehr gesetzt sein!<br />
<br />
Hoffe das Tut hat euch gefallen und vor allem auch geholfen!<br />
<br />
Euer<br />
<br />
'''[[User:Sascha Willems|Sascha Willems]]''' (webmaster AT delphigl.de)<br />
<br />
<br />
==Nachtrag (Oktober 2005)==<br />
<br />
Das Tutorial hat bereits einige Jahre auf dem Buckel, und in einer so schnellen Industrie wie der Grafikkartenindustrie ist das von größerer Bedeutung. Die Technik Spiegelungen mittels des Stencilpuffers zu realisieren ist heute kaum noch von Relevanz. Heute benutzt man so gut wie immer Render-To-Texture (z.B. via Pixelpuffer oder EXT_FBO) um Spiegelungen aller Art zu realisieren. Dadurch hat man diverse Vorteile : <br />
<br />
* Die Spiegelung kann u.a. über Verschiebung der Mip-Maps schärfer bzw. dumpfer gemacht werden, so lassen sich auch einfach Reflektionen auf dumpfen Materialien wie z.B. verrostetes Metall darstellen.<br />
* Die Spiegelung liegt in einer Textur vor. Damit verbunden sind jede Menge Manipulationen möglich, sei es nun so was grundlegendes wie Multitexturing oder erweitertes Sachen wie Shader. Mit Shadern kann man dann recht einfach solche Sachen wie Verwirbelung oder Refraktion auf der Oberflächen (u.a. für Wasser genutzt) realisieren.<br />
<br />
Von daher sollte man heutzutage auf die Spiegelungsmethode im Stencilpuffer '''verzichten''' und die Render-To-Texture Methode verwenden.<br />
<br />
'''[[User:Sascha Willems|Sascha Willems]]''' (webmaster AT delphigl.de)<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_spiegelung_delphi_vcl|text=Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_spiegelung_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_MultiTexturing]]|[[Tutorial_StereoSehen]]}}<br />
[[Kategorie:Tutorial|StencilSpiegel]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23644DGL Wiki:Files2009-05-17T14:52:13Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:jvscript]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:particle1_exe]]<br />
* [[Archiv:particle1_src_api]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:spiegelung_exe]]<br />
* [[Archiv:spiegelung_src_vcl]]<br />
* [[Archiv:stereo_vcl]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:terrainclod_exe]]<br />
* [[Archiv:terrainclod_src_vcl]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Nebel&diff=23643Tutorial Nebel2009-05-17T14:50:23Z<p>Flo: </p>
<hr />
<div>=DGL Fogging Tutorial=<br />
<br />
==Vorwort==<br />
<br />
Herzlich Willkommen zu meinem zweiten Tutorial für die DGL! ;) Diesmal werde ich mich einer Technik widmen, die als Fogging bezeichnet wird. Bevor ich allerdings erkläre, wie man diese Technik verwendet, werde ich erstmal ein paar Worte darüber verlieren, worum es beim Fogging geht und wie man davon profitieren kann.<br />
<br />
==Physik trifft auf norddeutsches Wetter!==<br />
<br />
Beginnen wir mit einem kleinen Beispiel aus der realen Welt: Blickt man bei diesigem Wetter aus dem Fenster, so fällt auf, dass weit entfernte Objekte im Nebel zu verschwinden scheinen. Je weiter ein Objekt vom Betrachter entfernt ist, desto stärker lässt Kontrast und Farbintensität nach. Grund für diesen Effekt ist, dass Luft nicht vollkommen transparent ist, sondern ein Teil des Lichts an Verunreinigungen (Nebel, Staub, Regen oder Rauch) gebrochen wird. Je dichter der Nebel und je weiter der Weg, den ein Lichtstrahl vom Objekt zum Betrachter zurückzulegen hat, desto weniger Licht kommt ungebrochen und damit in Originalfarbe an.<br />
<br />
Was in der Realität gilt, das sollte möglichst auch in der Computergrafik berücksichtigt werden, sofern man einen realistischen Look erreichen will. Aber OpenGL wäre nicht OpenGL, wenn es uns nicht bereits eine Möglichkeit bieten würde, atmosphärische Effekte in Szenen nachzubilden: das Fogging. (von engl. Fog = Nebel)Beim Fogging passiert nichts weiter, als dass die Objekte einer Szene basierend auf dem Abstand zum Betrachter (Z-Buffer) und weiterer Parameter wie Nebel-Dichte und ähnlichem in eine Nebel-Farbe überblendet werden. Geschickt eingesetzt wirkt sich diese Technik nicht nur positiv auf die optische Qualität der Szene aus, sondern kann auch die Rendergeschwindigkeit erhöhen!<br />
<br />
==Was dicke Suppe mit hohen Frameraten zu tun hat.==<br />
<br />
Okay, wir wissen nun, was Fogging bedeutet, aber wann macht es Sinn, Fogging wirklich einzusetzen? Im Allgemeinen lohnt es sich vor allem dann, wenn man Szenen mit hohen Sichtweiten hat. Das wären z.B. die Außenlevel in First-Person-Shootern oder Flug-Simulatoren. Allerdings ist darauf zu achten, dass das Szenario atmosphärische Effekte zulässt. Es macht wenig Sinn, in Weltraum-Spielen Fogging zu verwenden - denn mangels Atmosphäre ist etwas nur schwer zu rechtfertigen, was für die atmosphärischen Effekte verantwortlich ist. ;)<br />
<br />
Sieht man mal von der Ästhetik und dem Realismus ab, gibt es noch andere, nicht weniger wichtige, Gründe für den Einsatz von Fogging. Denn auch für die aktuellsten Monster-3D-Beschleuniger gilt: Die am schnellsten gezeichneten Polygone sind die, die man nicht sieht, und so werden häufig weit entfernte Objekte einfach bewusst beim Rendering nicht mehr berücksichtigt (Clipping). Nun sehen plötzlich aufploppende Bäume und halbe Häuser aber nicht besonders gut aus - eine unauffällige Möglichkeit, die Sichtweite einzuschränken, kommt da gelegen. Und hier kommt das Fogging ins Spiel: Ein bisschen Nebel über die Landschaft gelegt und schon tauchen unsere Bäume sanft aus dem Nebel auf, statt sich einfach an den Straßenrand zu beamen.<br />
<br />
Eine spezielle Form des Fogging ist das Depth Cueing, welches die Tiefenwirkung in Szenen erhöht, wo es eigentlich keine atmosphärischen Effekte gibt. Das Depth Cueing funktioniert im Grunde genommen genau wie das normale Fogging. Die Intensität der Objekte einer Szene nimmt mit der Distanz also ab, nur wird dabei ins Schwarze überblendet.<br />
<br />
[[Bild:Nebeltut_fogsample.jpg|center]]<br />
<br />
Mittlerweile dürfte klar geworden sein, was man unter Fogging versteht und wozu man diese Technik einsetzt. Bleibt nur noch zu klären, wie genau man nun den Nebel in die Delphi OpenGL-Anwendung bekommt:<br />
<br />
==Es wird Zeit für den Hauptgang!==<br />
<br />
Da Fogging bereits in OpenGL integriert ist, ist die Benutzung recht einfach. Es reicht, die Technik per [[glEnable#GL_FOG|glEnable(GL_FOG)]] einzuschalten und ein Reihe von Parametern zu setzen, die das Aussehen des Fog-Effekts steuern. Zum Einstellen der Parameter benötigt man nur einen Befehl, nämlich [[glFog]]. Dessen Signatur fordert zwei Parameter. Mit dem ersten bestimmt man, welche Eigenschaft gesetzt werden soll, der zweite Parameter ist der zu setzende Wert.<br />
<br />
Will man z.B. die Farbe des Nebels festlegen, so ruft man '''glFogfv(GL_FOG_COLOR, fogColor)''' auf. fogColor ist dabei die Adresse eines Arrays, das 4 Fließkommazahlen enthält: Rot, Grün, Blau und Alpha. Dabei ist zu beachten, dass die Werte dieser Zahlen zwischen 0 und 1 liegen müssen. Klingt in der Theorie umständlich, ist aber eigentlich ganz leicht zu programmieren:<br />
<br />
Zuerst definieren wir einen neuen Typ, der die Nebelfarbe enthalten soll.<br />
<br />
<source lang="pascal">type<br />
<br />
TFogColor = record<br />
Red : single;<br />
Green : single;<br />
Blue : single;<br />
Alpha : single;<br />
end;</source><br />
<br />
nun können wir eine Instanz dieses Typs erstellen, die Farbe festlegen und mit glFog() setzen:<br />
<br />
<source lang="pascal">var FogColor : TFogColor;<br />
<br />
FogColor.Red := 1; //Volles Rot<br />
FogColor.Green := 0; //Kein Grün<br />
FogColor.Blue := 0; //Kein Blau<br />
FogColor.Alpha := 0; //Kein Alpha<br />
<br />
glFogfv(GL_FOG_COLOR, @FogColor);</source><br />
<br />
In Delphi liegen Farben üblicherweise nicht im OpenGL konformen RGB-Format vor, sondern als Konstante vom Typ TColor (clAqua, clBlack etc). Deshalb ist es nützlich, eine Funktion zu schreiben, die eine Farbe von TColor in TFogColor umwandelt:<br />
<br />
<source lang="pascal">Function getFogColor(color : TColor) : TFogColor;<br />
begin<br />
result.Red := getrvalue(colorToRgb(color)) / 255;<br />
result.Green := getgvalue(colorToRgb(color)) / 255;<br />
result.Blue := getbvalue(colorToRgb(color)) / 255;<br />
result.Alpha := 0;<br />
end;</source><br />
<br />
Die Funktion ist schnell erklärt: colorToRGB wandelt einen TColor-Wert in eine RGB-Entsprechung der Farbe um. Dann wird der jeweilige Farbwert genommen und durch 255 geteilt, damit aus der Windows-typischen Byte-Darstellung (0 bis 255) das OpenGl konforme Format mit Fließkommawerten zwischen 0 und 1 wird.<br />
<br />
Nachdem wir nun bestimmt haben, welche Farbe der Nebel haben soll, legen wir als nächstes fest, nach welcher internen Gleichung der Nebel berechnet werden soll. Nichts leichter als das! Der Befehl '''glFogi(GL_FOG_MODE, fogMode)''' ermöglicht die Wahl zwischen drei verschiedenen Modi: '''GL_EXP, GL_EXP2''' und '''GL_LINEAR'''.<br />
<br />
Hat man den Fogging-Mode '''GL_LINEAR''' gewählt, so verlangt OpenGL die Definition zweier weiterer Parameter. Mit '''glFogf (GL_FOG_START, fogStart)''' legt man fest, ab welcher Entfernung eines Objekts vom Betrachter der Nebeleffekt einsetzen soll. Außerdem muss noch eine Entfernung bestimmt werden, ab der ein Objekt komplett im Nebel verschwindet. Dies geschieht durch '''glFogf''' (GL_FOG_END, fogEnd); Ist die Entfernung eines Objekts vom Betrachter geringer als '''fogStart''' so wird es normal gezeichnet, ist die Entfernung größer als die mit '''fogEnd''' definierte maximale Sichtweite so wird es in der Nebelfarbe gezeichnet, aber was passiert dazwischen? Ganz einfach - es wird linear interpoliert. Ein kleines Beispiel zur Veranschaulichung: Als Nebelfarbe haben wir weiß definiert, fogStart liegt bei 10 und fogEnd ist 20 - ein zum Bildschirm paralleles rotes Quad, das in einer Entfernung von 15 gezeichnet wird, erstrahlt in einem hübschen Rosa. Es liegt nämlich genau in der Mitte des Nebel-Bereichs. Die Farbe eines Pixels setzt sich also jeweils zur Hälfte aus dem Rot des Quads und dem Weiß des Nebels zusammen.<br />
<br />
Hat man sich bei der Wahl des Modus für '''GL_EXP''' oder '''GL_EXP2''' entschieden, so wird die Nebelstärke mit einer exponentiellen Gleichung berechnet. Hierbei ist es vollkommen egal, welche Werte '''fogEnd''' bzw. '''fogStart''' haben. Stattdessen wird hier die Dichte des Nebels mit einbezogen. Je größer die Dichte des Nebels, desto schneller verschwinden die Objekte im Nebel. Gesetzt wird die Dichte durch '''glFogf(GL_FOG_DENSITY, fogDensity)'''. Der als '''fogDensity''' übergebene Wert sollte dabei zwischen 0 und 1 liegen.<br />
<br />
Der wesentliche Unterschied zwischen den exponentiellen Nebel-Gleichungen und der linearen liegt darin, dass die Nebelwirkung bei einem sich vom Betrachter entfernenden Objekt nicht gleichmäßig zunimmt. Stattdessen ist die Zunahme am Anfang sehr stark, je weiter das Objekt jedoch bereits entfernt ist, desto geringer wird sie. So verschwinden Objekte eigentlich nie ganz, auch wenn sie ab einer gewissen Entfernung fast nicht mehr zu sehen sind. Wo diese Entfernung liegt, lässt sich über die bereits erwähnte Dichte des Nebels einstellen. Der Unterschied zwischen '''GL_EXP''' und '''GL_EXP2''' ist gering. Bei letzterem wird bei der Berechnung der Exponent quadriert, wodurch die anfängliche Zunahme der Nebelwirkung noch verstärkt wird.<br />
<br />
==Das Ende naht!==<br />
<br />
So eigentlich habe ich alles Wichtige, was es zum einfachen Fogging zu wissen gibt, erwähnt. Natürlich könnte ich jetzt noch ein bisschen technischer werden und die Gleichungen hinschreiben. Aber das spar ich mir einfach mal und verweise auf die OpenGL-SDK oder das Redbook (und vorallem dieses Wiki). Um zu wissen, an welchen Parametern man drehen muss, um den gewünschten Effekt zu erhalten, ist eine anschauliche Erklärung in meinen Augen sinnvoller. Um ein Gefühl dafür zu bekommen, wie die Parameter einander beeinflussen, liegt diesem Tutorial noch ein Sample bei, wo sich alle Parameter zur Laufzeit einstellen lassen.<br />
<br />
Mir bleibt jetzt nur noch eins: Das Tutorial mit den Worten Happy Coding zu beenden! Ich hoffe, es hat gefallen und war lehrreich. ;)<br />
<br />
Happy Coding,<br />
<br />
'''Lithander'''<br />
<br><br><br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_fog_delphi_vcl|text=Beispiel-Quelltext (Delphi)}}<br />
* {{ArchivLink|file=tut_fog_exe|text=Beispiel-Programm}}<br />
<br />
{{TUTORIAL_NAVIGATION|-|[[Tutorial_Partikel1]]}}<br />
[[Kategorie:Tutorial|Nebel]]</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_fog_exe&diff=23642Archiv:tut fog exe2009-05-17T14:49:58Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Windows-Binary zum Tutorial Nebel.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Windows-Binary zum [[Tutorial Nebel]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_fog_delphi_vcl&diff=23641Archiv:tut fog delphi vcl2009-05-17T14:49:29Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-VCL-Quelltext zum Tutorial Nebel.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial Nebel]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_renderpass_exe&diff=23640Archiv:tut renderpass exe2009-05-17T14:44:43Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Windows-Binary zum Tutorial Renderpass.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Windows-Binary zum [[Tutorial Renderpass]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_renderpass_delphi_api&diff=23639Archiv:tut renderpass delphi api2009-05-17T14:44:31Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-API-Quelltext zum Tutorial Renderpass.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-API-Quelltext zum [[Tutorial Renderpass]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Renderpass&diff=23638Tutorial Renderpass2009-05-17T14:43:45Z<p>Flo: </p>
<hr />
<div>= Renderpass - Die Welt daneben =<br />
<br />
== Vorwort ==<br />
<br />
Greetings!<br />
<br />
Nach einer längeren Pause gibt es dann auch mal wieder eine Kleinigkeit von mir. Ich setze an dieser Stelle nicht die bisherige Tutorial-Serie fort, sondern mache diesmal etwas zu einem Spezial-Thema. Mir war gerade privat danach, mit einer anderen Technik rumzuspielen und da dachte ich, ich könnte gleich ein Tutorial drauß machen. Vor allem, weil hier eine Grundlage vermittelt wird, die unter anderem auch dafür eingesetzt werden kann, Objekte zu spiegeln oder für sie einen Schatten zu erzeugen. So kompliziert werden wir das Ganze aber heute gar nicht machen, sondern ich werde nur ein Beispiel geben, wie man mit Hilfe von mehren Render-Durchgängen eine Szene in Echtzeit rendert und auf einen Bildschirm projizieren kann.<br />
<br />
Vielleicht kennen einige ja noch die etwas älteren Spiele. Man ist an eine Kamera gegangen und konnte plötzlich das Geschehen auf einem anderen Teil der Karte verfolgen. Der Trick dabei bestand darin, dass man damals die Spielersicht nicht mehr berechnen mußte, sondern nur die Kamera an einen anderen Ort setzte. Dank der modernen Technik können wir jedoch direkt im Spiel eine Szene sehen, die in Echtzeit berechnet wird und irgendwo anders geschieht. Man stelle sich vor, dass man bei einem Auto-Rennen durch eine Stadt fährt und am Himmel ist ein Zeppelin mit einer Holo-Wand. Darauf wird gerade die Ziellinie gezeigt, die die Konkurrenz gerade überfährt! Oder einen Weltraum-Shooter, in dem man gerade einen Angriff auf den Feind fliegt und in dem plötzlich die Warnlampen angehen, ein kleines HUD-Fenster aufspringt und man in Echtzeit verfolgen kann, wie die Wärme-Rakete einem den Antrieb zerfetzt ... ahem *hust* Irgendwie alles nur negative Beispiele, das liegt an mir, nicht an der Technik ;D Viel Spaß beim "Spielen" ;))<br />
<br />
== Multiple Render-Passes ==<br />
<br />
=== Die Welt, die neben unserer liegt ===<br />
<br />
Zunächst sollten wir erstmal klären, was eigentlich ein Render-Pass ist. Moderne Spiele wie Doom3 versprechen bis zu 50 Render-Passes. Damit sind keineswegs Frames gemeint, sondern wie oft eine Szene gerendert wird, bevor sie letztendlich auf den Bildschirm gebracht wird. Was? Wieso sollte man denn eine Szene mehrfach rendern? Nun, dafür kann es unterschiedliche Gründe geben. Nehmen wir doch einfach mal an, wir wollen Multi-Texturing auf einem Rechner mit nur einer Textur-Unit realisieren. Folglich müssen wir zweimal ein Quad mit unterschiedlichen Texturen und Blending zeichnen, um das Ergebnis zu erzielen, welches wir wollen. Genauso kann es sein, dass wir auch andere Dinge mehrfach rendern müssen, um den Effekt zu erzielen, den wir auch wollen. Bei jedem dieser Vorgänge redet man von einem Pass (Durchgang). Man sollte klar dazu sagen, dass jeder zusätzliche Durchgang tödlich für die Geschwindigkeit ist und auch vermieden werden sollte! Wenn es geht...<br />
<br />
Aber nun nehmen wir doch mal an, wir wollen viele Bäume rendern, die in der Ferne stehen. Es bietet sich dann an, ein 3D-Modell aus der gebrauchten Perspektive auf eine Textur zu rendern und dann nur noch Quads mit der Textur zu rendern. Ein Quad hat in jedem Fall weniger Polygone als mehrere Bäume und auf die Distanz fällt das nicht mehr auf! Man redet hier übrigens von "Impostern", einem modernen Billboarding-Verfahren, auf das wir sicher aber auch nochmal irgendwann zurückkommen. Wir befassen uns erstmal mit etwas Simplerem ;)<br />
<br />
=== Die Welt ist eine (Matt-)Schreibe ===<br />
<br />
Stellen wir uns einfach mal eine Szene vor, bei der wir dies gebrauchen könnten! In der Mitte der Szene befindet sich ein Objekt, welches sich bewegt. In unserem Fall habe ich die beliebte Pyramiden-Szene genommen ;) Jeweils links und rechts sind leicht schräg zwei Monitore. Genau in der Mitte leicht oberhalb befindet sich ein weiterer. Auf den Bildschirmen soll nun jeweils die Szene angezeigt werden, und zwar so, wie der Betrachter es auch vor einem Bildschirm sieht. Ich denke, bei dieser Aufgabenstellung würden die meisten schon aussteigen, da zahlreiche Probleme auf einen warten. Die Lösung ist jedoch verblüffend einfach!<br />
<br />
Damit sich jeder was drunter vorstellen kann, einmal ein kleines Bild der Szene:<br />
<br />
[[Bild:Tutorial_Renderpass_whitescreen.gif]]<br />
<br />
Die weißen Flächen sind jeweils unsere Screens. Sehen erschreckend billig aus, gell? ;)<br />
<br />
Die Frage, die sich nun stellt ist, wie wir die Szene auf diese weißen Flächen bekommen. Wie wir bereits festgestellt haben, bieten sich hierfür Texturen an, und mit simplestem UV-Mapping haben wir dann auch eine Textur drauf. Mit jeder geladenen Textur ist dies kein Problem, allerdings wollen wir ja eine Szene darauf zeichnen. Nun, dann erzeugen wir sie uns eben, und zwar direkt im Speicher. Gleich nach dem Initialisieren von OpenGL reservieren wir uns ausreichend Speicher für die neue Textur.<br />
<br />
<source lang="pascal"><br />
procedure CreateScreenTexture;<br />
var pTexData: Pointer;<br />
begin<br />
GetMem(pTexData, 256*256*3);<br />
<br />
<br />
// Textur generieren und drauf zeigen<br />
glGenTextures(1, @Screen);<br />
glBindTexture(GL_TEXTURE_2D, Screen);<br />
<br />
// Daten in den Speicher, Lineares Filtering aktivieren<br />
glTexImage2D(GL_TEXTURE_2D, 0, 3, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, pTexData);<br />
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);<br />
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);<br />
<br />
// Freigeben<br />
FreeMem(pTexData);<br />
end;<br />
</source><br />
<br />
Ich denke, dass niemand hier wirklich schockiert sein sollte oder man Unmögliches erwartet. Wir erzeugen im Speicher den Platz, den wir benötigen, erzeugen eine OpenGL Texture, übergeben den reservierten Speicher als Bilddaten, aktivieren gleich noch lineares Filtern (weil’s einfach besser aussieht *g*) und lassen den Pointer dann wieder "frei". Fertig. Schon ist bei uns im Video-Speicher ein Bereich für unsere Textur reserviert und wartet darauf, von uns mißbraucht zu werden :)<br />
<br />
=== The first try ===<br />
<br />
Nun lautet die Frage, wie wir die Szene auf die Texture bekommen. Man kann es bereits erahnen, dass man die Szene rendern müßte und dann nicht auf dem Bildschirm, sondern auf der Textur ausgeben muß. Der Plan steht also fest! Zunächst rendern wir die Szene einmal auf eine Textur, rendern sie dann nochmal und kleben die "gewonnene" Textur auf die Screens. Zwei Renderpasses. Und wie sonst auch fangen wir erstmal mit etwas Simplem an! Setzen der glClearColor und löschen des Color- und Z-Buffers :)<br />
<br />
<source lang="pascal"><br />
glClearColor(0.0,0.0,0.0,0);<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
</source><br />
<br />
Soweit nichts wirklich Neues. Wir behalten im Hintergrund, dass wir für die Textur nur Speicher für 256x256 reserviert haben. Der Grund dafür leuchtet glaube ich auch ein! Viele Grafikkarten unterstützen nicht sehr große Texturen und der Speicher ist rar. Warum also für weiter entfernte Texturen soviel Speicher "verschwenden"? Allerdings werden wir in den meisten Fällen in weitaus höheren Auflösungen auf den Bildschirm rendern. Damit der ganze Bildschirm auch auf die Texture paßt und wir nicht nur einen Teil haben, müssen wir also die Ausgabe auf die Größe der Textur reduzieren. Kinderleicht:<br />
<br />
<source lang="pascal"><br />
glViewport(0, 0, 256, 256);<br />
</source><br />
<br />
Ich denke, man benötigt zum Erraten der Parameter auch keinen Hellseher. Unser Viewport (Ausgabe-Bereich) fängt bei 0/0 an und hört bei 256/256 auf. Wir beachten, dass bei OpenGL der Ursprung nicht oben links ist, sondern unten links. Ich gehe nun nicht näher darauf ein, wie genau die Szene gezeichnet wird. Wir gehen einfach davon aus, dass wir brav die Bildschirme und das Objekt in der Mitte gerendert haben:<br />
<br />
[[Bild:Tutorial_Renderpass_viewport.gif]]<br />
<br />
Und kein Aufschrei, weil noch die Texturen fehlen! Ich habe der Übersicht halber diese noch nicht eingefügt. Anstatt die Szene nun allerdings auf dem Bildschirm auszugeben, "leiten" wir den Inhalt nun auf die Textur um:<br />
<br />
<source lang="pascal"><br />
glBindTexture(GL_TEXTURE_2D, Screen);<br />
glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 0, 0, 256, 256, 0);<br />
</source><br />
<br />
Mit [[glBindTexture]] weisen wir Screen als "aktuelle Textur" zu. Die darauf folgende Zeile sorgt dann dafür, dass wir auf Screen den momentanen Inhalt des Color-Buffers (ergo unserer Szene) schreiben. Die Parameter sollte man auch nicht weiter verändern, man wird meist damit zu recht kommen. Die beiden 256 erklären sich diesmal wohl auch von selbst.<br />
<br />
=== Und ab in die "richtige" Welt ===<br />
<br />
Erschreckend einfach oder? Wir haben nun unseren ersten Render-Pass hinter uns und haben die Szene auf einer Textur! Nun beginnen wir unseren zweiten Render-Pass und haben natürlich im Hinterkopf, dass der Color-Buffer noch vom ersten Durchgang voll ist! Also weg damit:<br />
<br />
<source lang="pascal"><br />
glClearColor(0,0.0,0.3,0);<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
</source><br />
<br />
Man sollte zur Kenntnis nehmen, dass ich als "Reinigungs-Farbe" einen leicht blauen Schwarzton nehme. Macht die Szene interessanter und man erkennt die Screens anschließend besser ;)<br />
<br />
<source lang="pascal"><br />
glViewport(0, 0, 800, 600);<br />
</source><br />
<br />
Auch diese Zeile sollte nicht vergessen werden, damit wir wieder auf voller Bildschirmbreite rendern. Es bietet sich hier natürlich evtl. an, nicht mit fixen Werten zu arbeiten, sondern jeweils die aktuelle Fensterbreite und Höhe anzugeben.<br />
<br />
<source lang="pascal"><br />
glBindTexture(GL_TEXTURE_2D,Screen);<br />
RenderScreens;<br />
DrawPyramidScene;<br />
</source><br />
<br />
Huch! Das ist alles? Yes... Wir weisen als aktuelle Textur „Screen“ an und zeichnen dann die Bildschirme. Anschließend kommt noch das Objekt in der Mitte dazu. Drawpyramids bindet übrigens seine eigenen Texturen ein, so dass nicht "Screen" verwendet wird. Und fertig ist der zweite Pass und damit auch unsere Szene. OpenGL bringt den Color-Buffer an die Wand, Verzeihung, den Screen ;)<br />
<br />
Es kommt nur sehr darauf an, wie wir den ersten Pass durchführen. Ob wir uns mit dem Objekt in der Mitte zufrieden geben…<br />
<br />
[[Bild:Tutorial_Renderpass_simple.jpg]]<br />
<br />
…oder ob wir uns wie beim zweiten Render-Pass die Screen-Textur krallen und noch zusätzlich die Bildschirme einzeichnen. Wir erhalten dann einen verdammt interessanten rekursiven Effekt:<br />
<br />
[[Bild:Tutorial_Renderpass_multiple.jpg]]<br />
<br />
=== Und die Perfektion... ===<br />
<br />
Damit wäre unser Ziel doch eigentlich erreicht, oder? Dachte ich mir zu diesem Zeitpunkt auch, allerdings habe ich mir noch etwas einfallen lassen. Die Szene ist eigentlich noch einen kleinen Tick zu langweilig! Man kann doch sicherlich noch ein wenig spielen?! Ich dachte mir, dass es doch ganz nett wäre, wenn man die Bildschirme abschalten könnte. Eigentlich nichts Schweres... einfach eine Noise-Textur nehmen und statt dem Screen-Bild drauf zeichnen. Allerdings würde das an sich recht langweilig aussehen, da man nur eine feste "Bildstörung" hätte. Jemand, der mal vor einem Fernseher ohne Empfang gesessen hat, wird jedoch festgestellt haben, dass wir dort durchaus eine Bewegung feststellen können. Was also tun? Einmal meinte einer zu mir ganz cool: "Hey, da nehmen wir einfach viele solcher Noise-Texturen und..." *zack* Schöner Blödsinn ;)<br />
<br />
Wir verwenden einfach die Textur-Matrix, um die Texture so zu bewegen, dass der Eindruck entsteht, dass sie sich bewegen würde. Zunächst erhöhen wir eine Variable vor dem ersten Render-Pass:<br />
<br />
<source lang="pascal"><br />
x:=x+0.01;<br />
</source><br />
<br />
Diese Variable verwenden wir dazu, dass der Bildschirm sich immer leicht von oben nach unten bewegt. Es entsteht somit die Illusion des Bildtasters, der das Signal auf die Röhre projiziert. (*hust* Seit wann haben Flachbildschirme Kathoden-Strahler *ggg*... bin echt geil im Märchen erzählen ^__-) Nun haben wir mehr eine einfache Bewegung der Textur von oben nach unten... langweilig! Das Bild soll flackern! Also werden wir chaotisch und erzeugen einfach wilde Zufallswerte, so dass die Textur sehr schnell von links nach rechts springt. Schon ergibt sich folgende Abfrage, bevor ein Screen gerendert wird:<br />
<br />
<source lang="pascal"><br />
if s1 then<br />
begin<br />
glBindTexture(GL_TEXTURE_2D,screen);<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glMatrixMode(GL_MODELVIEW);<br />
end<br />
else<br />
begin<br />
glBindTexture(GL_TEXTURE_2D,noise);<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
gltranslate(random(100)/10,x,0);<br />
glMatrixMode(GL_MODELVIEW);<br />
end<br />
</source><br />
<br />
In S1 ist gespeichert, welchen Zustand der Screen haben soll. If TRUE (an), dann nehmen wir die Screen-Textur und setzen die Textur-Matrix auf den Standard zurück; falls der Bildschirm aus sein soll, nehmen wir die Noise-Texture und verändern die Textur-Matrix so, wie wir sie brauchen. Das Ganze ist an sich nichts Neues, da dies bereits beim Matrix-Tutorial gezeigt wurde. Allerdings sieht man hier sehr schön, wie man zahlreiche KB für einen Effekt sparen kann, den man so ganz leicht "simulieren" kann ;) (sicherlich wäre es noch besser, die Textur ebenfalls im RAM zu erzeugen!)<br />
<br />
Besonders geil hierbei: auch in der Rekursion sind die Bildstörungen enthalten ohne weitere Source! Ist ein Bildschirm aus, sieht man eh nur noch das Rauschen und keine Rekursion! Der Code ist perfekt ;D<br />
<br />
[[Bild:Tutorial_Renderpass_noise.jpg]]<br />
<br />
== Nachwort ==<br />
<br />
So, das war es mal wieder. Das Ganze war sicherlich nicht so ein Monster-Tutorial, wie ihr es sonst von mir gewohnt seid, aber ich denke, dass es trotzdem spannend und unterhaltsam war ;) Mir hat es zumindest sehr viel Spaß gemacht, weil man sich endlich mal kreativ auslassen konnte und ich denke, dass wir inzwischen auch ein Niveau erreicht haben, bei dem man sich den Techniken an sich widmen kann und nicht jeden einzelnen Schritt erklären muss. Laßt mich wissen, wie es Euch gefallen hat und ob ihr alle gut mitgekommen seid. Für Ideen und Kritiken stehe ich wie immer Verfügung!<br />
<br />
Btw: Ich frage mich langsam ernsthaft, wie ich auf den Gedanken gekommen bin, dass ein futuristischer Monitor sowas wie eine Störung beim Bildaufbau haben kann. Ich meine... nicht nur technisch! Man wird doch sowas sicherlich in der Zukunft in den Griff bekommen. Interessant ist dabei nur, dass in fast jedem Sciene-Fiction-Film sowas vorkommt. Lauter digitale Screens mit Bildstörungen, die auf analoge Übertragung schließen lassen :-/ . Wer ne Antwort weiß... i-Mehl an mich ;D<br />
<br />
Btw2: Dank an Anita für die germanistische Beratung ;)<br />
<br />
Viel Spaß beim Experimentieren!<br />
<br />
Euer<br />
<br />
[[Benutzer:Phobeus|Phobeus]]<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_renderpass_delphi_api|text=Beispiel-Quelltext (Delphi)}}<br />
* {{ArchivLink|file=tut_renderpass_exe|text=Beispiel-Programm}}<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_Abseits_eckiger_Welten]]|[[Tutorial_Selection]]}}<br />
<br />
[[Kategorie:Tutorial|NURBS]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23637DGL Wiki:Files2009-05-17T14:41:10Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fog_sample_bin]]<br />
* [[Archiv:fog_sample_src]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:jvscript]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:particle1_exe]]<br />
* [[Archiv:particle1_src_api]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:spiegelung_exe]]<br />
* [[Archiv:spiegelung_src_vcl]]<br />
* [[Archiv:stereo_vcl]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:terrainclod_exe]]<br />
* [[Archiv:terrainclod_src_vcl]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23636DGL Wiki:Files2009-05-17T14:38:31Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fog_sample_bin]]<br />
* [[Archiv:fog_sample_src]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:jvscript]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:particle1_exe]]<br />
* [[Archiv:particle1_src_api]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:renderpass_exe]]<br />
* [[Archiv:renderpass_src_api]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:spiegelung_exe]]<br />
* [[Archiv:spiegelung_src_vcl]]<br />
* [[Archiv:stereo_vcl]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:terrainclod_exe]]<br />
* [[Archiv:terrainclod_src_vcl]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_bump_exe&diff=23635Archiv:tut bump exe2009-05-17T14:37:54Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Windows-Binary zum Tutorial BumpMap}}“</p>
<hr />
<div>{{Archiv|beschreibung=Windows-Binary zum [[Tutorial BumpMap]]}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_bump_delphi_vcl&diff=23634Archiv:tut bump delphi vcl2009-05-17T14:37:23Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-VCL-Quelltext zum Tutorial BumpMap}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial BumpMap]]}}</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_BumpMap&diff=23633Tutorial BumpMap2009-05-17T14:36:22Z<p>Flo: + Dateien Abschnitt</p>
<hr />
<div>=Bumpmapping=<br />
==Vorwort==<br />
<br />
'''Bumpmapping, was ist das?'''<br />
<br />
Das fragen sicherlich einige von euch. Also die Idee von Bumpmapping wurde schon 1978 von James Blinn entwickelt (obwohl es damals noch keine 3D Grafik gab???). Bumpmapping verleiht Mithilfe einer speziellen Textur einer Fläche eine richtige Oberfläche. Dabei wird aus den Farbwerten der speziellen Textur (Normalmap genannt) die Normalen der Fläche verändert und so entsteht der 3D-Effekt. Es gibt zwei (mir bekannte ) Arten von Bumpmapping. Einerseits das leicht veraltete Emboss Bumpmapping. Emboss-Bumpmapping funktioniert zwar auf fast jeder Grafikkarte, aber ergibt kein wirklich befriedigendes Ergebniss. Deshalb verwenden wir das neuere Dot-3 Bumpmapping. Dafür braucht man zwar eine neuere Grafikkarte (glaube mindestens Geforce-1), doch das Ergebniss sieht dann wirklich gut aus. So wird aus einem Quad ein Quad mit richtiger Oberfläche:<br />
<br />
[[Bild:Tutorial_BM_bump_01.jpg|framed|center|Dieses Einfache Quadrat wurde aus einer normalen Textur und einer Normalmap gerendert]]<br />
<center><br />
{|border=0<br />
|[[Bild:Tutorial_BM_bump_02.jpg|framed|Normale Textur]] || [[Bild:Tutorial_BM_bump_04.jpg|framed|RGB Normalmap (bump_out.tga)]]<br />
|}</center><br />
<br />
Erstellen von RGB Normalmaps<br />
<br />
So erstmal genug Theorie, jetzt geht's los mit der Praxis. Als erstes müssen wir eine Normalmap erstellen. Dafür nutzten wir NormalMapGen von http://www.developers.nvidia.com. Wenn man jetzt z.B. aus der Datei bump_in.tga eine Normalmap machen will, startet man es wie folgt:<br />
<br />
NormalMapGen.exe bump_in.tga bump_out.tga<br />
<br />
Für die Leute, die nicht aus DOS-Zeiten stammen oder nicht wissen wie man die Konsole bedient, genauer:<br />
* Unter "Start->Programme->MS-DOS-Eingabeaufforderung" die Konsole starten<br />
* Jetzt muss da irgendwas wie C:\Windows\ stehen (variiert von System zu System)<br />
* In dem man erst das Laufwerk mit Doppelpunkt und dann cd (Verzeichnis) eingibt, in das Verzeichnis wechseln, wo NormalMapGen.exe ist. Bei mir:<br />
- D:<br />
- cd \Tools\NormalMapGen\<br />
* Wie man sieht liegt bei mir NormalMapGen im Verzeichnis "D:\Tools\NormalMapGen\"<br />
* Dann einfach wie oben das Programm starten. Dabei muss Bump_in.tga im gleichen Verzeichniss sein:<br />
NormalMapGen Bump_In.tga Bump_out.tga <br />
* Es versteht sich von selbst, dass die Bilder anders heißen können.<br />
<br />
Jetzt wird aus bump_in.tga eine RGB Normalmap wie oben gemacht. Die In-Datei enthält die Graustufen, so wie die Normalmap später aussehen soll. Die Eingangsdatei muss bestimmte Kriterien erfüllen. Weil Programmierer aber faul sind, wiederhol ich die nicht alle (stehen in der Readme von NormalmapGen).<br />
<br />
Direktlink zum Tool : http://developer.nvidia.com/view.asp?IO=map_generator <br><br />
Normalmapgenerator als Plug-In für Photoshop : http://developer.nvidia.com/object/photoshop_dds_plugins.html<br />
<br />
==Initialisierung==<br />
<br />
Wenn ihr dann so eine schön bunte Normalmap habt, muss die ja auch irgendwie in das Programm. Jetzt solltet ihr erstmal nur weiterlesen, wenn ihr schon mal was von Multitexturing gehört habt, denn das braucht ihr jetzt (Tutorials gibt's dazu in unsrer Tutorialsektion, Tutorial Nr.5).<br />
Zuerst müssen wir überprüfen, ob die Grafikkarte Multitexturing unterstützt.<br />
<br />
<source lang="pascal">glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, @TMUs);<br />
if TMUs < 2 then<br />
begin<br />
ShowMessage('Grafikkarte unterstützt Multitexturing nicht');<br />
Close;<br />
end;</source><br />
<br />
Dann noch Combiner:<br />
<br />
<source lang="pascal">glGetIntegerv( GL_MAX_GENERAL_COMBINERS_NV, @MaxCombiners);<br />
ReadExtensions;<br />
ReadNVREGCOMBExtension;<br />
if not (Assigned(@glCombinerParameteriNV) and Assigned(@glActiveTextureARB)) then<br />
begin<br />
ShowMessage('Grafikkarte unterstützt Combiner nicht');<br />
Close;<br />
end;</source><br />
<br />
Die beiden Befehle in Zeile 2 und 3 brauchen die Unit ''NVREGCOMB''. Das war auch schon alles für die GLInit Funktion. Dann müsst ihr noch die Texturen laden, die ihr braucht (sollten am Anfang nur zwei sein).<br />
<br />
==Und jetzt ... ?==<br />
<br />
Bevor wir jetzt dem eigentlichen Programmieren zuwenden, müssen wir noch überlegen, was überhaupt gemacht werden soll. Bevor wir das können, müssen wir uns überhaupt im Klaren sein, wie so eine Grafikkarte, genauer die Register, funktionieren (so mit Strom, oder?):<br />
<br />
[[Bild:Tutorial_BM_bump_05.jpg|center]]<br />
<br />
Hier sieht man den Aufbau des Registers in unserem Fall. Halt! Was ist ein Register? Ein Register ist der Teil der Grafik-Pipeline, bei dem die genaue Farbe jedes zu zeichnenden Pixels bestimmt wird. Der Register ist also fast das letzte Stück in der Grafik-Pipeline. Danach wird das Bild auf den Bildschirm gebracht. Doch was ist das da oben für ein Bild? Dort sieht man das Funktionsprinzip in unserem Fall. Links stehen alle Inputs. Es wird für jeden Pixel auf dem Bildschirm die Texturfarbe der normalen Textur und der Bumpmap angegeben. Hinzu kommt die Diffuse und Specular Farbe. Zuerst geht ein RGB und ein Alpha Wertder Bumpmap-Textur und ein RGB und ein Alpha Wert der Diffuse-Farbe in den ersten Combiner. Dort werden beide kombiniert (wie der Name schon sagt) und heraus kommt ein RGB und ein Alpha Wert. Diese werden beide in der Zwischenvariable Spare 0 gespeichert. Jetzt könnte man noch weitere Combiner für andere Effekte einbeziehen, doch das ist nicht meine Aufgabe. Wenn man jetzt alles benötigte berechnet hat, geht Spare 0 mit der Specular-Farbe und einem RGB und ein Alpha Wert zusammen in den Final-Combiner. Dort werden die alle nochmal zusammen gerechnet und das ganze geht über Drähte ab zum Bildschirm!!! Doch jetzt hab ich so oft das Wort Combiner benutzt ohne es wirklich zu erklären. Das hole ich jetzt nach:<br />
<br />
[[Bild:Tutorial_BM_bump_06.jpg|center]]<br />
<br />
Also ein Combiner ist ein Teil des Registers, der Verschiedene Eingaben in Form von Farbwerten, zusammenrechnet. Bei uns gibt es nur zwei Eingaben, es können aber z.B. bei einer Geforce-3 bis zu vier pro Combiner sein. Wenn wir schon mal bei technischen Details sind. Bei einer Geforce-3 gibt es 8 Combiner. Die Anzahl der Combiner, die eure Grafikkarte unterstützt, bekommt ihr mit<br />
<br />
glGetIntegerv( GL_MAX_GENERAL_COMBINERS_NV, @MaxCombiners);<br />
<br />
raus, wobei MaxCombiners eine Integer-Zahl ist.<br />
<br />
==Pflicht==<br />
<br />
Da jetzt (hoffentlich) alle das Prinzip verstanden haben, können wir uns jetzt an das echte Progarmmieren machen. Als erstes müssen wir die beiden Texturen einbinden:<br />
<br />
<source lang="pascal">{ Textur }<br />
glActiveTextureARB(GL_TEXTURE1_ARB);<br />
glEnable(GL_TEXTURE_2D);<br />
Textures[1].Bind;<br />
<br />
{ Bumpmap }<br />
glActiveTextureARB(GL_TEXTURE0_ARB);<br />
glEnable(GL_TEXTURE_2D);<br />
Textures[0].Bind;<br />
<br />
glEnable(GL_REGISTER_COMBINERS_NV);</source><br />
<br />
Die letzte Zeile aktiviert die Combiner. Das sollte alles (bis auf die letzte Zeile) nichts Neues sein. Jetzt haben wir die Normalmap in der ersten und die normale Textur in der zweiten Texturunit der Grafikkarte. Als nächstes bestimmen wir wie viele Combiner verwendet werden sollen:<br />
<br />
glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 1);<br />
<br />
<br />
Für den Anfang reicht erst mal einer. Für besondere Effekte braucht man später mehr. Als nächstes müssen wir der Grafikkarte mitteilen, wie sie das Ganze mischen soll. Zuerst rechnen wir bei einem Combiner Bumpmap und RGB Farbe zusammen. Letztere brauchen wir später, wenn dann Licht dazukommt:<br />
<br />
<source lang="pascal">glCombinerInputNV(<br />
GL_COMBINER0_NV, //Combiner, der benutzt wird<br />
GL_RGB,<br />
GL_VARIABLE_A_NV, //Variable beim Combiner<br />
GL_TEXTURE1_ARB, //Wert: Bumpmap Textur<br />
GL_EXPAND_NORMAL_NV,<br />
GL_RGB<br />
);<br />
<br />
glCombinerInputNV(<br />
GL_COMBINER0_NV, //Combiner, der benutzt wird, wie oben<br />
GL_RGB,<br />
GL_VARIABLE_B_NV, // Variable beim Combiner<br />
GL_PRIMARY_COLOR_NV, //Wert: RGB Wert der Oberfläche<br />
GL_EXPAND_NORMAL_NV,<br />
GL_RGB<br />
);</source><br />
<br />
<br />
So, was haben wir hier gemacht? Zuerst haben wir der Variable A des ersten Combiners die Bumpmap-Textur zugewiesen. Der Variable B des gleichen Combiners haben wir dann die Farbe zugewiesen, die man mit glColor bestimmt. Die Farbe braucht man später für die Lichtrichtung. Alles was nicht kommentiert ist, solltet ihr erst mal so lassen. Soweit zum Input.<br />
<br />
<source lang="pascal">glCombinerOutputNV(<br />
GL_COMBINER0_NV,<br />
GL_RGB,<br />
GL_SPARE0_NV, // Output von A und B<br />
GL_DISCARD_NV, // Output von C und D<br />
GL_DISCARD_NV, // Summe von allen Variablen<br />
GL_NONE, // Skalierung<br />
GL_NONE, // Bias<br />
GL_TRUE, // Ja/Nein Output von A und B<br />
GL_FALSE, // Ja/Nein Output von C und D<br />
GL_FALSE // Muxsum<br />
);</source><br />
<br />
Jetzt brauchen wir noch den Output. Die ersten beiden Zeilen sollten sich von selbst erklären. Dann definiert man wohin das Ergebnis von A und B gespeichert werden soll. Wir speichern das erstmal in einer temporären Variablen '''GL_SPARE0_NV'''. Die nächsten beiden Zeilen sind für den Output anderer Ergebnisse zuständig und interessieren erstmal nicht. Skalieren und Bias sind zu irgendwelchen Transformationen da. Als nächstes bestimmen wir, was ausgegeben wird. Die letzte Zeile kenne ich selber nicht. Als letztes nur noch bestimmen wie der Final-Combiner arbeiten soll, und dann sind wir fertig:<br />
<br />
glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);<br />
<br />
glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);<br />
<br />
glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);<br />
<br />
glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB); <br />
<br />
glFinalCombinerInputNV(GL_VARIABLE_E_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB); <br />
<br />
glFinalCombinerInputNV(GL_VARIABLE_F_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);<br />
<br />
<br />
Das ist alles nicht schwer zu verstehen. Uns interessieren nur die ersten beiden Zeilen. Sie weisen der Variable A des Final-Combiners die Variable '''GL_SPARE0_NV''' und der Variable B die normale Textur. Die anderen Variablen werden als leer deklariert. Man muss die vier letzten Zeilen nicht schreiben (ich hab's nur der Ordnung wegen gemacht). Der restliche Code erzeugt nur noch ein Quad mit zwei Texturen.<br />
<br />
==Kür==<br />
<br />
Da das Quad zwar schon richtig gut aussieht, aber noch ein bisschen statisch wirkt wollen wir uns jetzt einer kleinen Verbesserung zuwenden: Das Licht. Die Lichtrichtung gibt man einfach über glColor an. Dabei muss man jedoch immer relativ von der Fläche ausgehen. Also rot entspricht der x, grün der y und blau der z Achse. Ach ja, rot 0 heißt z.B. das das Licht von links kommt, 0.5 von der Mitte und 1 von rechts. Das gleiche nur von oben nach unten gilt für grün. Mit Blau ist das so'ne Sache. Kleiner als 0.5 sollte es nie werden, weil sonst das Quad vollkommen schwarz wird. Ein guter Wert liegt zwischen 0.8 und 1.<br />
<br />
[[Bild:Tutorial BM bump 07.jpg|center]]<br />
<br />
Nochmal zurück zur Berechnung: wie schon gesagt das alles muss man relativ zu dem Polygon ausrechnen. Weil ein paar andere Leute und ich aber noch keine Möglichkeit gefunden haben dieses Licht so zu berechnen, kann ich euch leider nicht sagen, wie das funktioniert. Das ihr in dem Sample trotzdem die Wirkungsweise bestaunen könnt, wird einfach eine "Drehung" des Lichtes mit folgendem Codeschnipsel simuliert:<br />
<br />
<source lang="pascal">procedure InitLight;<br />
var<br />
light_position: TVertex;<br />
begin<br />
light_position[0] := cos(lightangle);<br />
light_position[2] := 1;<br />
light_position[1] := sin(lightangle);<br />
NormalizeVector(light_position);<br />
glColor3fv(@light_position[0]);<br />
end; (*InitLight*)</source><br />
<br />
Die Wirkungsweise sollte eigentlich klar sein: Mit einem Sinus und Cosinus einer Variable, die ständig erhöht wird, wird eine gleichmäßige Bewegung simuliert.<br />
<br />
==Nachwort==<br />
<br />
So das war auch schon mein erstes Tutorial. Das Ganze ist nicht einfach, doch wenn man es mal verstanden hat, geht's und es gibt sogar ganze ordentliche Ergenbisse. Es gibt jetzt sehr viele Möglichkeiten das Ergebniss zu modifizieren. Wen das ganze interessiert sollte unbedingt auf Jan Horns Homepage gehen (http://www.sulaco.co.za). Dort gibt's u.a. ein Sample für Bumpmapping. Auch auf http://developer.nvidia.com gibt's mehr zum Thema.<br />
<br />
<br />
MfG '''HomerS'''<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_bump_delphi_vcl|text=Beispiel-Quelltext (Delphi)}}<br />
* {{ArchivLink|file=tut_bump_exe|text=Beispiel-Programm}}<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_Partikel1]]|[[Tutorial_Bumpmaps_mit_Blender]]}}<br />
[[Kategorie:Tutorial|BumpMap]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23632DGL Wiki:Files2009-05-17T14:00:27Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:bump_bin]]<br />
* [[Archiv:bump_vcl]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fog_sample_bin]]<br />
* [[Archiv:fog_sample_src]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:jvscript]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:particle1_exe]]<br />
* [[Archiv:particle1_src_api]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:renderpass_exe]]<br />
* [[Archiv:renderpass_src_api]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:spiegelung_exe]]<br />
* [[Archiv:spiegelung_src_vcl]]<br />
* [[Archiv:stereo_vcl]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:terrainclod_exe]]<br />
* [[Archiv:terrainclod_src_vcl]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Selection&diff=23631Tutorial Selection2009-05-17T13:59:33Z<p>Flo: </p>
<hr />
<div>=Objektselektion=<br />
==Einleitung==<br />
<br />
Hi Leute!<br />
Viele werden nun erst mal denken, was denn ein Tut von mir hier zu suchen hat. Na ja: Über dieses Thema gab es bei der DGL bislang reichlich wenig und deshalb habe ich mich entschossen es denn auch hier zu veröffentlichen. (Juhuu! Endlich darf ich hier auch mein Debüt feiern!) Es soll nun also über die Selektion von OpenGL-Objekten (Na ja ihr wisst schon: Dreiecke, Quadrate, Würfel, usw...) gehen. Viele werden sich sicherlich denken, wozu das überhaupt gut sein soll...<br />
<br />
Na ja, wenn ich hier gerade so in die Tasten haue, da fällt mir etwas ein, was wir bislang mit OpenGL noch nicht können (wir können Level basteln, wenn wir gut sind evtl. unser Eigenheim nachbauen, usw..): ich interagiere mit meiner Welt bzw. ich bediene die Tastatur. Was wäre das den für eine Welt, wenn wir nichts anheben, Türen öffnen oder einen Lichtschalter betätigen könnten? Na ja: eine ziemlich üble würde ich mal so vermuten... Und genau darum geht es in meinem Tut heute!<br />
<br />
Ich denke mal, ihr alle werdet erkannt haben, dass es wirklich nicht gerade einfach ist, herauszubekommen, auf welches Objekt eurer Szene der User gerade geklickt hat, obwohl diese Infos für viele Programme ziemlich unerlässlich sind. Stellt euch mal vor, ihr habt einen super genialen Map-Editor gebastelt und ihr könnt nicht herausfinden, auf welches Objekt der User gerade zwecks Editieren geklickt hat! Oder ihr habt ein super geniales Hacker-Agenten-Spiel geschrieben und ihr wisst nicht, welche Kombination der gute Herr Spieler gerade in den Computer einer Sicherheitstür getippt hat! Wäre doch ne echt schwache Leistung, oder? Und um dieses Defizit auszugleichen habe ich hier niedergeschrieben, wie es geht.<br />
<br />
==OpenGL und der Name-Stack==<br />
<br />
Etwas (aber nur ganz wenig) Theorie vorweg.<br />
Und zwar will ich euch zunächst sagen, wie OpenGL Namen überhaupt ansieht: und zwar als reine Integer-Werte! Es gibt keine echten Namen, sondern leider nur Nummern, die man seiner Szene zuweisen kann... Dass soll uns aber nicht wirklich davon abhalten, auch echte Namen zu verwenden. So gibt man normalerweise die Namen selber als Konstanten an, aber wir können ja zum Beispiel ein Array erschaffen, welches die dazugehörigen Namen enthält... Das ganze könnte man sich dann so vorstellen:<br />
<br />
<source lang="pascal">const<br />
dreieck : 1;<br />
viereck : 2;<br />
stern : 3;<br />
namen : array[-1..3] of string = ('nichts', '', 'das Dreieck', 'das Viereck', 'den Stern'); </source><br />
<br />
<br />
Erklären muss ich das nicht wirklich, oder? Na ja evtl. wäre es für euch noch ganz interessant, weshalb das Namens-Array den Wert "-1" hat: OpenGL kann nämlich auch den Wert "-1" ausgeben, wenn man auf rein gar nichts geklickt hat! Daher brauchen wir auch ein Element, welches "-1" heißt.<br />
<br />
Damit euch das ganze etwas klarer wird, stelle ich euch nun erst mal vor, was ich heute mit euch machen will:<br />
[[Bild:Tutorial_selection_01.jpg|center]]<br />
<br />
Dieses nun wirklich einfache Programm hat folgende Funktion:<br />
Es werden wie man sieht ein Dreieck, ein Viereck und ein Stern gezeichnet und je nachdem, auf was man geklickt hat, soll die Message unten in der Statusbar angezeigt werden.<br><br />
Wie man eine derartige (für unsere Verhältnisse einfache) Szene rendert, dürfte recht einfach sein, nicht aber, wie man den Objekten nun die Namen zuweißt... aber das zeige ich euch hier:<br />
<br />
<source lang="pascal">procedure render;<br />
begin<br />
glMatrixMode(GL_MODELVIEW);<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); //Farb und Tiefenpuffer löschen<br />
glLoadIdentity;<br />
glTranslatef(0,0,-6);<br />
<br />
glInitNames;<br />
glPushName(0);<br />
<br />
glColor3f(1,0,0);<br />
glLoadName(dreieck);<br />
glBegin(gl_triangles);<br />
glVertex3f(-0.5,1,0);<br />
glVertex3f(0.5,1,0);<br />
glVertex3f(0,2,0);<br />
glEnd;<br />
<br />
glColor3f(0,1,0);<br />
glLoadName(viereck);<br />
glBegin(gl_quads);<br />
glVertex3f(-1,0,0);<br />
glVertex3f(-2,0,0);<br />
glVertex3f(-2,-1,0);<br />
glVertex3f(-1,-1,0);<br />
glEnd;<br />
<br />
glColor3f(1,1,0);<br />
glLoadName(stern);<br />
glBegin(gl_triangles);<br />
glVertex3f(1,0,0);<br />
glVertex3f(2,0,0);<br />
glVertex3f(1.5,-1,0);<br />
<br />
glVertex3f(1,-0.65,0);<br />
glVertex3f(2,-0.65,0);<br />
glVertex3f(1.5,0.35,0);<br />
glEnd;<br />
<br />
SwapBuffers(form1.myDC); //scene ausgeben<br />
end;</source><br />
<br />
Also, das Erste ist, dass wir auf jeden Fall in die ModelViewMatrix schalten müssen. In dieser Matrix sind wir zwar auch normalerweise, aber in unserer späteren Selection-Funktion müssen wir zeitweilig in eine andere. Da es zum Rendern aber unabdingbar ist, dass wir gerade diese Matrix gesetzt haben, wechsle ich auf jeden fall vor dem Rendern in diese... sicher ist sicher :-)<br />
<br />
Die nächsten markierten Zeilen sind "{{INLINE_CODE|[[glInitNames]];}}" und "{{INLINE_CODE|[[glPushName]](0);}}" Diese beiden Zeilen Quellcode bewirken, dass zunächst mal der Name-[[Stack]] initialisiert wird und danach der "Name" "0" auf diesen Stack gelegt wird. Die Null hat eine ganz einfache Funktion: Wird nicht vor dem Laden des ersten Namens mindestens eine Zahl auf den Stack gepusht, dann endet der Versuch, einen Namen zu laden mit einem netten Error... wer's nicht glaubt, kann es ja gerne mal ausprobieren ;-)<br />
<br />
Der Befehl "{{INLINE_CODE|[[glLoadName]]}}", der den restlichen interessanten Teil dieser Render-Prozedur stellt, hat die einfache Wirkung, dass OpenGL nun einen Namen lädt (halt aus unserer Konstanten) und dass alle Objekte, die nun folgen, mit diesem "Namen" belegt werden, bis ein weiteres mal "{{INLINE_CODE|glLoadName}}" aufgerufen wird... ist doch easy, oder?<br />
<br />
Leider wird es nicht so einfach bleiben... Na ja, nützt ja nichts, nun müssen wir nämlich die Funktion anlegen, welche die eigentliche Selektion durchführt. Ein Tipp: Die Funktion muss im Quelltext unter der Render-Prozedur angelegt werden, weil dieselbige nämlich in dieser Funktion aufgerufen wird und Delphi ansonsten die Prozedur nicht findet.<br />
<br />
<source lang="pascal">function Selection : integer;<br />
var<br />
Puffer : array[0..256] of GLUInt;<br />
Viewport : TGLVectori4;<br />
Treffer,i : Integer;<br />
Z_Wert : GLUInt;<br />
Getroffen : GLUInt;<br />
begin<br />
glGetIntegerv(GL_VIEWPORT, @viewport); //Die Sicht speichern<br />
glSelectBuffer(256, @Puffer); //Den Puffer zuordnen<br />
<br />
glMatrixMode(GL_PROJECTION); //In den Projektionsmodus<br />
glRenderMode(GL_SELECT); //In den Selectionsmodus schalten<br />
glPushMatrix; //Um unsere Matrix zu sichern<br />
glLoadIdentity; //Und dieselbige wieder zurückzusetzen<br />
<br />
gluPickMatrix(xs, viewport[3]-ys, 1.0, 1.0, viewport);<br />
gluPerspective(60.0, Viewport[2]/Viewport[3], 1, 256);<br />
<br />
render; //Die Szene zeichnen<br />
glMatrixMode(GL_PROJECTION); //Wieder in den Projektionsmodus<br />
glPopMatrix; //und unsere alte Matrix wiederherzustellen<br />
<br />
treffer := glRenderMode(GL_RENDER); //Anzahl der Treffer auslesen<br />
<br />
Getroffen := High(GLUInt); //Höchsten möglichen Wert annehmen<br />
Z_Wert := High(GLUInt); //Höchsten Z - Wert<br />
for i := 0 to Treffer-1 do<br />
if Puffer[(i*4)+1] < Z_Wert then<br />
begin<br />
getroffen := Puffer[(i*4)+3];<br />
Z_Wert := Puffer[(i*4)+1];<br />
end;<br />
<br />
if getroffen=High(GLUInt) <br />
then Result := -1<br />
else Result := getroffen;<br />
end;</source><br />
<br />
<br />
Alle Stellen, zu denen ich bereits Kommentare geschrieben habe, brauche ich (glaube ich) nicht mehr wirklich erklären... aber zu den Variablen will ich durchaus was sagen: Der "{{INLINE_CODE|Puffer}}" ist der eigentliche Selektions-Puffer. In ihm werden die Ergebnisse der Selektionsprüfung gespeichert. Das Array "{{INLINE_CODE|Viewport}}" speichert quasi unsere View, da die ja auch irgendwie wichtig ist ;-) Die Variable "{{INLINE_CODE|Treffer}}" ist ein reiner Integerwert, der die Anzahl der Treffer aufnimmt und die Variable "{{INLINE_CODE|Z_Wert}}" nimmt die Tiefendaten auf.<br />
<br />
Was nun in dieser Funktion passiert, ist einfach zu beschreiben. Zunächst wird unsere View zwischengespeichert und dann OpenGL klar gemacht, was für einen Puffer mit welcher Größe es verwenden soll. Eine Größe von 64 ist quasi der Normalwert, da jede OpenGL-Implementation (auf den verschiedenen Grafikkarten) diese Größe mindestens liefern muss... größere Werte sind aber durchaus möglich.<br><br />
Der Puffer hat ja nun an sich aber die 4-Fache Größe dieses Wertes. (bzw. wir übergeben als Parametergröße ja auch den Wert 256) Das hat folgenden Grund:<br />
Für jedes getroffene Objekt werden immer 4 Werte gespeichert. Und zwar<br />
# Anzahl der Namen auf dem Stack<br />
# Kleinster Z-Wert des getroffenen Objektes<br />
# Größter Z-Wert des getroffenen Objektes<br />
# Name des Objektes<br />
<br />
Danach wird in den Selektions-Modus geschaltet, die Matrix konfiguriert und OpenGL übergeben, welche x bzw. y- Koordinate auf dem Bildschirm angeklickt wurde. Dieses geschieht in der Prozedur "{{INLINE_CODE|[[gluPickMatrix]](xs, viewport[3]-ys, 1.0, 1.0, @viewport);}}". In dieser stehen "{{INLINE_CODE|xs}}" bzw. "{{INLINE_CODE|ys}}" für unsere X und Y-Koordinate, auf die geklickt wurde. Die nächsten zwei Parameter bilden eine Art "''Klick-Rechteck''". Wenn wir da einen Wert >1 angeben, dann wird praktisch eine Art Rechteck um den Punkt, auf den wir geklickt haben, gebastelt, welches komplett ausgewertet wird. Dadurch würde nicht nur das Objekt, welches direkt unter dem Mauscursor lag ausgewertet, sondern auch die Objekte, welche evtl. direkt daneben lagen.<br><br />
''Die nächste Zeile ist auch von Bedeutung: Die gesamte Selektion funktioniert nur dann korrekt, wenn bei [[gluPerspective]] die selbe Perspektive gesetzt ist wie beim normalen Rendern (was logisch erscheint wenn man einmal darüber nachdenkt).''<br><br />
Mit der Zeile "{{INLINE_CODE|treffer <nowiki>:=</nowiki> [[glRenderMode]](GL_RENDER);}}" fragt man nun noch ab, wie viele Treffer es insgesamt gegeben hat. Hierbei gilt es zu beachten: angenommen, man hat eine Große Szene und klickt auf ein Objekt. Dann ist schon fast davon auszugehen, dass dahinter noch irgendetwas liegt (kann auch ganze 100 Einheiten entfernt liegen... das spielt keine echte Rolle), dann werden auch diese Objekte als Treffer zurückgegeben. Um nun nur das Objekt herauszubekommen, welches der Kamera am nächsten lag, ließt man nun in einer Schleife alle Z-Werte "{{INLINE_CODE|Puffer[(i*4)+1]}}" der getroffenen Objekte aus und vergleicht sie mit dem bislang niedrigsten Wert. Ist der ausgelesene Wert niedriger, als der bislang kleinste, dann wird der neue Z-Wert gespeichert und der Name des bislang nächsten Objektes wird gespeichert.<br><br />
Ist die Schleife durchgelaufen, dann steht am Ende also in der Variable "getroffen" der Integer-Name des nächsten, getroffenen Objektes, welches wir dann auch als Rückgabewert der Funktion verwenden.<br />
<br />
Das war nun alles zwar recht viel komplizierter Kram, aber ich kann euch versichern, das nicht mehr kommt. Denn mehr, als das was wir nun eben geschrieben haben, werden wir nie für einen Selektionsvorgang brauchen... egal, ob wir den genialsten Geheimdienstthriller der Welt oder nur einen Moorhuhn - Klon schreiben. *g*<br />
<br />
Zu guter Letzt müssen wir nun noch diese Prozedur aufrufen, wozu ich das "onMouseDown" - Ereignis des Formulars verwendet habe:<br />
<br />
<source lang="pascal">procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;<br />
Shift: TShiftState; X, Y: Integer);<br />
begin<br />
xs := x;<br />
ys := y;<br />
<br />
statusbar1.Simpletext := 'Sie haben auf ' + namen[selection] + ' geklickt!';<br />
end;</source><br />
<br />
<br />
Ich denke, es besteht kaum Erklärungsbedarf...<br />
<br />
==Geht's auch größer?==<br />
<br />
Jupp, natürlich kann man diese ganze Technik auch zu was anderem verwenden, als nur Formen zu identifizieren. (Das Sample oben ist ja nun wirklich unterhalb unseres Niveaus, auch wenn es einen schönen Einblick in die Materie gebracht hat) Aber - größenwahnsinnig, wie ich nun mal bin - ich strebe zu größerem *muhaha*: Der Weltherrschaft ;-)<br><br />
Nein, Spaß beiseite, ich will euch noch eben ein kleines Briefing geben, wie ihr auch etwas Größeres bauen könnt. (Zum anderen will Phobeus die auch gerne *g*)<br><br />
So habe ich ein kleines Sample gebastelt, in welchem man zwei Räume hat (mit einem kleinen Zwischengang), von denen der zweite Raum unbeleuchtet ist, wobei man das Licht mithilfe eines Schalters im ersten (beleuchtetem) Raum einschalten kann. Das ganze sieht dann etwas so aus: (links: Licht aus, rechts: Licht an)<br />
<br />
<div align="center"><br />
{|{{Prettytable}}<br />
|width="50%" |[[Bild:Tutorial_selection_02.jpg|center]]<br />
|width="50%" |[[Bild:Tutorial_selection_03.jpg|center]]<br />
|-<br />
|align="center" | Licht Aus<br />
|align="center" | Licht An (siehe Hintergrund)<br />
|-<br />
|}<br />
</div><br />
<br />
Das macht doch durchaus was her, oder? OK, ich gebe zu, die Texturen sind nicht alle von mir (die Lightmaps aber schon), aber ansonsten finde ich die Szene recht gelungen. Um euch eine kleine Bauanleitung zu geben, habe ich hier eben den Grundriss der Szene für euch:<br />
<br />
[[Bild:Tutorial_selection_04.jpg|center]]<br />
<br />
An sich sollte so was für euch kein echtes Problem mehr sein... die x und z-Koordinaten stehen alle auf dem Plan, den ihr hier seht. Dazu sei gesagt, dass alles in der Szene genau 2 Einheiten hoch ist.<br />
Des weiteren könnte es für euch noch nützlich sein, zu wissen, dass ich für die Lightmaps den Blendmodus "{{INLINE_CODE|[[glBlendFunc]](gl_dst_color, gl_zero);}}" verwende und dass ansonsten alles etwa so ist, wie auch in den Teilen 7 und 8. Das letzte, was ein kleiner Stolperstein sein könnte, ist die Distanz zum Schalter. Es ist zwar schon wunderherrlich, wenn man ihn überhaupt betätigen kann, aber irgendwie ist es auch schwachsinnig, wenn man am anderen Ende des Raumes stehen kann und ihn dennoch verwenden kann. Deshalb habe ich meine Klick rozedur so verändert, dass die Distanz zum Schalter, wen er funktionieren soll höchstens 3 Einheiten betragen darf:<br />
<br />
<source lang="pascal">procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;<br />
Shift: TShiftState; X, Y: Integer);<br />
var<br />
xdis : double;<br />
ydis : double;<br />
begin<br />
xdis := my_x+4;<br />
ydis := my_y-3.85;<br />
<br />
xs := x;<br />
ys := y;<br />
<br />
if selection=1 then<br />
begin<br />
if sqrt(xdis*xdis + ydis*ydis) <= 3 then<br />
lichtan := not lichtan;<br />
end;<br />
<br />
end;</source><br />
<br />
Und bitte nicht vergessen: bindet die Unit "Math" mit ein... ansonsten dürftet ihr auf die Wurzelfunktion (sqrt) keinen Zugriff haben! Im Übrigen: Der Schalter hat von mir den Namen "1" bekommen und die ganze Umgebung hat die Nummer "2".<br />
<br />
Damit solltet ihr eigentlich kaum ein Problem haben, dieses Sample zu verstehen, bzw. nachzubasteln. Notfalls stehe ich per Mail, ICQ oder auch in unserem DCW / DGL- Board immer zur Hilfe zur Verfügung. Im Übrigen: falls ihr mal hängt, könnt ihr ja auch ins Sample gucken und sehen, wie ich es gelöst habe.<br />
<br />
==Nachwort==<br />
<br />
Stellt euch mal vor, dass war es auch schon. Ich hoffe, ihr habt alle einigermaßen Spaß gehabt bei dem ganzen und ihr werdet (wenn ihr die DCW-Fassung des Tutorials lest) auch beim nächsten Mal wieder reinschauen. Von allen anderen erwarte ich aber zumindest, dass ihr möglichst viel von diesem Tut behaltet und ich demnächst viele schöne Maps mit vielen schönen Lichtschalten sehe ;-)<br />
Bis denne<br />
<br />
:'''DCW_Mr_T'''<br />
<br />
<br />
==Nachtrag==<br />
Weitere Infos/Beispiele im Artikel "[[Selektion]]".<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_selektion_delphi_vcl|text=Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_selektion_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial Renderpass]]|[[Tutorial Objektselektion]]}}<br />
<br />
[[Kategorie:Tutorial|Selection]]</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_selektion_delphi_vcl&diff=23629Archiv:tut selektion delphi vcl2009-05-17T13:57:48Z<p>Flo: hat „Archiv:tut selektion vcl src“ nach „Archiv:tut selektion delphi vcl“ verschoben</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial Selection]]}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_selektion_exe&diff=23628Archiv:tut selektion exe2009-05-17T13:54:59Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Windows-Binary zum Tutorial Selection}}“</p>
<hr />
<div>{{Archiv|beschreibung=Windows-Binary zum [[Tutorial Selection]]}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_selektion_delphi_vcl&diff=23627Archiv:tut selektion delphi vcl2009-05-17T13:54:33Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-VCL-Quelltext zum Tutorial Selection}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial Selection]]}}</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Lektion_7&diff=23626Tutorial Lektion 72009-05-17T13:47:51Z<p>Flo: </p>
<hr />
<div>= Verblendet! =<br />
<br />
== Vorwort ==<br />
<br />
Nachdem wir nun ein wenig Erfahrung in der Praxis gesammelt haben, wenden wir uns einen anderen Bereich in der OpenGL-Programmierung zu, der uns helfen wird einige wirklich tolle Spezial-Effekte zu realisieren. Die meisten unter Euch werden den Begriff bereits das eine oder andere Mal gehört haben - die Rede ist vom so genannten Blending. Das berühmteste davon wird sicherlich das "Alpha Blending" sein. Wer nun glaubt Blending ist, wenn man sich ne Halogen-Werfer nimmt und damit versucht seinen Nachbarn zu quälen, ist auf dem Holzweg ;)<br />
<br />
Blending weist OpenGL dazu an, zu entscheiden was passieren soll, wenn man zwei Objekte übereinander zeichnet. Im Normalfall wird das obere Objekt das untere überdecken, aber vielleicht wollen wir ja auch, dass das obere Objekt transparent ist und man hindurch blicken kann auf das untere? Wer denkt, dass hier bereits Schluss sei mit dem Blending, der ist schief gewickelt, denn hier beginnt erst die Welt des kreativen Geistes! Lasst Euch verzaubern von wunderschönen OpenGL-Effekten und heizt Euren Programmen grafisch ordentlich ein. Ich hoffe ihr habt Verständnis, dass ich nur auf Teilbereiche eingehen kann, denn die Palette von Möglichkeiten ist keine Grenze gesetzt ;)<br />
<br />
Ich hoffe dennoch, dass auch das Tutorial gefallen wird und es wenigstens halbwegs verständlich rüberkommt. Auch gibt es für viele Dinge besser Lösungsmöglichkeiten, vieles hängt von der benutzen Textur ab oder aber auch wie viele davon verwendet werden. Um ein ausgiebiges Testen der Funktionen wird man nicht herum kommen. Also ran an die Arbeit und viel Spaß dabei ;)<br />
<br />
== Die vorhandene Welt und was darüber liegt ==<br />
<br />
=== Was ist den nun blenden? ===<br />
<br />
Wichtig beim Blending ist es zu wissen, was genau eigentlich beim rendern geschieht. OpenGL erhält eine Anweisung etwas zu zeichnen. Dieses wird dann in den so genannten "Frame-Buffer" gespeichert, das ist der Buffer,den wir dann auch meistens bei jedem Rendering mit glClear "löschen". (Wir erinnern uns, führen wir kein glClear aus, sehen wir nach jedem Rendering den alten Inhalt… habe versprochen, dass wir darauf zurück kommen ;) ). Das heißt eigentlich, dass wir bei jedem Rendern eines Objektes immer wieder etwas in den Frame-Buffer schreiben und dann wenn wir denken, dass alles fertig ist diesen Inhalt auf dem Bildschirm projizieren.<br />
<br />
Das Blending setzt nun genau an der Stelle an, an dem etwas in den Frame-Buffer geschrieben werden soll. OpenGL hat praktisch bereits ein 3D-Objekt im Frame-Buffer und zeichnet dann das neue 3D Objekt. Nun können wir OpenGL mitteilen, was in einer solchen Situation geschehen soll. Standard gemäß würde das obere Objekt, dass darunter liegende verdecken.<br />
<br />
<source lang="pascal"><br />
glBlendFunc(source,destination);<br />
</source><br />
<br />
Mit dieser Anweisung können wir dieses Verhalten ändern. Wir geben zwei Faktoren an, den für die Quelle und den für das Ziel. Ich versuche das mal abstrakt zu erklären. Wie bereits gesagt, handelt es sich dabei um Faktoren. Nehmen wir mal an Source wäre 0 und Destination 1, dann würde OpenGL beim zusammensetzen der Szene, das was bereits im Frame-Buffer ist total vernachlässigen und das neue, was darüber gezeichnet werden soll komplett drauf zeichnen. Je höher der Faktor einer dieser Beiden Werte ist, desto stärke werden die Farben beim Blending berücksichtigt.Das ganze funktioniert natürlich auch nur, wenn wir OpenGL dazu anhalten das Blending zu verwenden…<br />
<br />
<source lang="pascal"><br />
glEnable(GL_BLEND);<br />
</source><br />
<br />
Das ganze klingt sicherlich sehr komplex, (ist es auch *sg*), aber an Hand einiger Beispiele sollte es einem klar werden. Nehmen wir folgende Bilder:<br />
<br />
{| align="center"<br />
|[[Bild:Tutorial_lektion7_rock_tex.jpg]]<br />
|[[Bild:Tutorial_lektion7_gras_tex.jpg]]<br />
|}<br />
<br />
Diese beiden Texturen zeichnen wir jeweils auf einem Quad und zwar exakt aufeinander. Wir zeichnen zunächst die steinige Fläche und erhalten:<br />
<br />
[[Bild:Tutorial_lektion7_stone.jpg]]<br />
<br />
Soweit so gut… nun setzen wir folgenden Blend-Faktor:<br />
<br />
<source lang="pascal"><br />
glBlendFunc(GL_ONE,GL_ZERO);<br />
</source><br />
<br />
und zeichnen die Gras-Textur darüber:<br />
<br />
<br />
[[Bild:Tutorial_lektion7_gras.jpg]]<br />
<br />
Hmm… scheint nicht, als ob das Blending wirklich gut funktioniert hat, da wir nur die Textur sehen, die wir als letztes gezeichnet haben. Scheint soweit alles wie bisher zu sein. :-/<br />
<br />
<source lang="pascal"><br />
glBlendFunc(GL_ONE,GL_ZERO);<br />
</source><br />
<br />
Quatsch! Natürlich hat alles funktioniert wie wir es erwartet haben! Wir haben schließlich OpenGL dazu angewiesen von der neuen Textur "alles" zu nehmen und von dem, was im Frame-Buffer ist "nichts". Auf neudeutsch, wir haben ihn angewiesen die alte Textur über die andere zu zeichnen. Würden wir hingegen:<br />
<br />
<source lang="pascal"><br />
glBlendFunc(GL_ZERO,GL_ONE);<br />
</source><br />
<br />
Verwenden, so würden wir OpenGL dazu anhalten vom Frame-Buffer "alles" zu übernehmen und von der neuen Textur "nichts", das würde bedeuten dass wir ins leere Rendern (sehr gute Methode seine Engine zu optimieren… *sg*)<br />
<br />
Ach so noch etwas… nehmt das mit dem GL_ZERO und GL_ONE und meinem "nichts und alles" nicht ganz so wörtlich. Es ist mehr eine kleine Eselsbrücke, die einem am Anfang hilft nicht total zu verwirren. Technisch macht OpenGL nichts anderes als bei GL_ONE die Farbe der Texture auf (1,1,1,1) zu setzen bei GL_ZERO auf (0,0,0,0); Und sobald es "schwarz" ist, sieht man sie halt nicht mehr, klingt logisch oder?<br />
<br />
=== Claim for the World Order ===<br />
<br />
Wie alles hat auch das Blending einen kleinen Schönheitsfehler. Viele Leute denken immer wieder, dass auch beim Blending praktisch eine 3D-Welt existiert. Das ist jedoch falsch, da die Quelle ja im Frame-Buffer bereits vorliegt. Praktisch bereits fertig gerendert. Es macht also für das Blending einen enormen Unterschied, in welcher Reihenfolge die Objekte zur Kamera hin gerendert werden.<br />
<br />
Würden wir zum Beispiel ein Haus mit einem Fenster render wollen und das Fensterglas mit Hilfe von Blending darstellen und wir würden zunächst das Glas rendern, so würde OpenGL dieses mit dem Hintergrund (vermutlich ne Himmel) blenden und dann anschließend das Haus drum herum zeichnen. Der ahnungslose Programmierer wird dann mit großen Augen vor dem Fenster stehen und sich wundern, wieso das Fenster so merkwürdig aussieht und man praktisch durch das Glas ins Freie sehen kann. Würden wir kein Blending haben, so würde es nicht weiter auffallen in welcher Reihenfolge wir die Objekte gerendert haben.<br />
<br />
Darum gilt, alle Objekte rendern, bevor man zum Blending greift. Man sollte sich also durchaus Gedanken dazu machen, wie man seine Objekte rendert. Es gibt verschiedene Verfahren seine Objekte so ordnen zu lassen, dass das hier beschrieben Problem nicht auftritt, darauf möchte ich nicht näher eingehen, ihr seit gewarnt worden ;D Eigentlich ist es ein recht logischer Vorgang, allerdings erliegt man doch recht schnell diesem kleinen Irrglauben ;)<br />
<br />
=== Gleichheit für Alles! ===<br />
<br />
Das war ja nun auch ein interessanter Ausflug… gelernt haben wir immer noch nichts wirklich Neues, zumindest nichts, was man auch sehen kann. Üben wir uns doch nun einmal richtig ein und versuchen zwei Texturen zu gleichen Maßen zusammen zu blenden, so dass es aussieht, als hätten wir eine Textur. Sicherlich gibt es viele Möglichkeiten dies zu realisieren, allerdings möchte ich an dieser Stelle einen Alpha-Farbwert verwenden. Wie ihr sicherlich wisst, werden Farben meist im RGB-Werten gespeichert, sprich Rot, Grün und Blau! Gesetzt wird dieses mit glColor3f. Ebenfalls gibt es noch die Funktion glColor4f mit der wir einen RGBA-Wert vergeben können. Dieser wird uns vor allem beim Blending nützliche Dienste erweisen.<br />
<br />
Weil beide Texturen zu gleichen Prozentsätzen geblendet werden sollen ergibt sich daraus folglich ein Aufruf von "glColor4f(1,1,1,0.5)";<br />
<br />
Wie ihr sehen könnt setzen wir diesmal glBlendFunc(GL_SRC_ALPHA,GL_ONE); ein. Von der Quelle wird der Alpha-Wert als Blend-Faktor genommen, dass Ziel wird übernommen.<br />
<br />
[[Bild:Tutorial_lektion7_stonegras.jpg]]<br />
<br />
Wenn das nicht aussieht wie eine Textur, die wie aus dem gleichen Guss kommt! Würde noch jemand erahnen, dass es sich dabei eigentlich um zwei Texturen handelt? Für alle, interessierten noch einmal kurz eine Tabelle zur Übersicht der Funktionen, die wir bisher eingesetzt haben und wir noch einsetzen werden, sowie der mathematischen Erklärung ihrer Bedeutung:<br />
<br />
{| {{Prettytable_B1}}<br />
! Konstante<br />
! Berücksichtige Faktoren<br />
! Berechneter Blend-Faktor<br />
|-<br />
| GL_ZERO<br />
| source or destination<br />
| (0, 0, 0, 0)<br />
|-<br />
| GL_ONE<br />
| source or destination<br />
| (1, 1, 1, 1)<br />
|-<br />
| GL_DST_COLOR<br />
| source<br />
| (Rd, Gd, Bd, Ad)<br />
|-<br />
| GL_SRC_COLOR<br />
| destination<br />
| (Rs, Gs, Bs, As)<br />
|-<br />
| GL_ONE_MINUS_DST_COLOR<br />
| source<br />
| (1, 1, 1, 1)-(Rd, Gd, Bd, Ad)<br />
|-<br />
| GL_ONE_MINUS_SRC_COLOR<br />
| destination<br />
| (1, 1, 1, 1)-(Rs, Gs, Bs, As)<br />
|-<br />
| GL_SRC_ALPHA<br />
| source or destination<br />
| (As, As, As, As)<br />
|-<br />
| GL_ONE_MINUS_SRC_ALPH<br />
| source or destination<br />
| (1, 1, 1, 1)-(As, As, As, As)<br />
|-<br />
| GL_DST_ALPHA<br />
| source or destination<br />
| (Ad, Ad, Ad, Ad)<br />
|-<br />
| GL_ONE_MINUS_DST_ALPHA<br />
| source or destination<br />
| (1, 1, 1, 1)-(Ad, Ad, Ad, Ad)<br />
|-<br />
| GL_SRC_ALPHA_SATURATE<br />
| source<br />
| (f, f, f, 1); f=min(As, 1-Ad)<br />
|}<br />
<br />
Sieht anfangs etwas verwirrend aus, aber der Mensch kann sich an vieles gewöhnen. Immerhin lest ihr bereits seit einiger Zeit Texte, die mit meinem Rechtschreibfehlern und meiner Sprache angereichert sind ;D<br />
<br />
Um sicher zu gehen, dass auch wirklich alles verstanden ist nehmen wir uns noch einmal kurz folgenden Aufruf zu herzen:<br />
<br />
<source lang="pascal"><br />
glBlendFunc(GL_SRC_COLOR,GL_ONE);<br />
</source><br />
<br />
Wieder lassen wir den Frame-Buffer wie er ist, verwenden diesmal jedoch keinen Alpha-Wert zu blenden, sondern jeweils die RGB-Werte der Textur. OpenGL macht also praktisch nichts anderes als die Farbwerte zu addieren und anschließend durch zwei zu teilen. Wie man der Tabelle entnehmen kann spielt bei einem solchen Aufruf nur die Destination eine Rolle. Folglich sieht das Ergebnis wie folgt aus:<br />
<br />
[[Bild:Tutorial_lektion7_stonegras2.jpg]]<br />
<br />
Soweit nun zum einfachen Blending. Ihr könnt Euch vorstellen, dass wir damit bereits eine Menge sehr interessanter Effekte erzeugen können, aber nun wollen wir einige davon auch gleich einmal testen und einsetzen ;)<br />
<br />
== Blending-Verfahren ==<br />
<br />
=== Die Welt unter einer Maske ===<br />
<br />
Ein Verfahren, welches sehr häufig zum Einsatz kommt ist das "Masking-Verfahren". Aus dem Namen kann man sich bereits grob vorstellen, worum es geht. Wenn der Mensch sich eine Maske aufsetzt, so versucht er etwas damit zu verdecken (zum Beispiel sein Gesicht). Genauso machen wir es auch mit OpenGL um etwas zu verdecken. In diesem Fall jedoch, das Objekt welches wir unter der Maske liegen haben. Es gibt einen Haufen von sinnvollen Verwendungs-Möglichkeiten. Beim Masking an sich geht es darum, nur einen bestimmten Bereich sichtbar zu machen. Nehmen wir also einmal wieder unsere schön Stein-Textur und erzeugen in einem Malprogramm unserer Wahl eine Maske. Diese wird in diesem Fall komplett weiß sein und die eigentliche Masken-Bereiche sind schwarz.<br />
<br />
[[Bild:Tutorial_lektion7_dgl.gif]]<br />
<br />
Diese Maske zeichnen wir nun selbstredend über der Steintextur und verwenden dabei folgende Blending-Funktion:<br />
<br />
<source lang="pascal"><br />
glBlendFunc(GL_ONE,GL_DST_COLOR);<br />
</source><br />
<br />
Als Ergebnis erhalten wir folgendes:<br />
<br />
[[Bild:Tutorial_lektion7_whitemask.jpg]]<br />
<br />
Alles was an der Quelle weiß ist, wird bei dem Source-Faktor enorm hoch bewertet also sehr "weiß" gemacht. Da Schwarz das Gegenteil von Weiß ist, wird dieses eben total vernachlässigt. Sprich wir können hindurch sehen. Da der Destination-Faktor auf die Farbe des "Ziel-Pixels" gesetzt ist sehen wir dann an den entsprechenden Stellen auch den Hintergrund. Wollen wir das Spielchen mal umdrehen, so dass man nur noch die schwarze Schrift sieht und ansonsten durch die zweite Textur komplett hindurch blicken kann? Kein Problem!<br />
<br />
[[Bild:Tutorial_lektion7_blackmask.jpg]]<br />
<br />
Und die dazugehörige Blend-Funktion :<br />
<br />
<source lang="pascal"><br />
glBlendFunc(GL_ZERO,GL_SRC_COLOR);<br />
</source><br />
<br />
Besonders hoch bei der Quelle werten wir in diesem Fall das Schwarz. Bei der Zielfarbe bewerten wir die Farbe sehr hoch die die zweite Textur hat. D.h. alles was schön weiß ist wird entsprechend geblendet. Wer sich nun am Kopf kratzt sollte sich ein wenig Code schnappen und ein wenig damit herum spielen. Recht schnell merkt man, wie es funktioniert, den Blending ist unter OpenGL gar nicht so schwer! Am besten auch gleich die obere Tabelle ausdrucken und daneben liegen haben ;)<br />
<br />
=== Der Überläufer ===<br />
<br />
Ich hoffe sehr, dass ich Euch jetzt hier nicht zu sehr langweile in dem ich andauernd die eine oder andere Funktion immer wieder und wieder in anderer Form zeige. Ich möchte schlicht und ergreifend damit erreichen, dass ihr ein wenig für die Möglichkeiten die man hat sensilibisiert werdet. Die Vielfalt an Effekten, die man erzielen kann ist nämlich wirklich groß, vor allem, wenn man einzelne Effekte miteinander kombiniert!<br />
<br />
Nehme wir uns noch einmal unsere Stein- und Gras-Textur zur Hand. Diesmal wollen wir versuchen, dass auf der linken Seite die Stein-Textur ist und auf der Rechten die Gras-Textur. Das ganze soll einen flüssigen Übergang ergeben.<br />
<br />
[[Bild:Tutorial_lektion7_graswall.jpg]]<br />
<br />
Auch ein solcher Effekt ist nicht sonderlich schwer. Alles was wir hierfür brauchen sind unterschiedliche Alpha-Werte an den Eckpunkten der Quads. Beim ersten müssen diese links 1 sein und rechts 0, beim anderen entgegengesetzt. Setzen tun wir die Alpha-Werte mit glColor4f:<br />
<br />
<source lang="pascal"><br />
Rock.SetTexture;<br />
glBegin(gl_Quads);<br />
glColor4f(1,1,1,1);<br />
glTexCoord2f(0,0);<br />
glVertex3f(-1,-1,0);<br />
<br />
glColor4f(1,1,1,0);<br />
glTexCoord2f(1,0);<br />
glVertex3f(1,-1,0);<br />
glTexCoord2f(1,1);<br />
glvertex3f(1,1,0);<br />
<br />
glColor4f(1,1,1,1);<br />
glTexCoord2f(0,1);<br />
glvertex3f(-1,1,0);<br />
glEnd;<br />
<br />
glEnable(GL_BLEND);<br />
glBlendFunc(GL_SRC_ALPHA,GL_DST_ALPHA);<br />
<br />
gras.SetTexture;<br />
glBegin(gl_Quads);<br />
glColor4f(1,1,1,0);<br />
glTexCoord2f(0,0);<br />
glVertex3f(-1,-1,0);<br />
<br />
glColor4f(1,1,1,1);<br />
glTexCoord2f(1,0);<br />
glVertex3f(1,-1,0);<br />
glTexCoord2f(1,1);<br />
glvertex3f(1,1,0);<br />
<br />
glColor4f(1,1,1,0);<br />
glTexCoord2f(0,1);<br />
glvertex3f(-1,1,0);<br />
glEnd;<br />
</source><br />
<br />
Während das erste Quad ohne Blending gezeichnet wird, wird das zweite geblendet, jeweils mit den Alpha-Werten der Quelle und des Zieles. Um dies zu verdeutlichen:<br />
<br />
<br />
[[Bild:Tutorial_lektion7_redbluealpha.jpg]]<br />
<br />
Je blauer die Farbe, desto weniger wird die Wand gefadet, je roter, desto mehr Gras wird angezeigt. In der Mitte verlaufen beide Texturen zusammen. Natürlich kann man nun anfangen zu spielen und gibt den Seiten andere Alpha-Werte. Genauso wäre es möglich drei Texturen ineinander einzublenden etc. Seit kreativ ;D<br />
<br />
=== Der Überläufer - Teil 2 ===<br />
<br />
Der eben beschriebene Effekt lässt sich auch noch sehr schön erweitern. Haben wir dort eine gerade Trennlinie zwischen den beiden Texturen gehabt, so werden wir uns nun mit einer Technik befassen, die es einem ermöglicht unregelmäßige Übergange zu erzeigen. Dieses Verfahren wird besondern oft bei Terrains angewandt, um zum Beispiel eine Schneelandschaft langsam ins Geröll überwandern zu lassen. Je nach Verfahren benötigen wir hierfür drei oder vier Texturen. Wir beschränken uns hierbei einfach auf drei.<br />
<br />
Würden wir zwei Texturen übereinander zeichnen, so würde die obere die untere Verdecken, würden wir bei einem Quad mit Alpha-Werten arbeiten würden die Übergange nicht flüssig sein. Würden wir die Farben addieren, würde so ziemlich alles dabei rauskommen, nur nicht einen Tetxuren-Verlauf, wie wir ihn erreichen wollen.<br />
<br />
[[Bild:Tutorial_lektion7_blendmap.jpg]]<br />
<br />
Auf diesem Bild erkennt man sehr schön den Texturen-Verlauf, während im oberen Bereich herum bis nach links unten und rechts oben die steinige Felstextur ist, ist im unteren Bereich mehr die Gras-Textur, mit einer Ausnahme mitten in der Gras-Textur ist wiederum ein kleiner Abschnitt mit einer felsigen Textur (hier lila). Auch erkennt man auf dem zweiten Blick, dass an den Grenz-Bereichen ein wenig das Grüne auf dem Felsen durchschimmert, wir haben einen fließenden Übergang zu beiden Texturen erzeugt und können sogar im "Gebiet des Anderen" eine Textur durchscheinen lassen. Die Lösung ist wiederum eine Art von Masking:<br />
<br />
[[Bild:Tutorial_lektion7_blendmap2.jpg]]<br />
<br />
Vergleicht man dieses Bild mit dem oberen wird man erkennen, dass die hellern Flecken "Stein-Textur" und die dunklen "Gras" bedeuten. Man redet von solchen Texturen meist von "Blend-Maps". Technisch braucht man also nur eine Blend-Map für eine Textur und ein invertiertes Bild davon. An diesem Punkt können wir uns das invertieren bereits sparen, weil wir nur zwei Texturen blenden wollen und das Gegenteil von weiß, schwarz ist ;)<br />
<br />
== Lichterkarten ==<br />
=== Und es ward Licht… ===<br />
<br />
Ein wirklich tolles Spielzeug, welches einen mit Blending in die Hand gelegt wird sind "Lightmaps". Vielleicht hat jemand von Euch bereits einmal versucht in seinem Projekt so schöne Lichtquellen einzubauen, wie er es aus bekannten kommerziellen Spielen kennt und hat sofort anschließend ein langes Gesicht gezogen als die Augen auf die Framerate blickten. Dazu kommt, dass das OpenGL-Licht meist nicht einmal besonders ansehnlich ist. Ärgerlich…<br />
<br />
Doch es gibt natürlich eine Möglichkeit schnell und effizient mit wenig Aufwand genauso schöne Lichter in seinem Projekt einzubauen. Das Geheimnis ist schlicht und ergreifend, dass wir nicht auf OpenGL-Lichter aufbauen, sondern einen kleinen Trick auf unsere Benutzer spielen. Wir zeichnen vor dem Zielobjekt eine neue Texture, die nur aus Graustufen besteht. Je heller ein Punkt ist, desto mehr soll er anschließend leuchten.<br />
<br />
<br />
{| align="center"<br />
|[[Bild:Tutorial_lektion7_lightmap.jpg]]<br />
|[[Bild:Tutorial_lektion7_wall_tex.jpg]]<br />
|}<br />
<br />
Macht grafisch doch schon einiges mehr her als die ursprüngliche Textur oder? Wie bereits gesagt, dass Geheimnis sind die Lightmaps, die wir vor der Wand zeichnen:<br />
<br />
{| align="center"<br />
|[[Bild:Tutorial_lektion7_light.jpg]]<br />
|[[Bild:Tutorial_lektion7_light2.jpg]]<br />
|}<br />
<br />
Mit etwas Fantasie kann man sie sogar immer noch erkennen. Man beachte, dass die erste Lightmap vor allem im oberen Bereich besonders hell ist und vor allem einen sehr hellen Punkt im oberen Bereich hat. Blicken wir auf das fertige Bild, so sehen wir dort einen roten Punkt. Die zweite Lightmap ist eher gleichmäßig und matt, nicht sonderlich hell, zumindest nicht auffällig. Sie sorgt dafür, dass ein gleichmäßiger Grünton auf der Wand liegt. Doch wie kann aus der weißen Lightmap z.B. eine Rote werden? Das ist relativ leicht, wir zeichnen das Quad vor der Wand einfach rot mit Hilfe von glColor3f(1,0.7,0.7); Damit die Farbe auch richtig zur Geltung kommt müssen diese natürlich auch beim Blending entsprechend einarbeiten!<br />
<br />
<source lang="pascal"><br />
glBlendFunc(GL_SRC_COLOR,GL_DST_COLOR);<br />
</source><br />
<br />
Bei der Quelle wird vor allem die Farbe berücksichtigt, bei der Destination die Zielfarbe. Jeweils zwei Quads werden nach diesem Verfahren über die Wand gezeichnet, um den Effekt zu erreichen. Bin mir durchaus bewusst, dass man schönere Lightmaps machen kann, auch ist es zum Beispiel sehr interessant bei der Zielfarbe GL_SRC_COLOR zu verwenden, wodurch die gesamte Wand verdunkelt wird und man nur noch um das rote leuchten oben etwas sehen könnte (in diesem Fall nur eine Lightmap!)<br />
<br />
[[Bild:Tutorial_lektion7_lightmap2.jpg]]<br />
<br />
Der Kreativität sind eigentlich keine Grenzen gesetzt. Alle modernen 3D-Engines berechnen bei einem Level an Hand der Lichtquellen solche Lightmaps und projizieren diese dann vor den Wänden, um sie aussehen zu lassen, wie sie aussehen sollen. Keiner dieses wunderschönen Effekts wird man mit einem OpenGL-Licht realisieren können. Wozu auch, wenn es so doch viel schneller geht ;)<br />
<br />
=== Die Kraft des Gemeinsamen! ===<br />
<br />
Wir haben uns hier sehr viel mit Blending auseinander gesetzt und sicherlich juckt es Euch bereits unter den Fingern Wände mit wunderschönen Lichteffekten an die Wand zu zaubern. So mindestens 300 Lichter sollten sicherlich zum Minimum zählen, oder ;D<br />
<br />
Nun schnell werdet ihr vor allem in größeren Projekten merken, dass Euch das auf die Rechenleistung schlägt. Das Problem ist logisch! Nicht das Blending an sich ist die größte CPU-Last, sondern das Transformieren der Quads, auf denen sich die Lightmaps befinden. Eigentlich sind es fast nur die Transformationen, die so richtig mit Leistung zu Buch schlagen. Es gilt also so wenig wie mögliche Quads hintereinander zu erzeugen und somit die Anzahl der Durchläufe zu minimieren (Renderpass). Das ließe sich zum Beispiel damit erreichen, dass man versucht möglichst viele Lichtinformationen auf einer Lightmap zu setzen. So würde es keinen Sinn machen zwei rote Lichter einmal oben und einmal unten.<br />
<br />
Dies sind jedoch nur logische Einsparungen, die jeder Programmierer in seinem Projekt selbst entscheiden muss. Seit einiger Zeit gibt es jedoch auch ein Hardware-Verfahren, welches uns helfen kann ein Spiel zu optimieren. Zu Beginn der 3D-Beschleunigung verlief das Rendering wie folgt:<br />
<br />
# Texture setzen<br />
# Objekt transformieren<br />
# Texture setzen<br />
# Objekt transformieren<br />
<br />
Klingt soweit auch alles logisch! Wie bereits jedoch erwähnt frisst vor allem das transformieren die Rechenpower und in unserem Fall liegt das Objekt übereinander, es wäre eine Transformation der Vertizen eigentlich gar nicht nötig. Wir müssten also nur die beiden Texturen auf ein transformiertes Objekt bekommen. Würden wir einfach nur zwei Texturen hintereinander setzen, so würde OpenGL die zuletzt gewählte verwenden und die andere schlichtweg ignorieren. Die meisten heutigen Grafikkarten (z.B: GeForce2 MX) haben zwei Textur-Units. Das muss man sich vorstellen wie zwei Schubladen. In beiden legen wir eine Textur rein und wenn OpenGL rendert werden reingeschaut und beide auf das Ziel gepappt. Somit haben wir uns eine Transformation gespart, was vor allem wenn man sehr viel mit Lightmaps arbeitet schon ein beachtlicher Geschwindigkeitsgewinn von knapp 50% ist! Einige sehr moderne Grafikkarten haben bereits 4 Texturen-Units und die Zeit bis es welche mit 8 gibt ist auch schon bereits voraussehbar ;)<br />
<br />
Wie funktioniert das ganze nun für uns? OpenGL verwendet als Standard die Unit 0. Binden wir eine Textur oder definieren das Blending so beziehen sich alle diese Angaben immer auf die momentan selektierte Textur-Unit. Alles was wir machen müssen ist, die gewünschte auszuwählen:<br />
<br />
<source lang="pascal"><br />
glActiveTextureARB(GL_TEXTURE0_ARB);<br />
</source><br />
<br />
Ich denke der Parameter erklärt sich praktisch von selbst! GL_TEXTURE1_ARB wäre entsprechend die zweite Textur-Unit. Nun setzen wir alle Einstellungen für diese Unit:<br />
<br />
<source lang="pascal"><br />
glEnable(GL_TEXTURE_2D);<br />
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);<br />
wall.SetTexture;<br />
</source><br />
<br />
Zunächst teilen wir der Unit mit, dass sie bei der 2D-texturierung zur Verfügung stehen soll. Anschließend setzen wir die Textur-Envroiment auf die "Standard-Einstellung". Wenn mich jetzt jemand fragt, wofür die Parameter gut sind, dann zitiere ich das RedBook "es muss so sein" ;D Wichtig ist eigentlich nur, dass hinten GL_MODULATE steht, damit die Textur ordnungsgemäß, so wie wir sie sehen auf das Quad wandern.<br />
<br />
<source lang="pascal"><br />
glActiveTextureARB(GL_TEXTURE1_ARB);<br />
glEnable(GL_TEXTURE_2D);<br />
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);<br />
glEnable(gl_BLEND);<br />
glBlendFunc(GL_ONE,GL_ONE);<br />
light.SetTexture;<br />
</source><br />
<br />
Wir wechseln danach zur zweiten Textur-Unit und machen ähnliche Einstellungen, aktivieren zusätzlich jedoch Blending für diese Funktion. Diesmal etwas sehr simples.<br />
<br />
Wer nun gehofft hat, dass dies alles gewesen wäre, was man für das MultiTexturing benötigt, der wird enttäuscht sein. Eine Kleinigkeit gibt es noch zu beachten. Da wir zwei Texturen verwenden kann es ja auch sein, dass die UV-Koordinaten unterschiedlich sein sollen. In unserem Fall ist dies nicht so, aber es kann durchaus passieren. Deswegen müssen wir auch für jeden Eckpunkt eine UV-Koordinate vergeben. Hierfür verwenden wir nicht glTexCoord2f, sondern die Funktion:<br />
<br />
<source lang="pascal"><br />
glMultiTexCoord2fARB(GL_TEXTURE0_ARB,0,0);<br />
glMultiTexCoord2fARB(GL_TEXTURE1_ARB,0,0);<br />
</source><br />
<br />
Zunächst geben wir bei der Funktion den Parameter ein gefolgt von der eigentlichen UV-Koordinate. So gehen wir dann bei jedem Punkt vor und vorausgesetzt, dass unsere Grafikkarte auch Multitexturing unterstütz sollten wir uns an folgendem Bild auf dem Bildschirm ergötzen können (natürlich nur, wenn man auch die gleichen Texturen verwendet! ;D)<br />
<br />
[[Bild:Tutorial_lektion7_multitex.jpg]]<br />
<br />
== Nachwort ==<br />
<br />
Oh je, ich weiß, ich habe inzwischen einen Ruf dafür, dass ich immer gleich so große Portionen liefere und nicht in der Lage bin, einmal kurz ein kleines Tutorial zu schreiben. Das liegt einfach daran, dass wenn ich einmal angefangen bin und mich für etwas begeistern konnte nicht mehr aufhören kann. Und gerade bei solchen Themen, wo man so richtig schön mit Effekten experimentieren kann fällt es einem besonders schwer einen Punkt zu finden.<br />
<br />
Ich denke spätestens nach diesem Tutorial sollte man gelernt haben wie leicht man mit OpenGL einige wunderschöne Effekte zaubern kann ohne wirklich großen Aufwand zu betreiben. Viel leichter wird es kaum möglich sein. Sollte jemand nun sich am Kopf kratzen und sich fragen, ob es einen Grund hat, warum die ganzen hier vorgestellten Beispiele 2D sind? Nein, eigentlich nicht, denn alle der hier vorgestellten Techniken lassen sich auch prima auf 3D-Objekte übertragen. So wäre es zum Beispiel auch Möglich einen 3D-Soldaten mit einer Lightmap und Multitexturing zu überziehen und somit unterschiedliche Helligkeit auf ihn zu projizieren. Aber darauf will ich nicht weiter eingehen, nun kommt wieder Euer Part. ;)<br />
<br />
Auch bin ich sehr froh zu sehen, dass die Community wächst und auch endlich einen Punkt erreicht an der man gezielt auf Probleme einwirken kann. Ich hoffe sehr, dass dies auch weiterhin so bleibt und bin auch sehr froh, dass zunehmend Feedback von Eurer Seite kommt, gar einige damit beginnen Kontent für uns zur Verfügung zu stellen oder uns auf kleinere Fehler aufmerksam macht oder aber auch einfach nur auf eine gute Seite aufmerksam machen, die wir bisher nicht gelinkt haben. Wenn jemand von Euch einmal ein kleines OpenGL-Projekt vorstellen möchte, etwas an dem er arbeitet, schreibt uns doch ruhig auch einmal an. Mehr als Ablehnen könnten wir auch nicht ;D<br />
<br />
In diesem Sinne viel Spaß und auf Wiedersehen.<br />
<br />
Euer [[Benutzer:Phobeus|Phobeus]]<br />
<br />
== Dateien == <br />
* {{ArchivLink|file=tut_lektion_7_delphi_api|text=Alter Delphi-API-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_7_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION | [[Tutorial Lektion 5]] | [[Tutorial Lektion 8]]}}<br />
<br />
[[Kategorie:Tutorial|Lektion7]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Lektion_5&diff=23625Tutorial Lektion 52009-05-17T13:46:51Z<p>Flo: </p>
<hr />
<div>= Artenvielfalten und Ihre Folgen =<br />
<br />
== Vorwort ==<br />
<br />
Und wieder einmal aufraffen und etwas tippen. Es ist wirklich nicht immer leicht solche Tutorials zu schreiben, vor allem wenn man mal wieder eine Null-Bock-Phase hat. Ich hoffe doch sehr, dass auch Ihr dafür Verständnis habt, den… auch ich bin ein Mensch… egal wie viele Augen und Beine ich habe *sg* Gut… genug "gefaxt", es geht wieder um den Ernst im Leben: Delphi<br />
<br />
Ich habe wirklich eine zeitlang überlegt, was wir machen sollten. Einige haben sich Lichter gewünscht, aber ich konnte mich nicht dazu aufraffen… Stattdessen habe ich irgendwie Lust gehabt, mal was anderes zu machen, etwas wo man etwas kreativ sein kann und was auch mir Spaß macht! :-D Und deswegen, habe ich mir gedacht, dass wir das eine oder andere Wissen, welches zwischendurch behandelt wurde noch etwas vertiefen sollten… einfach in dem wir andere Möglichkeiten aufzeigen, elegant ein Problem zu umschiffen und auch den einen oder anderen Effekt erzielen!<br />
<br />
Und genau darum geht es auch diesmal! Ich denke, dass sich dieses Tutorial weitestgehend nur an die richten wird, die bereits ein solides Wissen in OpenGL und Delphi haben, ansonsten wird es wohl schwer sein mir zu folgen, ich werde einiges an Wissen woraus setzen! Wer folgen kann, wird dann mit einem Wissen belohnt werden, dass ihm das eine oder andere Probleme sehr elegant umschiffen lässt in dem man einfach seine Software entsprechend mit OpenGL optimiert! Den nur wer sein Handwerk bis ins Detail beherrscht, darf sich Meister nennen ;)<br />
<br />
== Unbekannte Zeichen-Arten ==<br />
<br />
=== OpenGL-Maxime ===<br />
<br />
Wer nicht gerade erst jetzt hier eingestiegen ist, wird sicherlich bereits bemerkt haben, dass OpenGL streng genommen nach einem sehr einfachen Prinzip arbeitet. Ständig wird an unserer "Zustand-Maschine" etwas manipuliert und mit Matrizen setzen wir die Positionen fest. Jedoch nur an einer einzigen Stelle kommen alle diese Werte zusammen. Nämlich dann, wenn etwas gerendert wird. Genau in diesem Moment werden alle Werte "zusammengerechnet" und erzeugen etwas Sichtbares auf dem Bildschirm. In den meisten Fällen wird dies Eben zwischen glBegin und glEnd geschehen. Und genau diese beiden Funktionen wollen wir nun näher betrachten. Interessant hierbei ist nämlich der Parameter von glBegin…<br />
<br />
Streng genommen definieren wir nur eine Anzahl von Punkten zwischen glBegin und glEnd, der Parameter bei glBegin bestimmt aber letztendlich wie diese Verstanden werden sollen. Nur als kleine Übersicht alle verfügbaren Parameter:<br />
<br />
GL_POINTS<br />
GL_LINES<br />
GL_LINE_STRIP<br />
GL_LINE_LOOP<br />
GL_TRIANGLES<br />
GL_TRIANGLE_STRIP<br />
GL_TRIANGLE_FAN<br />
GL_QUADS<br />
GL_QUAD_STRIP<br />
GL_POLYGON<br />
<br />
Ich denke aus ethischen Gründen werde ich nun darauf verzichten Euch erneut zu erklären wofür, die bisher für Euch bekannten Parameter (gl_Triangle und gl_Quad) gut sind… jeder wird sich denken können, dass das erste immer per 3er Punkte ein Dreieck bildet, dass zweiter aus 4 Punkten ein Quadrat erzeugt… (mist… *g*) ;)<br />
<br />
<br />
=== Zunächst war der Strich ===<br />
<br />
Interessanter, da neu für uns, sind die restlichen Parameter, auch wenn diese sich weitestgehend selbst erklären. Gl_Points z.B. … könnte es vielleicht bedeuten, dass OpenGL alle mit [[glVertex3f]] definierten Punkte auch nur als Punkte zeichnet?<br />
<br />
[[Bild:Tutorial_lektion5_gl_point.gif]]<br />
<br />
Scheint zu stimmen… ;) Es ist nun auch nicht besonders schwer herzuleiten, was OpenGL mit Vertex-Definitionen macht, die mit gl_Lines beginnen. Versuchen wir doch mal ein Dreieck damit zu zeichnen! Ergo brauchen wir 4 Punkte (der letzte muss, auf den ersten verweisen)…<br />
<br />
[[Bild:Tutorial_lektion5_gl_lines.gif]]<br />
<br />
Doch was ist das? Dies ist kein böswilliger Trick, den ich auf Euch spielen will, sondern folgender Code, ist dafür verantwortlich:<br />
<br />
<source lang="pascal"><br />
glBegin(GL_LINES);<br />
glVertex3f(-1,0,0);<br />
glVertex3f(0,1,0);<br />
glVertex3f(1,0,0);<br />
glVertex3f(-1,0,0);<br />
glEnd;<br />
</source><br />
<br />
Sieht soweit alles in Ordnung aus - ist es auch! Der Fehler liegt nämlich nicht am Code, sondern an uns selbst. Gl_Lines bewirkt nämlich nicht, dass alle Punkte miteinander verbunden werden, sondern nur, dass jeweils Punkte 1 mit Punkte2, Punkte 3 mit Punkt 4 etc. verbunden werden, d.h. immer zweiter Pärchen. Um das Ergebnis zu erreichen, welches wir angestrebt haben (und zwar ohne 6 Punkte zu definieren), wäre in diesem Fall nämlich gl_Line_Strip gewesen, was nämlich bewirkt, dass der "Zeichenstift" immer von seiner letzten Position zum nächst definierten Punkte einen Strich zieht. Dementsprechend sieht dann unser Dreieck mit praktisch dem gleichen Source wie folgt aus:<br />
<br />
[[Bild:Tutorial_lektion5_gl_line_strip.gif]]<br />
<br />
hr merkt bereits jetzt wie viele Möglichkeiten einen OpenGL bietet mit nur wenigen Zeilen eine Menge zu verändern. Man bedenkt, dass wir hier nur sehr wenige Punkte haben, allerdings kann man eine Menge Leistung rausschlagen, wenn man statt gl_Lines, gl_Line_Strip verwendet, da einfach weniger Punkte abgearbeitet werden müssen. Logisch oder?<br />
<br />
GL_LINE_LOOP<br />
<br />
Der Parameter entspricht praktisch gesehen GL_LINE_STRIP, nur das der letzte Punkt mit dem ersten verbunden wird. Man bräuchte mit diesem Parameter also nur 3 Punkte… das Optimum für unser Dreieck! ;)<br />
<br />
== Artenvielfalt ==<br />
<br />
Natürlich lassen sich solche Zeichenoperationen auch durch eine Menge anderer Faktoren beeinflussen. Vielleicht hat sich der eine oder andere ja bereits gefragt, wie man es schafft, dass der Rahmen des Dreiecks dicker gezeichnet wird. Sicherlich könnte man nun beginnen und ganz leicht versetzt daneben noch ein Dreieck zu zeichnen. Dies würde dann natürlich einen kleinen Tick größer wirken.<br />
<br />
Die Lösung liegt allerdings viel näher - den OpenGL bietet hierfür eine hauseigene Lösung:<br />
<br />
<source lang="pascal"><br />
glLineWidth(3);<br />
</source><br />
<br />
Wir stellen einfach ein, wie OpenGL die Linien Rastern soll. Wir nehmen in diesem Fall die dreifache Dicke von der normalen Einstellung:<br />
<br />
[[Bild:Tutorial_lektion5_gllinewidth.gif]]<br />
<br />
Auch werden einige von Euch sicherlich die unschönen Treppchen kennen, die vor allem bei solch einfache Konstruktionen wie diesm dickeren Dreieck auftreten können. Die Lösung dagegen heißt bekanntlich "AntiAliasing" … verschlägt es Euch die Sprache, dass ich gleich mit solch derben Geschützen auffahre?<br />
<br />
<source lang="pascal"><br />
glEnable(GL_LINE_SMOOTH);<br />
glDisable(GL_LINE_SMOOTH);<br />
</source><br />
<br />
Da verschlägt es einem die Sprache oder? Das war es nämlich auch bereits wieder. Wir schalten einfach einen Zustand um und fertig war die Geschichte! Der Zustands-Maschine von OpenGL sei dank, müssen wir nur noch sagen, was gemacht werden soll, ein lästiges "Wie?" entfällt komplett<br />
<br />
Und noch ein kleines Beispiel dafür, wie einfach OpenGL nicht nur Informationen schreiben lässt, sondern diese auch wieder preisgibt. Haben wir vergessen wie Dick wir die Linien eingestellt haben? Kein Problem, den OpenGL bietet folgende Funktionen um zu ermitteln, auf welchen Wert ein "Zustand" geschaltet ist:<br />
<br />
[[glGetBooleanv]], [[glGetDoublev]], [[glGetFloatv]], [[glGetIntegerv]]<br />
<br />
Ich glaube ich brauche nicht wieder damit zu beginnen darauf hinzuweisen, dass man bei jeder Abfrage auch den richtigen Variablen-Typen verwenden sollte *g*<br />
<br />
<source lang="pascal"><br />
glGetIntegerv(gl_line_width,@myint);<br />
</source><br />
<br />
Und schon haben wir in myint die Stärke mit der OpenGL momentan alle Linien zeichnen soll. Das geht mit fast allen Zuständen, die OpenGL haben kann. Ich denke, das Grundprinzip ist recht leicht verständlich. Beachtet auch, dass OpenGL bei solchen Funktionen immer einen Pointer auf eine Variable erwartet (sprich besser: eine Adresse) und nicht die Variable selbst!<br />
<br />
Achso… vergaß ich zu erwähnen, dass wir auch weitere Funktionen auf Striche anwenden können? Zum Beispiel glColor, um den Strich einzufärben? Auch der Z-Buffer lasst sich darauf anwenden, eben alles, was man auch bei einem Dreieck tun könnte (wovon man aber zwingend absehen sollte eine Linie zu texturieren, um keine unnötigen Berechnungen durchzuführen)<br />
<br />
=== Ein Dreieck, hat drei Ecken… ===<br />
<br />
Nun dreht sich erstmal alles um Dreiecke. Den auch hierfür bietet OpenGL mehre Möglichkeiten, wie die Reihenfolge der Punkte verstanden wird:<br />
<br />
GL_TRIANGLES<br />
GL_TRIANGLE_STRIP<br />
GL_TRIANGLE_FAN<br />
<br />
Zunächst widmen wir uns GL_TRIANGLES! Dieser Parameter sollte uns allen noch geläufig sein. Jeweils 3 Punkte werden zusammen zu einem eigenständigen Dreieck verbunden. Nicht neues für uns, bereits im ersten Tutorial könnt ihr den Beweis finden, dass es klappt :-D<br />
<br />
Sicherlich werdet ihr Euch denken können, dass dies sehr hilfreich ist, wenn man nur ein Dreieck zeichnen möchte, nicht jedoch, sobald man mehre in einem Rutsch auf dem Bildschirm bringen möchte. Hierzu bietet sich dann eher GL_TRIANGLE_STRIP an:<br />
<br />
<source lang="pascal"><br />
glBegin(GL_TRIANGLE_STRIP);<br />
glColor3f(1,0,0);<br />
glVertex3f(-1,0,0);<br />
<br />
glColor3f(1,1,0);<br />
glVertex3f(0,1,0);<br />
<br />
glColor3f(1,1,1);<br />
glVertex3f(1,0,0);<br />
<br />
glColor3f(0,1,1);<br />
glVertex3f(2,1,0);<br />
<br />
glColor3f(0,1,0);<br />
glVertex3f(3,0,0);<br />
glEnd;<br />
</source><br />
<br />
Wem das ganze zu schwer erscheint, sollte er sich die glColor3f weglassen, die habe ich reingesetzt, damit das ganze auch schön aussieht ;) (Programmierer lieben sinnlosen Spielkram der glänzt und bunt ist). Wer sich nur auf die Punkte konzentriert und sich die Position im Kopfe vorstellt, wird ein Gebilde wie folgt vorstellen können:<br />
<br />
[[Bild:Tutorial_lektion5_gl_triangle_strip.gif]]<br />
<br />
Streng genommen macht OpenGL nichts anderes, als die ersten drei Eckpunkte zu nehmen und ein Dreieck daraus zu rendern. Anschließend fällt der erste Punkt weg und es wird das Dreieck zwischen 2,3,4 gerendert usw. Im Anschluss dieses Kapitels werden wir nochmals hieraus zurückkommen und das Culling erklären!<br />
<br />
=== Ventilatoren und OpenGL ===<br />
<br />
Tja, und nur die wenigstens wissen, dass auch ein Ventilator ein Dreieck sein kann *hust* Okay, begnügen wir uns hier lieber mit dem englischen Begriff "Fan". (Das ist nicht der Kerl, der vorm Fenster steht, einem gierig anstarrt und laut ruft "Ich will ein Kind von Dir…" … nein, sicher nicht ;)<br />
<br />
Vielmehr sollten wir uns wirklich mal bildlich einen Fahrrad-Reifen vorstellen. Von den Außenseiten verlaufen die einzelnen Speichen alle zu einem Mittelpunkt. Nach einem ähnlichen Render-Prinzip funktioniert auch GL_TRIANGLE_FAN. Der einfachheitshalber werden wir hier jedoch kein komplexes Objekt anfertigen, sondern nur eine Möglichkeit zeigen, wie man mit dieser Einstellung sinnvoll ein Objekt zeichnen kann! In unserem Fall nehmen wir einfach einen Drachen (nein… nicht das Fabelwesen), denn auch dieser ist ein Ventilator (Chaos… perfekt….) ;)<br />
<br />
Bevor nun jeder abdreht, schauen wir uns doch mal das Objekt an, von dem ich sprach:<br />
<br />
[[Bild:Tutorial_lektion5_gl_triangle_fan.gif]]<br />
<br />
In diesem Fall benötigen wir nur 6 (!) Punkte, um dieses Gebilde zu erzeugen. Zentraler Ausgangspunkt ist hierbei die Nummer 1. Wie man erkennen kann besteht das erste Dreieck aus 1,2,3. Das zweite aus 1,3,4… für die abstrakt denkenden Menschen lässt sich daraus folgern, dass OpenGL die Punkte wie folgt abarbeitet 1, n+1, n+2. Oder um es neudeutsch zu sagen, der erste Punkte wird immer mit zwei weiteren verbunden. Im Gegensatz zum Strip fällt allerdings nicht der erste Punkt raus, sondern immer der zweite.<br />
<br />
Gl_Triangle_Fans und Gl_Triangle_Strips sind wohl die besten Möglichkeiten, um die Anzahl der im Video-Speicher befindlichen Daten zu begrenzen. Allerdings lässt sich schnell erahnen, dass nicht jeder dieser Methoden wirklich komplexe Objekte zulässt. Man sollte sie jedoch nicht total ignorieren, sondern mit Verstand einsetzen. Ach so, bevor ich es vergesse, der Code dafür:<br />
<br />
<source lang="pascal"><br />
glBegin(GL_TRIANGLE_FAN);<br />
glColor3f(1,1,0);<br />
glVertex3f(0,0,0);<br />
glColor3f(1,0,0);<br />
glVertex3f(0,1,0);<br />
glVertex3f(1,0,0);<br />
glColor3f(0,0,1);<br />
glVertex3f(0,-3,0);<br />
glColor3f(1,0,0);<br />
glVertex3f(-1,0,0);<br />
glColor3f(1,0,0);<br />
glVertex3f(0,1,0);<br />
glEnd;<br />
</source><br />
<br />
Man beachte, dass Punkte 2 und Punkte 6 identisch sind, da er sonst das letzte Dreieck weglassen würde ;)<br />
<br />
=== Quadratisch, praktisch, gut! ===<br />
<br />
Das ist ne wahrer Marathon geworden. Schlimm, wenn man bedenkt, dass wir uns hier nur mit einem Parameter für eine Funktion beschäftigen. Ihr versteht, warum ich so oft geschrieben haben "lassen wir es lieber und klären nicht jeden Parameter" ;) Aber gut, ich halte es für sehr wichtig, solche Dinge zu beherrschen, weil es einfach zu den Grundlagen dazugehört! Immerhin bleiben nicht mehr sonderlich viele übrig, als ran an die letzten drei!<br />
<br />
GL_QUADS<br />
<br />
Sollte auch niemanden von uns mehr sonderlich schockieren können. Jeweils vier übergebende Punkte werden zusammengefasst zu einem Quadrat… nichts weiter Aufregendes. Eigentlich sollten inzwischen auch die GL_QUAD_STRIP nichts wirklich erstaunliches mehr liefern. Wir übergeben zunächst vier Punkte und bei jedem Durchgang entfallen die beiden ersten und werden durch die nächsten zwei ersetzt. OpenGL erstellt dann daraus jedes Mal ein Viereck. Bitte nicht verwechseln mit der Punkt-Reihenfolge von GL_QUADS. Folgendes Beispiel sollte die Problematik verdeutlichen:<br />
<br />
<source lang="pascal"><br />
glBegin(GL_QUAD_STRIP);<br />
glColor3f(1,0,0);<br />
glVertex3f(0,0,0);<br />
glVertex3f(0,1,0);<br />
<br />
glColor3f(0,0,1);<br />
glVertex3f(1,0,0);<br />
glVertex3f(1,1,0);<br />
<br />
glColor3f(0,1,0);<br />
glVertex3f(2,0,0);<br />
glVertex3f(2,1,0);<br />
<br />
glColor3f(0,1,1);<br />
glVertex3f(3,0,0);<br />
glVertex3f(3,1,0);<br />
glEnd;<br />
</source><br />
<br />
<br />
[[Bild:Tutorial_lektion5_gl_quad_strip.gif]]<br />
<br />
Unser erstes Quadrat besteht aus den Punkten 1,2,3,4, dass zweite aus 3,4,5,6 etc. Ich denke, dass das Prinzip dahinter leicht verständlich ist. Anbieten tut sich diese Lösung meist in Schleifen, wenn man längere solcher Quad-Strukturen braucht, die aneinander gereiht sind<br />
<br />
=== Vieleckerei ===<br />
<br />
Und zu guter letzt GL_POLYGON, was wohl am einfachsten nachzuvollziehen ist ;) Es wird einfach eine Liste von Punkten übergeben und daraus wird schlicht und ergreifend dann ein Vieleck gemacht. Wie variantenreich dies werden kann, könnt ihr Euch vorstellen. Vor allem komplexere und exotischere Formen lassen sich damit darstellen, auch wenn es in diesem Fall eher Leitung kostet als wirklich einbringt. Zeichnet also niemals ein Quadrat mit GL_POLYGON.<br />
<br />
Eine mögliche Form wäre zum Beispiel diese:<br />
<br />
[[Bild:Tutorial_lektion5_gl_polygon.png]]<br />
<br />
<source lang="pascal"><br />
glBegin(GL_POLYGON);<br />
glColor3f(1,0,0);<br />
glVertex3f(0,0,0);<br />
glColor3f(0,1,0);<br />
glVertex3f(3,0,0);<br />
glColor3f(0,0,1);<br />
glVertex3f(4,1,0);<br />
glColor3f(1,1,0);<br />
glVertex3f(2,2,0);<br />
glColor3f(0,1,1);<br />
glVertex3f(-2,3,0);<br />
glColor3f(1,1,1);<br />
glVertex3f(-1,1,0);<br />
glEnd;<br />
</source><br />
<br />
Ich denke zusammen mit dem Code sollte es keine weiteren Fragen mehr dazu geben. Am besten setzt Ihr Euch nun alle einmal hin und wendet das Wissen Testweise an, denn es muss sitzen und stellt vor allem, wenn es um optimierte Programmierung geht ein absolutes Grundwissen da! Ihr werdet mit etwas praktischer Erfahrung schnell die Grenzen finden, die die einzelnen Parametern mit sich bringen z.B. bei der Texturierung von Objekten!<br />
<br />
Ein Quadrat besteht aus einem Face (Fläche), ein Quadrat zusammengesetzt aus zwei Triangle aus zwei. Man könnte zum Beispiel letzteres mit zwei Texturen versehen, ersteres nur mit einer!<br />
<br />
== Das Culling-Verfahren ==<br />
<br />
=== Wahrheiten und Wirklichkeiten ===<br />
<br />
;Dramatik:<br />
Bevor wir uns mit der eigentlichen Technik des Cullings befassen, möchte ich eine radikale Aufklärung betreiben, die Euer Weltbild für immer verändern wird. Seit gewarnt, dass Ihr nach dem lesen der folgenden Zeilen, Eure Quake3-Gegner mit ganz anderen Augen sehen werdet und eventuell nie wieder zu Eurer alten Denkweise zurückkehren werden könnt!<br />
<br />
Habt Ihr Euch bereits gefragt wie die kommerziellen Spiele es schaffen ohne Ruckler (*hust), super Grafiken auf den Bildschirm zu bringen, die auch nach der Optimierung immer noch durch grafische Qualität überzeugen können? Nun, es gibt viele Möglichkeiten seine Szenen zu optimieren, dass wohl einfachste und auch mit am effizientesten ist das so genannte Culling-Verfahren.<br />
<br />
[[Bild:Tutorial_lektion5_trooper1.gif]]<br />
<br />
So sieht unser Protagonist wie gewohnt aus. Wir sehen ihn wie er liebt und lebt. (ja…) Um es philosophisch auszudrücken, sehen wir hier jedoch nur die halbe Wahrheit… ich stelle die wage Behauptung aus, dass wir der Rückseite von ihm gar nicht sehen können und daher auch nicht sagen können, ob sie existiert! Jedes Mal, wenn wir uns um ihn herum bewegen sehen wir nie seine Rückseite. Nun… das wäre auch nicht weiter fatal, wenn ich nicht sofort eine weitere These aufstellen würde: "Der Kerl hat gar keine Rückseite" :-O<br />
<br />
"Aber! Wenn wir um ihn herum gehen, dann sehen wir doch seine Rückseite, also ist sie da!", könnte man mir nun skeptisch zurufen. Tja… beweist mir das Gegenteil und schickt einen Wetteinsatz an mich, ich werde Euch beweisen, dass er immer nur eine Seite hat und seine Rückseite erst erzeugt wird, wenn sie auch benötigt wird. Es würde dann aussage gegen Aussage stehen und ihr könntet mir nicht beweisen, dass Ihr Recht habt, denn wenn wir um ihn herum gehen, würden wir ja wieder seine Rückseite nicht sehen. Ich hingegen würde den Beweis antreten und Euch mitteilen, dass wir ja einfach mal uns die Rückseite ansehen, dann allerdings auf die Forderseite verzichten müssen:<br />
<br />
[[Bild:Tutorial_lektion5_trooper2.gif]]<br />
<br />
Was auf den ersten Blick wie ein verschwommendes Etwas aussieht entpuppt sich auf den zweiten Blick bereits als unser Soldat vom ersten Bild. Skeptisch wird man sich sicherlich fragen, was geschehen ist. Und wenn man genau durch ihn durch sieht, erkennen wir, dass wir hier z.B. seine Hose sehen… und seinen Kopf auch irgendwie von … hinten! Was ist geschehen? Wir betrachten Ihn von vorne und sehen, was hinten ist! (Oh Gott… eine ganze Wirklichkeit stürzt in sich ein *sg* ).<br />
<br />
Natürlich ist das verschwommende Ding da oben nicht ein grafisches Ziel, was wir erreichen wollen, sondern nur der Beweis dafür, dass jede Figur zwei Seiten hat (Erkenntnis, aufschreiben!) und wir in OpenGL bestimmen können, welche Seite wir von Vorne oder Hinten betrachten können.<br />
<br />
Ich gebe ja auch zu, dass ich ein wenig getrickst habe und im oberen Bild, die Figur zweimal genredert habe, davon einmal die Vorderseite mit Alpha Blending, weil auf einem unbewegten Bild es schwer zu erkennen wäre, welches die Vorder und welches die Rückseite ist, da wir die Texturen eben gespiegelt auf dem Modell sehen würden.<br />
<br />
=== Eine abstrakte Wahrheit… ===<br />
<br />
An sich klingt bisher doch auch noch alles recht logisch oder? Den wieso sollte OpenGL die Rückseite von Objekten rendern, wenn man sie gar nicht sehen kann. Grob würde dies eben die doppelte Arbeit sein, die sinnlos getätigt wird. Der Laie wird nun vor Freude an die Decke springen, der erfahrene Programmierer skeptisch die Falten runzeln. "Wie erkennt OpenGL, den das es sich um die Rückseite handelt!". Gute Frage oder? Des Lösung-Rätsel sollte ein Blick auf die Uhr zeigen… (<== nein, er spinnt nicht (Anm. v. Flo2))<br />
<br />
Aber was meine ich damit? Was haben unsere Objekte mit einer Uhr gemeinsam? Was zunächst einem komisch vorkommt ist eigentlich logisch: Es ist die Laufrichtung! Unsere Uhr sollte im Normalfall im Uhrzeigersinn laufen (… scheiß Übergang!). Die Entgegengesetzte Richtung nennen wir dann entgegen des Uhrzeigersinnes. Das wird nun vielleicht den einen oder anderen dazu gebracht haben ein leises Aua von sich zu geben und sich mit der Hand an die Rübe zu schlagen. Diese Erkenntnis ist jedoch grundlegend ^__-<br />
<br />
Denn auch unsere Objekte haben eine gewissen Definitionsreihenfolge. Wir werden die Problematik im Weiteren an Hand eines Quadrates verfolgen ;) Folgender Code ist nicht gleich…<br />
<br />
<source lang="pascal"><br />
glBegin(GL_QUADS);<br />
glColor3f(1,0,0);<br />
glVertex3f(0,0,0);<br />
glColor3f(0,1,0);<br />
glVertex3f(1,0,0);<br />
glColor3f(1,1,1);<br />
glVertex3f(1,1,0);<br />
glColor3f(0,0,1);<br />
glVertex3f(0,1,0);<br />
glEnd;<br />
<br />
glBegin(GL_QUADS);<br />
glColor3f(1,0,0);<br />
glVertex3f(0,0,0);<br />
<br />
glColor3f(1,1,0);<br />
glVertex3f(0,1,0);<br />
<br />
glColor3f(1,1,1);<br />
glVertex3f(1,1,0);<br />
<br />
glColor3f(0,0,1);<br />
glVertex3f(1,0,0);<br />
glEnd;<br />
</source><br />
<br />
Wer das Culling nicht verstanden hat, wird sich fragen, wo der unterschied liegt, den beide Stücke beschreiben ein und das gleiche Dreieck. Der Unterschied wird erst deutlich, wenn wir uns die Definitionsreihenfolge vor Augen führen.<br />
<br />
<br />
[[Bild:Tutorial_lektion5_order.png]]<br />
<br />
Und genau hier ist auch für OpenGL die Definition von "Rückseite". Normalerweise erwartet OpenGL nämlich, dass alle Punkte gegen den Uhrzeigersinn definiert werden, also so wie beim ersten Quadrat. OpenGL würde in seinem Normalzustand dies dann als die Vorderseite ansehen. Mit einer einzigen Zeile können wir dieses Verhalten jedoch auch verändern:<br />
<br />
<source lang="pascal"><br />
glFrontFace(GL_CW); //Clock-Wise<br />
glFrontFace(GL_CCW); // Counter Clock-Wise<br />
</source><br />
<br />
Es ist jedoch zu empfehlen diese Reihenfolge beizubehalten, da andere Programmierer, die Euren Source lesen, ebenfalls davon ausgehen werden, dass ihr die Punkte gegen den Uhrzeigersinn definieren werdet. Nur bei einigen Optimierungsverfahren macht ein umschalten wirklich Sinn.<br />
<br />
=== … und eine verlogene Wirklichkeit ===<br />
<br />
Vielleicht springt mal wieder jemand auf und wird mich beschuldigen totalen Mist erzählt zu haben… immerhin zeichnet er vielleicht bei Euch mit beiden Code-Schnipseln die Quadrate? Das liegt einfach daran, dass OpenGL das Culling standardgemäß deaktiviert hat und somit auch beide Seiten rendert. Wie man Culling aktivieren kann, ist schon fast ratbar:<br />
<br />
<source lang="pascal"><br />
glEnable(GL_CULL_FACE);<br />
</source><br />
<br />
Mit glDisable dementsprechend können wir es wieder deaktivieren. Sollten wir beide Quadrate nebeneinander gerendert haben und eben an den Grundeinstellungen nichts verändert haben, sollte mit der Aktivierung dieser Seite nur noch das linke Dreieck angezeigt werden, weil OpenGL es als Vorderseite ansieht. Wollen wir, dass er nur die "Rückseiten" rendert, so lässt sich dies erfolgreich mit einem weiteren Funktionsauruf realisieren:<br />
<br />
<source lang="pascal"><br />
glCullface(GL_FRONT);<br />
</source><br />
<br />
Mit glCullface können wir immer die Seite angeben, die OpenGL vernachlässigen soll. In diesem Fall wäre dies dann die Vorderseite, die nicht gezeichnet werden würde (oder eben das rechte Dreieck).<br />
<br />
<source lang="pascal"><br />
glCullface(GL_BACK);<br />
</source><br />
<br />
Würde wieder den ursprünglichen Status herstellen. Zusammengefasst : glCullFace definiert, ob Vorder oder Rückseite weggelassen werden sollen, glFrontFace definiert, wie die Vorderseite definiert ist (im oder gegen Uhrzeigersinn!) und mit glEnable müssen wir das Culling zunächst aktivieren (und das sollten wir auch immer tun, wenn es möglich ist!).<br />
<br />
Ich hoffe, ich habe es halbwegsverständlich erklärt. Wenn jemand sich jetzt fragt, wie man nun die Rückseite sehen kann, wenn wir ein Quadrat haben, dass im Uhrzeigersinn definiert ist… Wenn wir von vorne drauf sehen, werden wir es nicht sehen, da die Punkte in der falschen Reihenfolge definiert sind. Bewegen wir uns durch das Dreieck hin durch und drehen uns um, so werden wir feststellen, dass die Punkte wieder in der "richtigen" Reihenfolge definiert sind und OpenGL sie rendern wird. Wir haben dies hier nur bei sehr simplen Gebilden betrachtet, aber auch bei komplexen ist das Prinzip gleich!<br />
<br />
Auch bei möchte ich noch einmal ein gutes Beispiel sehen, wo man bei komplexeren Objekten das Culling gut erkennen kann. Nämlich unserer Landschaft aus dem vierten Tutorial:<br />
<br />
[[Bild:Tutorial_lektion5_culledland.jpg]]<br />
<br />
Dies sieht zunächst nach einem schweren Grafikfehler aus… ist es jedoch nicht. Wir betrachten unsere Landschaft hier nur von einer Seite, die der Spieler nicht sehen würde, nämlich von der Unterseite. Wir sehen die Landschaft von unten bei aktivieren Culling (Anm.: Beim vierten Tutorial ist KEIN Culling von mir aktiviert worden!) und da ich die Quadrate alle richtig definiert habe sehen wir auch nur noch die, die dem Spieler zu gewannt sind. Etwas Fantasie benötigt man schon dazu, um dies wieder zu erkennen, allerdings sieht man es oben rechts doch noch recht gut. Ich hoffe, dass spätestens dieses kleine Beispiel es noch verständlicher gemacht hat, wenn nicht… schaut Euch die Samples an und fragt dann im Forum nach ;)<br />
<br />
BTW: Für psychische Folgen, die beim Lesen dieses Tutorials entstanden sind, übernimmt der Autor keine Haftung. Auch er hat bisher keinen glaubwürdigen Beweis dafür gefunden, dass seine Mitmenschen eine Rückseite haben. Auch die Betrachtung dieser mit Hilfe eines Spiegels kann eine zusätzlich Render-Routine der Engine sein, in der wir leben. Es ist jedoch höchst wahrscheinlich, dass sie keine haben, weil … wer sollte die ganze Rechenleistung aufbringen, um die Vorder- und Rückseite aller Menschen zu rendern! :-D<br />
<br />
== Für das Protokoll ==<br />
<br />
=== Grundgedanken zu Display-Listen ===<br />
<br />
Bisher sind wir sicherlich noch nicht in die Verlegenheit gekommen, komplexere Programme zu schreiben. Hat es doch jemand bereits versucht, so wird er schnell gemerkt haben, dass man ohne eine solide Organisation keine Chance hat, ein größeres Projekt zu verwirklichen. Die goldene Regel für eine gelungene Organisation ist sehr einfach! Redundanzen vermeiden um jeden Preis. Man sollte nicht wenn man z.B. ein Quad zeichnen will, diesen 20.000 Mal hintereinander erzeugt. Vielleicht tut es ja auch eine Schleife? Genauso, wie man nicht mehrfach ein Modell in den Speicher laden sollte, wenn es bereits einmal geladen wurde.<br />
<br />
Das klingt nun sicherlich wie eine große Verarschung meinerseits. Aber wer jetzt hier geschmunzelt hat, sollte aufpassen, dass ihm nicht gleich das Grinsen im Gesichte stecken bleibt. Würdest Ihr Euer Programm richtig aufbauen? Versuchen eben doppelte Daten zu vermeiden, wo man es nur kann? Wer halbwegs geschickt vorgeht, wird mit Prozeduren, die er immer wieder aufrufen kann eine Menge sinnlosen Code vermeiden. Zum Beispiel wenn wir einen Würfel zeichnen wollen! Wir würden diesen in einer Funktion zeichnen lassen und dann einfach diese Prozedur aufrufen, wenn er gerendert werden soll.<br />
<br />
Wünschen wir ihn an einer anderen Stelle in der Welt, so rufen wir einfach die entsprechende Matrix vor dem rendern auf und schon haben wir zwei Würfel in einer Welt mit nur einer Prozedur erzeugt… und die Hälfte an Code gespart. Das lässt sich doch sehen! Auch können wir nun zahlreiche andere Dinge mir in diese Funktion setzen, z.B. eine andere Farbe. Das klingt doch sehr nach einem richtige Ansatz!<br />
<br />
=== Vorsicht! Aufnahme ===<br />
<br />
OpenGL bietet für ein solches vorgehen ein eigenes System und eine Anzahl von Funktionen, die man unter den Namen "Display-Listen" zusammenfassen kann. Dies kann man sich so vorstellen, dass man einen Namen für eine Liste vergibt (nicht irritieren lassen, ein Name ist in diesem Fall eine Nummer!) und sagt OpenGL, dass alles, was jetzt geschieht mitprotokolliert werden soll. Man kann nun anfangen Objekte zu zeichnen, Farben, Texturen, Materialen, einfach alles was einem in den Sinn kommt zu rendern und OpenGL wird dann all dies in einer Liste zusammen fassen. Es steht dann im fortan für uns auf Abruf zur Verfügung.<br />
<br />
<br />
Technisch gesehen ließen sich wohl diese Listen auch mit Prozeduren realisieren, allerdings gibt es auch einen weiteren Vorteil (angeblich… ich konnte ihn nicht nachweisen, evtl. nur einen Sinn in seiner Frühzeit, wo es keine 3D-Karten gab oder nur bei extrem vielen Objekten spürbar…). OpenGL kann diese Befehle nämlich schneller durchführen, weil es die Operationen praktisch vormerkt. Wie auch immer man es sehen mag, Display-Listen sind unglaublich praktisch bei der Programmierung und meist weicht die anfängliche Skepsis durch Begeisterung (und wenn nicht, gehört es zum guten Ton es zu wissen, weil man es häufiger sehen wird)<br />
<br />
Zunächst holen wir uns von OpenGL eine Nummer ab, unter welcher wir die Display-Liste später erreichen werden. Diese wird im Idealfall von Typ Integer sein. Der Aufruf ist kinderleicht:<br />
<br />
<source lang="pascal"><br />
Displaylist := glGenLists(1);<br />
</source><br />
<br />
glGenLists liefert und eben den gewünschten Wert zurück. Wir können auch stattdessen ein Array of GLUint nehmen und gleich mehre anfordern.<br />
<br />
Der Rest ist dann praktisch aufgebaut wie ein glBegin und glEnd, nur dass die entsprechenden Funktionen diesmal glNewList und glEndList heißen und wie folgt verwendet werden:<br />
<br />
<source lang="pascal"><br />
glNewList(DisplayList,GL_COMPILE);<br />
[…]<br />
glEndList;<br />
</source><br />
<br />
Das GL_COMPILE bewirkt schlicht und ergreifend, dass sich OpenGL die verwendeten Funktionen merkt. Alternativ könnten wie auch noch GL_COMPILE_AND_EXECUTE einsetzen, wenn wir wollen, dass die Liste auch gleich ausgeführt wird. Praktisch alles was zwischen diesen beiden Funktionen steht wird auch mitgeschrieben. Ausnahmen gibt es wir immer und ich werde dies Mal aufführen, ich denke, dass man erahnen kann, warum diese nicht mit unterstützt werden. Es gibt praktisch keinen Sinn diese mit aufzuzeichnen:<br />
<br />
[[glColorPointer]], [[glDeleteLists]], [[glDisableClientState]], [[glEdgeFlagPointer]],<br />
[[glEnableClientState]], [[glFeedbackBuffer]], [[glFinish]], [[glFlush]], [[glGenLists]],<br />
[[glIndexPointer]],[[glInterleavedArrays]], [[glIsEnabled]], [[glIsList]], [[glNormalPointer]],<br />
[[glPopClientAttrib]],[[glPixelStore]], [[glPushClientAttrib]], [[glReadPixels]],<br />
[[glRenderMode]], [[glSelectBuffer]], [[glTexCoordPointer]], [[glVertexPointer]] und<br />
alle [[glGet]] Routinen<br />
<br />
=== Und Cut! ===<br />
<br />
Idealerweise erzeugen wir diese Listen nicht bei jedem Render-Vorgang, sondern nur einmal im Init und führen diese dann nur noch beim Rendern aus. Dies geschieht mit der Funktion glCallList und sollte genauso leicht zu beherrschen sein, wie die bisherigen auch:<br />
<br />
<source lang="pascal"><br />
glCallList(displaylist);<br />
</source><br />
<br />
Wir übergeben einfach den Namen der Liste und schon wird alles was wir drinnen aufgezeichnet haben auch ausgeführt… Perfekt, oder? Das ganze ist doch recht leicht hand zu haben und sollte niemanden vor einem großen Problem stellen.<br />
<br />
Im Sample haben wir ein kleines Astroiden-Feld nachgebildet, eben mit Hilfe dieser Display-Listen. Ich denke, dass dort anschaulich gezeigt wird, wie man sie sinnvoll einsetzen kann. Auch die Demo von Jan Horn "Biohazzard" zeigt eindrucksvoll, wie man es am Geschicktesten machen kann ;)<br />
<br />
*g* Ach ja… einen kleinen Nachteil haben die Display-Listen doch noch! (ich weiß, dass ist jetzt so, als würde ich euch ins ein tiefes Becken tauchen lassen und am Boden steht: "Du hast die Sauerstoff-Falsche vergessen…") Sie verbrauchen relativ viel Speicher! Alle Vorgänge werden nämlich direkt im Arbeitsspeicher geschoben. Eine gesunde Mischung zwischen normalen Rendering und Display-Listen ist also anzuraten, denn wenn der Speicher voll ist, hilft keine Optimierung mehr, dass Programm vorm ruckeln zu schützen ;)<br />
<br />
Auch sollte man bedenken, dass ein Aufbau z.B. eines Quads sich nicht mehr sehr optimieren lässt. Richtig bringen werden Euch die Display-Listen nur dann etwas, wenn ihr mit komplexen Gebilden arbeitet, die sich immer wiederholen. Würdet ihr z.B. bei jedem Render-Durchgang ein Model verändern, also seine Form (nicht seine Farbe), so würde eine Display-Liste eher hinderlich als nützlich sein!<br />
<br />
== Nachwort ==<br />
<br />
Bitte nicht traurig sein, wenn hier bereits wieder Ende ist ;) Ich weiß, dass ich in diesem Tutorial mehr versprochen habe, als letztendlich drin ist, aber ich habe vermehrt zu hören bekommen, dass ich dazu neige, wahre Monster-Tutorials zu machen und wollte dem hier mal entgegen wirken, da ich noch rund das doppelte an Stoff gehabt hätte. Drum ist hier nun erstmal Schluss! Es ist nicht ganz das geworden, was zunächst geplant war, aber ich denke, dass das Ziel, nämlich das Grundwissen zu vertiefen erfüllt werden konnte!<br />
<br />
Das schreiben dieses Tutorials hat mir sehr viel Spaß gemacht, da ich mich teilweise auch selbst momentan damit beschäftige und es auch unglaublich interessant finde. In diesem Sinne möchte ich dann auch beim sechsten Tutorial weiter machen und mal eine kleine Exkursion unternehmen, nämlich Möglichkeiten aufzuzeigen, Daten aus einem Modellierer in unsere Software zu bekommen. Die Direct3D-Welt hat gesagt, dass sie bereit ist für einen Kampf ist - also sollen sie ihn auch bekommen ^__-<br />
<br />
Den mit OpenGL lässt sich weitaus mehr machen als man zunächst erahnt und da die meisten Samples immer nur Dreiecke zeigen wollen wir doch mal etwas Komplexeres machen! Ihr dürft also gespannt sein und wie immer gilt… Ideen und Vorschläge sind herzlich willkommen ;) Im Gegensatz zu D3D hat man bei OpenGL erkannt, dass eine Grafik-API keine Model- und Texture-Loader bieten sollte. Hier ist dann eine Hardcodierung notwendig, die wir allerdings nicht als Nachteil ansehen sollten, sondern vielmehr die Chancen nutzen, unsere Programme mit eigenen Routinen zu optimieren. ;)<br />
<br />
<br />
Have Fun!<br />
<br />
Euer [[Benutzer:Phobeus|Phobeus]]<br />
<br />
Siehe Auch: [[Displayliste]]<br />
<br />
== Dateien == <br />
* {{ArchivLink|file=tut_lektion_5_delphi_vcl|text=Alter Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_5_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION | [[Tutorial Lektion 4]] | [[Tutorial Lektion 7]]}}<br />
<br />
[[Kategorie:Tutorial|Lektion5]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Lektion_4&diff=23624Tutorial Lektion 42009-05-17T13:44:59Z<p>Flo: + Dateien Abschnitt</p>
<hr />
<div>=Texturen, Tapeten und Ihre Tücken=<br />
<br />
==Vorwort==<br />
Hi Leute,<br />
kaum zu glauben aber wahr. Dieses Tutorial wird ausnahmsweise mal etwas mehr Erholung sein. Zumindest am Anfang. Was wir bisher erreicht haben ist ja alles schön, nett, praktisch und auch wichtig als Grundlage aber wenn wir aus dem Fenster sehen hören wir die Vöglein zwitschern. Hö? Wir hören was sehend? Es ist tiefste Nacht? Es muss Frühling sein ^__^.<br><br />
Und was macht man da normalweise? Wir kramen herum und machen einen großen Frühjahrsputz... die Schränke abziehen und alles schön sauber und ordentlich machen. Hach... es dürstet mich richtig danach... :D (means... *würg*).<br><br />
Da unsere Tutorials bisher Gott sei Dank keine dreckige Sache waren werden wir das mit dem Saubermachen einfach mal wegfallen lassen und uns nur mit einem Tapetenwechsel begnügen. In der Tat werden wir ab jetzt unseren Tutorials mehr grafisches Gewicht zumuten. Endlich haben die blauen Dreiecke ein Ende! Ich wünsche Euch viel Spaß ;).<br />
<br />
==Crash-Kurs im Handwerk des Tapezierens==<br />
<br />
===Ich verstehe nur "Renovierung"?===<br />
Ich finde es immer wieder erschreckend Leute im Internet zu treffen, die nicht wissen was eine Textur ist... ich meine, kennen keinen Kafka, keine Quantenphysik und wissen nicht einmal wo man die Systemsteuerung findet. Wie soll man solch einem Menschen erklären, was eine Textur ist?<br />
Nun, am Besten fangen wir mit einem möglichst praktischen Beispiel an. Texturen sind wie... Tapeten. Wir blicken nun zu unserer linken Seite. Der Autor erwartet nun ein DirectMind-Uplink zum Empfangen der visuellen Bildsignale. Dort haben wir eine schöne Wand... quadratisch in ihrer Form. Wollen wir diese mit einer Tapete verzieren, gehen wir in den nächsten Baumarkt, suchen uns eine hübsche aus und bringen diese an der Wand an. Natürlich haben wir eine Große geholt, und fangen nicht mit kleinen Streifen an.<br><br />
Wenn dann alles geklappt hat stehen wir vor dieser Wand und begutachten die Tapete in voller Pracht direkt vor uns. Wir haben die Wand texturiert. Streng genommen machen wir auch bei OpenGL nichts anderes, als ein Bild zu laden und dieses auf eine Fläche zu kleben. Wir werden uns jedoch auf Dauer nicht damit begnügen immer nur quadratische Flächen zu texturieren, sondern durchaus auch mal Dreiecke oder andere Vielecke. Und auch hat man uns Werkzeuge in die Hand gegeben um diese Textur noch nachträglich an der Wand zu verschieben ohne dass wir sie abnehmen müssen. Ist doch super. Es lebe die virtuelle Welt!<br />
<br />
===Tapezieren leicht gemacht!===<br />
Okay, wir haben lange genug um den heißen Brei herumgeredet und sollten uns nun endlich auf die Arbeit stürzen. Jeder von uns sollte in der Lage sein, ein Quadrat in OpenGL genau vor unserer Nase zu erzeugen.<br />
<br />
[[Bild:Tutimg_lektion4_nonetex.png|thumb|256px|left|Leeres Quadrat]]<br />
Spätestens nun sollte die gleiche Frage aufkommen, wie bei jedem, der zum ersten Mal versucht, eine Tapete an die Wand zu bringen: "Wie rum muss ich das Ding da befestigen?". Immerhin müssen wir uns nicht um den Leim kümmern, das erledigt unsere Grafikkarte bzw. OpenGL für uns ;).<br />
<br />
Wer noch nie 3D programmiert hat wird im ersten Moment vielleicht fälschlicherweise denken, dass man die Position der Textur per Weltkoordinaten definiert. Das hätte allerdings fatale Folgen sobald sich das Objekt im Raum bewegt. Wir müssten die Position jedes mal neu berechnen. Um eben dieses Problem zu umgehen, geht man in der 3D-Programmierung einen anderen Weg. Man vergibt einfach für jede Ecke eines Objektes eine Koordinate der Textur. OpenGL errechnet dann aus diesen Texturkoordinaten das Stück der Textur, das dann über das gesamt Objekt projiziert wird.<br />
<br />
Die Rede ist hierbei vom so genannten UV-Mapping, welches vor allem bei Anfängern ein leichtes Gefühl des Unbehagens auslösen sollte. Es ist jedoch nur halb so wild, wenn man es halbwegs verstanden hat.<br />
<br />
Betrachten wir gleich mal das Bild rechts und versuchen, uns in die Problematik hineinzuversetzen. Da Texturen unterschiedliche Größen haben können wurden so genannte Texturkoordinaten eingeführt. Es ist total egal wie groß eine Textur ist, da sie nur einen Wertebereich von 0 bis 1 haben kann. Das heißt, wenn eine Textur 256x256 groß ist und eine andere 512x512, so haben beide eine maximale Größe von 1. Wir brauchen also das UV-Mapping nicht verändern, selbst wenn ein Objekt eine neue Textur mit anderer Größe erhält.<br />
<br />
[[Bild:Tutimg_lektion4_texuv_02.png|thumb|256px|right|Textur und leere Quadratfläche. Eingezeichnet ist das Standardmapping]]<br />
Auf diesem Bild sehen wir zum einen die Textur, die wir verwenden wollen, zum anderen das Objekt, auf das "geklebt" werden soll hintereinander. Natürlich sieht das in der Realität nicht so aus (die Textur würde das gesamte Objekt überdecken). Ich denke aber diese Darstellung erleichtert das Verstehen ;).<br />
<br />
Die obere linke Ecke der Textur trägt die Texturkoordinaten von (0 / 0) (d.h. u = 0 und v = 0), die untere linke Ecke (0 / 1), die untere rechte Ecke (1 / 1) und schließlich die obere rechte Ecke die Koordinaten (1 / 0). Das heißt alles was wir machen müssen um auf unser Objekt eine Textur zu kleben ist dem jeweiligen Eckpunkt unseres Quadrates die entsprechende Texturkoordinate zuzuweisen. Wobei in diesem Sinne korrekt in Anführungszeichnen stehen sollte. Es gibt kein falsches UV-Mapping. Man kann tolle Sachen mit diesen Textur-Koordinaten machen und so z.&nbsp;B. auch eine Textur auf einem Objekt spiegeln. Dafür müssten wir in unserem Beispiel nur die linken und rechten UV-Mapping vertauschen und z.&nbsp;B. für den zweiten Punkt die Koordinaten von (0 / 1) setzen und dafür beim dritten (1 / 1). Genauso würden wir auch die unteren vertauschen. Die UV-Koordinaten, wie sie oben angegeben sind bewirken nur, dass die Textur, so wie sie in der Datei vorkommt auch auf das Objekt geklebt wird. Selbstverständlich ist es auch möglich eine Textur gekachelt aufzukleben, nämlich indem Ihr Texturkoordinaten > 1 vergebt. Ebenso ist es möglich nur Teile einer Textur zu verwenden. Spielt ruhig ein wenig damit herum und schaut Euch an was passiert! Auf einige tolle Spielereien kommen wir zum Schluss noch mal zurück :).<br />
<br />
Sicherlich brennt es einigen von Euch nun bereits unter den Finger und Ihr fragt "Wie kann ich denn die UV-Koordinaten den Eckpunkten der Fläche zuweisen?". Nun, zunächst funktioniert das ähnlich wie bei allen Dingen unter OpenGL. Wir setzen eine UV-Koordinate und solange wir nichts verändern werden alle Punkte mit diesen Koordinaten versehen bis wir andere Instruktionen geben. In unserem Fall müssen wir dies natürlich nach jedem Punkt machen. Die Funktion, die dafür verwendet wird heißt [[glTexCoord]]. Um also eine Textur auf unserem Quad zu projizieren, benötigen wir folgenden Code:<br />
<br />
<source lang="pascal">glBegin(GL_QUADS);<br />
glTexCoord2f(0,0); glVertex3f(-1,1,0); //lo<br />
glTexCoord2f(0,1); glVertex3f(-1,-1,0); //lu<br />
glTexCoord2f(1,1); glVertex3f(1,-1,0); //ru<br />
glTexCoord2f(1,0); glVertex3f(1,1,0); //ro<br />
glEnd;</source><br />
<br />
Das klappt doch wunderbar oder? Damit wir die Textur aber auch wirklich (bzw. überhaupt) genießen können müssen wir Texturing mit Hilfe von '''glEnable''' und dem Token '''GL_TEXTURE_2D''' aktivieren. In unserem Fall wo das gesamte Projekt(ein Quad) texturiert werden soll, könnt ihr den Aufruf direkt in eure GL-Initialisierung schreiben, ansonsten gehört selbiger direkt vor die zu texturierenden Flächen in die Renderschleife.<br />
glEnable(GL_TEXTURE_2D);<br />
Mit Hilfe von glDisable und demselben Token ist es dann auch möglich Objekte zu Zeichnen, die über keine Texturen verfügen. Andernfalls würde nämlich die zuletzt gesetzte Textur und die Texturkoordinaten des letzten glTexCoord-Aufrufs verwendet werden. <br />
<br />
Doch eine Kleinigkeit habe ich Euch nun doch noch verschwiegen! Wir müssen natürlich noch die richtige Textur setzen damit OpenGL überhaupt weiß, was auf diesem Quad gezeichnet werden soll. Dies ist an sich noch relativ einfach, allerdings müssen wir diese auch noch in den Speicher bekommen, damit sie überhaupt zur Verfügung steht. Nun wird's theoretisch ;).<br />
<br />
===Tapeten besorgen===<br />
Ich werde jetzt nicht näher darauf eingehen, wie man Texturen erstellt. Vielleicht gibt's ja einige Photoshopexperten unter Euch, die Lust haben einige Ihrer Tricks den anderen in Form eines Tutorials zu zeigen.<br />
<br />
Zunächst einmal müssen wir uns die eigentlichen Bilddaten besorgen. Wir werden das jetzt in diesem Tutorial mit [[SDL]] machen es gibt jedoch auch die Möglichkeit die Daten manuell zu laden das könnt Ihr hier nachlesen:<br />
* [[TGA Bilder laden]]<br />
Es gibt allerdings auch Texturloader die Euch die nächsten Kapitel abnehmen und das alles für Euch machen. glBitmap ist so ein Loader. Mehr dazu erfahrt Ihr in dem [[Glbitmap_loader|glBitmap]]-Artikel.<br />
<br />
Bei SDL rufen wir nur [[IMG_Load]] auf und prüfen dann ob das Laden erfolgreich war. Hierbei sei erwähnt, dass es unter Linux zu Problemen führen kann, wenn ein Programm nicht aus einer Konsole heraus gestartet wurde. In diesem Fall sind die Pfade zu den Texturen nämlich falsch gesetzt und das Laden würde fehlschlagen. Abhilfe schafft man durch das Verwenden von absoluten Pfaden.<br />
<br />
<source lang="pascal">uses SDL, SDL_Image;<br />
<br />
var <br />
tex : PSDL_Surface;<br />
begin<br />
tex := IMG_Load(PChar(ExtractFileDir(paramStr(0))+'/wiki.jpg'));<br />
if assigned(tex) then<br />
begin</source><br />
<br />
Das war es dann auch schon... Wir haben die Textur im Speicher. Doch was nun?<br />
<br />
===Texturen richtig zubereitet===<br />
Nachdem sich unsere Textur nun im Speicher des Computers befindet, geht es darum daraus auch eine richtige Textur zu machen, damit wir diese in OpenGL anzeigen können. Bisher befinden sich ja nur die Rohdaten im Speicher. Hierfür teilen wir OpenGL mit, dass wir eine neue Textur erzeugen wollen:<br />
<br />
<source lang="pascal">glGenTextures(1, @TexID);</source><br />
<br />
TexID ist in diesem Fall ein gluInt, kann aber genauso gut ein Array davon sein, um mehrere Texturen zu erzeugen. Genau dafür wird dann auch der erste Parameter verwendet, der OpenGL mitteilt wieviele Texturen in dieses Array geschrieben werden sollen. In unserem Fall ist dies eben nur ein Element. Aber was ist ist das für ein Wert in TexID? OpenGL verwaltet die Texturen anhand eindeutiger Namen. [[glGenTextures]] ermittelt einen oder mehrere bisher ungenutzte Namen und schreibt diese in TexID. Durch TexID können wir unsere Textur ab sofort also eindeutig identifizieren.<br />
<br />
<source lang="pascal">glBindTexture(GL_TEXTURE_2D, TexID);</source><br />
<br />
Wir teilen OpenGL mit, dass sich von nun an alle Änderungen und Anweisungen, die sich auf Texturen beziehen auf die Textur TexID beziehen.<br><br />
Die folgenden beiden Zeilen sind zwar nicht wirklich nötig, um eine Textur zu erzeugen aber glaubt mir, sie werden ansonsten potthässlich aussehen. Wir werden in einem anderen Tutorial näher auf dessen Bedeutung eingehen, nämlich den so genannten Texturfiltern. Die momentane Einstellung ist leicht rechenlastig jedoch auch von recht guter Qualität. Ihr werdet anfangs keine Probleme mit der Geschwindigkeit bekommen ;).<br />
<br />
<source lang="pascal">glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);<br />
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);</source><br />
<br />
Zu guter Letzt müssen wir die Bildinformationen irgendwie an OpenGL übergeben. Dies übernimmt die Funktion [[glTexImage2D]]:<br />
<br />
<source lang="pascal">glTexImage2D(GL_TEXTURE_2D, 0, 3, tex^.w, tex^.h,0, GL_RGB, GL_UNSIGNED_BYTE, tex^.pixels);</source><br />
<br />
Der erste Parameter steht für den Typ der Textur. Die Dimension des Typs muss hier mit der des Befehls übereinstimmen (glTexImage2D erlaubt also nur GL_TEXTURE_2D). Der zweite Parameter gibt die Nummer des Level of Detail (LoD) an. Für den Anfang reicht hier der Level 0. Der dritte Parameter gibt an, wie viele Farbkomponenten in dem Bild enthalten sind (1-4). Die zwei folgenden Parameter übermitteln OpenGL die Breite und die Höhe des Bildes. Der sechste Parameter gibt die Breite des Rahmens an. Im siebenten Parameter wird das Format verlangt, in welcher Reihenfolge die einzelnen Farbkomponenten gespeichert sind. Der Typ, der einzelnen Farbwerte muss im 8. Parameter angegeben werden. Letztendlich müssen im 9. Parameter nur noch die Bildpunkte selbst übergeben werden.<br><br />
Mit Hilfe dieser Funktion sollten nur Texturen der Größe 2^n x 2^n erzeugt werden. Andernfalls werdet Ihr die Textur nicht in Ihrer vollen Schönheit, d.h. überhaupt nicht betrachten können. Es gibt jedoch Möglichkeiten Texturen zu laden, die nicht die Größe 2^n entsprechen. Die Funktion [[gluBuild2DMipmaps]] bietet hier beispielsweise eine Alternative.<br><br />
Das war es auch schon. Wer mehr über die einzelnen Parameter und Befehle wissen will ist herzlich eingeladen in unserem Wiki umherzustöbern und sein Wissen zu erweitern um später selbst vielleicht einmal ein paar Artikel im Wiki zu veröffentlichen.<br />
<br />
Die Daten im Arbeitsspeicher brauchen wir nun nicht mehr. Bei SDL geben wir sie so frei:<br />
<br />
<source lang="pascal">SDL_FreeSurface(tex);</source><br />
<br />
Fassen wir das ganze bei SDL nochmal zusammen:<br />
<source lang="pascal">var<br />
tex : PSDL_Surface;<br />
begin<br />
tex := IMG_Load('./wiki.jpg');<br />
if assigned(tex) then<br />
begin <br />
glGenTextures(1, @TexID);<br />
glBindTexture(GL_TEXTURE_2D, TexID);<br />
<br />
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);<br />
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);<br />
<br />
// Achtung! Einige Bildformate erwarten statt GL_RGB, GL_BGR. Diese Konstante fehlt in den Standard-Headern<br />
glTexImage2D(GL_TEXTURE_2D, 0, 3, tex^.w, tex^.h,0, GL_RGB, GL_UNSIGNED_BYTE, tex^.pixels);<br />
<br />
SDL_FreeSurface(tex);<br />
end;</source><br />
<br />
Nun kommt aber bitte nicht auf die Idee die Textur in euerer Hauptschleife wieder und wieder neu zu laden. Es reicht die Textur einmal zu laden und von da an steht sie einem solange zur Verfügung bis man gedenkt sie wieder aus dem Grafikkartenspeicher zu entfernen.<br><br />
Das übernimmt die Funktion [[glDeleteTextures]]. glDeleteTextures funktioniert ähnlich wie glGenTextures, nur dass die Texturen entfernt werden. Der erste Parameter gibt die Anzahl der zu löschenden Texturen an, während der zweite Parameter den Namen der Textur bzw. ein Array der Namen mehrerer Texturen verlangt.<br><br />
Das ist doch für den Anfang nicht schlecht. Ihr solltet nun in der Lage sein, zumindest einfache Objekte zu texturieren. Das ist eigentlich das gesamte Grundprinzip. Natürlich gestaltet es sich schwieriger ein komplexeres Objekt mit UV-Koordinaten zu versehen als ein Quad. An der Technik selbst ändert sich aber nur wenig. <br />
<br />
An dieser Stelle noch eine Anmerkung zu den Texturformaten. Man sollte immer darauf achten, dass die verwendeten Texturen als Kantenlänge eine 2er-Potenz besitzen. Also z.B. 64x128, 256x1024 oder 32x32. Dies liegt daran, dass ältere Grafikkarten nur diese Formate unterstützen. Erst die neueren Generationen können auch sogenannte ''non power of two'' texturen darstellen. <br><br />
Falls ihr also einmal nur ein weißes Viereck seht, wo eigentlich eine Textur sein müßte, dann prüft ob ihr auch tatsächlich ein korrektes Format verwendet.<br />
<br />
Wir werden nun einige Spielereien zeigen, die man mit Texturen machen kann damit Ihr ein Gefühl dafür bekommt, wie man ein Problem elegant umschiffen kann!<br />
<br />
==Die Rückkehr der Matrizen==<br />
===Texturen===<br />
Tja... und da ich ein Sadist bin [Anm. des Lektors: Ooooh ja!] werden wir uns nun nochmals den Matrizen zuwenden! Dachtet Ihr etwa, Ihr seid die Dinger schon wieder los? Ich habe Euch im letzten Tutorial angedroht, dass es nicht nur eine Matrix für die "Welt" gibt, sondern auch noch weitere. Unter anderem eben auch für Texturen.<br />
<br />
Die Texturmatrix funktioniert streng genommen genauso wie auch die Worldmatrix. Solange wir sie aktiv haben, wird sie von jedem Matrixbefehl berücksichtigt! Einziger wirklicher Unterschied ist, dass sie nicht die Position oder die Form eines Objektes beeinflusst, sondern nur das Rendern der Textur selbst. Hö? Was meint der Kerl bloß damit?! Nun... stellt Euch vor, Ihr habt ein Quad und wollt darauf eine Textur bewegen, so dass es aussieht, als würde sie sich von rechts nach links bewegen! Was wir jedoch nicht wollen ist, dass sich das Objekt bewegt, sondern nur, das was darauf zu sehen ist. Stellt Euch vor, Ihr schaut aus einem Fenster und seht einen wolkigen Himmel, der Wind bläst die Wolken von einer Himmelsrichtung zur anderen. Ihr könntet so z.B. das Fenster als Quad nehmen und dann die Textur darauf zeichnen und glaubt mir, die Illusion würde auffliegen, sobald sich das Fenster mit der Textur bewegen soll. Nein, stattdessen bewegen wir nur die Textur auf dem Quad und zwar ohne das UV-Mapping anzutasten. Wir beeinflussen einfach die Art, wie die Textur auf das Quad projiziert werden soll. Dafür dient die Texturmatrix.<br />
<br />
Stellen wir uns mal vor, dass wir unser Quad zeichnen und eine Variable X haben, die wir bei jedem Rendervorgang leicht erhöhen. Dies soll die Bewegung darstellen. Alles was wir nun tun müssen ist, die Texturmatrix entsprechend anzupassen.<br />
<br />
<source lang="pascal">glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glTranslatef(x,0,0);<br />
glMatrixMode(GL_MODELVIEW);<br />
</source><br />
<br />
Das ist bereits der ganze Spuk! Wir teilen OpenGL durch glMatrixMode mit, dass sich ab sofort alle Veränderungen der Matrix nur noch auf die Texturmatrix beziehen sollen. Danach setzen wir diese sofort auf ihren Standardwert zurück. Der Grund hierfür sollte Euch von der Worldmatrix noch in Erinnerung sein. Anschließend ändern wir die Position der Textur auf dem Quad. Würden wir X jedes Mal um 1 Einheit erhöhen, so würde diese Operation ohne Effekt bleiben, da wir die Textur immer um ihre ganze Größe nach links projizieren würden. Würden wir X z.&nbsp;B. bei jedem Vorgang um 0.01 erhöhen, so würde die Textur sich langsam von rechts nach links bewegen. Ich denke, Ihr könnt bereits erahnen, welche Parameter dafür verwendet werden, um eine Textur von unten nach oben zu bewegen ;).<br />
<br />
Wichtig ist auf jeden Fall, dass Ihr anschließend mit glMatrixMode wieder die Worldmatrix aktiviert, da sonst alle weiteren Matrixmanipulationen auf die Texturmatrix angewandt werden würden. Denkt nicht, dass die Texturmatrix damit deaktiviert wird! Ab sofort wird auf das Objekt sowohl die World- als auch die Texturmatrix angewendet. Seht Ihr? Das Ganze ist doch gar nicht ganz so schlimm. Es versteht sich auch von selbst, dass Ihr glRotate und glScale ebenfalls darauf anwenden könnt, natürlich auch nach den gleichen Regeln auf die Worldmatrix. Experimentiert am Besten auch etwas mit diesen Einstellungen herum!<br />
<br />
==Wir tapezieren unsere Welt mal anders==<br />
===Am Fließband===<br />
Eigentlich sollte an dieser Stelle bereits Schluss sein. Ich wurde allerdings während des Schreibens des Tutorials nach einer Sache gefragt und möchte die Chance nutzen, diese Frage zu beantworten und gleichzeitig das angewandte Wissen der vorhergehenden Lektion etwas zu vertiefen. <br />
<br />
Wir haben folgende Problematik: Wir brauchen eine animierte Textur auf einem Objekt. Dies könnte z.&nbsp;B. eine bewegte Lavamasse sein oder irgendwas Glibbriges, was am Boden wabbelt. Oder eben in unserem Fall eine Folge von Zahlen, die wie ein Countdown aufgelistet werden. Sicherlich könnte nun jemand von Euch auf die Idee kommen, viele einzelne Texturen zu laden und diese in einem Array zu speichern. Dies mag auch durchaus sinnvoll sein nicht jedoch, wenn es sich um kleine Bilder handelt (bei uns z.&nbsp;B. 32x32 Pixel).<br />
<br />
Auch bei Bitmap-Fonts würde man nie auf die Idee kommen, für jeden Buchstaben eine einzelne Textur zu verwenden, sondern vielmehr eine Textur mit allen Buchstaben darauf erstellen, da dies u.a. den Ladevorgang erheblich beschleunigt! Klingt einleuchtend oder? Aber wie sollen wir dem Programm mitteilen, welcher Teil der Textur auf welche "Ecke" geklebt werden soll? Nun... auch hierbei heißt des Lösungs Rätsel [Anm. des Lektors: Er macht's schon wieder. Herrlich...] UV-Mapping!<br />
<br />
[[Bild:Tutimg_lektion4_numbers.gif]]<br />
<br />
Dies ist unsere Textur! Sie hat eine Gesamtlänge von 256x32 Bildpunkten und wie man leicht sehen kann, soll sie aus 8 Teilstücken bestehen. Die V-Koordinate können wir in diesem Fall getrost vernachlässigen, weil sie in diesem Fall immer konstant sein wird, weil wir die ganze Höhe der Textur verwenden wollen. Sie wäre nur dann interessant, wenn wir z.&nbsp;B. noch eine zweite Reihe darunter setzen würden. Ich denke jedoch, dass jemand der das gleich folgende verstanden hat, sofort eine Lösung für diese "Problematik" finden wird ;).<br />
<br />
[[Bild:Tutimg_lektion4_numbers2.gif]]<br />
<br />
Wichtig ist, dass wir uns bewusst werden, dass eine Textur beim UV-Mapping immer von 0 bis 1 reicht. Um nun die einzelnen Bilder aus einer Textur auf ein Objekt zu setzen, müssen wir nichts anderes machen, als zu errechnen, an welcher Stelle ein Bild anfängt und wo es aufhört. Nur zur Kontrolle, damit es auch jeder begreift: Würden wir nur die erste Hälfte der Textur auf ein Objekt kleben, so müsste unsere U-Koordinate von 0 bis 0.5 reichen. Die zweite Hälfte hingegen von 0.5 - 1.0. Soweit klingt es doch noch alles logisch oder?<br />
<br />
Genauso müssen wir auch vorgehen, wenn wir einzelne Bilder auf einem Quad abbilden wollen. In unserem Fall müsste die U-Koordinate von 0 bis 1/8 reichen. Das zweite Bildchen hingegen von 1/8 bis 2/8 etc. D.&nbsp;h. wir wissen, dass jedes unserer Bilder 1/8 "Einheiten" lang ist! Und somit haben wir ja bereits eine Lösung für unser Problem. Um das ganze dynamisch auszudrücken: Wir brauchen nur die Größe der Textur durch die Anzahl der Bilder zu teilen. Bevor jemand einen Denkfehler macht: Es ist hierbei ganz egal, wie groß die Textur wirklich ist (hier 256x32 Pixel). Dank OpenGL errechnen wir das UV-Mapping ja in absoluten Größen.<br />
Nun der Code:<br />
<br />
<source lang="pascal">PicLength:= 1 / PicCount;<br />
PicPos:=Round(Pic)*PicLength;<br />
glBegin(GL_QUADS);<br />
glTexCoord2f(PicPos, 1); glVertex3f(-1,-1,0);<br />
glTexCoord2f(PicPos + PicLength, 1); glVertex3f(1,-1,0);<br />
glTexCoord2f(PicPos + PicLength, 0); glVertex3f(1,1,0);<br />
glTexCoord2f(PicPos, 0); glVertex3f(-1,1,0);<br />
glEnd;<br />
<br />
Pic:=Pic + MovementValue;<br />
</source><br />
<br />
Pic ist in diesem Fall eine Variable vom Typ Single, die langsam erhöht wird. Wir runden den Wert hier, so dass beim Erreichen eines jeden ganzzahligen Wertes das nächste Bild angezeigt wird. Wir multiplizieren die Nummer des anzuzeigenden Bildes mit der Breite eines Bildes, um die Anfangsposition zu erhalten und addieren dann noch eine volle Bildbreite dazu, um die Endposition zu erhalten. Hört sich gewaltig gefährlich an, liegt aber mehr an meinem mangelnden Ausdruck als an der Schwierigkeit dieses Problems ;).<br />
<br />
Im Sample werdet Ihr noch sehen, was passiert, wenn man den Wert nicht rundet. Man erhält in diesem Fall einen "flüssigen" Bildübergang. Letztendlich gibt es viele Möglichkeiten, solche Ideen zu implementieren. Nehmt dies einfach als kleineren Gedankenschub und vor allem: Werdet Euch bewusst, was genau dort passiert! Eine Menge toller Dinge lassen sich mit einem guten UV-Mapping erzielen.<br />
Wer noch etwas umherexperimentieren will kann gern versuchen selbes Ziel mit Hilfe der Texturenmatrix zu erreichen. Die Lösung ist verblüffend einfach.<br />
<br />
===Terraforming mal anders===<br />
Relativ lange habe ich nach einem guten Beispiel für folgende Problematik gesucht: Ich wollte Euch die UV-Koordinaten etwas näher bringen und Euch zeigen, wofür man sie einsetzen kann. Irgendwie wollte mir nichts Interessantes aus meinem Kopf entspringen bis ich irgendwann in einigen alten Programmen von mir rumgewühlt habe und einen alten Terrainrenderer von mir fand. Schon war die Idee da! Wir schreiben ein kleines Programm, das eine ganz simple, unoptimierte Landschaft rendern wird. Das hört sich sicherlich Anfangs relativ gewaltig an, mit etwas Verständnis für die oberen Probleme sollte dies jedoch kein Problem für Euch sein.<br />
<br />
Zuvor allerdings ein paar Gedankenspiele. Zunächst widmen wir uns kurz der Texturiering. Wie würde die simpelste Landschaft aussehen, die wir uns vorstellen können? Richtig! Sie wäre ein einfaches, flach liegendes Quadrat mit einer Textur überzogen, der Wand aus unserem ersten Versuch sehr ähnlich! Dies hat jedoch einen klitzekleinen Nachteil: Wir könnten keine Höhenstufen einbauen und ohne die wäre die Landschaft nur halb so realistisch. Denn wir wollen versuchen, folgende Szene zu zaubern:<br />
<br />
[[Bild:Tutimg_lektion4_landscape.gif|thumb|256px|none|eine Lanschaft]]<br />
Um allerdings Höhen einzubinden, müssen wir die Landschaft in viele kleinen Quads unterteilen, die natürlich an Ihren Eckpunkten unterschiedliche Höhen haben, sich jedoch jeweils einige Punkte teilen. Es versteht sich von selbst, dass diese die gleiche Höhe haben müssen, damit die Landschaft auch durchgängig ist und nicht irgendwelche mysteriösen Löcher darauf erscheinen ;).<br />
Auf folgendem Screenshot kann man dies deutlich erkennen:<br />
<br />
[[Bild:Tutimg_lektion4_landwire.gif|thumb|256px|left|Gitter Ansicht der Landschaft]]<br />
Technisch gesehen ist das Ganze einfach zu realisieren, da wir nur begreifen müssen, dass alle Quads nebeneinander liegen und sich - bis auf die äußeren alle einen Eckpunkt teilen. Nun müssen wir beim Rendern jeden Eckpunkt nur noch vom angrenzenden Quad lesen und fertig ist die Landschaft. Ich will da nicht näher drauf eingehen, weil es sich hier nicht um ein Tutorial zur Landschaftsgestaltung handelt. Der Code sollte sich eigentlich von selbst erklären.<br />
<br />
Vielmehr sollten wir uns einer anderen Problematik widmen! Nämlich wie zur Hölle texturieren wir die Landschaft so, dass sie nicht zur Marke "augenfeindlich" gehört?<br />
<br />
[[Bild:Tutimg_lektion4_landscapeerror.gif|thumb|256px|right|wiederkehrende Landschaft]]<br />
Wenn Euer erster Gedanke dabei "Boah, cool!" ist, dann gibt's eins auf die Finger! Das wollen wir doch nicht... wie sieht den die Landschaft aus. Eine immer wiederkehrende Landschaft -> man erkennt sofort, dass wir hierbei auf jedes Quad die gleiche Textur geklebt haben. Doch wie schaffen wir es nun, dass wir eine Textur über alle Quads ziehen, so dass die ganze Landschaft mit einer Textur überzogen ist anstatt nur über ein einzelnes Feld?<br />
Nun, des Lösungs Geheimnis [Anm. des Lektors: Ich liebe ihn dafür! Andere lösen Rätsel. Er geheimnist Lösungen] sind eben unsere UV-Koordinaten und einige pfiffige Köpfe unter Euch sollten bereits einen ersten Verdacht haben. Denn die erste Idee sollte es sein, den linken unteren Punkt eine UV-Koordinate von (0/0) zu geben und der rechten oberen (1/1).<br />
Alles was wir nun also machen müssen, ist, uns die entsprechenden UV-Koordinaten für die Quads dazwischen auszurechnen und sie dann den Punkten zuzuweisen. Dafür benötigen wir zunächst die Breite eines Quads. Mit normaler Logik lässt sich folgende Aussage aufstellen.<br />
<source lang="pascal">qw:=1 / XCount<br />
qh:=1 / YCount;<br />
</source><br />
Ein Quad benötigt die Länge von 1 durch die Anzahl der Quads in einer Reihe oder Spalte. Genauso verhält es sich auch mit der Höhe. Nun brauchen wir beim Rendern nur noch dem jeweiligen Quad in einer For-Schleife die entsprechende Koordinate zuzuweisen:<br />
<source lang="pascal">U:=1 / XCount;<br />
V:=1 / YCount;<br />
<br />
for y:=0 to YCount-1 do<br />
begin<br />
glPushMatrix;<br />
for x:=0 to XCount-1 do<br />
begin<br />
glBegin(GL_QUADS);<br />
glTexCoord2f(U*x, V*(y+1)); <br />
glVertex3f(0, Map[x,y+1], 0);<br />
glTexCoord2f(U*x, V*y); <br />
glVertex3f(0, Map[x,y], 1);<br />
glTexCoord2f(U*(x+1), V*y); <br />
glVertex3f(1, Map[x+1,y], 1);<br />
glTexCoord2f(U*(x+1), V*(y+1)); <br />
glVertex3f(1, Map[x+1,y+1], 0);<br />
glEnd;<br />
glTranslatef(1,0,0);<br />
end;<br />
glPopMatrix;<br />
glTranslatef(0,0,-1);<br />
end;<br />
</source><br />
Das sieht nach einem herben Stück Arbeit aus oder? Aber wenn Ihr Euch das ganze mal aufzeichnet und im Kopf durchspielt, wird der Groschen fallen. Denkt mal etwas darüber nach! Wenn es dann doch noch Probleme mit dem Verständnis geben sollte, wird das nächste Kapitel hoffentlich jedes Missverständnis aus dem Wege räumen ;).<br />
<br />
==Nachwort==<br />
Okay... ich hoffe Ihr fühlt Euch nach der Abarbeitung dieses Tutorials genauso wie ich, nachdem ich es für Euch geschrieben habe... nämlich elend. Irgendwie wurde das immer mehr und ich sagte mir immer wieder "nein, dass ist nicht genug. Das muss genauer und anschaulicher werden". Dies ist auch der Grund dafür, warum dieses eines der größten Tutorials mit den meisten Bildern usw geworden ist. Erst sollte es noch etwas mehr werden und dann in mehrere Tutorials aufgespaltet werden, allerdings glaube ich mit diesem Tutorial einen guten Mittelweg zwischen Information und Totlabern gefunden zu haben. Lasst es mich wissen, wie Ihr dazu steht!<br><br />
Und bevor die Kritiker gleich wieder alle aus Ihren Löchern kommen und mir sagen, dass einige Bereich einfach als gegeben angenommen werden; denen sei nur gesagt, dass wir u.&nbsp;a. auch versuchen Rücksicht auf Leute zu nehmen, die noch nie 3D programmiert haben und vielleicht Eurem Wissen nicht standhalten können. Daher halte ich es hier für wichtiger, Grundlagen wie UV-Koordinaten und den Einsatz von Texturen zu erklären, als ihnen tausende von Zeilen um die Ohren zu hauen, wie sie die Bytes von der Festplatte in den Arbeitsspeicher laden können. Um jedoch keine Wissenslöcher offen zu lassen: Ihr könnt Euch sicher sein, dass spezielle Tutorials folgen werden, die sich speziell mit dem Laden verschiedener Formate beschäftigen und die genaue Interna (was im Hintergrund abläuft) zu erklären versuchen. Auch das Alpha Blending wird hier mehr zu "Show-Zwecken" verwendet und wird zusammen mit dem Z-Buffer genauer erklärt werden! <br><br />
Also bloß keine falsche Hektik! Ich weiß ... einige von Euch sind recht ungeduldig, aber ständiges Nachfragen und Drängeln führt zu nichts. Versucht Fragen lieber ins Forum zu setzen, damit dort ein wenig Leben reinkommt. Denn wenn es dort belebter wird, kommen auch schneller neue Leute und vielleicht sind ja auch welche dabei, die dann das DGL-Team entlasten können. Also nutzt bitte das Forum, anstatt andauernd per ICQ oder Mail irgendwelche Fragen zu stellen! Die Antwort wird auch nicht viel länger auf sich warten lassen. Im Forum jedoch ist alles dokumentiert und auch anderen zugänglich, so dass ich nicht die gleiche Frage bis zu 5 mal am Tag beantworten muss. Sorry aber das geht einem auf die Nerven und vor allem auf die Zeit ;). Schließlich sind wir keine Maschinen sondern Menschen, die auch ein privates Leben haben ^__-.<br />
<br />
Glaubt uns, wir investieren einen sehr großen Teil unserer Zeit in dieses Projekt und solch ein Tutorial lässt sich nicht binnen weniger Tage anfertigen. Jeder der so etwas bereits einmal gemacht hat, wird wissen was ich meine. Es muss ein Konzept her, es müssen Samples geschrieben, Screenshots gemacht werden und ein halbwegs verständlicher Text her. Und nichtsdestotrotz soll es am Ende auch noch passen, einem möglichst großen Publikum Wissen vermitteln, am Besten von Schreib- und Sprachfehlern befreit und in einem halbwegs ansehnlichen HTML-Dokument präsentiert werden. Ein langer Weg ;).<br />
Okay, in diesem Sinne, bis bald ;)!<br />
btw: Vielen Dank an Magellan für die Bereitstellung und Integration seiner "besonderen Lernleistung".<br />
<br />
Euer<br><br />
'''Phobeus'''<br />
<br />
== Dateien == <br />
* {{ArchivLink|file=tut_lektion_4_delphi_api|text=Alter Delphi-API-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_4_delphi_vcl|text=Alter Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_4_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial Lektion 3]]|[[Tutorial Lektion 5]]}}<br />
<br />
[[Kategorie:Tutorial|Lektion4]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Lektion_3&diff=23623Tutorial Lektion 32009-05-17T13:44:20Z<p>Flo: /* Anhang */</p>
<hr />
<div>= Eine Welt des Grauens =<br />
== Vorwort ==<br />
Liebe Leser,<br />
nachdem Ihr hoffentlich den kleinen Schock des letzten Tutorials alle gut überstanden habt, freue ich mich, Euch hier wieder begrüßen zu können. Wer dachte, dass die letzte Lektion bereits schwer war, der sollte dringend Urlaub nehmen. Seid gewarnt! Bevor ich jetzt richtig loslege, solltet Ihr einigermaßen entspannt sein. Wenn dies einer der Tage ist, an denen Ihr nur noch durch eine Kippe oder die Flasche Coke am Leben gehalten werdet, ordne ich erstmal eine kleine Zwangspause an ;).<br />
<br />
Der Stoff der jetzt kommt ist sicherlich nicht gerade die leichteste Lektüre. Wer Mathematik studiert, hat Vorteile, wer einigermaßen logisch denken kann auch. Ein Fachidiot, wie ich, hat nur eine Chance... probieren, testen und lernen :). Und weil ich weiß, wie schwer es eventuell sein kann, werde ich nun einfach mal versuchen, ganz simpel anzufangen und das Ganze mit vielen Bildern so gut wie nur irgend möglich zu illustrieren. <br />
<br />
Und wenn Ihr nun Angst habt, dass Ihr das nicht packt, weil ich hier so eine Panik verbreite, dann solltet Ihr mich sehen, wie ich hier verzweifelt vor meinen Tasten hänge und mich frage, wie ich das alles bloß in Worte fassen soll :). Aber was soll es? Ran an den Kram!!! Heulen könnt Ihr anschließend im Forum ;).<br />
<br />
== Das Grauen hat einen Namen ==<br />
=== "Kinofilm" vs. "Bahnhof" ===<br />
Wer kennt nicht "Matrix" und hätte gedacht, dass es davon nicht nur eine, sondern unendlich viele gibt die so genannten "Matrizen". Vor allem am Anfang werden diese Dinger Euch das Leben erschweren und Ihr werdet leichte Neigungen tief in Euch verspüren, dass am besten Euer ganzes Projekt sich nur im Zentrum Eurer Welt abspielt (und dies ist nicht im wahrsten Sinne des Wortes gemeint).<br />
<br />
Wer mit Matrizen umgehen kann, beherrscht das Wichtigste an der 3D-Programmierung. Wer nicht zu den Genies zählt, sollte nicht sofort aufgeben, sondern sich viele Beispiele ansehen und viel mit diesen herumspielen.<br />
<br />
Wie auch immer... Ihr solltest wissen, dass es bei jedem Rendervorgang mehrere Matrizen gibt, die ganz drastisch das Aussehen der Szene bestimmen. Es gibt in OpenGL drei Bereiche, in denen Matrizen eingesetzt werden. Die so genannte World- oder Modelviewmatrix, die Texturenmatrix und die Perspektivenmatrix. Die zweifellos wichtigste dieser Matrizen ist die Worldmatrix, da sie die Position Eures "Zeichenstiftes" beschreibt und somit Objekte positioniert. Die Texturenmatrix funktioniert fast genauso wie die Worldmatrix nur definiert sie die Ausrichtung der Texturen. Wir werden später auf sie zurückkommen. Die Perspektivenmatrix definiert wesentliche Eigenschaften des Blickfeldes des Betrachters.<br />
<br />
Der Befehl [[glMatrixMode]] erlaubt eine Änderung der aktuellen Matrix. Mit Hilfe der Konstanten GL_MODELVIEW, GL_TEXTURE oder GL_PROJECTION kann man die Matrix bestimmen. Die Namen der Konstanten sind soweit hoffentlich selbst erklärend. Von nun an bewirken alle Matrixoperationen wie beispielsweise [[glLoadIdentity]] oder [[glTranslate|glTranslate*]] eine Veränderung der aktuell gesetzten Matrix.<br />
<br />
=== Die Perspektivenmatrix ===<br />
Wie bereits erwähnt beschreibt die Perspektivenmatrix das aktuelle Sichtfeld. Zum Setzen der Perspektivenmatrix verwendet man in der Regel Befehle wie [[glFrustum]], [[gluPerspective]], [[glOrtho]] oder [[gluOrtho2D]].<br />
<br />
gluPerspective lässt den Raum beispielsweise dreidimensional erscheinen, indem sich die Größe von Objekten mit zunehmender Entfernung vom Betrachter verringert. Diese Verringerung ist abhängig von dem im ersten Parameter übergebenen Winkel. <br />
<br />
glOrtho hingegen ist phantastisch für ein 2D-Blickfeld geeignet, denn diese Art der Projektion enthält keine Tiefe mehr.<br />
<br />
Genauere Informationen zu den einzelnen Funktionen könnt ihr unserem OpenGL-Wiki entnehmen.<br />
<br />
Auf eine Sache möchte ich jedoch noch eingehen: Einige dieser Funktionen verlangen die Parameter "znear" und "zfar". Diese Parameter beschreiben die zwei Schnittflächen, die das Blickfeld vor dem Betrachter begrenzen. "znear" definiert die Entfernung der Nearclippingplane vom Betrachter. Alle Objekte, die sich vor dieser Ebene befinden sind nicht sichtbar. "zfar" beschreibt die Entfernung der Farclippingplane vom Betrachter. Alle Objekte, die sich hinter dieser befinden werden weggeschnitten. Wenn also wieder einmal nur die Hälfte Eurer Szene auf dem Bildschirm sichtbar ist, versucht die Schnittflächen entsprechend anzupassen!<br />
<br />
== Die Worldmatrix ==<br />
=== D3D-Verrat ===<br />
Wenn Ihr ebenfalls zu den D3D-Verrätern gehört (wie z.&nbsp;B. ich *g*), dann solltet Ihr Euch möglichst von eurer Denkweise trennen: Jede Positionierung der Kamera erfolgt über ein Drehen und Bewegen der Szene. Das ist vor allem am Anfang ein wenig gewöhnungsbedürftig ("Die Welt dreht sich! Nicht Du!").<br />
<br />
Vielmehr wird jede Manipulation direkt an die Worldmatrix übergeben. Das heißt, wenn Ihr glTranslate* aufruft, so ist diese Manipulation bereits bei Euch in die Worldmatrix eingetragen, alle weiteren Objekte werden mit dieser Matrix transformiert. Wer zweimal glTranslate*(2, 0, 0) aufruft wird feststellen, dass glTranslate* nicht zwei Matrizen zurückliefert, sondern nur eine, die dann auf (4, 0, 0) verweist. Am Anfang solltet Ihr also sehr darauf achten, es wird eine Weile dauern bis Ihr dies fest in Eurem Herzen tragt. Nicht sofort aufgeben :)!<br />
<br />
== Einfach, Kompakt und Funktional ==<br />
Neben dem Befehl glTranslate*, den wir im letzten Tutorial lieben gelernt haben, gibt es noch weitere Befehle, die sich auf die einzelnen Matrizen auswirken und auf die ich in dem folgenden Abschnitt eingehen möchte.<br />
<br />
=== Dreh- und Angelpunkt des Geschehens ===<br />
Zunächst beschäftigen wir uns mit der Rotation. Wir versuchen ein Objekt zu erzeugen, welches um 90° um seine eigene Achse gedreht wurde. Das ist nicht sonderlich schwer: <source lang="pascal">glRotatef(90,0,1,0);</source><br />
Ein Objekt auf das wir diese Matrix anwenden, wird um 90° um seine Y-Achse gedreht sein. Daraus schließen wir, dass der erste Parameter den Winkel im Gradmaß um den wir rotieren möchten definiert und die 3 folgenden Parameter den Vektor beschreiben um den die Rotation durchgeführt werden soll. Rotationen bergen tatsächlich eine Schwierigkeit, denn es ist nicht immer einfach ein Objekt um die Eigene Achse zu rotieren. Verschiebt Ihr euer Objekt zuerst und beginnt dann die Rotation, so rotiert das Objekt um die eigene Achse.<br />
<br />
Ruft Ihr jedoch zuerst glRotate* und anschließend glTranslate* auf, so unterscheidet sich erzielte Ergebnis von dem vorherigen.<br />
<br />
Um diesen Problemen aus dem Weg zu gehen so müsst Ihr Euch vorstellen, dass Ihr den Rotationspunkt im Moment des Aufrufs von glRotate* setzt. Verschiebt Ihr Euer Objekt anschließend so bleibt der Rotationspunkt derselbe und das Objekt beginnt den Rotationspunkt auf einer Bahn zu umkreisen. So gesehen gibt es keinen wirklichen Unterschied und die Rotation um die eigene Achse ist ein Ausnahmefall, denn der Rotationspunkt liegt dann in dem von uns erwarteten Mittelpunkt des Objektes.<br />
<br />
=== Eine Frage der Größe ===<br />
Nun ja... was kann man denn noch alles in einer 3D-Welt machen? Ihr habt gelernt, wie man Objekte bewegt und diese durch Rotationen ausrichten kann. Das ist doch schon eine Menge zum Spielen oder? Nun, bei einer Transformation kann für uns noch eine Sache sehr wichtig werden, nämlich die Möglichkeit, zu bestimmen in welcher Größe ein Objekt dargestellt werden kann.<br />
<br />
Sicher würde es Sinn machen ein benötigtes Objekt gleich in der richtigen Größe in die Anwendung zu laden. Manchmal ist es aber von Vorteil, wenn man die Größe eines Objektes ohne großen Aufwand verändern kann oder aber mehrere Objekte desselben Typs mit unterschiedlicher Größe darzustellen.<br />
<br />
Eine Größenveränderung ist mit Hilfe des Befehls [[glScale|glScale*]] möglich.<br />
<source lang="pascal">glScalef(1,1,1);</source><br />
In diesem Fall würden wir das Objekt so zeichnen, wie wir es auch angegeben haben. Das heißt in seiner Originalgröße. Sicherlich ahnst Du bereits, was die Parameter aussagen... sie stehen für das Verhältnis der einzelnen Seiten nach dem Muster X, Y und Z. Würden wir als ersten Parameter statt einer 1 eine 2 einsetzen, würde unser Objekt in seiner räumlichen Ausdehnung bezogen auf der X-Achse doppelt so groß sein. Würden wir stattdessen 0.5 angeben, wäre es nur noch halb so groß.<br />
<br />
In Wirklichkeit bewirkt glScale* jedoch keine Veränderung der Größe sondern eine Verzerrung unseres Koordinatensystems. Daher wirkt jeder Aufruf von glScale* auch auf Befehle wie glTranslate*. Aus Sicht der Matrizen lässt sich dieses Phänomen auch recht leicht erklären. In Wirklichkeit erzeugt jeder Aufruf von glRotate*, glTranslate* oder glScale* eine eigene Matrix, die dann mit der aktuellen Matrix (z.B. der Modelviewmatrix) multipliziert wird. Wurde in der Modelviewmatrix bereits ein glScale* verewigt, so wirkt sich dieses bei der Multiplikation der Matrizen auf die Transformationsmatrix aus.<br />
<br />
Ich denke ich muss nicht näher darauf eingehen, dass es keine gute Idee ist das Koordinatensystem mit einem Aufruf von glScalef(0, 0, 0) zu zerstören. Was würde es auch für einen Sinn ergeben, das Koordinatensystem in einem Punkt unterzubringen? <br><br />
'''''Hinweis''': Es ist bereits gefährlich nur eine Achse auf 0 zu skalieren, denn dadurch wird die aktuelle Matrix beschädigt ([http://www.math.unizh.ch/fachverein/forum/detail.jsp?FORUM=120 singulär]) und Befehle wie [[gluProject]] und [[gluUnProject]] funktionieren nicht mehr.''<br />
<br />
Ich hoffe ich habe Euch nun nicht den letzten Funken Hoffnung OpenGL zu verstehen geraubt. Ich kann Euch versichern, dass dieses Verständnis mit der Zeit heranreifen wird.<br />
[[bild:Tutimg_lektion3_skalierung.gif|256px|right]]<br />
glScale* ist aber aufgrund seiner phantastischen Eigenschaften nicht nur in der Lage die Größe von Objekten zu verändern sondern es ist auch möglich Objekte zu spiegeln indem man negative Werte an glScale* übergibt. Somit kann sich z.&nbsp;B. ein D3D-Umsteiger das leben vereinfachen indem er mit einem Aufruf von glScalef(1, 1, -1) sicherstellt, dass die Z-Achse in den Bildschirm hinein positiv verläuft.<br />
<br />
Jeder, der die Funktion der Modelviewmatrix begreifen möchte, dem möchte ich das Programm Matrixcontrol von Lithander (siehe Anhang) ans Herz legen. Es erlaubt Euch bestimmte Änderungen an der Matrix direkt nachzuvollziehen und wenn man mit dem Programm ein wenig umherspielt wird selbst dem letzten ziemlich schnell ein Licht aufgehen. Wenn nicht, dann hilft nur fleißiges Programmieren und Anwenden und irgendwann klatscht es dann laut, wenn die eigene Handfläche auf der Stirn zum stehen kommt ;).<br />
<br />
== Virtuelle Gedächtnisse ==<br />
=== Vom Pushen und Poppen ===<br />
Wer diesen Titel ließt und sich nun zwangsläufig daran erinnert, was er bereits alles in seinem Leben konsumiert hat oder neben wem er am nächsten Morgen aufgewacht ist, sei entwarnt! Wir reden hier von etwas ganz anderem...<br />
<br />
Wie wir bereits gelernt haben, verändert jeder Aufruf von glTranslate*, glRotate* oder glScale* die Worldmatrix und wirkt sich unmittelbar auf andere Objekte aus. Dies kann durchaus sehr erwünscht sein. Wenn wir allerdings in einer großen Welt verschiedene Objekte positionieren, kann dies schnell zum Fluch werden. Natürlich können wir dem entgegenwirken und zum Beispiel jedes Mal glLoadIdentity aufrufen. Dies hat jedoch den Nachteil, dass die World- Matrix dann eben wieder leer ist und wir z.B. die Camera erst wieder setzen müssen, damit die Objekte an die richtige Stelle transformiert werden.<br />
<br />
Eine weitaus elegantere Lösung ist die Verwendung des "Matrixstacks"! Jeder Aufruf von [[glPushMatrix]] bewirkt, dass die momentane Matrix auf den [[Stack]] geschrieben wird. Alle Manipulationen, die wir dann durchführen, werden wie gewohnt vollzogen. Ist alles gezeichnet, rufen wir mit [[glPopMatrix]], die letzte Matrix vom Stack wieder herunter und haben im Prinzip den letzten Zustand vor unseren Veränderungen rekonstruiert und können dann wieder anfangen, auf dieser Matrix aufzubauen. Natürlich lassen sich auch mehre Matrizen auf den Stack pushen! Dies kann reichlich Zeit sparen, wenn man bedenkt, dass man ansonsten andauernd die Matrix der Szene neu setzen muss.<br />
<br />
Um das ganze etwas anschaulicher zu machen, verwenden wir ein einfaches Beispiel. Wir werden zwei Dreiecke jeweils rechts und links vom Ursprung zeichnen. Nachdem wir das linke Objekt gezeichnet haben, werden wir nicht glLoadIdentity einsetzen oder das zweite Objekt entsprechend nach rechts transformieren, sondern eben den Matrixstack zu Hilfe nehmen.<br />
<source lang="pascal">glLoadIdentity();<br />
glTranslatef(0,0,-10);<br />
glPushMatrix();<br />
glTranslatef(-2,0,0);<br />
glBegin(GL_TRIANGLES);<br />
glColor3f(1,0,0); glVertex3f(-1,-1, 0);<br />
glColor3f(0,0,1); glVertex3f( 1,-1, 0);<br />
glColor3f(0,1,0); glVertex3f( 0, 1, 0);<br />
glEnd();<br />
glPopMatrix();<br />
<br />
glTranslatef(2,0,0);<br />
glBegin(GL_TRIANGLES);<br />
glColor3f(1,0,0); glVertex3f(-1,-1, 0);<br />
glColor3f(0,0,1); glVertex3f( 1,-1, 0);<br />
glColor3f(0,1,0); glVertex3f( 0, 1, 0);<br />
glEnd();</source><br />
Wie Ihr seht, setzen wir am Anfang praktisch die Distanz zum Objekt, speichern die Matrix und zeichnen das erste Objekt. Wir setzen dann die Matrix wieder zurück, so dass sie nur noch den Aufruf von glTranslatef(0, 0, -10) beschreibt und transformieren das zweite Objekt... fertig. Man kann auch ohne diese Matrixstacks leben sie können einem aber das Leben auch sehr erleichtern.<br />
<br />
== I wanna all! ==<br />
Wer glaubt, dass es das bereits war der irrt. Es gibt noch weitere Möglichkeiten auf die Matrix Einfluss zu nehmen. Der Befehl [[glLoadMatrix|glLoadMatrix*]] erlaubt es eine beliebige 4x4 Matrix zu laden und die aktuelle Matrix durch die geladene komplett zu ersetzen.<br />
<br />
Der Befehl [[glMultMatrix|glMultMatrix*]] multipliziert die übergebene 4x4-Matrix mit der aktuellen Matrix. Mit diesem Befehl könnte man beispielsweise eigene glScale*- und glRotate*-Befehle schreiben, obgleich sich auch hier über den Sinn streiten lässt. Eine sinnvollere Anwendungsmöglichkeit wird in einem späteren Tutorial vorgestellt werden.<br />
<br />
Der Befehl [[glGet|glGet*]] mit den Tokens GL_MODELVIEW_MATRIX, GL_TEXTURE_MATRIX und GL_PROJECTION_MATRIX ermöglicht euch die einzelnen Matrizen anzufordern.<br />
<br />
== Weltenformeln ==<br />
=== Hier kommt die Sonne... ===<br />
Genug der Theorie! Es wird langsam Zeit für etwas Praxis...<br />
<br />
Nun, um ehrlich zu sein, habe ich an der folgenden Idee ein wenig Zeit benötigt, um ein einigermaßen gut anschauliches Beispiel zu finden... ich will hier nicht weiter darauf eingehen, was ich gerade gehört habe, als mir die Sonne aufging :-D.<br />
<br />
Wie auch immer, man kann komplexere Matrixtransformationen sehr leicht mit unseren Sonnensystem beschreiben (Hey, wenn jetzt jemand denkt, dass ich spinne...). Um das Ganze etwas übersichtlicher zu gestalten, werden wir unser Prinzip auf Sonne, Mond und Erde beschränken.<br />
<br />
Wie funktioniert unser Sonnensystem? (Warnung, der Autor betritt mal wieder seine ausgeprägte Fantasywelt! [Anm. d. Lektors: Die richtige Sprache dafür hat er ja schon]).<br />
<br />
Die Sonne steht im Mittelpunkt und sollte sich in unserem Beispiel nicht selbst bewegen. Sie ist einfach nur im Mittelpunkt und vegetiert dort vor sich hin! Nun gibt es einen kleinen blauen Planeten, namens Erde (wieso er im Sample ein Dreieck ist, sei Eurer Kreativität überlassen...), der zum einen um seine eigene Achse rotiert, viel wichtiger jedoch, sich auch noch um die Sonne dreht. Nun werden sich vor allem die Neulinge unter Euch sadistisch freuen, das Fenster schließen, ihr Delphi starten und drauf los proggen! Das ist doch alles kein Ding, der gerade mal frisch aufgeschnappte Sinus-Satz lässt sich prima mit einbringen! Und schon berechnet man mit Hilfe von Sinus, die Position auf der Kreisbahn und positioniert den Planeten mit glTranslate* an seine Position. Und oh Wunder es klappt. HALT! Wer es nicht gemerkt hat, da war ne Portion Ironie dabei. Bitte Weiterlesen ^__^!<br />
<br />
Denn spätestens wenn wir folgende Ergänzung zum Besten geben, werden die ersten Augen wässrig werden, weil die meisten Hirne einem Informationskoller unterliegen. Meines stieg ja schon beim einfachen Sinus aus :-D. Ab und an kann man nämlich nachts weißgräuliche Flecken am Himmel beobachten! Wer annimmt, dass es sich hierbei um eine geschickt platzierte, in Echtzeit berechnete, Textur handelt, ist schief gewickelt! Es handelt sich dabei nämlich um ein Decal, namens Mond. Oha... so schlimm war es schon lange nicht mehr. Dieser bewegt sich in unserem Beispiel auch nicht entlang der Erdachse, sondern auf einer schiefen Bahn, damit man auf der unteren Hemissphäre den Mond auch am Tage noch sehen kann ^___^ (*selbstgefälliges, göttliches Grinsen*). Dies lässt sich nur sehr schwer mit Sinus beschreiben. Bevor wir uns nun jedoch mit einem Block bewaffnen oder den nächsten Mathematik-Professor entführen, damit er die Mathematik für uns übernimmt, greifen wir lieber zu den Sternen und nehmen ein einfacheres Verfahren! Les Matrices!<br />
<br />
=== Und es dreht sich doch! ===<br />
Die Vorgehensweise ist eigentlich relativ simpel. Wir Zeichnen unsere Sonne im Mittelpunkt unseres Universums. Mit dem Aufruf von glRotate* setzen wir den Rotationspunkt an die Position unseres "Zeichenstiftes". In unserem Fall ist das noch immer die Position der Sonne. <br />
<br />
Nun verschieben wir die Erde nach links und die erste Hürde ist genommen. Jetzt müssen wir nur noch den Mond setzen und das funktioniert genauso einfach wie zuvor mit der Erde.<br />
<br />
Unser Zeichenstift befindet sich nun an der Position der Erde und daher rufen wir glRotate* auf. Nun müssen wir den Zeichenstift nur noch ein kleines Stück neben der Erde positionieren und den Mond zeichnen. Voila! Es ist vollbracht!<br />
<br />
Wenn man genauer darüber nachdenkt erkennt man sehr schnell wie elegant man dieses verhältnismäßig komplizierte Problem gelöst hat.<br />
<br />
Wer nun auf Wolke Sieben schwebt und glaubt er könnte sich nun an den nächsten DOOMTitel werfen, der sollte das Programm zuvor noch so erweitern, dass die Sonne, die Erde und auch der Mond sich zusätzlich um die eigene Achse drehen.<br />
<br />
== [[Timebased Movement]] ==<br />
Wer bereits versucht hat eines der ersten selbst geschriebenen OpenGL-Programme voller Stolz seinem Kumpel vorzuführen wird festgestellt haben, dass die Bewegungen auf dem anderen Rechner schneller oder langsamer abgelaufen sind als es auf dem eigenen Rechner der Fall war.<br />
<br />
Dieses Phänomen lässt sich sehr einfach erklären. Nehmen wir an wir bewegen ein Dreieck von links nach rechts um den Wert 1 über den Bildschirm. Eine alte Krücke wie mein PC schafft vielleicht 50 FPS. Das bedeutet das Objekt wird in einer Sekunde genau 50mal um eine Einheit nach rechts verschoben. Das bedeutet insgesamt also um 50 Einheiten in einer Sekunde. Wenn das Programm nun aber auf einem High-End-System läuft werden wir die Szene... sagen wir... 500mal aktualisieren können. Folglich bewegt sich auf diesem Rechner das Dreieck in einer Sekunde um 500 Einheiten.<br />
<br />
Das einzige was wir von unserem Dreieck erhaschen können ist ein blitzartiges Zucken auf dem Bildschirm, welches wir schnell als optische Täuschung abtun würden. Wie kann man diesem Problem nun aber entgegen wirken?<br />
<br />
Nehmen wir an wir kennen die Zeit, die wir zum Zeichnen eines Frames benötigen und ermitteln anhand dieses Wertes einen Zeitfaktor, den wir in die Bewegung mit einfließen lassen.<br />
<source lang="pascal">NewPosition := OldPosition + Movement * TimeFactor;</source><br />
Handelt es sich um einen schnellen Rechner so ist der Zeitfaktor entsprechend niedrig und die Bewegung pro Frame verringert sich. Ist der Rechner hingegen langsam so besitzen wir einen hohen Zeitfaktor. Folglich legt das Dreieck auf dem schnelleren Rechner pro Frame weniger Weg zurück als auf dem langsamen. Insgesamt jedoch legen die beiden Dreiecke im gleichen Zeitabstand (z.&nbsp;B. in einer Sekunde) denselben Weg zurück.<br />
<br />
Um den Zeitfaktor zu ermitteln muss man lediglich die Zeit für einen Schleifendurchlauf ermitteln. Um eine möglichst hohe Genauigkeit zu erhalten sollte man die Zeit für den letzten Schleifendurchlauf ermitteln und im aktuellen Schleifendurchlauf verwenden.<br />
<br />
== Nachwort ==<br />
Wow... und wieder ein wenig Zeit zum Ausspannen und ein paar persönlichen Worten. Ich kann getrost nur eines sagen: Wenn Ihr nun denkt "das war alles ja sehr leicht" habt Ihr irgendetwas falsch gemacht und solltet das Tutorial noch einmal von Vorn durcharbeiten ;). Wenn Ihr jedoch leichte Kopfschmerzen verspürt (ob es nun die Matrizen sind oder der fiese Elementarbereich *sg*...) so habt Ihr alles richtig gemacht ;).<br />
<br />
Matrizen sind vor allem vielen Einsteigern sehr fremd. "Übung macht den Meister". Versucht Euch doch einfach mal die eine oder andere Aufgabe selbst zu stellen oder unser "Sonnensystem" selbst einmal nachzuschreiben. Wenn Ihr glaubt, dass das Ganze ganz nett geworden ist, dann schickt es mir doch einfach mal zu. Ich freue mich immer darüber zu sehen, was für Früchte meine Tutorials tragen. *lach* Und wenn sich plötzlich alles um die Erde dreht, dann habe ich a) das Gefühl, dass die Kenntnisse über das Sonnensystem nicht mehr ganz up to date sind oder b) ich einen ziemlich fatalen Fehler beim Erklären gemacht habe :->.<br />
<br />
Schreckt auch bitte nicht davor zurück, eine Frage diesbezüglich im Forum zu stellen. Es ist keine Schande, mit einer Matrix Probleme zu haben und vor allem die Erfahrenen unter uns, sollten nur zu gut wissen, was alles die Ursache dafür sein kann, wenn man plötzlich gar nichts mehr auf dem Screen sieht :). <br />
<br />
Das [[Tutorial Matrix2]] behandelt das Thema Matrizen, und vor allem wie man Sachen in OpenGL positioniert, noch genauer.<br />
<br />
Wir selbst werden uns in den folgenden Kapiteln noch etwas intensiver mit den Matrizen beschäftigen und unter anderem die eine oder andere interessante Spielerei zeigen!<br />
<br />
'''Euer'''<br><br />
'''Phobeus'''<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=tut_lektion_3_delphi_api|text=Alter Delphi-API-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_3_delphi_vcl|text=Alter Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_3_exe|text=Windows-Binary zum Tutorial}}<br />
* [http://www.pixelpracht.net Pixelpracht] - Hier könnt Ihr das Programm "Matrix Control" finden<br />
* [http://www.phobeus.de/hosting/shared/pixelpracht/downloads/mc_v02.zip Matrix Control]<br />
<br />
{{TUTORIAL_NAVIGATION | [[Tutorial Lektion 2]] | [[Tutorial Lektion 4]]}}<br />
[[Kategorie:Tutorial|Lektion3]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Lektion_2&diff=23622Tutorial Lektion 22009-05-17T13:42:48Z<p>Flo: </p>
<hr />
<div>= Entdeckung einer neuen Welt =<br />
== Vorwort ==<br />
Ich möchte Euch an dieser Stelle bei DGL herzlich willkommen heißen. Vermutlich wird dies eines der ersten Tutorials sein, das Ihr als Einsteiger lesen werdet. Wahrscheinlich werdet Ihr dann auch noch nicht lange bei uns sein und Euch nicht vorstellen können, dass all die Schreiber auf unserer Seite keine Gurus, sondern ganz normale Menschen sind.<br />
<br />
Man kann uns also jeder Zeit im Forum "anfassen", uns Fragen stellen, Vorschläge unterbreiten oder einfach nur einmal kurz mit einem Lob ermutigen weitere Texte zu verfassen ;-).<br />
<br />
Ich wünsche Euch an dieser Stelle viel Erfolg bei dem Einstieg in die Thematik OpenGL und ermahne Euch noch einmal, dass Ihr Eure Ziele nicht zu weit steckt. Wirklich Spaß beginnt OpenGL nämlich erst dann zu machen, wenn man stets kleinere Erfolge feiern kann.<br />
== Höhere Mächte - Matrizen und ihre Folgen ==<br />
=== Saubere Arbeit ===<br />
Bevor wir direkt beginnen, etwas zu zeichnen, müssen wir erst einmal das Bild löschen, denn wie in der vorherigen Lektion beschrieben zeichnen wir die Szene jeden Schleifendurchlauf neu. Das Löschen der Puffer, in dem die Bildinformationen enthalten sind, übernimmt die Funktion [[glClear]]. Die Farbinformationen unser [[Fragment|Fragmente]] (Fragmente sind vergleichbar mit den [[Pixel|Pixeln]] auf dem Bildschirm, besitzen aber weitere Informationen wie z.&nbsp;B. Tiefenwerte. Bei der Ausgabe des Bildes werden diese Fragmente in Pixel umgewandelt) sind in dem so genannten [[Farbpuffer]] (Colorbuffer) gespeichert. Aus diesem Grund übergeben wir an die Funktion [[glClear]] die Konstante '''GL_COLOR_BUFFER_BIT'''. Die oben bereits angesprochenen Tiefenwerte stehen im [[Tiefenpuffer]]. Diese Daten werden benutzt, um zu entscheiden, welche Pixel durch andere verdeckt werden. Deshalb sollten wir den Tiefenpuffer auch mit löschen. Die Konstante dafür heißt '''GL_DEPTH_BUFFER_BIT'''.<br />
<br />
<source lang="pascal">//Farbbuffer und Tiefenpuffer entleeren<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);</source><br />
<br />
[[bild:Tutimg_lektion2_puffereffekt.gif|right]]<br />
Mit Hilfe des Befehls [[glClearColor]] können wir festlegen, welche Farbinformationen mit dem Aufruf von glClear in den Colorbuffer geschrieben werden sollen. Wenn wir es also genau nehmen, so löscht glClear den Colorbuffer nicht, sondern es schreibt ihn mit den definierten Werten voll. Standardmäßig erhalten die Fragmente eine schwarze Farbe. (Der Vollständigkeit halber sei erwähnt, dass der Tiefenpuffer immer mit Nullen überschrieben wird.)<br />
<br />
Es wird also nicht bei jedem Zeichenvorgang (Rendervorgang) der Bildspeicher gelöscht, sondern der Programmierer entscheidet, wann dies geschehen soll. Theoretisch könnt Ihr auch die Szene wiedergeben und dann löschen, bevor sie ausgegeben wird... wer's mag :).<br />
<br />
Wer sich entscheidet, den Colorbuffer nicht zu löschen, der kann so auch einige nette Effekte erzeugen. Den Cheatern unter euch sollte der erreichte Effekt bekannt vorkommen, wenn man beispielsweise bei Counter-Strike durch die Wand gelaufen ist. Der Grund für das Verwischen ist in beiden Fällen derselbe: Das neue Bild wird gezeichnet, ohne dass das alte Bild aus dem Puffer entfernt wurde.<br />
<br />
=== Und die Welt ist doch keine Scheibe... ===<br />
[[bild:Tutimg_lektion2_koordinatensystem.jpg|256px|right]]<br />
Nachdem wir nun wieder Ordnung im Speicher geschafft haben, können wir zum interessanten Teil kommen: "Wie darf man sich einen 3D-Raum vorstellen?" Nun... warum schaut Ihr Euch nicht einmal in Eurem Zimmer um?<br />
<br />
Versuchen wir doch mal, ein Beispiel direkt aus dem Leben gegriffen zu nehmen und stellen uns unsere Umgebung als Koordinaten-System vor (Igitt!). Der Monitor, der hoffentlich direkt vor uns steht, ist in diesem Fall unser Ursprung, d.h. er liegt an dem Punkt (0, 0, 0).<br />
<br />
Blicken wir seitwärts neben den Monitor, so sehen wir eine Linie (wenn Ihr es nicht tun solltest, macht Euch keine Sorgen ... solche Anfälle hat der Autor häufiger *g*), die von links nach rechts verläuft. Es handelt sich hierbei um die X-Achse, die links vom Monitor im negativen Bereich verläuft, rechts davon in den positiven.<br />
<br />
Nun blicken wir einmal nach oben und einmal nach unten und schon erspähen wir die Y-Achse, die oberhalb des Monitors positiv verläuft, unterhalb negativ. Wunderbar! Wenn Ihr bereits in 2D gearbeitet habt, solltet Ihr an dieser Stelle ein dümmliches Grinsen auf Eurem Gesichte haben und das wohltuende Gefühl, dass Euch das doch alles bereits irgendwie bekannt vorkommt. Doch zu unserem Entsetzen können wir uns ja auch noch von unserem Bildschirm wegbewegen oder auch dichter heran (alle Kurzsichtigen unter uns sollten das nicht so persönlich nehmen, auch sie leben in einem 3D-Raum ^__-).<br />
<br />
Dieses Phänomen ist im 3D-Raum typisch, jedoch nicht unerklärlich, da wir uns auf der Z-Achse bewegen. Rollen wir mit dem Stuhl vom Monitor weg, so gelangen wir in den positiven Bereich der Z-Achse, bewegen wir uns drauf zu, gelangen wir in den negativen Bereich. Jeder, der bereits in D3D programmiert hat, sollte sich schleunigst einprägen, dass das ein großer Unterschied zwischen D3D und OpenGL ist. Das kann sonst zu einigen wirklich fiesen Fehlern führen, wenn man es nicht weiß... *fg*.<br />
<br />
Soweit so gut! Klingt bisher hoffentlich immer noch nicht so kompliziert. Nur keine Sorge, das Niveau versuchen wir zu halten ;).<br />
<br />
Speziell wenn Ihr bereits 2D-Spiele programmiert habt, solltet Ihr Euch sehr schnell von folgenden Gedanken trennen: Die Koordinaten, die Ihr seht und angebt, entsprechen in der Regel nicht den Pixeln des Bildschirmes, sondern so genannten Weltkoordinaten. Eine Definition für diese Weltkoordinaten gibt es nicht, denn wie groß diese sind, ist dem Programmierer überlassen. Ob Ihr euer Objekt eine Einheit vor Euch und 0,1 Einheit rechts von Euch oder aber 100 Einheiten vor euch und 10 Einheiten neben Euch positioniert ist egal. Die euch zur Verfügung stehende Welt ist grenzenlos ;). Ihr könnt Eure Szene theoretisch unendlich klein oder aber unendlich groß darstellen. Das Einzige, was euch wirklich daran hindert, ist die Größe und Auflösung der Euch zur Verfügung stehenden Typen wie Integer oder Single ;). Der eigentliche Größeneindruck eines Objektes entsteht durch die Geschwindigkeit mit der sich der Betrachter und andere Objekte in der Welt bewegen.<br />
<br />
=== Der erste Kontakt ===<br />
Um nun etwas mit OpenGL rendern zu können, müssen wir uns bewusst werden, was die Worldmatrix ist. In dieser Matrix wird festgehalten, wie und wo ein Objekt gezeichnet wird. Das hört sich vielleicht zunächst recht merkwürdig an, lässt sich aber leicht veranschaulichen.<br />
<br />
Stellen wir uns vor, die Worldmatrix zeigt auf einen Punkt, an dem ein Objekt gezeichnet werden soll (ganz übel... wir werden später detaillierter darauf eingehen. Sollte es jemand also nach der folgenden Erläuterung nicht verstanden haben, kann er mich selbstverständlich persönlich per Mail zur "Rechenschaft" ziehen oder aber er wirft einen Blick in unser OpenGLWiki :)).<br />
<br />
Diesen Punkt setzen wir nun am Anfang in den Mittelpunkt unseres Koordinatensystems. Um oberes Beispiel aufzugreifen: Den Bildschirm. Dies geschieht mit dem Befehl [[glLoadIdentity]] und entspricht einem Reset der Worldmatrix. Theoretisch können wir nun an dieser Stelle etwas zeichnen. Dies würde allerdings dazu führen, dass das Objekt sehr groß oder gar nicht zu sehen ist, weil wir uns eben auch an diesen Stellen befinden. Gehen wir doch einmal 1,5 Einheiten nach links und 6 Einheiten nach hinten!<br />
<br />
Hierfür sollte man sich bewusst werden, dass wir in OpenGL praktisch an einen Stuhl gefesselt sind, d.h. wir können uns gar nicht bewegen. Das soll uns aber nicht davon abhalten. Wir brauchen ja nur die ganze Welt so zu bewegen, dass es für uns aussieht, als ob wir uns bewegen. Denn wenn sich alles außer uns nach links bewegt, haben wir den Eindruck, wir wären nach rechts gewandert, right?<br />
<br />
Nun aber zu der Bewegung unseres ersten Objektes:<br />
<source lang="pascal">glTranslatef(-1.5,0,0);<br />
glTranslatef(0, 0,-6);</source><br />
Dies hat bewirkt, dass sich unser Zeichenstift 1,5 Einheiten nach links (negativer Bereich der X-Achse) und anschließend 6 Einheiten nach hinten bewegt hat. Ich habe diesen Fall absichtlich in zwei Schritten gefasst, um es zu verdeutlichen. Sicherlich wäre es einfacher, alles mit nur einem Aufruf von [[glTranslate|glTranslate*]] zu machen:<br />
<source lang="pascal">glTranslatef(-1.5, 0,-6);</source><br />
Man beachte, dass in diesem Fall die 6 Schritte nach hinten notwendig sind um ein bisschen Distanz zum Objekt zu bekommen und nicht direkt in ihm zu stehen!<br />
<br />
Fertig! Schon haben wir dort unseren "Zeichenstift" positioniert, der nun auf weitere Zeichenkommandos von uns wartet. Das mag sicherlich alles ein wenig verwirrend klingen... probiert es am Besten aus und spielt mit den Parametern und erkennt, was gemeint ist :).<br />
== Von Sichtungen... ==<br />
=== Die Büchse der Pandora ===<br />
Nun sind wir aber auch alle scharf darauf, endlich etwas zu rendern und auf dem Bildschirm auszugeben. Dies geschieht z.&nbsp;B. mit folgenden Zeilen:<br />
<source lang="pascal">glBegin(GL_TRIANGLES);<br />
glVertex3f(-1,-1, 0); <br />
glVertex3f( 1,-1, 0);<br />
glVertex3f( 0, 1, 0);<br />
glEnd;</source><br />
Wir erkennen hierbei eindeutig eine Art Block. Gerade wir Pascaler sollten diese ja lieben :->. Wir teilen OpenGL mit Hilfe von [[glBegin]] mit, dass wir ein Objekt zeichnen wollen. In diesem Fall übergeben wir den Parameter '''GL_TRIANGLE''', der OpenGL angibt, dass folgende Punkte als ein Dreieck interpretiert werden sollen.<br />
<br />
Anschließend folgt ein dreifacher Aufruf von [[glVertex|glVertex3f]]. Jeder Aufruf erzeugt nun einen Punkt (Vertex) in unserem 3D-Raum. Da wir OpenGL bei glBegin mitgeteilt haben, dass diese Punkte zu einem Dreieck zusammengefügt werden sollen, wird das auch so getan ;).<br />
<br />
Wie wir auf dem Bild erkennen können, wurde unser Dreieck wie erwartet nach links verschoben abgebildet und auch mit einem leichten Abstand zur Kamera, nämlich 6 Einheiten entlang der Z-Achse.<br />
=== Guter Stoff! ===<br />
Nun... ein wenig trostlos sieht unser Dreieck nun doch aus, oder? Ich habe übrigens damals bei D3D rund eine Woche benötigt, bis ich ein schwarzes Dreieck hatte. Wir haben immerhin schon ein weißes... aber wir gehen nun einen Schritt weiter und werden das Dreieck schön bunt einfärben.<br />
<br />
Bevor nun irgendjemand anfängt und Pixel für Pixel den Bildschirm nach zu pinseln oder gar sein PaintShop bereits offen hat, um die ersten Texturen zu erstellen, sei gestoppt! Es gibt für einfache Einfärbungen in OpenGL eine bessere Methode. Wir definieren einfach für jeden Eckpunkt eine Farbe und OpenGL wird dann sogar eigenständig die Farbverläufe dafür erstellen. Jeder der D3D kennt, wird diese Vorgehensweise bekannt vorkommen und er wird beginnen, verzweifelt nach dem FVF zu suchen, um es korrekt zu definieren... Wenn Ihr einen Vertex zeichnet, so rendert OpenGL diesen automatisch "weiß" (Ah huch! Deswegen ist unser Dreieck auch weiß???). Alles was wir nun machen müssen, ist OpenGL mitzuteilen, dass es eine andere Farbe einsetzen soll. Und zwar wird OpenGL diese Farbe für alle folgenden Eckpunkte nutzen, so lange bis von uns ein anderes Kommando kommt.<br />
<br />
Der mysteriöse Befehl, um den ich nun schon die ganze Zeit herumschwafle ist [[glColor|glColor*]]:<br />
<source lang="pascal">// Alle folgenden Eckpunkte werden rot gefärbt<br />
glColor3f(1,0,0);</source><br />
Wobei jeder Parameter einen Farbwert repräsentiert und zwar nach dem Muster RGB. Es wird ein Wert zwischen 1 und 0 erwartet, wobei eine Eins "volle Farbsättigung" bedeutet. Das heißt in unserem Fall haben wir als Farbe "rot" gesetzt.<br />
<br />
Und weil wir schließlich die Welt ein wenig bunter machen und nicht nur das weiße Dreieck rot färben wollten, werden wir nach jedem Eckpunkt eine neue Farbe setzen:<br />
<source lang="pascal">glBegin(GL_TRIANGLES);<br />
glColor3f(1, 0, 0); glVertex3f(-1,-1, 0); <br />
glColor3f(0, 0, 1); glVertex3f( 1,-1, 0);<br />
glColor3f(0, 1, 0); glVertex3f( 0, 1, 0);<br />
glEnd;</source><br />
Und dies ist dann unser farbenfrohes Ergebnis. Beeindruckend, wenn man bedenkt, wie wenig Aufwand letztendlich dahinter steckt, oder?<br />
<br />
[[bild:Tutimg_lektion2_dreieck.gif]]<br />
<br />
== Nachwort ==<br />
Wer meine Tutorials kennt weiß, dass zum Abschluß noch immer ein wenig Geblubber von mir kommt (man beachte diese entzückende Wortwahl von meiner einer... hoffe, der Lektor findet es auch amüsant...). (Anm. des Lektors: Und wie!) Schauen wir doch mal stolz auf das zurück, was wir heute erreicht haben! Wir haben OpenGL initialisiert, ein erstes Dreieck auf den Bildschirm gezaubert und dieses in den Farbtopf gesteckt! Das solltet Ihr als ein historisches Ereignis ansehen. Als ich damals mit D3D angefangen habe, brauchte ich, um es mir selbst zu erarbeiten, ca. eine Woche (Das ist jetzt der Moment, in dem Ihr Eure Hände heben solltest und ein lautes "Call me God" aus Euch herauskommen sollte, so dass zumindest Eure unmittelbaren Mitmenschen denken, dass Ihr einen Dachschaden habt!!! Das gehört einfach mit dazu ^__- )<br />
<br />
Wie immer solltet Ihr Euch nun hinsetzen und Euch ein wenig mit den Parametern vertraut machen. Speziell bei glTranslate* solltet Ihr ein wenig mit den Parametern experimentieren, damit Ihr seht, was es mit den Matrizen auf sich hat und wie diese funktionieren. Wenn Ihr dann auch denkt, dass Ihr damit vertraut seid, versucht ein wenig mit den einzelnen Vertexpositionen zu experimentieren und verändert diese. Wenn auch das klar ist, setzt Euch in die Ecke und warte sehnsüchtig auf mehr von uns :D! (Anm: Die "Ecke" ist nicht die Kneipe nebenan... :)<br />
<br />
Im nächsten Kapitel werden wir dann Matrizen-Hardcore machen ... legt also schon mal Euer Aspirin parat, es wird witzig werden *grunz* :).<br />
<br />
Und wie immer freuen wir uns sehr über Feedback. Schreibt uns doch einfach ein paar Worte in unser [http://www.delphigl.com/forum/viewforum.php?f=8 Feedback-Forum]. Sagt, was Ihr gut und was hingegen Ihr als schlecht empfunden habt! Auch freuen wir uns immer über Ideen für weitere Tutorials, teilt uns also bitte Eure Ideen mit ;)!<br />
<br />
Okay... ich wünsche Euch einen angenehmen Tag / Nacht!<br />
<br />
'''Euer'''<br><br />
'''Phobeus'''<br />
<br />
== Dateien == <br />
* {{ArchivLink|file=tut_lektion_2_delphi_api|text=Alter Delphi-API-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_2_delphi_vcl|text=Alter Delphi-VCL-Quelltext zum Tutorial}}<br />
* {{ArchivLink|file=tut_lektion_2_exe|text=Windows-Binary zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION | [[Tutorial Lektion 1]] | [[Tutorial Lektion 3]]}}<br />
<br />
[[Kategorie:Tutorial|Lektion2]]</div>Flohttps://wiki.delphigl.com/index.php?title=Tutorial_Lektion_1&diff=23621Tutorial Lektion 12009-05-17T13:39:36Z<p>Flo: /* Dateien */</p>
<hr />
<div>= Nicht zu weit aus dem Fenster lehnen =<br />
<br />
== Vorwort ==<br />
<br />
Hallo,<br />
in dem folgenden Tutorial möchte ich Euch etwas näher mit OpenGL vertraut machen. Der Großteil dieses Tutorials besteht aus bloßer Theorie, die jedoch absolut notwendig ist. Es ist nicht leicht über ein so trockenes Thema zu schreiben, denn in aller Regel sind Eure Erwartungen groß und wenn es schon keine aufwendige Grafikdemo wird, so wollt Ihr doch wenigstens ein Dreieck am Ende auf den Bildschirm gezaubert sehen. Seit nicht frustriert, wenn ich Euch nun verrate, dass das eigentliche Zeichnen (Rendern) erst im zweiten Tutorial behandelt wird. Ein leeres Fenster ist doch auch nicht zu unterschätzen oder ;)?<br />
<br />
{{Hinweis|Hilfreich für das Verständniss ist es wenn man das [[Tutorial_Quickstart]] gelesen hat.}}<br />
<br />
== Was ist (die) OpenGL? ==<br />
Womit wir schon bei der ersten Frage wären. Ist es der, die oder das OpenGL. Um diese Frage zu beantworten müssen wir wissen, dass OpenGL ein Akronym für "Open Graphics Library" ist. Der englischen Begriff "Library" bedeutet Bibliothek, womit wir auf "die OpenGL" schließen. Häufig findet OpenGL aber auch ganz ohne Pronomen Verwendung. Nachdem wir diese Sache klargestellt haben kommen wir zur eigentlichen Frage zurück. Was ist OpenGL?<br />
<br />
OpenGL ist eine API, die es uns erlaubt relativ einfach Grafiken auf dem Bildschirm auszugeben ohne jedoch zu wissen, was im Hintergrund genau geschieht. Das ganze ist in gewisser Weise mit der Windows-API vergleichbar. Wir wissen zwar, wie man ein Fenster erzeugt, was im Hintergrund jedoch genau geschieht wissen wir nicht. <br />
<br />
Weiterhin ist OpenGL plattformunabhängig. Theoretisch könntet Ihr OpenGL also unter jedem Betriebssystem nutzen und Eure Programme würden auch auf jeder Hardware laufen. Dies bringt jedoch den Nachteil mit sich, dass bestimmte Dinge wie die Fensterverwaltung, Tastatur- oder Maussteuerung aus der Bibliothek ausgeschlossen sind, da diese sich von Betriebssystem zu Betriebssystem unterscheiden können. Um diese Dinge müssen wir uns also selbst kümmern. Zum Glück bietet uns die VCL (Visual Component Library) von Delphi ein mächtiges Werkzeug um solchen Herausforderungen entgegenzutreten.<br />
<br />
OpenGL selbst besitzt nur einen relativ kleinen Sprachumfang. Die wenigen Funktionen, die jedoch vielseitig einsetzbar sind lassen sich sehr leicht erlernen. Die Folge hiervon ist aber auch, dass es keine Funktion zum Zeichnen komplexer Objekte wie z.&nbsp;B. die eines Vogels gibt. Solche Objekte muss man sich daher selbst aus den so genannten Primitiven, wie z.&nbsp;B. Punkten, Linien oder Dreiecken zusammensetzen.<br />
<br />
OpenGL ist ein riesiger Zustandsautomat. Je nachdem wie Ihr die einzelnen Zustände schaltet können die Bilder, welche ihr auf dem Bildschirm ausgebt völlig unterschiedlich aussehen. Beispielsweise sieht ein Würfel bei aktiviertem Licht gezeichnet wurde anders aus als derselbe Würfel ohne Licht. Verändert Ihr einmal einen Zustand wie z.&nbsp;B. die Farbe so werden sämtliche Primitive, die Ihr anschließend zeichnet mit dieser Farbe versehen, bis ihr die Farbe erneut wechselt.<br />
<br />
== Von Tausend und einer Funktion ==<br />
<source lang="pascal"> glColor3b, glColor3d, glColor3f, glColor3i, glColor3s, <br />
glColor3ub, glColor3ui, glColor3us, glColor4b, glColor4d, <br />
glColor4f, glColor4i, glColor4s, glColor4ub, glColor4ui, <br />
glColor4us, glColor3bv, glColor3dv, glColor3fv, glColor3iv, <br />
glColor3sv, glColor3ubv, glColor3uiv, glColor3usv, <br />
glColor4bv, glColor4dv, glColor4fv, glColor4iv, glColor4sv, <br />
glColor4ubv, glColor4uiv, glColor4usv</source><br />
Es sind nicht ganz tausend und eine geworden, allerdings kommt dies schon sehr nahe dran ;). Sprach ich nicht gerade noch von einem kleinen Sprachumfang? Schnell werdet Ihr aber erkannt haben, dass das Grundgerüst dieser Funktion glColor heißt. All diese Funktionen verhalten sich gleich und unterscheiden sich lediglich in der Übergabe der Parameter. So werden bei glColor3i 3 Parameter vom Typ Integer erwartet. glColor4b verlangt hingegen 4 Parameter vom Typ Byte.<br />
<br />
Alle OpenGL-Funktionen besitzen den Präfix "gl" gefolgt von dem Befehlsstamm. Einige Funktionen besitzen noch einen Suffix, der den Typ oder auch die Anzahl der übergebenen Parameter wieder spiegelt. (siehe auch [[Funktions_Anhang|diese Erklärung]])<br />
<br />
OpenGL-Konstanten sind immer an ihren Großbuchstaben erkennbar und beginnen mit der Vorsilbe "GL_". Obgleich Delphi keine Unterschiede zwischen Groß- und Kleinschreibung macht empfehle ich doch diese Schreibweise beizubehalten, denn sie steigert die Übersicht ungemein.<br />
<br />
== Wie sind OpenGL-Programme aufgebaut? ==<br />
Um ein Dreieck zu rendern reicht es vielleicht das Bild einmal zu Zeichnen und auszugeben. Der Großteil von Euch wird sich jedoch nicht mit einem Dreieck zufrieden geben und möchte doch wenigstens Bewegungen und Animationen in seinen Programmen verwenden. Die Vorgehensweise ist so einfach wie genial: Um Bewegungen zu ermöglichen ist es notwenig, das Bild mehrmals zu zeichnen und auszugeben. Stellt Euch vor Ihr möchtet ein Dreieck von links nach rechts über den Bildschirm bewegen! Ihr zeichnet Euer Dreieck ganz links und gebt es aus. Noch bevor das menschliche Auge genug Zeit hat dieses Bild als einzelnes Bild zu deuten habt ihr dank der hohen Geschwindigkeit der heutigen PCs das Dreieck bereits um ein paar Pixel nach rechts verschoben und erneut gezeichnet. Ist Euer Rechner schnell genug, so erscheint den Betrachter die Bewegung als völlig flüssig und die eigentlichen Sprünge von wenigen Pixeln sind als solche nicht erkennbar. Das menschliche Auge kann etwa 25-30 Bilder pro Sekunde unterscheiden, also solltet Ihr immer versuchen mehr als 30 Bilder pro Sekunde (FPS = Frames per Second) zu zeichnen. <br />
<br />
Spätestens jetzt sollte Euch klar sein, warum es bei modernen Spielen manchmal ruckeln kann, wenn Euer Rechner zu langsam ist.<br />
<br />
Soweit zur Theorie. Wie aber realisiert man diesen Vorgang. Ganz klar: Wir zeichnen in einer Schleife. Nun stellt sich die Frage wie man dieses Problem elegant löst. Man könnte sich z.B. eine eigene Hauptprogrammschleife schreiben, in der man rendert. In dieser müsste man dann aber auch die Botschaften des Betriebssystems verarbeiten.<br />
<br />
Eine elegantere Lösung ist meiner Meinung nach das OnIdle-Ereignis der Anwendung hierfür zu nutzen. Wenn Ihr darin den Parameter "Done" auf "False" setzt wird diese Nachricht so oft ausgeführt, wie es nur irgendwie möglich ist. Das OnIdle-Ereignis wird im Hauptthread der Anwendung aufgerufen, was den positiven Nebeneffekt hat, dass die Nachrichten des Betriebssystems von der VCL verarbeitet werden und man die VCL in vollem Umfang nutzen kann.<br />
<br />
Nachteil der vorgestellten Methoden ist, dass die Anwendung nie zur Ruhe kommt, da die Schleife permanent durchlaufen wird. Damit steigt die Prozessorauslastung in der Regel auf 100%. Wenn man aus bestimmten Gründen in keiner echten Schleife rendern möchte könnte man sich auch mit einem Timer Abhilfe schaffen.<br />
<br />
== Die Initialisierung ==<br />
Wer sofort loslegen möchte, den kann ich beruhigen, denn die Initialisierung von OpenGL ist mit unserem Header sehr einfach. Ihr müsst einfach bevor Ihr OpenGL nutzen möchtet, z.&nbsp;B. im OnCreate-Ereignis Eures Formulars die Funktion "InitOpenGL" aufrufen, nachdem Ihr die Unit "dglOpenGL" in Eurer Usesklausel eingebunden habt.<br />
<br />
Damit OpenGL auch weiß, wohin gezeichnet werden soll müssen wir ihr das irgendwie mitteilen. Hierfür ermitteln wir den Gerätekontext unseres Formulars mit Hilfe der Funktion "GetDC" und dem Handle unseres Formulars.<br />
<br />
Anschleißend erstellen wir einen Zeichenkontext mit Hilfe der Funktion "CreateRenderingContext".<br />
<br />
Nachdem wir diesen erstellt haben müssen wir Ihn noch aktivieren. Dies geschieht mit der Funktion "ActiveRenderingContext". Das ganze sieht dann in etwa so aus:<br />
<br />
<source lang="pascal">var<br />
DC, RC:HDC;<br />
procedure TfrmMain.FormCreate(Sender: TObject);<br />
begin<br />
DC:=GetDC(Handle);<br />
RC:=CreateRenderingContext(DC, //Device Contest<br />
[opDoubleBuffered], //Optionen<br />
32, //ColorBits<br />
24, //ZBits<br />
0, //StencilBits<br />
0, //AccumBits<br />
0, //AuxBuffers<br />
0); //Layer<br />
ActivateRenderingContext(DC, RC);<br />
end;</source><br />
<br />
Eine Änderung der Parameterwerte von CreateRenderingContext bzw. das genaue Verständniss dieser ist für Anfänger nicht nötig. Für alle die es trotzdem wissen wollen:<br />
<br />
*'''DC''' ist der Gerätekontext an dem der Rendercontext hängt. Zum Beispiel der Kontext eines Formulars oder aber der eines Panels. Je nachdem wo später drauf gezeichnet werden soll.<br />
*'''Optionen''' sind hinweise wie z.B: opDoubleBuffered. Diese Option gibt an, dass [[Doppelpufferung]] genutzt werden soll. TODO Bitte weitere Optionen bzw. Link wo man nachlesen kann.<br />
*'''ColorBits''', hier 32, gibt die Farbtiefe an. 32 bedeutet, dass für die 4 Farbkanäle (Rot, Grün, Blau, Alpha(Transparenz)) jeweils 1Byte=8Bit zur Verfügung stehen. Das bedeutet für jeden Kanal können 256 Abstufungen genutzt werden. Das macht insgesamt 256^4 = ... verdammt viele!<br />
*'''ZBits''', hier 24, gibt an wie viele Bits für den [[Tiefenpuffer]] reserviert werden. 24Bits bedeutet, dass Einträge von 0 bis 2^24=16,7Mio möglich sind. Je höher der Wert, desto feiner/genauer der [[Tiefentest]]. (Mehr dazu im Tiefetest-Artikel)<br />
*'''StencilBits''' werden für den [[Feste_Funktionspipeline#Stencil_Test|Stencil Test]] benötigt. (Maskieren von Bildschirmteilen)<br />
*'''AccumBits''' geben an, wie viele Bits im [[Akkumulationspuffer]] gespeichert werden können.<br />
*'''AuxBuffer''' gibt an, wie viele Bits im Hilfspuffer gespeichert werden können.<br />
*'''Layer''' gibt die Anzahl ebenen an. (Für OpenGL fällt mir hier kein Nutzen ein. Falls jemand Ideen hat, bitte ins Forum posten.)<br />
<br />
<br />
Im Prinzip könntet Ihr nun loslegen. Wenn Ihr Euer Bild nun im OnIdle-Ereignis zeichnet werdet Ihr schnell feststellen, dass Ihr nichts zu sehen bekommt. Das ist auch ganz normal, denn Ihr müsst das Bild auch noch ausgeben. Das geschieht mit Hilfe der Funktion "SwapBuffers".<br />
<br />
Da wir sauber programmieren wollen müssen wir den Renderingkontext und den Gerätekontext wieder freigeben, wenn wir unser Programm beenden oder aber OpenGL nicht mehr benötigen. Die Funktionen "ReleaseDC" und "DestroyRenderingContext" erfüllen diese Aufgaben.<br />
<br />
Jetzt müssen wir nur noch die Größe des Bildes, welches wir rendern möchten definieren. Mit dem Befehl [[glViewport]] definieren wir die Zeichenfläche. Im Gegensatz zu Windows liegt der Ursprung bei OpenGL unten links. Da die Größe unserer Zeichenfläche der des Fensters entsprechend soll rufen wir diesen Befehl im OnResize-Ereignis unseres Formulars auf:<br />
<source lang="pascal">glViewport(0, 0, ClientWidth, ClientHeight);</source><br />
<br />
Die vier kommenden Zeilen solltet Ihr ganz schnell wieder aus Eurem Gedächtnis streichen und erst einmal als gegeben hinnehmen. In Tutorial 2 und 3 werden wir näher darauf eingehen. Soviel sei jedoch gesagt: Es wird nichts weiter unternommen als unsere dreidimensionale Welt aufzuspannen. Damit ist sichergestellt, dass Objekte in großer Entfernung kleiner erscheinen, so wie wir es gewohnt sind.<br />
<br />
<source lang="pascal">glMatrixMode(GL_PROJECTION);<br />
glLoadIdentity;<br />
gluPerspective(60, ClientWidth/ClientHeight, 0.1, 100);<br />
glMatrixMode(GL_MODELVIEW);</source><br />
<br />
Mit diesem Wissen könnt Ihr nun direkt in Tutorial 2 einsteigen.<br />
<br />
== Nachwort ==<br />
Genug dazu. Ich freue mich sehr, dass Ihr Euch die Zeit genommen habt, dieses Tutorial zu lesen und OpenGL lernen wollt. Ich hoffe, dass wir die nächsten Tutorials genauso gut durchbekommen und dass am Ende jeder etwas gelernt hat und die Zeit nicht total vergebens war. Ich schreibe sicherlich nicht immer alles auf dem direktesten Weg, sondern rede gerne mal um den Brei herum. Wer das nicht mag, soll sich die OpenGL-Dokumentation zu Herzen nehmen, dort ist alles kurz und schmerzlos beschrieben ;). Für alle, die es nicht so trocken mögen, ist mein Schreibstil hoffentlich eine gute Alternative ;).<br />
<br />
'''Euer'''<br><br />
'''Magellan'''<br />
<br />
== Dateien ==<br />
* {{ArchivLink|file=template_delphi_vcl|text=Delphi-VCL-Vorlage für OpenGL}}<br />
* {{ArchivLink|file=template_delphi_vcl_2rc|text=Delphi-VCL-Vorlage für OpenGL mit 2 Renderkontexten}}<br />
* {{ArchivLink|file=template_delphi_api|text=Delphi-Vorlage für OpenGL ohne VCL-Overhead}}<br />
* {{ArchivLink|file=template_delphi_sdl|text=Delphi-SDL-Vorlage für OpenGL}}<br />
* {{ArchivLink|file=dglsdk_win32_2006_1|text=DGLSDK 2006.1 für Windows}}<br />
* {{ArchivLink|file=dglsdk_linux_2006_1|text=DGLSDK 2006.1 für Linux}}<br />
* {{ArchivLink|file=tut_lektion_1_delphi_api|text=Alter Delphi-API-Quelltext zum Tutorial}}<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial Quickstart]]|[[Tutorial Lektion 2]]}}<br />
[[Kategorie:Tutorial|Lektion1]]</div>Flohttps://wiki.delphigl.com/index.php?title=DGL_Wiki:Files&diff=23620DGL Wiki:Files2009-05-17T13:37:31Z<p>Flo: </p>
<hr />
<div>==Fehlende Datei Artikel==<br />
{{Hinweis|Die Dateien wurden aus den Verzeichnissen in dem SVN-Repositorz http://svn.delphigl.com/dglfiles generiert. Falls Dateien veraltet sind, dann erstellt keinen Artikel sondern löscht das entsprechende Verzeichnis. Falls etwas den falschen Namen hat, dann benennt bitte das entsprechende SVN-Verzeichnis um. Dies muss jedoch so geschehen das SVN das mit bekommt. Etwa unter Linux wäre die Benutzung des "mv"-Befehles die falsche Wahl. Stattdessen sollte die Datei mit "svn mv" umbenannt werden.}}<br />
* [[Archiv:ac3d]]<br />
* [[Archiv:ac3d_plugin]]<br />
* [[Archiv:asc_tutorial]]<br />
* [[Archiv:bump_bin]]<br />
* [[Archiv:bump_vcl]]<br />
* [[Archiv:burg_vcl]]<br />
* [[Archiv:cubes_src]]<br />
* [[Archiv:darkhanoi]]<br />
* [[Archiv:darkhanoi_src]]<br />
* [[Archiv:dblocks_bin]]<br />
* [[Archiv:dblocks_src]]<br />
* [[Archiv:firstone]]<br />
* [[Archiv:fog_sample_bin]]<br />
* [[Archiv:fog_sample_src]]<br />
* [[Archiv:fur]]<br />
* [[Archiv:jvscript]]<br />
* [[Archiv:kamera_heyroth]]<br />
* [[Archiv:lighttut_vcl_bin]]<br />
* [[Archiv:lightut_vcl_src]]<br />
* [[Archiv:linearealgebra]]<br />
* [[Archiv:mcad14]]<br />
* [[Archiv:milletron]]<br />
* [[Archiv:minimalxapp.dpr]]<br />
* [[Archiv:ms3d_loader]]<br />
* [[Archiv:obb-tetris_exe]]<br />
* [[Archiv:obb-tetris_vcl]]<br />
* [[Archiv:objmov_src_api]]<br />
* [[Archiv:objrot_src_api]]<br />
* [[Archiv:occlusion_query_exe]]<br />
* [[Archiv:occlusion_query_vcl]]<br />
* [[Archiv:opengl10_api_template]]<br />
* [[Archiv:opengl12_vcl_template]]<br />
* [[Archiv:opengl2_demo_vcl]]<br />
* [[Archiv:OpenGL_TemplateNet]]<br />
* [[Archiv:particle1_exe]]<br />
* [[Archiv:particle1_src_api]]<br />
* [[Archiv:pong]]<br />
* [[Archiv:ppfx_demo]]<br />
* [[Archiv:prong]]<br />
* [[Archiv:renderpass_exe]]<br />
* [[Archiv:renderpass_src_api]]<br />
* [[Archiv:sample_sdl_mutex]]<br />
* [[Archiv:sample_sdl_thread]]<br />
* [[Archiv:sample_sdl_timer]]<br />
* [[Archiv:selektion_exe]]<br />
* [[Archiv:selektion_vcl_src]]<br />
* [[Archiv:softsynth_src]]<br />
* [[Archiv:solaris]]<br />
* [[Archiv:spiegelung_exe]]<br />
* [[Archiv:spiegelung_src_vcl]]<br />
* [[Archiv:stereo_vcl]]<br />
* [[Archiv:template_clx_kylix]]<br />
* [[Archiv:template_sdl_fpc]]<br />
* [[Archiv:temp_part_exe]]<br />
* [[Archiv:temp_part_src]]<br />
* [[Archiv:terrainclod_exe]]<br />
* [[Archiv:terrainclod_src_vcl]]<br />
* [[Archiv:texgen]]<br />
* [[Archiv:treedemo_bin]]<br />
* [[Archiv:treedemo_src]]<br />
* [[Archiv:tut_opengl2d_vcl]]</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_lektion_5_delphi_vcl&diff=23619Archiv:tut lektion 5 delphi vcl2009-05-17T13:36:52Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-VCL-Quelltext zum Tutorial Lektion 5.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial Lektion 5]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_lektion_4_delphi_vcl&diff=23618Archiv:tut lektion 4 delphi vcl2009-05-17T13:36:35Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-VCL-Quelltext zum Tutorial Lektion 4.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial Lektion 4]].}}</div>Flohttps://wiki.delphigl.com/index.php?title=Archiv:tut_lektion_3_delphi_vcl&diff=23617Archiv:tut lektion 3 delphi vcl2009-05-17T13:36:21Z<p>Flo: Die Seite wurde neu angelegt: „{{Archiv|beschreibung=Delphi-VCL-Quelltext zum Tutorial Lektion 3.}}“</p>
<hr />
<div>{{Archiv|beschreibung=Delphi-VCL-Quelltext zum [[Tutorial Lektion 3]].}}</div>Flo