https://wiki.delphigl.com/api.php?action=feedcontributions&user=Ireyon&feedformat=atomDGL Wiki - Benutzerbeiträge [de]2024-03-29T11:15:44ZBenutzerbeiträgeMediaWiki 1.27.4https://wiki.delphigl.com/index.php?title=GL_ARB_occlusion_query&diff=24858GL ARB occlusion query2010-05-21T21:11:31Z<p>Ireyon: /* Beispiel */</p>
<hr />
<div>= GL_ARB_occlusion_query =<br />
{{Hinweis|Die Orginalspezifikation finden Sie unter "Ressourcen" am Ende des Artikels.}}<br />
<br />
<br />
== Abfragestring ==<br />
GL_ARB_occlusion_query<br />
<br />
<br />
<br />
== Beschreibung ==<br />
GL_ARB_occlusion_query bietet eine Möglichkeit festzustellen, wieviele Samples (Pixel) einer [[Primitive]] gezeichnet werden.<br />
<br><br />
<br />
== Neue Prozeduren ==<br />
<br />
procedure '''glGenQueriesARB'''(n : integer; ids : PGLuint);<br />
procedure '''glDeleteQueriesARB'''(n : integer; const ids : PGLuint);<br />
function '''glIsQueryARB'''(id : Cardinal) : Boolean;<br />
procedure '''glBeginQueryARB'''(target : Cardinal; id : Cardinal);<br />
procedure '''glEndQueryARB'''(target : Cardinal);<br />
procedure '''glGetQueryivARB'''(target : Cardinal; pname : Cardinal; params : PGLInt);<br />
procedure '''glGetQueryObjectivARB'''(id : Cardinal; pname : Cardinal; params : PGLInt);<br />
procedure '''glGetQueryObjectuivARB'''(id : Cardinal, pname : Cardinal; params : PGLuint);<br />
<br />
<br />
== Neue Tokens ==<br />
''Für glBeginQueryARB, glEndQueryARB oder glGetQueryivARB als <target> Parameter'' <br><br />
'''GL_SAMPLES_PASSED_ARB''' <br><br />
<br><br />
''Für glGetQueryivARB als <pname> Parameter'' <br><br />
'''GL_QUERY_COUNTER_BITS_ARB''' <br><br />
'''GL_CURRENT_QUERY_ARB''' <br><br />
<br><br />
''Für glGetQueryObjectivARB oder glGetQueryObjectuivARB als <pname> Parameter'' <br><br />
'''QUERY_RESULT_ARB''' <br><br />
'''QUERY_RESULT_AVAILABLE_ARB''' <br><br />
<br />
== Anwendung ==<br />
Bevor der Occlusion Test zur Verfügung steht, muss erst einmal eine Query mit glGenQueriesARB() erzeugt werden. Danach wird das Zählen der Samples mit glBeginQueryARB() und GL_SAMPLES_PASSED_ARB als <target> Parameter gestartet. Dann wird die Szene gerendert. Um den Test auszuwerten, muss das Zählen erst einmal mit glEndQueryARB() beendet werden. Anschließend kann man sich das Ergebnis des Test, also die gezeichneten Pixel, mittels glGetQueryObjectivARB() (QUERY_RESULT_ARB als <pname> Parameter) zurückliefern lassen.<br />
<br />
== Beispiel ==<br />
<br />
'''var''' Query : Gluint;<br />
Pixel : Integer;<br />
<br /><br />
[...]<br />
<br /><br />
<font color="#000080">''//== Query erzeugen''</font><br />
glGenQueriesARB'''('''1,@Query''')''';<br />
<font color="#000080">''//== mit dem Zählen beginnen''</font><br />
glBeginQueryARB'''('''GL_SAMPLES_PASSED_ARB,Query''')''';<br />
<br /><br />
<font color="#000080">''//== Szene/Primitive zeichnen''</font><br />
<br /><br />
<font color="#000080">''//== aufhören zu Zählen''</font><br />
glEndQueryARB'''('''GL_SAMPLES_PASSED_ARB''')''';<br />
<font color="#000080">''//== Ergebnis wird nach Pixel zurückgeliefert''</font><br />
glGetQueryObjectivARB'''('''Query,GL_QUERY_RESULT_ARB,@Pixel''')''';<br />
<font color="#000080">''//== Query wieder freigeben''</font><br />
glDeleteQueriesARB'''('''1,@Query''')''';<br />
<br />
== Ressourcen ==<br />
[http://delphigl.com/script/do_show.php?name=nv_occlusion_query&action=2 Occlusion Query Tutorial mit GL_OCCLUSION_QUERY_NV]<br><br />
[http://opengl.org/registry/specs/ARB/occlusion_query.txt Original Extension-Spezifikation]</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_Vertexbufferobject&diff=22438Tutorial Vertexbufferobject2009-01-05T17:20:07Z<p>Ireyon: /* Weitere Vertexformate */</p>
<hr />
<div>=GL_ARB_Vertex_Buffer_Object=<br />
==Ave!==<br />
<br />
Und willkommen zu meinem zweiten GL_ARB-Extension Tutorial. Diesmal beschäftigen wir uns mit der recht neuen '''GL_ARB_Vertex_Buffer_Object'''-Extension, die vom ARB am 12.Februar 2003 fertiggestellt und vor kurzem auch als Corefeature in OpenGL 1.5 promoted wurde.<br />
<br />
VBOs (Vertex Buffer Object) sind quasi eine Erweiterung der recht weit verbreiteten Vertexarrays, die jedoch den großen Vorteil besitzen, dass ihre Vertexdaten serverseitig, also im schnellen VRAM der Grafikkarte, statt wie bei den VAs im Hauptspeicher, abgelegt werden. Das ist bei einer Displayliste zwar (fast) genauso der Fall, allerdings können die Daten nicht so einfach dynamisch geändert werden, was bei einem VBO aber bei Bedarf sehr einfach ist.<br />
<br />
Kurz gesagt vereint das VBO also die Flexibilität eines Vertexarrays mit der Geschwindigkeit einer Displayliste und eignet sich daher besonders für das Rendern aufwändiger Geometrie, egal ob statisch oder dynamisch.<br />
<br />
Eine weitere tolle Neuerung der VBO-API ist die Tatsache, dass man sich von OpenGL einen Pointer übergeben lassen kann, mit dessen Hilfe man Vertexdaten direkt in den VRAM schreiben kann. Dadurch spart man sich natürlich den Umweg über den Hauptspeicher und somit auch den erhöhten Speicherbedarf des Programmes beim Laden von Modellen o.&nbsp;Ä.<br />
<br />
VBOs bieten übrigens auch die Möglichkeit, sie je nach Anwendungsfall vom Grafikkartentreiber "optimieren" zu lassen. So legt man Vertexdaten, die im Nachhinein nicht mehr verändert werden sollenm am besten mit dem Schlüsselwort ''STATIC_DRAW_ARB'' ab, während dynamische Vertexdaten über ''DYNAMIC_DRAW_ARB'' abgelegt werden sollten. Doch dazu gibts später nähere Informationen.<br />
<br />
==Hardwareunterstützung==<br />
<br />
Da '''GL_ARB_Vertex_Buffer_Object''' eine noch recht junge Extension ist, siehts mit der Hardwareunterstützung besonders auf älteren 3D-Beschleunigern nicht so toll aus. Allerdings haben sowohl ATI als auch NVidia in ihre aktuellsten Treiber für ältere Beschleuniger Softwareemulationen dieser Extension eingebaut, hinter der wohl im Endeffekt ein simples Vertexarray seinen Dienst verrichtet. Von daher wirds auf älteren Karten keine Geschwindigkeitszuwächse ggü. Vertexarrays geben, aber man erspart sich das umständliche Schreiben mehrerer Renderpfade für verschiedene Grafikkartengenerationen. (Infos zur Unterstützung von '''GL_ARB_VBO''' gibts [http://www.delphi3d.net/hardware/extsupport.php?extension=GL_ARB_vertex_buffer_object hier]).<br />
<br />
==Erstellen des Vertexbufferobjects==<br />
<br />
Wie der Name vermuten lässt, handelt es sich beim VBO um ein Objekt, welches in Sachen Syntax an die anderen in OpenGL verfügbaren Objekte wie z.&nbsp;B. Texturenobjekte angelehnt ist. Das Erstellen eines VBOs geht daher genauso einfach von der Hand:<br />
<br />
<pascal><br />
glGenBuffersARB(1, @VBO);<br />
</pascal><br />
<br />
Nun sollten wir in VBO eine gültige ID zurückgeliefert bekommen haben, welche ein Vertex buffer object referenziert.<br />
<br />
Nachdem wir nun ein VBO erstellt haben, sollten wir dieses natürlich auch "aktivieren" (also binden), damit wir mit ihm arbeiten können:<br />
<br />
<pascal><br />
glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBO);<br />
glEnableClientState(GL_VERTEX_ARRAY);<br />
</pascal><br />
<br />
Wie erwähnt binden wir in der ersten Zeile unser frisch erstelltes VBO, während wir in der zweiten Zeile Vertexarrays auf der Clientseite aktivieren. Dies ist deshalb nötig, da wir das VBO später genauso zeichnen werden wie dies bei einem normalen VA der Fall wäre.<br />
<br />
==Übergabe der Vertexdaten (Variante 1: Ohne Umweg über den Hauptspeicher)==<br />
<br />
Wie in der Einleitung bereits erwähnt, bietet uns die VBO-API die Möglichkeit, unsere Vertexdaten ohne Umweg über den Hauptspeicher direkt in den Grafikkartenspeicher zu schreiben. Da dies eines der besten Features dieser neuen Extension ist, wollen wir uns auch zuerst mit dieser Übergabemethode beschäftigen.<br />
<br />
''Allerdings erstmal eine kleine Warnung vorweg: Pointer sind ein recht komplexes Thema, und ihre Nutzung erfordert einige Kenntnisse. Wer weder weiß, wie man damit umgeht, noch wie man sie einsetzt, sollte sich da zuerstmal schlau machen. Denn mit Pointer kann man ganz böse Sachen machen, wenn man sie nicht richtig nutzen kann!''<br />
<br />
Um unsere Vertexdaten als Pointer zu nutzen,müssen wir erstmal eine Vertexstruktur erstellen und auch einen passenden Pointertyp. Der Pointertyp ist zwar nicht zwingend (ein ^ täte es auch), aber macht die Sache übersichtlicher:<br />
<br />
<pascal><br />
PVertex = ^TVertex;<br />
TVertex = packed record<br />
S,T,X,Y,Z : TGLFloat;<br />
end;<br />
</pascal><br />
<br />
Sicher fragen sich einige von euch jetzt, warum unser TVertex gerade so aussieht (also zuerst S,T und dann der Rest). Das liegt daran, dass wir unser VBO später über die Funktion [[glInterleavedArrays]] rendern werden, welche wissen will, wie unsere Vertexdaten im Speicher abgelegt sind. Unsere Vertexstruktur entspricht dabei dem Format ''GL_T2F_V3F''. ''T2F'' entspricht 2 Floats für die Texturenkoordinaten (S&T), gefolgt von ''V3F'' für die drei Floatwerte die unsere Vertexkoordinaten repräsentieren. Wenn ihr ein anderes Vertexformat verwendet, müsst ihr euch auch ein passendes Vertexformat aus der Spezifikation heraussuchen.<br />
Ich verwende oben übrigens einen '''packed Record''' (auch wenn dies bei unserem Vertexformat nicht unbedingt ein Muss ist). Dies tue ich deshalb, da Delphi Werte bei einem normalen Record für einen beschleunigten Zugriff an einem (Double)Word-Raster ausrichtet und dies deshalb bei bestimmten Vertexformaten Probleme bereiten könnte.<br />
<br />
Hoffe mal das euch obiges klar ist,denn jetzt gehts ans Eingemachte. Wir kommen zum "schwersten" (ist ja ein relativer Begriff) Teil des Tutorials, nämlich der Übergabe der Vertexdaten an das VBO. Dazu müssen wir OpenGL zuerstmal mitteilen, wie groß unser VBO eigentlich sein soll,damit wir auch genug Platz im VRAM bekommen:<br />
<br />
<pascal><br />
glBufferDataARB(GL_ARRAY_BUFFER_ARB, VertexAnzahl*SizeOf(TVertex), nil, GL_STATIC_DRAW_ARB);<br />
</pascal><br />
<br />
Schauen wir uns also mal die Parameter von [[glBufferDataARB]] an. Zuerst müssen wir OpenGL mitteilen, was für einen Puffer wir erstellen wollen (in zukünftigen Versionen werden wohl noch andere folgen, z.&nbsp;B. Überbuffer). In unserem Falle also einen Arraypuffer. Parameter zwei übergibt dann den Speicherplatz, den wir reservieren wollen und ist recht selbsterklärend. Der dritte Parameter würde normalerweise einen Pointer auf unsere Vertexdaten enthalten. Da wir diese aber nicht im Hauptspeicher abgelegt haben, sondern über einen Pointer dort noch ablegen wollen, übergeben wir hier '''nil'''. OpenGL nimmt dies zur Kenntnis und spuckt dementsprechend auch keinen Fehler aus.<br />
<br />
Den letzten Parameter hab ich auch schon kurz in der Einführung angesprochen. Er teilt OpenGL mit, auf welchen Anwendungsfall unser VBO optimiert werden soll. Dabei gibt es folgende Anwendungsfälle,die ihr euch dann entsprechend eurer VBO-Verwendung aussuchen könnt:<br />
<br />
{|rules="all" border = "1"<br />
! STREAM_DRAW_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann eher selten als Quelle für OpenGL-Befehle genutzt.<br />
|-<br />
! STREAM_READ_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann selten von der Anwendung angefordert.<br />
|-<br />
! STREAM_COPY_ARB<br />
| Die Vertexdaten werden einmal durch Kopieren von der GL übergeben und dann eher selten als Quelle für OpenGL-Befehle genutzt.<br />
|-<br />
! STATIC_DRAW_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! STATIC_READ_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann recht oft von der Anwendung angefordert.<br />
|-<br />
! STATIC_COPY_ARB<br />
| Die Vertexdaten werden einmal durch Kopieren von der GL übergeben und dann recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! DYNAMIC_DRAW_ARB<br />
| Die Vertexdaten werden wiederholt angegeben (verändert) und recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! DYNAMIC_READ_ARB<br />
| Die Vertexdaten werden wiederholt durch das Auslesen von Daten durch OpenGL angegeben (verändert) und oft von der Anwendung angefordert.<br />
|-<br />
! DYNAMIC_COPY_ARB<br />
| Die Vertexdaten werden wiederholt durch das Auslesen von Daten durch OpenGL angegeben (verändert) und recht oft zum Rendern genutzt.<br />
|}<br />
<br />
Beachtet werden sollte, dass es sich bei obigen Anwendungsmuster nur um Hinweise (Hints) handelt, die es auch in anderen Bereichen von OpenGL gibt. Diese Hinweise sind allerdings nicht verbindlich und werden von Treiber zu Treiber unterschiedlich behandelt.<br />
<br />
Nachdem OpenGL nun weiss was für ein VBO wir genau haben wollen, brauchen wir letztendlich nur noch einen Pointer, der "auf" das VBO zeigt:<br />
<br />
<pascal><br />
VBOPointer := glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);<br />
</pascal><br />
<br />
Die Funktion [[glMapBufferARB]] liefert uns einen Pointer zurück, der den Speicherplatz des VBOs in den Adressraum des Clients "ummappt", also in den Hauptspeicher. Dadurch wird es dann möglich, über eine Adresse im Hauptspeicher des Rechners direkt in den Grafikkartenspeicher zu schreiben. Der zweite Parameter gibt übrigens an, das wir in das VBO schreiben wollen. Der einzige weitere zulässige Parameter ist hier '''GL_READ_ONLY_ARB''', welcher zum Auslesen des VBOs dient.<br />
<br />
Endlich haben wir einen Pointer auf unser inzwischen auch im VRAM der Grafikkarte richtig dimensioniertes Vertexbufferobject, und sind nun in der Lage dort unsere Vertexdaten abzulegen. Was jetzt folgt ist ein wenig Pseudo-Code der diesen Vorgang darstellt. Pseudo-Code deshalb, weil die Vertexdaten die ins VBO kommen ja in jeder Anwendung unterschiedlich sind, und man so auch selbst ein wenig denken muss:<br />
<br />
<pascal><br />
VertexDatenLaenge := 0;<br />
for i := 0 to High(MeineVertexDaten) do<br />
begin<br />
VertexPointer := VBOPointer;<br />
<br />
VertexPointer^.X := GeneriertesVertex.X;<br />
VertexPointer^.Y := GeneriertesVertex.Y;<br />
VertexPointer^.Z := GeneriertesVertex.Z;<br />
VertexPointer^.S := GeneriertesVertex.S;<br />
VertexPointer^.T := GeneriertesVertex.T;<br />
<br />
inc(Integer(VBOPointer), SizeOf(TVertex));<br />
<br />
inc(VertexDatenLaenge);<br />
end;<br />
</pascal><br />
<br />
Da sich dieses Tutorial (und v.&nbsp;a. diese Methode der Vertexdatenübergabe) an die fortgeschritteneren wendet, rede ich nicht lange um den heissen Brei herum. Wir durchlaufen also eine Schleife, in der wir unsere Vertexdaten dynamisch erstellen. Entweder in jedem Durchlauf, oder wir laden die Daten vor der Schleife z.&nbsp;B. aus einem 3D-Modell und entfernen sie danach aus dem Hauptspeicher. Erste Methode spart sowohl beim Programmstart, also auch während des Programmlaufes Arbeitsspeicher, während letztere dies nur während des Programmablaufs macht.<br />
<br />
Wir setzen die Adresse des Pointers der auf das momentan zu verändernde Vertex zeigt also auf die aktuelle Zeigerposition im VBO und schreiben dann unser Vertex direkt in den Grafikkartenspeicher. Ist dies getan, müssen wir die Adresse unseres VBOPointers natürlich um die Größe des gerade geschriebenen Vertexes erhöhen. Der Typecast auf ein Integer geschieht deshalb, weil man Pointer in Delphi nicht so einfach erhöhen oder verringern kann, Integers hingegen schon.<br />
<br />
Ich gehe mal davon aus,das meine Leserschaft mit obigem Codeschnippsel keinerlei Probleme hatte und bringe diese Methode der Übergabe von Vertexdaten an das VBO dann auch mit folgender Quellcodezeile zu Ende:<br />
<br />
<pascal><br />
glUnMapBufferARB(GL_ARRAY_BUFFER_ARB);<br />
</pascal><br />
<br />
Wie unschwer zu erraten geben wir hier unser VBO für den weiteren Zugriff wieder frei. Dies ist nötig um dies später rendern zu können.<br />
<br />
==Übergabe der Vertexdaten (Variante 2: Über den Hauptspeicher)==<br />
<br />
Wem obige Variante nicht zugesagt hat, weil sie ihm zu komplex war, oder weil er seine Vertexdaten aus diversen Gründen im Hauptspeicher braucht, dem wird in diesem Abschnitt geholfen.<br />
<br />
Das Übergeben der Vertexdaten an das VBO über den Hauptspeicher gestaltet sich nämlich sehr einfach:<br />
<br />
<pascal><br />
glBufferDataARB(GL_ARRAY_BUFFER_ARB, SizeOf(VertexDaten), @VertexDaten, GL_STATIC_DRAW_ARB);<br />
</pascal><br />
<br />
Sieht einfach aus,hört sich einfach an und ist auch einfach! Statt wie im letzten Kapitel für die Vertexdaten einen Zeiger auf '''nil''' zu übergeben, übergeben wir OpenGL jetzt einen direkten Zeiger auf die Vertexdaten die im Hauptspeicher abgelegt sind. Die Daten werden dann zum VBO hochgeladen, und können anschliessen auch wieder aus dem Hauptspeicher entfernt werden.<br />
<br />
==Rendern des VBOs==<br />
<br />
Kommen wir nun also schon zum Ende unseres VBO-Tutorials. Dank der Einfachheit dieser Extension gabs halt einfach nicht mehr zu erklären. Das Zeichnen gestaltet sich nämlich OpenGL-typisch mehr als einfach:<br />
<br />
<pascal><br />
glInterleavedArrays(GL_T2F_V3F, SizeOf(TVertex), nil);<br />
glDrawArrays(GL_QUADS, 0, VertexDatenLaenge);<br />
</pascal><br />
<br />
Schnell gezeichnet und schnell erklärt. Der erste Parameter gibt an, in welchem Format die Vertexdaten unseres VBOs abgelegt wurden (wir erinnern uns: '''GL_T2F_V3F'''), während der zweite Parameter den Speicherplatz, der zwischen zwei Vertices liegt, angibt. Würden wir ein einfaches Vertexarray zeichnen, so wäre der letzte Parameter ein Pointer auf die Vertexdaten im Hauptspeicher. Da wir allerdings vorher unser VBO gebunden haben, teilt hier ein '''nil''' mit, dass die vorher ins VBO geschriebenen Vertexdaten gerendert werden sollen.<br />
<br />
Mittels [[glDrawArrays]] sagen wir OpenGL dann noch, das es unsere VBO-Daten als Quads rendern soll.<br />
<br />
==Freigabe==<br />
<br />
Wie mit allen Objekten in der OpenGL ists auch keine schlechte Idee, unser VBO freizugeben,wenn es nicht mehr benötigt wird. Und obwohl beim Löschen des Renderkontextes solche Objekte vom Grafikkartentreiber freigegeben werden sollten, wäre es keine schlechte Idee des selbst zu erledigen,für den Fall das der Treiber da schlampig arbeitet:<br />
<br />
<pascal><br />
glDeleteBuffersARB(1, @VBO);<br />
</pascal><br />
<br />
==Weitere Vertexformate==<br />
Wem allein Texturkoordinaten und Vertices nicht reichen, sondern auch Farbangaben oder Vertexnormalen braucht, muss sich eines der folgenden Formate aussuchen. Die Konstantennamen sind logisch aufgebaut: ''GL_AngabeAnzahlTyp_AngabeAnzahlTyp...''<br />
Als Angabe gibt es '''V'''ertex, '''T'''exturkoordinaten, '''N'''ormalen und '''C'''olor-Werte, also Farben.<br />
Anzahl ist die Anzahl der Komponenten pro Angabe (V3 wären zum Bespiel die X, Y und Z-Koordinaten eines dreidimensionalen Vertex).<br />
Typ ist entweder '''F'''loat oder '''U'''nsigned '''B'''yte. Die Vertexdaten müssen in der richtigen Reihenfolge mit den richtigen Typen an das VBO übergeben werden.<br />
<br />
<pascal><br />
GL_V2F<br />
GL_V3F<br />
GL_C4UB_V2F<br />
GL_C4UB_V3F<br />
GL_C3F_V3F <br />
GL_N3F_V3F <br />
GL_C4F_N3F_V3F <br />
GL_T2F_V3F<br />
GL_T4F_V4F<br />
GL_T2F_C4UB_V3F<br />
GL_T2F_C3F_V3F<br />
GL_T2F_N3F_V3F // Wohl eines der sinnvollsten Vertexformate. 2 Floats für Texturkoordinaten, 3 Floats für den Normalenvektor und 3 Floats für den eigentlichen Vertex.<br />
GL_T2F_C4F_N3F_V3F<br />
GL_T4F_C4F_N3F_V4F<br />
</pascal><br />
<br />
Es ist sinnvoll, sich für jedes der Vertexformate, die man verwendet, einen Typen anzulegen, um später einfacher mit den VBOs arbeiten zu können.<br />
<br />
==Nachwort==<br />
<br />
Das wars auch schon. Wie zu erkennen ist das Vertexbufferobjekt mal wieder eine gut durchdachte, recht nützliche und zudem auch weit nutzbare Erweiterung, die OpenGL technisch wieder einen Schritt weitergebracht hat.<br />
<br />
Wer mehr wissen will, der sollte sich unbedingt mal die passende [http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt Spezifikation] ansehen.<br />
<br />
Hoffe das Tutorial hat euch soviel Spaß gemacht wie mir das Verfassen des selbigen und ich hoffe auf reges Feedback!<br />
<br />
Euer<br />
<br />
Sascha Willems (webmaster_at_delphigl.de)<br />
<br />
==Links==<br />
[http://developer.nvidia.com/attach/6427 Nvidia: Using Vertex Buffer Objects]<br />
<br />
[http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt Spezifikation]<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_Cubemap]]|[[Tutorial_Vertexprogramme]]}}<br />
<br />
[[Kategorie:Tutorial|Vertexbuffer]]</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_Wassereffekt&diff=22430Tutorial Wassereffekt2009-01-03T22:56:24Z<p>Ireyon: /* Texturen füllen */</p>
<hr />
<div>= Einleitung =<br />
Erstmal willkommen zu meinem zweiten Tutorial. Ich möchte Euch hier einen Einblick in die „hohe Kunst“ der Wassereffekte geben. Hierbei wird erst einmal nur ein relativ stehendes Gewässer, also keines, welches in einer Rinne fließt beschrieben. Auch werde ich hier nicht auf die Technik für Wasser an Abhängen (abwärts fließende Flüsse) oder Wasserfälle eingehen. Hier geht es schlicht und einfach um eine Wasserebene, wie man sie zum Beispiel für ein Meer verwenden könnte.<br />
<br />
Voraussetzung für dieses Tutorial ist eigentlich nur ein Delphi/FP-Kompiler, einige grundlegende Kenntnisse in OpenGL und in GLSL. Falls letztere fehlen, kann ich nur die beiden Tutorials zum Thema GLSL hier im Wiki empfehlen: [[Tutorial_glsl|Tutorial GLSL]] und [[Tutorial_glsl2|Tutorial GLSL 2]].<br />
<br />
Noch etwas, dass man wissen sollte ist, dass die Kamera in die GL_PROJECTION_MATRIX geschrieben wird. Dies wird später für die Shader wichtig. Das ist kaum mehr Arbeit mit großer Wirkung, da sich dieses Tutorial an fortgeschrittene Benutzer richtet, werde ich hier allerdings nicht näher drauf eingehen. Durch diese Trennung haben wir dann den Vorteil, dass die eigene Rotation der Modelle von den Operationen der Kamera getrennt ist. Das wird wie erwähnt bei den Shadern wichtig.<br />
<br />
In dem Sinne: glBegin(GL_WATER_TUTORIAL) ;-)<br />
<br />
<br />
= Die Wasserebene =<br />
Das Erste, dass wir brauchen ist eine Ebene - am besten Quads - von mir aus auch zwei Triangles. Wichtig ist, dass es eine Ebene ist. Es darf keine Höhenunterschiede geben. Die Z-Achse sollte hierbei die Höhe darstellen, X und Y dienen als... als X und Y eben ;) Natürlich fehlt noch so ziemlich alles. Diese Ebene allein macht nicht einen Wassereffekt aus. Jetzt brauchen wir erst einmal ein paar Texturen. Diese sollten der "Power of Two"-Regel gemäß und quadratisch sein. Dabei muss man beachten, dass sie keinesfalls größer als der maximal beschreibbare Viewport sein sollten, weil die Texturen komplett gefüllt werden müssen. Der Grund dafür ist, dass man nicht über den Rand des Framebuffers (des Fensters) zeichnen kann, zuminest nicht ohne Erweiterungen, also muss die Textur kleiner oder genauso groß wie der Framebuffer sein. Diese Texturen sollten bereits zu Anfang des Programms erstellt und initialisiert werden. Genau genommen brauchen wir zwei Stück. Jeweils eine für die Reflektions- und Refraktionsmap. Wenn wir ohne Shader arbeiten, sollten sie nur als RGB initialisiert werden. Wenn wir später den Shader zuschalten, brauchen wir auch den Alphakanal. Die Größe sollte sich am originalen Viewport ausrichten. Die Texturen lassen sich übrigens am Besten mit glCopyTexImage2D initialisieren, da man dort keinen Puffer mit Pixeldaten braucht, den man übergeben muss.<br />
<br />
= Texturen füllen =<br />
Jetzt geht es darum, wie wir die Szene in die Texturen und dann auf die Wasserebene bekommen. Hier kann ich einen Trick empfehlen, den ich aus einer Demo von Delphi3d.net habe- aber dazu gleich noch mehr.<br />
<br />
Wichtig ist vor allem, das wir uns erst einmal Gedanken machen, was die Texturen eigentlich beinhalten sollen. Refract sollte - wie der Name bereits sagt - die Refraktionsinformationen, also alles, was unter dem Wasser ist, darstellen, Reflect, daher das, was über dem Wasser ist. Damit keine Darstellungsfehler entstehen, wenn die Kamera über dem Wasser ist, müssen wir beim Erstellen der Texturen die Parameter {{INLINE_CODE|GL_TEXTURE_WRAP_S}} und {{INLINE_CODE|GL_TEXTURE_WRAP_T}} auf {{INLINE_CODE|GL_CLAMP_TO_EDGE}} setzen.<br />
Wer schon etwas häufiger mit OpenGL gearbeitet hat, der kann sich vielleicht schon denken, wie man die Texturen am Besten füllen kann: natürlich mit ClipPlanes. Diese praktischen Dinger sorgen dafür, dass alles, was über bzw. unter ihnen liegt, abgeschnitten und nicht gezeichnet wird. Am besten für die Performance wäre es natürlich, wenn wir in der Anwendung noch einmal prüfen, ob das Objekt nicht vielleicht ganz unter/über Wasser ist und man es deshalb in diesem Renderpass weglassen könnte, aber das würde den Rahmen des Tutorials sprengen. Außerdem müssen wir dann noch entsprechend der aktuell zu füllenden Textur die Z-Achse invertieren je nachdem , ob wir den Teil über oder den Teil unter der Wasseroberfläche rendern wollen.<br />
<br />
<pascal>procedure DoRenderPass(InvertZ: Boolean);<br />
const<br />
// Deklaration der ClipPlane<br />
CP: array [0..3] of Double = (0.0, 0.0, 1.0, 0.0);<br />
begin<br />
glEnable(GL_CLIP_PLANE0);<br />
glDisable(GL_CULL_FACE);<br />
<br />
glLoadIdentity;<br />
glClipPlane(GL_CLIP_PLANE0, @CP);<br />
<br />
if InvertZ then<br />
glScalef(1, 1, -1);<br />
RenderGameScene;<br />
<br />
glDisable(GL_CLIP_PLANE0);<br />
glEnable(GL_CULL_FACE);<br />
end;</pascal><br />
Das sollte soweit klar sein. Jetzt zu dem eigentlichen Inhalt von RenderWaterMap.<br />
<br />
Dort muss zunächst einmal der komplette Viewport geleert werden. Danach sollte der Viewport auf die Größe von WaterTexSize verkleinert werden, damit das Gerenderte letztendlich auch auf die Texturen passt. Jetzt kommen wir langsam zu dem Trick, den ich weiter oben erwähnte: Erst einmal muss die Texturmatrix auf die Identitätsmatrix zurückgesetzt werden. Weiter geht es mit dem ersten Renderpass für die Reflektion. Das Ergebnis sollte in die Reflect-Textur gespeichert werden. Danach die Szene noch einmal rendern (nicht vergessen, vorher die Textur wieder zu unbinden), diesmal jedoch ohne Invertierung der Z-Achse. Das kommt dann logischerweise in die Refract-Textur, weil die ClipPlane hier alles unter der Wasseroberfläche da lässt und den Rest abschneidet. Zu guter Vorletzt den Viewport wieder auf den Zustand zurücksetzen, auf dem er vor dem Rendern war und nochmal den Framebuffer leeren.<br />
<br />
Jetzt wird es interessant: In der Texturmatrix müssen folgende Operationen durchgeführt werden: Verschieben um (0.5, 0.5, 0.0) und skalieren um (0.5, 0.5, 0.0). Dann eine Perspektive mit dem Seitenverhältnis, welches wir auch in unserer „echten“ Szene haben erzeugen und danach die Kameraoperationen durchführen.<br />
<br />
Und jetzt der Code dazu:<br />
<pascal><br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
<br />
glViewport(0, 0, WaterTexSize, WaterTexSize);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
DoRenderPass(True);<br />
<br />
glBindTexture(GL_TEXTURE_2D, Reflect);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
if UseWaterShader then<br />
glClearColor(0.0, 0.0, 0.0, 0.0);<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
<br />
DoRenderPass(False);<br />
<br />
glBindTexture(GL_TEXTURE_2D, Refract);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
// Hier bitte die eigenen Werte einsetzen<br />
glViewport(0, 0, Settings.ScreenWidth, Settings.ScreenHeight); <br />
<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glTranslatef(0.5, 0.5, 0);<br />
glScalef(0.5, 0.5, 0);<br />
<br />
gluPerspective(PERSPECTIVE_FOV, Settings.ScreenWidth/Settings.ScreenHeight, 0.01, 100000);<br />
DoCamTranslate;<br />
DoCamRotate;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
glBindTexture(GL_TEXTURE_2D, 0); <br />
</pascal><br />
<br />
= Wasser marsch =<br />
Jetzt zu der Wasserebene. Sicher kann man sich denken, dass aus dem Weiß noch mehr wird. Jetzt kommen wir zu der Sache mit der Texturmatrix. Die ist dank unserer Routine jetzt so aufgebaut, dass die übergebenen Texturkoordinaten wie in der normalen Projektionsmatrix verarbeitet werden. Das ermöglicht es als Texturkoordinaten genau die Werte zu übergeben, die auch glVertex3f bekommt. Das ist eine große Erleichterung, was vor allem nerviges hin- und hergerechne erspart. Da wir uns erst einmal mit dem non-shader-way befassen wollen, sieht unser Code für die Wasserebene jetzt so aus:<br />
<br />
<pascal>glEnable(GL_TEXTURE_2D);<br />
<br />
glColor4f(1.0, 1.0, 1.0, 1.0);<br />
glBindTexture(GL_TEXTURE_2D, Reflect);<br />
<br />
glDepthMask(GL_FALSE);<br />
glBegin(GL_QUADS);<br />
glTexCoord3f(-100.0, -100.0, 0.0);<br />
glVertex3f(-100.0, -100.0, 0.0);<br />
glTexCoord3f(-100.0, 100.0, 0.0);<br />
glVertex3f(-100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, 100.0, 0.0);<br />
glVertex3f(100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, -100.0, 0.0);<br />
glVertex3f(100.0, -100.0, 0.0);<br />
glEnd;<br />
<br />
glEnable(GL_BLEND);<br />
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);<br />
<br />
glDepthMask(GL_TRUE);<br />
glBindTexture(GL_TEXTURE_2D, Refract);<br />
glColor4f(1.0, 1.0, 1.0, RefractAlpha);<br />
glBegin(GL_QUADS);<br />
glTexCoord3f(-100.0, -100.0, 0.0);<br />
glVertex3f(-100.0, -100.0, 0.0);<br />
glTexCoord3f(-100.0, 100.0, 0.0);<br />
glVertex3f(-100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, 100.0, 0.0);<br />
glVertex3f(100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, -100.0, 0.0);<br />
glVertex3f(100.0, -100.0, 0.0);<br />
glEnd;<br />
<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
glDisable(GL_TEXTURE_2D);<br />
glDisable(GL_BLEND);</pascal><br />
Die Texturkoordinaten sollten ja jetzt klar sein. Zuerst wird die Reflektionstextur gerendert, danach folgt die Refraktionstextur. Dabei wird erstere mit vollem Alpha gerendert und die andere darüber geblendet. Bei den Shadern werden beide Texturen gebunden und an den Shadern übergeben, dazu jedoch gleich mehr. [[Bild:Ld_watertutorial_step1.jpg|thumb|right|Die einfache Wasserebene ohne Wellen (von littleDave)]] Rechts könnt ihr euch von eurem Ergebnis überzeugen.<br />
<br />
= Auf zu den Shadern =<br />
Für die Shader müssen wir eigentlich kaum Änderungen vornehmen, zumindest solange wir beim simplen Einbauen von Wellen bleiben. Gleich werde ich noch auf eine Technik eingehen, die nervige Kanten an Grenzen zu Objekten vermeidet. Die Änderungen betreffen zunächst allein den Code, der die Ebenen selber rendert. Außerdem brauchen wir noch eine weitere Textur, die die Bumpmap der Wasseroberfläche darstellt und für die Wellen verwendet wird. Jetzt werden im Rendercode für die Wasserebene noch einige Änderungen durchgeführt. Zunächst brauchen wir nur noch ein Quad, da der Shader das Überblenden übernimmt. Vor dem Rendern des Quads sollte der Shader gebunden werden, sowie die drei Texturen in drei Textureinheiten geschoben werden, in der Reihenfolge Reflect-Refract-WaterBumpmap. Dann muss noch ein Haufen Parameter an den Shader übergeben werden. Erstmal der Standardkram, die Zuweisung der Texturen. Der Uniform-Integer refractTex wird 1, reflectTex wird 0 und bumpMap wird 2. Dann gibt es noch einige Variablen, die die Darstellung und die Stärke des Wassereffektes beeinflussen: reflectRatio und refractRatio sind ein Faktor, mit dem die Bumpmap multipliziert wird und der die Stärke des Effektes beeinflusst. Ich empfehle für beides einen Wert um 1/20.0. Dann gibt es noch mappingRatio und extendedBlending, die erkläre ich später beim Shader selbst.<br />
<br />
Da wir jetzt zwei verschiedene Texturarten verwenden, einmal die projizierten Reflect- und Refracttexturen und die BumpMaptexturen, sollten wir auch noch eine zweite Texturkoordinate übergeben, und zwar für die Bumpmaps. Welchen Wert Ihr hier einsetzt, ist Eure Sache. <br />
<br />
Jetzt zu dem Shader, dem wir die Werte übergeben wollen. Erst einmal ein kurzes Brainstorming. Was brauchen wir? Zunächst müssen die Texturen entsprechend der Matrizen transformiert werden. Danach müssen sie wiederum entsprechend der Bumpmaps verschoben werden, so dass ein Welleneffekt entsteht. Dies alles passiert im Fragmentshader. Der Vertexshader dagegen sieht recht harmlos aus:<br />
<br />
<glsl>varying mat4 texMat;<br />
<br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
texMat = gl_TextureMatrix[0];<br />
gl_TexCoord[1] = gl_MultiTexCoord1;<br />
gl_FrontColor = gl_Color;<br />
}</glsl><br />
Was im Vertexshader passiert, sollte wohl ziemlich klar sein. In Zeile 5 wird erst einmal der Vertex anhand der normalen Matrizen projiziert, so dass es die gleiche Wirkung hat, als wenn man es durch die normale Renderpipe jagen würde. Zeilen Sechs und Acht sind dafür zuständig, dass die Texturkoordinaten auch im Fragmentshader verwendet werden können. Zeile Sieben speichert die Texturmatrix in einer Varying, so dass wir sie ebenfalls im Fragmentshader verwenden können. Die neunte Zeile speichert noch die Farbe - eher unwichtig und man könnte es auch weglassen, aber ich fand es schön ;). Genug der Scherze, weiter mit dem Fragmentshader:<br />
<br />
<glsl>uniform sampler2D refractTex;<br />
uniform sampler2D reflectTex;<br />
uniform sampler2D bumpMap;<br />
<br />
uniform float refractRatio;<br />
uniform float reflectRatio;<br />
<br />
uniform float mappingRatio;<br />
<br />
uniform int extendedBlending;<br />
<br />
varying mat4 texMat;<br />
<br />
void main(void)<br />
{<br />
vec4 refractcoord;<br />
vec4 reflectcoord;<br />
vec4 offsetColor = (texture2D(bumpMap, vec2(gl_TexCoord[1])) + <br />
texture2D(bumpMap, vec2(gl_TexCoord[1]) * 4.0)) / 2.0;<br />
vec4 origOffset = offsetColor;<br />
vec4 color;<br />
vec4 reflectColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 refractColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 blendedColor;<br />
<br />
offsetColor -= 0.5;<br />
offsetColor *= 2.0;<br />
<br />
<br />
refractcoord = gl_TexCoord[0];<br />
refractcoord.x += offsetColor[0] * refractRatio;<br />
refractcoord.z += offsetColor[1] * refractRatio;<br />
refractcoord = texMat * refractcoord;<br />
refractColor = texture2DProj(refractTex, refractcoord); <br />
<br />
reflectcoord = gl_TexCoord[0];<br />
reflectcoord.x += offsetColor[0] * reflectRatio;<br />
reflectcoord.z += offsetColor[1] * reflectRatio;<br />
reflectcoord = texMat * reflectcoord;<br />
reflectColor = texture2DProj(reflectTex, reflectcoord); <br />
<br />
reflectColor[3] = 1.0;<br />
refractColor[3] = 1.0;<br />
<br />
if (extendedBlending == 0)<br />
{<br />
float mappingRefract, mappingReflect;<br />
mappingRefract = mappingRatio * 255.0;<br />
mappingReflect = 255.0 - mappingRefract;<br />
mappingRefract /= 255.0;<br />
mappingReflect /= 255.0;<br />
blendedColor = refractColor * mappingRefract + reflectColor * mappingReflect;<br />
blendedColor.a = 1.0;<br />
}<br />
else<br />
{<br />
float Alpha, reflectAlpha, refractAlpha;<br />
Alpha = (refractColor.r + refractColor.g + refractColor.b) / 3.0;<br />
if (Alpha > 1.0)<br />
Alpha = 1.0;<br />
if (Alpha < 0.0)<br />
Alpha = 0.0;<br />
<br />
refractAlpha = Alpha * 255.0;<br />
reflectAlpha = 255.0 - refractAlpha;<br />
refractAlpha /= 255.0;<br />
reflectAlpha /= 255.0;<br />
blendedColor = reflectColor * reflectAlpha + refractColor * refractAlpha;<br />
}<br />
<br />
gl_FragColor = blendedColor;<br />
}</glsl><br />
Das ist recht viel Quelltext auf einmal. Aber wir schauen ihn uns nun stückchenweise an. Die ersten Zeilen deklarieren die verwendeten Uniforms. Dann wird noch das texMat-Varying aus dem Vertexshader übernommen. Wie wir bereits vorhin gelernt haben, ist die Texturmatrix unerlässlich, um die Textur einfacher auf die Ebene zu projizieren. Im Hauptcode haben wir dann einen ganzen Haufen Variablendeklarationen. Hervor sticht offsetColor, für das zwei Texturzugriffe verwendet werden, um die Farbe aus der Bumpmap zu lesen. Zwei Zugriffe deshalb, um das Wasser ein wenig detaillierter zu machen. Danach wird offsetColor skaliert: Eine normale Bitmap hat nun einmal nicht die Möglichkeit, negative Werte zu speichern, aber es würde ohne diese sehr eintönig aussehen. Danach wird das Offset für Reflektion und Refraktion berechnet. Das läuft bei beiden ähnlich ab: Die Texturkoordinate wird mit der Matrix multipliziert und erst einmal in einer Variable gespeichert, dann wird der Wert aus der Map gelesen und das Alpha auf 1.0 gesetzt. <br />
<br />
[[Bild:Ld_watertutorial_step2.jpg|thumb|right|Der zweite Schritt: Die Shader-Wellen sind deutlich sichtbar (von littleDave)]]Weiter geht es mit dem Schalter zwischen erweitertem Blending und normalem Blending. Das erweiterte Blending versucht eine Wasseroberfläche von der Lichtdurchlässigkeit möglichst realistisch darzustellen, indem die Sichtbarkeit der Objekte unterhalb der Oberfläche, also dem Teil in der Refraktionsmap, der Helligkeit der Reflektion angepasst: Wenn man mit einer Lampe auf eine Wasseroberfläche leuchtet, kann man schlechter durch die von der Lampe getroffene Stelle blicken, weil die Reflektion der Lampe das, was darunter liegt, überstrahlt. Zu guter Letzt wird im Shader noch die berechnete Farbe an OpenGL zurückgegeben, sodass sie dann in den Framebuffer geschrieben werden kann. mappingRatio legt übrigens das Verhältnis zwischen Reflektion und Refraktion fest, falls erweitertes Blending deaktiviert ist (also extendedBlending = 0). Ein Wert von 1.0 würde bedeuten, dass nur die Refraktionsmap gerendert wird, ein Blending von 0.0 bedeutet, dass nur die Reflektionsmap durchkommt. Diese Multiplikationen da oben mache ich übrigens, um ein Problem mit der Genauigkeit des Floats im Shader zu umgehen. Rechts nochmal ein Bild zum aktuellen Status. Da erkennt man schon deutlich die Wellen und, wenn man genauer hinschaut, erkennt man auch eine unschöne Linie beim Übergang von der Wasserebene zum Objekt.<br />
<br />
= Feintuning =<br />
Wenn man jetzt einfach einen Würfel in die Wasserfläche setzt, der auf der Vorder- und der Rückseite verschiedene Farben hat, dann wird sichtbar, dass durch die Wellen unschöne Kanten entstehen, weil die Refraktions-/Reflektionsmap keine Objektgrenzen kennt. Doch dafür gibt es eine simple aber dennoch wirkungsvolle Methode, die ich jetzt beschreibe. Hierzu müssen wir den Code für das Rendern der Texturen leicht anpassen:<br />
<br />
<pascal> glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
glViewport(0, 0, WaterTexSize, WaterTexSize);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
DoRenderPass(True);<br />
<br />
if UseDepthShader then<br />
begin<br />
glColorMask(False, False, False, True);<br />
glClear(GL_DEPTH_BUFFER_BIT);<br />
<br />
glUseProgramObjectARB(DepthShader);<br />
glUniform1fARB(glGetUniformLocationARB(DepthShader, 'waterplaneZ'), 0.0);<br />
DoRenderPass(True);<br />
glUseProgramObjectARB(0);<br />
glColorMask(True, True, True, True);<br />
end;<br />
<br />
glBindTexture(GL_TEXTURE_2D, Reflect);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
if UseWaterShader then<br />
glClearColor(0.0, 0.0, 0.0, 0.0);<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
<br />
if UseDepthShader then<br />
begin<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
glColorMask(True, True, True, True);<br />
<br />
glUseProgramObjectARB(DepthShader);<br />
DoRenderPass(False);<br />
glUseProgramObjectARB(0);<br />
<br />
glColorMask(True, True, True, False);<br />
glClear(GL_DEPTH_BUFFER_BIT);<br />
end;<br />
<br />
DoRenderPass(False);<br />
<br />
glColorMask(True, True, True, True); <br />
<br />
glBindTexture(GL_TEXTURE_2D, Refract);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
// Hier bitte die eigenen Werte einsetzen<br />
glViewport(0, 0, Settings.ScreenWidth, Settings.ScreenHeight); <br />
<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glTranslatef(0.5, 0.5, 0);<br />
glScalef(0.5, 0.5, 0);<br />
<br />
gluPerspective(PERSPECTIVE_FOV, Settings.ScreenWidth/Settings.ScreenHeight, 0.01, 100000);<br />
DoCamTranslate;<br />
DoCamRotate;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
glBindTexture(GL_TEXTURE_2D, 0);</pascal><br />
Wieder ein riesiger Codeblock, aber das meiste kennen wir ja bereits. Das einzige, was dazugekommen ist, sind die if UseDepthShader-Blöcke. UseDepthShader ist mal wieder eine Variable, die Ihr ruhig deklarieren solltet, um die Verwendung des DepthShaders einzuschränken. DepthShader sollte wieder mal eine Variable für den Namen des Shaderprogramms sein. Ich missbrauche hier den Alpha-Kanal der Texturen für Tiefeninformationen. Dies ist insofern sinnvoll, dass man nicht extra eine Textur braucht und daher auch die Shader relativ einfach zu erweitern sind. Aber jetzt erst einmal zum Tiefenshader. Hier haben wir wieder einen recht unspektakulären Vertexshader:<br />
<br />
<glsl>varying vec4 vpos;<br />
varying vec4 ppos;<br />
<br />
void main(void)<br />
{<br />
vpos = gl_ModelViewMatrix * gl_Vertex;<br />
ppos = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_FrontColor = gl_Color;<br />
gl_ClipVertex = vpos;<br />
}</glsl><br />
Obwohl es eher wie ein normaler Shader aussieht, ist es doch interessanter als man glaubt. Jetzt kommt uns der Vorteil zugute, dass wir die Kamera in der Projektionsmatrix haben. Wir können einfach die Kameraoptionen außen vor lassen und nur die Operationen, die in der Modelview-Matrix stehen auf den Vertex anwenden. Das erleichtert das Errechnen des Z-Wertes des Vertex ungemein. Jetzt zum Fragmentshader:<br />
<br />
<glsl>uniform float waterplaneZ;<br />
<br />
varying vec4 vpos;<br />
varying vec4 ppos;<br />
<br />
void main(void)<br />
{<br />
float y, z;<br />
y = waterplaneZ - vpos.z;<br />
z = ppos.z;<br />
<br />
gl_FragColor = vec4(z, z, z, y);<br />
}</glsl><br />
Hier werden die im Vertexshader berechneten Werte verwendet, um die Höhe des Vertices in den Alphakanal zu schreiben. Angemerkt werden sollte, dass die RGB-Informationen dank des glColorMask in RenderWaterMap verworfen werden. WaterplaneZ ist übrigens der Z-Wert der Wasserebene, in unserem Beispiel also 0.0, wie aus dem obigen Code hervorgeht.<br />
<br />
Jetzt müssen wir nur noch den Wassershader so verändern, dass er die jetzt im Alphakanal vorhandenen Informationen auch sinnvoll nutzt. Der Vertexshader bleibt dabei, wie er ist, aber der Fragmentshader sieht jetzt so aus:<br />
<br />
<glsl>uniform sampler2D refractTex;<br />
uniform sampler2D reflectTex;<br />
uniform sampler2D bumpMap;<br />
<br />
uniform float refractRatio;<br />
<br />
uniform float reflectRatio;<br />
<br />
uniform float mappingRatio;<br />
<br />
uniform int extendedBlending;<br />
<br />
varying mat4 texMat;<br />
<br />
void main(void)<br />
{<br />
vec4 refractcoord;<br />
vec4 reflectcoord;<br />
vec4 offsetColor = (texture2D(bumpMap, vec2(gl_TexCoord[1])) + <br />
texture2D(bumpMap, vec2(gl_TexCoord[1]) * 4.0)) / 2.0;<br />
vec4 origOffset = offsetColor;<br />
vec4 color;<br />
vec4 reflectColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 refractColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 blendedColor;<br />
<br />
offsetColor -= 0.5;<br />
offsetColor *= 2.0;<br />
<br />
refractcoord = texMat * gl_TexCoord[0];<br />
refractColor = texture2DProj(refractTex, refractcoord);<br />
refractcoord = gl_TexCoord[0];<br />
refractcoord.x += offsetColor[0] * refractColor[3] * refractRatio;<br />
refractcoord.z += offsetColor[1] * refractColor[3] * refractRatio;<br />
refractcoord = texMat * refractcoord;<br />
<br />
reflectcoord = texMat * gl_TexCoord[0];<br />
reflectColor = texture2DProj(reflectTex, reflectcoord);<br />
reflectcoord = gl_TexCoord[0];<br />
reflectcoord.x -= offsetColor[0] * reflectColor[3] * reflectRatio;<br />
reflectcoord.z -= offsetColor[1] * reflectColor[3] * reflectRatio;<br />
reflectcoord = texMat * reflectcoord;<br />
<br />
reflectColor = texture2DProj(reflectTex, reflectcoord);<br />
refractColor = texture2DProj(refractTex, refractcoord);<br />
<br />
reflectColor[3] = 1.0;<br />
refractColor[3] = 1.0;<br />
<br />
if (extendedBlending == 0)<br />
{<br />
float mappingRefract, mappingReflect;<br />
mappingRefract = mappingRatio * 255.0;<br />
mappingReflect = 255.0 - mappingRefract;<br />
mappingRefract /= 255.0;<br />
mappingReflect /= 255.0;<br />
blendedColor = refractColor * mappingRefract + reflectColor * mappingReflect;<br />
blendedColor.a = 1.0;<br />
}<br />
else<br />
{<br />
float Alpha, reflectAlpha, refractAlpha;<br />
Alpha = (refractColor.r + refractColor.g + refractColor.b) / 3.0;<br />
if (Alpha > 1.0)<br />
Alpha = 1.0;<br />
if (Alpha < 0.0)<br />
Alpha = 0.0;<br />
<br />
refractAlpha = Alpha * 255.0;<br />
reflectAlpha = 255.0 - refractAlpha;<br />
refractAlpha /= 255.0;<br />
reflectAlpha /= 255.0;<br />
blendedColor = reflectColor * reflectAlpha + refractColor * refractAlpha;<br />
}<br />
<br />
gl_FragColor = blendedColor;<br />
}</glsl><br />
<br />
[[Bild:Ld_watertutorial_step3.jpg|thumb|right|Der finale Wassereffekt mit Wellen und "Kantenerkennung" (von littleDave)]]Der neue Code multipliziert das Offset nochmal mit dem Alpha-Wert, in unserem Falle also die Distanz zwischen der Wasserebene und dem Pixel. Dadurch werden die Wellen je näher der Pixel an der Wasseroberfläche ist, immer niedriger. So werden an der direkten Kante zu dem Objekt, also da, wo der Alpha-Wert 0.0 ist, gar keine Wellen mehr erzeugt. Damit ist die nervige Kante weg. So sieht das ganze zum Schluss aus.<br />
<br />
= In the end... =<br />
Das war es eigentlich. Wenn alles so läuft, wie es soll, dann habt ihr jetzt eine schöne Ebene mit Wassereffekt. Die Shader sowie der andere Code dürfen natürlich beliebig weiterverwendet werden. Verbesserungsvorschläge, Kritik, Feedback jeglicher Art und Morddrohungen wie immer einfach an mich im Forum wenden.<br />
<br />
glEnd;<br />
<br />
Gruss [[Benutzer:Lord Horazont|Lord Horazont]]<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial Alphamasking]]|-}}</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_Wassereffekt&diff=22426Tutorial Wassereffekt2009-01-03T19:16:33Z<p>Ireyon: /* Texturen füllen */</p>
<hr />
<div>= Einleitung =<br />
Erstmal willkommen zu meinem zweiten Tutorial. Ich möchte Euch hier einen Einblick in die „hohe Kunst“ der Wassereffekte geben. Hierbei wird erst einmal nur ein relativ stehendes Gewässer, also keines, welches in einer Rinne fließt beschrieben. Auch werde ich hier nicht auf die Technik für Wasser an Abhängen (abwärts fließende Flüsse) oder Wasserfälle eingehen. Hier geht es schlicht und einfach um eine Wasserebene, wie man sie zum Beispiel für ein Meer verwenden könnte.<br />
<br />
Voraussetzung für dieses Tutorial ist eigentlich nur ein Delphi/FP-Kompiler, einige grundlegende Kenntnisse in OpenGL und in GLSL. Falls letztere fehlen, kann ich nur die beiden Tutorials zum Thema GLSL hier im Wiki empfehlen: [[Tutorial_glsl|Tutorial GLSL]] und [[Tutorial_glsl2|Tutorial GLSL 2]].<br />
<br />
Noch etwas, dass man wissen sollte ist, dass die Kamera in die GL_PROJECTION_MATRIX geschrieben wird. Dies wird später für die Shader wichtig. Das ist kaum mehr Arbeit mit großer Wirkung, da sich dieses Tutorial an fortgeschrittene Benutzer richtet, werde ich hier allerdings nicht näher drauf eingehen. Durch diese Trennung haben wir dann den Vorteil, dass die eigene Rotation der Modelle von den Operationen der Kamera getrennt ist. Das wird wie erwähnt bei den Shadern wichtig.<br />
<br />
In dem Sinne: glBegin(GL_WATER_TUTORIAL) ;-)<br />
<br />
<br />
= Die Wasserebene =<br />
Das Erste, dass wir brauchen ist eine Ebene - am besten Quads - von mir aus auch zwei Triangles. Wichtig ist, dass es eine Ebene ist. Es darf keine Höhenunterschiede geben. Die Z-Achse sollte hierbei die Höhe darstellen, X und Y dienen als... als X und Y eben ;) Natürlich fehlt noch so ziemlich alles. Diese Ebene allein macht nicht einen Wassereffekt aus. Jetzt brauchen wir erst einmal ein paar Texturen. Diese sollten der "Power of Two"-Regel gemäß und quadratisch sein. Dabei muss man beachten, dass sie keinesfalls größer als der maximal beschreibbare Viewport sein sollten, weil die Texturen komplett gefüllt werden müssen. Der Grund dafür ist, dass man nicht über den Rand des Framebuffers (des Fensters) zeichnen kann, zuminest nicht ohne Erweiterungen, also muss die Textur kleiner oder genauso groß wie der Framebuffer sein. Diese Texturen sollten bereits zu Anfang des Programms erstellt und initialisiert werden. Genau genommen brauchen wir zwei Stück. Jeweils eine für die Reflektions- und Refraktionsmap. Wenn wir ohne Shader arbeiten, sollten sie nur als RGB initialisiert werden. Wenn wir später den Shader zuschalten, brauchen wir auch den Alphakanal. Die Größe sollte sich am originalen Viewport ausrichten. Die Texturen lassen sich übrigens am Besten mit glCopyTexImage2D initialisieren, da man dort keinen Puffer mit Pixeldaten braucht, den man übergeben muss.<br />
<br />
= Texturen füllen =<br />
Jetzt geht es darum, wie wir die Szene in die Texturen und dann auf die Wasserebene bekommen. Hier kann ich einen Trick empfehlen, den ich aus einer Demo von Delphi3d.net habe- aber dazu gleich noch mehr.<br />
<br />
Wichtig ist vor allem, das wir uns erst einmal Gedanken machen, was die Texturen eigentlich beinhalten sollen. Refract sollte - wie der Name bereits sagt - die Refraktionsinformationen, also alles, was unter dem Wasser ist, darstellen, Reflect, daher das, was über dem Wasser ist. Damit keine Darstellungsfehler entstehen, wenn man über dem Wasser hin- und her navigiert, muss man beim Erstellen der Texturen die Parameter {{INLINE_CODE|GL_TEXTURE_WRAP_S}} und {{INLINE_CODE|GL_TEXTURE_WRAP_T}} auf {{INLINE_CODE|GL_CLAMP_TO_EDGE}} setzen.<br />
Wer schon etwas häufiger mit OpenGL gearbeitet hat, der kann sich vielleicht schon denken, wie man die Texturen am Besten füllen kann: natürlich mit ClipPlanes. Diese praktischen Dinger sorgen dafür, dass alles, was über bzw. unter ihnen liegt, abgeschnitten und nicht gezeichnet wird. Am besten für die Performance wäre es natürlich, wenn wir in der Anwendung noch einmal prüfen, ob das Objekt nicht vielleicht ganz unter/über Wasser ist und man es deshalb in diesem Renderpass weglassen könnte, aber das würde den Rahmen des Tutorials sprengen. Außerdem müssen wir dann noch entsprechend der aktuell zu füllenden Textur die Z-Achse invertieren je nachdem , ob wir den Teil über oder den Teil unter der Wasseroberfläche rendern wollen.<br />
<br />
<pascal>procedure DoRenderPass(InvertZ: Boolean);<br />
const<br />
// Deklaration der ClipPlane<br />
CP: array [0..3] of Double = (0.0, 0.0, 1.0, 0.0);<br />
begin<br />
glEnable(GL_CLIP_PLANE0);<br />
glDisable(GL_CULL_FACE);<br />
<br />
glLoadIdentity;<br />
glClipPlane(GL_CLIP_PLANE0, @CP);<br />
<br />
if InvertZ then<br />
glScalef(1, 1, -1);<br />
RenderGameScene;<br />
<br />
glDisable(GL_CLIP_PLANE0);<br />
glEnable(GL_CULL_FACE);<br />
end;</pascal><br />
Das sollte soweit klar sein. Jetzt zu dem eigentlichen Inhalt von RenderWaterMap.<br />
<br />
Dort muss zunächst einmal der komplette Viewport geleert werden. Danach sollte der Viewport auf die Größe von WaterTexSize verkleinert werden, damit das Gerenderte letztendlich auch auf die Texturen passt. Jetzt kommen wir langsam zu dem Trick, den ich weiter oben erwähnte: Erst einmal muss die Texturmatrix auf die Identitätsmatrix zurückgesetzt werden. Weiter geht es mit dem ersten Renderpass für die Reflektion. Das Ergebnis sollte in die Reflect-Textur gespeichert werden. Danach die Szene noch einmal rendern (nicht vergessen, vorher die Textur wieder zu unbinden), diesmal jedoch ohne Invertierung der Z-Achse. Das kommt dann logischerweise in die Refract-Textur, weil die ClipPlane hier alles unter der Wasseroberfläche da lässt und den Rest abschneidet. Zu guter Vorletzt den Viewport wieder auf den Zustand zurücksetzen, auf dem er vor dem Rendern war und nochmal den Framebuffer leeren.<br />
<br />
Jetzt wird es interessant: In der Texturmatrix müssen folgende Operationen durchgeführt werden: Verschieben um (0.5, 0.5, 0.0) und skalieren um (0.5, 0.5, 0.0). Dann eine Perspektive mit dem Seitenverhältnis, welches wir auch in unserer „echten“ Szene haben erzeugen und danach die Kameraoperationen durchführen.<br />
<br />
Und jetzt der Code dazu:<br />
<pascal><br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
<br />
glViewport(0, 0, WaterTexSize, WaterTexSize);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
DoRenderPass(True);<br />
<br />
glBindTexture(GL_TEXTURE_2D, Reflect);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
if UseWaterShader then<br />
glClearColor(0.0, 0.0, 0.0, 0.0);<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
<br />
DoRenderPass(False);<br />
<br />
glBindTexture(GL_TEXTURE_2D, Refract);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
// Hier bitte die eigenen Werte einsetzen<br />
glViewport(0, 0, Settings.ScreenWidth, Settings.ScreenHeight); <br />
<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glTranslatef(0.5, 0.5, 0);<br />
glScalef(0.5, 0.5, 0);<br />
<br />
gluPerspective(PERSPECTIVE_FOV, Settings.ScreenWidth/Settings.ScreenHeight, 0.01, 100000);<br />
DoCamTranslate;<br />
DoCamRotate;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
glBindTexture(GL_TEXTURE_2D, 0); <br />
</pascal><br />
<br />
= Wasser marsch =<br />
Jetzt zu der Wasserebene. Sicher kann man sich denken, dass aus dem Weiß noch mehr wird. Jetzt kommen wir zu der Sache mit der Texturmatrix. Die ist dank unserer Routine jetzt so aufgebaut, dass die übergebenen Texturkoordinaten wie in der normalen Projektionsmatrix verarbeitet werden. Das ermöglicht es als Texturkoordinaten genau die Werte zu übergeben, die auch glVertex3f bekommt. Das ist eine große Erleichterung, was vor allem nerviges hin- und hergerechne erspart. Da wir uns erst einmal mit dem non-shader-way befassen wollen, sieht unser Code für die Wasserebene jetzt so aus:<br />
<br />
<pascal>glEnable(GL_TEXTURE_2D);<br />
<br />
glColor4f(1.0, 1.0, 1.0, 1.0);<br />
glBindTexture(GL_TEXTURE_2D, Reflect);<br />
<br />
glDepthMask(GL_FALSE);<br />
glBegin(GL_QUADS);<br />
glTexCoord3f(-100.0, -100.0, 0.0);<br />
glVertex3f(-100.0, -100.0, 0.0);<br />
glTexCoord3f(-100.0, 100.0, 0.0);<br />
glVertex3f(-100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, 100.0, 0.0);<br />
glVertex3f(100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, -100.0, 0.0);<br />
glVertex3f(100.0, -100.0, 0.0);<br />
glEnd;<br />
<br />
glEnable(GL_BLEND);<br />
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);<br />
<br />
glDepthMask(GL_TRUE);<br />
glBindTexture(GL_TEXTURE_2D, Refract);<br />
glColor4f(1.0, 1.0, 1.0, RefractAlpha);<br />
glBegin(GL_QUADS);<br />
glTexCoord3f(-100.0, -100.0, 0.0);<br />
glVertex3f(-100.0, -100.0, 0.0);<br />
glTexCoord3f(-100.0, 100.0, 0.0);<br />
glVertex3f(-100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, 100.0, 0.0);<br />
glVertex3f(100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, -100.0, 0.0);<br />
glVertex3f(100.0, -100.0, 0.0);<br />
glEnd;<br />
<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
glDisable(GL_TEXTURE_2D);<br />
glDisable(GL_BLEND);</pascal><br />
Die Texturkoordinaten sollten ja jetzt klar sein. Zuerst wird die Reflektionstextur gerendert, danach folgt die Refraktionstextur. Dabei wird erstere mit vollem Alpha gerendert und die andere darüber geblendet. Bei den Shadern werden beide Texturen gebunden und an den Shadern übergeben, dazu jedoch gleich mehr. [[Bild:Ld_watertutorial_step1.jpg|thumb|right|Die einfache Wasserebene ohne Wellen (von littleDave)]] Rechts könnt ihr euch von eurem Ergebnis überzeugen.<br />
<br />
= Auf zu den Shadern =<br />
Für die Shader müssen wir eigentlich kaum Änderungen vornehmen, zumindest solange wir beim simplen Einbauen von Wellen bleiben. Gleich werde ich noch auf eine Technik eingehen, die nervige Kanten an Grenzen zu Objekten vermeidet. Die Änderungen betreffen zunächst allein den Code, der die Ebenen selber rendert. Außerdem brauchen wir noch eine weitere Textur, die die Bumpmap der Wasseroberfläche darstellt und für die Wellen verwendet wird. Jetzt werden im Rendercode für die Wasserebene noch einige Änderungen durchgeführt. Zunächst brauchen wir nur noch ein Quad, da der Shader das Überblenden übernimmt. Vor dem Rendern des Quads sollte der Shader gebunden werden, sowie die drei Texturen in drei Textureinheiten geschoben werden, in der Reihenfolge Reflect-Refract-WaterBumpmap. Dann muss noch ein Haufen Parameter an den Shader übergeben werden. Erstmal der Standardkram, die Zuweisung der Texturen. Der Uniform-Integer refractTex wird 1, reflectTex wird 0 und bumpMap wird 2. Dann gibt es noch einige Variablen, die die Darstellung und die Stärke des Wassereffektes beeinflussen: reflectRatio und refractRatio sind ein Faktor, mit dem die Bumpmap multipliziert wird und der die Stärke des Effektes beeinflusst. Ich empfehle für beides einen Wert um 1/20.0. Dann gibt es noch mappingRatio und extendedBlending, die erkläre ich später beim Shader selbst.<br />
<br />
Da wir jetzt zwei verschiedene Texturarten verwenden, einmal die projizierten Reflect- und Refracttexturen und die BumpMaptexturen, sollten wir auch noch eine zweite Texturkoordinate übergeben, und zwar für die Bumpmaps. Welchen Wert Ihr hier einsetzt, ist Eure Sache. <br />
<br />
Jetzt zu dem Shader, dem wir die Werte übergeben wollen. Erst einmal ein kurzes Brainstorming. Was brauchen wir? Zunächst müssen die Texturen entsprechend der Matrizen transformiert werden. Danach müssen sie wiederum entsprechend der Bumpmaps verschoben werden, so dass ein Welleneffekt entsteht. Dies alles passiert im Fragmentshader. Der Vertexshader dagegen sieht recht harmlos aus:<br />
<br />
<glsl>varying mat4 texMat;<br />
<br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
texMat = gl_TextureMatrix[0];<br />
gl_TexCoord[1] = gl_MultiTexCoord1;<br />
gl_FrontColor = gl_Color;<br />
}</glsl><br />
Was im Vertexshader passiert, sollte wohl ziemlich klar sein. In Zeile 5 wird erst einmal der Vertex anhand der normalen Matrizen projiziert, so dass es die gleiche Wirkung hat, als wenn man es durch die normale Renderpipe jagen würde. Zeilen Sechs und Acht sind dafür zuständig, dass die Texturkoordinaten auch im Fragmentshader verwendet werden können. Zeile Sieben speichert die Texturmatrix in einer Varying, so dass wir sie ebenfalls im Fragmentshader verwenden können. Die neunte Zeile speichert noch die Farbe - eher unwichtig und man könnte es auch weglassen, aber ich fand es schön ;). Genug der Scherze, weiter mit dem Fragmentshader:<br />
<br />
<glsl>uniform sampler2D refractTex;<br />
uniform sampler2D reflectTex;<br />
uniform sampler2D bumpMap;<br />
<br />
uniform float refractRatio;<br />
uniform float reflectRatio;<br />
<br />
uniform float mappingRatio;<br />
<br />
uniform int extendedBlending;<br />
<br />
varying mat4 texMat;<br />
<br />
void main(void)<br />
{<br />
vec4 refractcoord;<br />
vec4 reflectcoord;<br />
vec4 offsetColor = (texture2D(bumpMap, vec2(gl_TexCoord[1])) + <br />
texture2D(bumpMap, vec2(gl_TexCoord[1]) * 4.0)) / 2.0;<br />
vec4 origOffset = offsetColor;<br />
vec4 color;<br />
vec4 reflectColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 refractColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 blendedColor;<br />
<br />
offsetColor -= 0.5;<br />
offsetColor *= 2.0;<br />
<br />
<br />
refractcoord = gl_TexCoord[0];<br />
refractcoord.x += offsetColor[0] * refractRatio;<br />
refractcoord.z += offsetColor[1] * refractRatio;<br />
refractcoord = texMat * refractcoord;<br />
refractColor = texture2DProj(refractTex, refractcoord); <br />
<br />
reflectcoord = gl_TexCoord[0];<br />
reflectcoord.x += offsetColor[0] * reflectRatio;<br />
reflectcoord.z += offsetColor[1] * reflectRatio;<br />
reflectcoord = texMat * reflectcoord;<br />
reflectColor = texture2DProj(reflectTex, reflectcoord); <br />
<br />
reflectColor[3] = 1.0;<br />
refractColor[3] = 1.0;<br />
<br />
if (extendedBlending == 0)<br />
{<br />
float mappingRefract, mappingReflect;<br />
mappingRefract = mappingRatio * 255.0;<br />
mappingReflect = 255.0 - mappingRefract;<br />
mappingRefract /= 255.0;<br />
mappingReflect /= 255.0;<br />
blendedColor = refractColor * mappingRefract + reflectColor * mappingReflect;<br />
blendedColor.a = 1.0;<br />
}<br />
else<br />
{<br />
float Alpha, reflectAlpha, refractAlpha;<br />
Alpha = (refractColor.r + refractColor.g + refractColor.b) / 3.0;<br />
if (Alpha > 1.0)<br />
Alpha = 1.0;<br />
if (Alpha < 0.0)<br />
Alpha = 0.0;<br />
<br />
refractAlpha = Alpha * 255.0;<br />
reflectAlpha = 255.0 - refractAlpha;<br />
refractAlpha /= 255.0;<br />
reflectAlpha /= 255.0;<br />
blendedColor = reflectColor * reflectAlpha + refractColor * refractAlpha;<br />
}<br />
<br />
gl_FragColor = blendedColor;<br />
}</glsl><br />
Das ist recht viel Quelltext auf einmal. Aber wir schauen ihn uns nun stückchenweise an. Die ersten Zeilen deklarieren die verwendeten Uniforms. Dann wird noch das texMat-Varying aus dem Vertexshader übernommen. Wie wir bereits vorhin gelernt haben, ist die Texturmatrix unerlässlich, um die Textur einfacher auf die Ebene zu projizieren. Im Hauptcode haben wir dann einen ganzen Haufen Variablendeklarationen. Hervor sticht offsetColor, für das zwei Texturzugriffe verwendet werden, um die Farbe aus der Bumpmap zu lesen. Zwei Zugriffe deshalb, um das Wasser ein wenig detaillierter zu machen. Danach wird offsetColor skaliert: Eine normale Bitmap hat nun einmal nicht die Möglichkeit, negative Werte zu speichern, aber es würde ohne diese sehr eintönig aussehen. Danach wird das Offset für Reflektion und Refraktion berechnet. Das läuft bei beiden ähnlich ab: Die Texturkoordinate wird mit der Matrix multipliziert und erst einmal in einer Variable gespeichert, dann wird der Wert aus der Map gelesen und das Alpha auf 1.0 gesetzt. <br />
<br />
[[Bild:Ld_watertutorial_step2.jpg|thumb|right|Der zweite Schritt: Die Shader-Wellen sind deutlich sichtbar (von littleDave)]]Weiter geht es mit dem Schalter zwischen erweitertem Blending und normalem Blending. Das erweiterte Blending versucht eine Wasseroberfläche von der Lichtdurchlässigkeit möglichst realistisch darzustellen, indem die Sichtbarkeit der Objekte unterhalb der Oberfläche, also dem Teil in der Refraktionsmap, der Helligkeit der Reflektion angepasst: Wenn man mit einer Lampe auf eine Wasseroberfläche leuchtet, kann man schlechter durch die von der Lampe getroffene Stelle blicken, weil die Reflektion der Lampe das, was darunter liegt, überstrahlt. Zu guter Letzt wird im Shader noch die berechnete Farbe an OpenGL zurückgegeben, sodass sie dann in den Framebuffer geschrieben werden kann. mappingRatio legt übrigens das Verhältnis zwischen Reflektion und Refraktion fest, falls erweitertes Blending deaktiviert ist (also extendedBlending = 0). Ein Wert von 1.0 würde bedeuten, dass nur die Refraktionsmap gerendert wird, ein Blending von 0.0 bedeutet, dass nur die Reflektionsmap durchkommt. Diese Multiplikationen da oben mache ich übrigens, um ein Problem mit der Genauigkeit des Floats im Shader zu umgehen. Rechts nochmal ein Bild zum aktuellen Status. Da erkennt man schon deutlich die Wellen und, wenn man genauer hinschaut, erkennt man auch eine unschöne Linie beim Übergang von der Wasserebene zum Objekt.<br />
<br />
= Feintuning =<br />
Wenn man jetzt einfach einen Würfel in die Wasserfläche setzt, der auf der Vorder- und der Rückseite verschiedene Farben hat, dann wird sichtbar, dass durch die Wellen unschöne Kanten entstehen, weil die Refraktions-/Reflektionsmap keine Objektgrenzen kennt. Doch dafür gibt es eine simple aber dennoch wirkungsvolle Methode, die ich jetzt beschreibe. Hierzu müssen wir den Code für das Rendern der Texturen leicht anpassen:<br />
<br />
<pascal> glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
glViewport(0, 0, WaterTexSize, WaterTexSize);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
DoRenderPass(True);<br />
<br />
if UseDepthShader then<br />
begin<br />
glColorMask(False, False, False, True);<br />
glClear(GL_DEPTH_BUFFER_BIT);<br />
<br />
glUseProgramObjectARB(DepthShader);<br />
glUniform1fARB(glGetUniformLocationARB(DepthShader, 'waterplaneZ'), 0.0);<br />
DoRenderPass(True);<br />
glUseProgramObjectARB(0);<br />
glColorMask(True, True, True, True);<br />
end;<br />
<br />
glBindTexture(GL_TEXTURE_2D, Reflect);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
if UseWaterShader then<br />
glClearColor(0.0, 0.0, 0.0, 0.0);<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
<br />
if UseDepthShader then<br />
begin<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
glColorMask(True, True, True, True);<br />
<br />
glUseProgramObjectARB(DepthShader);<br />
DoRenderPass(False);<br />
glUseProgramObjectARB(0);<br />
<br />
glColorMask(True, True, True, False);<br />
glClear(GL_DEPTH_BUFFER_BIT);<br />
end;<br />
<br />
DoRenderPass(False);<br />
<br />
glColorMask(True, True, True, True); <br />
<br />
glBindTexture(GL_TEXTURE_2D, Refract);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
// Hier bitte die eigenen Werte einsetzen<br />
glViewport(0, 0, Settings.ScreenWidth, Settings.ScreenHeight); <br />
<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glTranslatef(0.5, 0.5, 0);<br />
glScalef(0.5, 0.5, 0);<br />
<br />
gluPerspective(PERSPECTIVE_FOV, Settings.ScreenWidth/Settings.ScreenHeight, 0.01, 100000);<br />
DoCamTranslate;<br />
DoCamRotate;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
glBindTexture(GL_TEXTURE_2D, 0);</pascal><br />
Wieder ein riesiger Codeblock, aber das meiste kennen wir ja bereits. Das einzige, was dazugekommen ist, sind die if UseDepthShader-Blöcke. UseDepthShader ist mal wieder eine Variable, die Ihr ruhig deklarieren solltet, um die Verwendung des DepthShaders einzuschränken. DepthShader sollte wieder mal eine Variable für den Namen des Shaderprogramms sein. Ich missbrauche hier den Alpha-Kanal der Texturen für Tiefeninformationen. Dies ist insofern sinnvoll, dass man nicht extra eine Textur braucht und daher auch die Shader relativ einfach zu erweitern sind. Aber jetzt erst einmal zum Tiefenshader. Hier haben wir wieder einen recht unspektakulären Vertexshader:<br />
<br />
<glsl>varying vec4 vpos;<br />
varying vec4 ppos;<br />
<br />
void main(void)<br />
{<br />
vpos = gl_ModelViewMatrix * gl_Vertex;<br />
ppos = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_FrontColor = gl_Color;<br />
gl_ClipVertex = vpos;<br />
}</glsl><br />
Obwohl es eher wie ein normaler Shader aussieht, ist es doch interessanter als man glaubt. Jetzt kommt uns der Vorteil zugute, dass wir die Kamera in der Projektionsmatrix haben. Wir können einfach die Kameraoptionen außen vor lassen und nur die Operationen, die in der Modelview-Matrix stehen auf den Vertex anwenden. Das erleichtert das Errechnen des Z-Wertes des Vertex ungemein. Jetzt zum Fragmentshader:<br />
<br />
<glsl>uniform float waterplaneZ;<br />
<br />
varying vec4 vpos;<br />
varying vec4 ppos;<br />
<br />
void main(void)<br />
{<br />
float y, z;<br />
y = waterplaneZ - vpos.z;<br />
z = ppos.z;<br />
<br />
gl_FragColor = vec4(z, z, z, y);<br />
}</glsl><br />
Hier werden die im Vertexshader berechneten Werte verwendet, um die Höhe des Vertices in den Alphakanal zu schreiben. Angemerkt werden sollte, dass die RGB-Informationen dank des glColorMask in RenderWaterMap verworfen werden. WaterplaneZ ist übrigens der Z-Wert der Wasserebene, in unserem Beispiel also 0.0, wie aus dem obigen Code hervorgeht.<br />
<br />
Jetzt müssen wir nur noch den Wassershader so verändern, dass er die jetzt im Alphakanal vorhandenen Informationen auch sinnvoll nutzt. Der Vertexshader bleibt dabei, wie er ist, aber der Fragmentshader sieht jetzt so aus:<br />
<br />
<glsl>uniform sampler2D refractTex;<br />
uniform sampler2D reflectTex;<br />
uniform sampler2D bumpMap;<br />
<br />
uniform float refractRatio;<br />
<br />
uniform float reflectRatio;<br />
<br />
uniform float mappingRatio;<br />
<br />
uniform int extendedBlending;<br />
<br />
varying mat4 texMat;<br />
<br />
void main(void)<br />
{<br />
vec4 refractcoord;<br />
vec4 reflectcoord;<br />
vec4 offsetColor = (texture2D(bumpMap, vec2(gl_TexCoord[1])) + <br />
texture2D(bumpMap, vec2(gl_TexCoord[1]) * 4.0)) / 2.0;<br />
vec4 origOffset = offsetColor;<br />
vec4 color;<br />
vec4 reflectColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 refractColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 blendedColor;<br />
<br />
offsetColor -= 0.5;<br />
offsetColor *= 2.0;<br />
<br />
refractcoord = texMat * gl_TexCoord[0];<br />
refractColor = texture2DProj(refractTex, refractcoord);<br />
refractcoord = gl_TexCoord[0];<br />
refractcoord.x += offsetColor[0] * refractColor[3] * refractRatio;<br />
refractcoord.z += offsetColor[1] * refractColor[3] * refractRatio;<br />
refractcoord = texMat * refractcoord;<br />
<br />
reflectcoord = texMat * gl_TexCoord[0];<br />
reflectColor = texture2DProj(reflectTex, reflectcoord);<br />
reflectcoord = gl_TexCoord[0];<br />
reflectcoord.x -= offsetColor[0] * reflectColor[3] * reflectRatio;<br />
reflectcoord.z -= offsetColor[1] * reflectColor[3] * reflectRatio;<br />
reflectcoord = texMat * reflectcoord;<br />
<br />
reflectColor = texture2DProj(reflectTex, reflectcoord);<br />
refractColor = texture2DProj(refractTex, refractcoord);<br />
<br />
reflectColor[3] = 1.0;<br />
refractColor[3] = 1.0;<br />
<br />
if (extendedBlending == 0)<br />
{<br />
float mappingRefract, mappingReflect;<br />
mappingRefract = mappingRatio * 255.0;<br />
mappingReflect = 255.0 - mappingRefract;<br />
mappingRefract /= 255.0;<br />
mappingReflect /= 255.0;<br />
blendedColor = refractColor * mappingRefract + reflectColor * mappingReflect;<br />
blendedColor.a = 1.0;<br />
}<br />
else<br />
{<br />
float Alpha, reflectAlpha, refractAlpha;<br />
Alpha = (refractColor.r + refractColor.g + refractColor.b) / 3.0;<br />
if (Alpha > 1.0)<br />
Alpha = 1.0;<br />
if (Alpha < 0.0)<br />
Alpha = 0.0;<br />
<br />
refractAlpha = Alpha * 255.0;<br />
reflectAlpha = 255.0 - refractAlpha;<br />
refractAlpha /= 255.0;<br />
reflectAlpha /= 255.0;<br />
blendedColor = reflectColor * reflectAlpha + refractColor * refractAlpha;<br />
}<br />
<br />
gl_FragColor = blendedColor;<br />
}</glsl><br />
<br />
[[Bild:Ld_watertutorial_step3.jpg|thumb|right|Der finale Wassereffekt mit Wellen und "Kantenerkennung" (von littleDave)]]Der neue Code multipliziert das Offset nochmal mit dem Alpha-Wert, in unserem Falle also die Distanz zwischen der Wasserebene und dem Pixel. Dadurch werden die Wellen je näher der Pixel an der Wasseroberfläche ist, immer niedriger. So werden an der direkten Kante zu dem Objekt, also da, wo der Alpha-Wert 0.0 ist, gar keine Wellen mehr erzeugt. Damit ist die nervige Kante weg. So sieht das ganze zum Schluss aus.<br />
<br />
= In the end... =<br />
Das war es eigentlich. Wenn alles so läuft, wie es soll, dann habt ihr jetzt eine schöne Ebene mit Wassereffekt. Die Shader sowie der andere Code dürfen natürlich beliebig weiterverwendet werden. Verbesserungsvorschläge, Kritik, Feedback jeglicher Art und Morddrohungen wie immer einfach an mich im Forum wenden.<br />
<br />
glEnd;<br />
<br />
Gruss [[Benutzer:Lord Horazont|Lord Horazont]]<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial Alphamasking]]|-}}</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_Wassereffekt&diff=22425Tutorial Wassereffekt2009-01-03T19:13:45Z<p>Ireyon: /* Texturen füllen */</p>
<hr />
<div>= Einleitung =<br />
Erstmal willkommen zu meinem zweiten Tutorial. Ich möchte Euch hier einen Einblick in die „hohe Kunst“ der Wassereffekte geben. Hierbei wird erst einmal nur ein relativ stehendes Gewässer, also keines, welches in einer Rinne fließt beschrieben. Auch werde ich hier nicht auf die Technik für Wasser an Abhängen (abwärts fließende Flüsse) oder Wasserfälle eingehen. Hier geht es schlicht und einfach um eine Wasserebene, wie man sie zum Beispiel für ein Meer verwenden könnte.<br />
<br />
Voraussetzung für dieses Tutorial ist eigentlich nur ein Delphi/FP-Kompiler, einige grundlegende Kenntnisse in OpenGL und in GLSL. Falls letztere fehlen, kann ich nur die beiden Tutorials zum Thema GLSL hier im Wiki empfehlen: [[Tutorial_glsl|Tutorial GLSL]] und [[Tutorial_glsl2|Tutorial GLSL 2]].<br />
<br />
Noch etwas, dass man wissen sollte ist, dass die Kamera in die GL_PROJECTION_MATRIX geschrieben wird. Dies wird später für die Shader wichtig. Das ist kaum mehr Arbeit mit großer Wirkung, da sich dieses Tutorial an fortgeschrittene Benutzer richtet, werde ich hier allerdings nicht näher drauf eingehen. Durch diese Trennung haben wir dann den Vorteil, dass die eigene Rotation der Modelle von den Operationen der Kamera getrennt ist. Das wird wie erwähnt bei den Shadern wichtig.<br />
<br />
In dem Sinne: glBegin(GL_WATER_TUTORIAL) ;-)<br />
<br />
<br />
= Die Wasserebene =<br />
Das Erste, dass wir brauchen ist eine Ebene - am besten Quads - von mir aus auch zwei Triangles. Wichtig ist, dass es eine Ebene ist. Es darf keine Höhenunterschiede geben. Die Z-Achse sollte hierbei die Höhe darstellen, X und Y dienen als... als X und Y eben ;) Natürlich fehlt noch so ziemlich alles. Diese Ebene allein macht nicht einen Wassereffekt aus. Jetzt brauchen wir erst einmal ein paar Texturen. Diese sollten der "Power of Two"-Regel gemäß und quadratisch sein. Dabei muss man beachten, dass sie keinesfalls größer als der maximal beschreibbare Viewport sein sollten, weil die Texturen komplett gefüllt werden müssen. Der Grund dafür ist, dass man nicht über den Rand des Framebuffers (des Fensters) zeichnen kann, zuminest nicht ohne Erweiterungen, also muss die Textur kleiner oder genauso groß wie der Framebuffer sein. Diese Texturen sollten bereits zu Anfang des Programms erstellt und initialisiert werden. Genau genommen brauchen wir zwei Stück. Jeweils eine für die Reflektions- und Refraktionsmap. Wenn wir ohne Shader arbeiten, sollten sie nur als RGB initialisiert werden. Wenn wir später den Shader zuschalten, brauchen wir auch den Alphakanal. Die Größe sollte sich am originalen Viewport ausrichten. Die Texturen lassen sich übrigens am Besten mit glCopyTexImage2D initialisieren, da man dort keinen Puffer mit Pixeldaten braucht, den man übergeben muss.<br />
<br />
= Texturen füllen =<br />
Jetzt geht es darum, wie wir die Szene in die Texturen und dann auf die Wasserebene bekommen. Hier kann ich einen Trick empfehlen, den ich aus einer Demo von Delphi3d.net habe- aber dazu gleich noch mehr.<br />
<br />
Wichtig ist vor allem, das wir uns erst einmal Gedanken machen, was die Texturen eigentlich beinhalten sollen. Refract sollte - wie der Name bereits sagt - die Refraktionsinformationen, also alles, was unter dem Wasser ist, darstellen, Reflect, daher das, was über dem Wasser ist. Damit keine Darstellungsfehler entstehen, wenn man über dem Wasser hin- und her navigiert, muss man beim Erstellen der Texturen die Parameter '''GL_TEXTURE_WRAP_S''' und '''GL_TEXTURE_WRAP_T''' auf '''GL_CLAMP_TO_EDGE''' setzen.<br />
Wer schon etwas häufiger mit OpenGL gearbeitet hat, der kann sich vielleicht schon denken, wie man die Texturen am Besten füllen kann: natürlich mit ClipPlanes. Diese praktischen Dinger sorgen dafür, dass alles, was über bzw. unter ihnen liegt, abgeschnitten und nicht gezeichnet wird. Am besten für die Performance wäre es natürlich, wenn wir in der Anwendung noch einmal prüfen, ob das Objekt nicht vielleicht ganz unter/über Wasser ist und man es deshalb in diesem Renderpass weglassen könnte, aber das würde den Rahmen des Tutorials sprengen. Außerdem müssen wir dann noch entsprechend der aktuell zu füllenden Textur die Z-Achse invertieren je nachdem , ob wir den Teil über oder den Teil unter der Wasseroberfläche rendern wollen.<br />
<br />
<pascal>procedure DoRenderPass(InvertZ: Boolean);<br />
const<br />
// Deklaration der ClipPlane<br />
CP: array [0..3] of Double = (0.0, 0.0, 1.0, 0.0);<br />
begin<br />
glEnable(GL_CLIP_PLANE0);<br />
glDisable(GL_CULL_FACE);<br />
<br />
glLoadIdentity;<br />
glClipPlane(GL_CLIP_PLANE0, @CP);<br />
<br />
if InvertZ then<br />
glScalef(1, 1, -1);<br />
RenderGameScene;<br />
<br />
glDisable(GL_CLIP_PLANE0);<br />
glEnable(GL_CULL_FACE);<br />
end;</pascal><br />
Das sollte soweit klar sein. Jetzt zu dem eigentlichen Inhalt von RenderWaterMap.<br />
<br />
Dort muss zunächst einmal der komplette Viewport geleert werden. Danach sollte der Viewport auf die Größe von WaterTexSize verkleinert werden, damit das Gerenderte letztendlich auch auf die Texturen passt. Jetzt kommen wir langsam zu dem Trick, den ich weiter oben erwähnte: Erst einmal muss die Texturmatrix auf die Identitätsmatrix zurückgesetzt werden. Weiter geht es mit dem ersten Renderpass für die Reflektion. Das Ergebnis sollte in die Reflect-Textur gespeichert werden. Danach die Szene noch einmal rendern (nicht vergessen, vorher die Textur wieder zu unbinden), diesmal jedoch ohne Invertierung der Z-Achse. Das kommt dann logischerweise in die Refract-Textur, weil die ClipPlane hier alles unter der Wasseroberfläche da lässt und den Rest abschneidet. Zu guter Vorletzt den Viewport wieder auf den Zustand zurücksetzen, auf dem er vor dem Rendern war und nochmal den Framebuffer leeren.<br />
<br />
Jetzt wird es interessant: In der Texturmatrix müssen folgende Operationen durchgeführt werden: Verschieben um (0.5, 0.5, 0.0) und skalieren um (0.5, 0.5, 0.0). Dann eine Perspektive mit dem Seitenverhältnis, welches wir auch in unserer „echten“ Szene haben erzeugen und danach die Kameraoperationen durchführen.<br />
<br />
Und jetzt der Code dazu:<br />
<pascal><br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
<br />
glViewport(0, 0, WaterTexSize, WaterTexSize);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
DoRenderPass(True);<br />
<br />
glBindTexture(GL_TEXTURE_2D, Reflect);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
if UseWaterShader then<br />
glClearColor(0.0, 0.0, 0.0, 0.0);<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
<br />
DoRenderPass(False);<br />
<br />
glBindTexture(GL_TEXTURE_2D, Refract);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
// Hier bitte die eigenen Werte einsetzen<br />
glViewport(0, 0, Settings.ScreenWidth, Settings.ScreenHeight); <br />
<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glTranslatef(0.5, 0.5, 0);<br />
glScalef(0.5, 0.5, 0);<br />
<br />
gluPerspective(PERSPECTIVE_FOV, Settings.ScreenWidth/Settings.ScreenHeight, 0.01, 100000);<br />
DoCamTranslate;<br />
DoCamRotate;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
glBindTexture(GL_TEXTURE_2D, 0); <br />
</pascal><br />
<br />
= Wasser marsch =<br />
Jetzt zu der Wasserebene. Sicher kann man sich denken, dass aus dem Weiß noch mehr wird. Jetzt kommen wir zu der Sache mit der Texturmatrix. Die ist dank unserer Routine jetzt so aufgebaut, dass die übergebenen Texturkoordinaten wie in der normalen Projektionsmatrix verarbeitet werden. Das ermöglicht es als Texturkoordinaten genau die Werte zu übergeben, die auch glVertex3f bekommt. Das ist eine große Erleichterung, was vor allem nerviges hin- und hergerechne erspart. Da wir uns erst einmal mit dem non-shader-way befassen wollen, sieht unser Code für die Wasserebene jetzt so aus:<br />
<br />
<pascal>glEnable(GL_TEXTURE_2D);<br />
<br />
glColor4f(1.0, 1.0, 1.0, 1.0);<br />
glBindTexture(GL_TEXTURE_2D, Reflect);<br />
<br />
glDepthMask(GL_FALSE);<br />
glBegin(GL_QUADS);<br />
glTexCoord3f(-100.0, -100.0, 0.0);<br />
glVertex3f(-100.0, -100.0, 0.0);<br />
glTexCoord3f(-100.0, 100.0, 0.0);<br />
glVertex3f(-100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, 100.0, 0.0);<br />
glVertex3f(100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, -100.0, 0.0);<br />
glVertex3f(100.0, -100.0, 0.0);<br />
glEnd;<br />
<br />
glEnable(GL_BLEND);<br />
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);<br />
<br />
glDepthMask(GL_TRUE);<br />
glBindTexture(GL_TEXTURE_2D, Refract);<br />
glColor4f(1.0, 1.0, 1.0, RefractAlpha);<br />
glBegin(GL_QUADS);<br />
glTexCoord3f(-100.0, -100.0, 0.0);<br />
glVertex3f(-100.0, -100.0, 0.0);<br />
glTexCoord3f(-100.0, 100.0, 0.0);<br />
glVertex3f(-100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, 100.0, 0.0);<br />
glVertex3f(100.0, 100.0, 0.0);<br />
glTexCoord3f(100.0, -100.0, 0.0);<br />
glVertex3f(100.0, -100.0, 0.0);<br />
glEnd;<br />
<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
glDisable(GL_TEXTURE_2D);<br />
glDisable(GL_BLEND);</pascal><br />
Die Texturkoordinaten sollten ja jetzt klar sein. Zuerst wird die Reflektionstextur gerendert, danach folgt die Refraktionstextur. Dabei wird erstere mit vollem Alpha gerendert und die andere darüber geblendet. Bei den Shadern werden beide Texturen gebunden und an den Shadern übergeben, dazu jedoch gleich mehr. [[Bild:Ld_watertutorial_step1.jpg|thumb|right|Die einfache Wasserebene ohne Wellen (von littleDave)]] Rechts könnt ihr euch von eurem Ergebnis überzeugen.<br />
<br />
= Auf zu den Shadern =<br />
Für die Shader müssen wir eigentlich kaum Änderungen vornehmen, zumindest solange wir beim simplen Einbauen von Wellen bleiben. Gleich werde ich noch auf eine Technik eingehen, die nervige Kanten an Grenzen zu Objekten vermeidet. Die Änderungen betreffen zunächst allein den Code, der die Ebenen selber rendert. Außerdem brauchen wir noch eine weitere Textur, die die Bumpmap der Wasseroberfläche darstellt und für die Wellen verwendet wird. Jetzt werden im Rendercode für die Wasserebene noch einige Änderungen durchgeführt. Zunächst brauchen wir nur noch ein Quad, da der Shader das Überblenden übernimmt. Vor dem Rendern des Quads sollte der Shader gebunden werden, sowie die drei Texturen in drei Textureinheiten geschoben werden, in der Reihenfolge Reflect-Refract-WaterBumpmap. Dann muss noch ein Haufen Parameter an den Shader übergeben werden. Erstmal der Standardkram, die Zuweisung der Texturen. Der Uniform-Integer refractTex wird 1, reflectTex wird 0 und bumpMap wird 2. Dann gibt es noch einige Variablen, die die Darstellung und die Stärke des Wassereffektes beeinflussen: reflectRatio und refractRatio sind ein Faktor, mit dem die Bumpmap multipliziert wird und der die Stärke des Effektes beeinflusst. Ich empfehle für beides einen Wert um 1/20.0. Dann gibt es noch mappingRatio und extendedBlending, die erkläre ich später beim Shader selbst.<br />
<br />
Da wir jetzt zwei verschiedene Texturarten verwenden, einmal die projizierten Reflect- und Refracttexturen und die BumpMaptexturen, sollten wir auch noch eine zweite Texturkoordinate übergeben, und zwar für die Bumpmaps. Welchen Wert Ihr hier einsetzt, ist Eure Sache. <br />
<br />
Jetzt zu dem Shader, dem wir die Werte übergeben wollen. Erst einmal ein kurzes Brainstorming. Was brauchen wir? Zunächst müssen die Texturen entsprechend der Matrizen transformiert werden. Danach müssen sie wiederum entsprechend der Bumpmaps verschoben werden, so dass ein Welleneffekt entsteht. Dies alles passiert im Fragmentshader. Der Vertexshader dagegen sieht recht harmlos aus:<br />
<br />
<glsl>varying mat4 texMat;<br />
<br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
texMat = gl_TextureMatrix[0];<br />
gl_TexCoord[1] = gl_MultiTexCoord1;<br />
gl_FrontColor = gl_Color;<br />
}</glsl><br />
Was im Vertexshader passiert, sollte wohl ziemlich klar sein. In Zeile 5 wird erst einmal der Vertex anhand der normalen Matrizen projiziert, so dass es die gleiche Wirkung hat, als wenn man es durch die normale Renderpipe jagen würde. Zeilen Sechs und Acht sind dafür zuständig, dass die Texturkoordinaten auch im Fragmentshader verwendet werden können. Zeile Sieben speichert die Texturmatrix in einer Varying, so dass wir sie ebenfalls im Fragmentshader verwenden können. Die neunte Zeile speichert noch die Farbe - eher unwichtig und man könnte es auch weglassen, aber ich fand es schön ;). Genug der Scherze, weiter mit dem Fragmentshader:<br />
<br />
<glsl>uniform sampler2D refractTex;<br />
uniform sampler2D reflectTex;<br />
uniform sampler2D bumpMap;<br />
<br />
uniform float refractRatio;<br />
uniform float reflectRatio;<br />
<br />
uniform float mappingRatio;<br />
<br />
uniform int extendedBlending;<br />
<br />
varying mat4 texMat;<br />
<br />
void main(void)<br />
{<br />
vec4 refractcoord;<br />
vec4 reflectcoord;<br />
vec4 offsetColor = (texture2D(bumpMap, vec2(gl_TexCoord[1])) + <br />
texture2D(bumpMap, vec2(gl_TexCoord[1]) * 4.0)) / 2.0;<br />
vec4 origOffset = offsetColor;<br />
vec4 color;<br />
vec4 reflectColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 refractColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 blendedColor;<br />
<br />
offsetColor -= 0.5;<br />
offsetColor *= 2.0;<br />
<br />
<br />
refractcoord = gl_TexCoord[0];<br />
refractcoord.x += offsetColor[0] * refractRatio;<br />
refractcoord.z += offsetColor[1] * refractRatio;<br />
refractcoord = texMat * refractcoord;<br />
refractColor = texture2DProj(refractTex, refractcoord); <br />
<br />
reflectcoord = gl_TexCoord[0];<br />
reflectcoord.x += offsetColor[0] * reflectRatio;<br />
reflectcoord.z += offsetColor[1] * reflectRatio;<br />
reflectcoord = texMat * reflectcoord;<br />
reflectColor = texture2DProj(reflectTex, reflectcoord); <br />
<br />
reflectColor[3] = 1.0;<br />
refractColor[3] = 1.0;<br />
<br />
if (extendedBlending == 0)<br />
{<br />
float mappingRefract, mappingReflect;<br />
mappingRefract = mappingRatio * 255.0;<br />
mappingReflect = 255.0 - mappingRefract;<br />
mappingRefract /= 255.0;<br />
mappingReflect /= 255.0;<br />
blendedColor = refractColor * mappingRefract + reflectColor * mappingReflect;<br />
blendedColor.a = 1.0;<br />
}<br />
else<br />
{<br />
float Alpha, reflectAlpha, refractAlpha;<br />
Alpha = (refractColor.r + refractColor.g + refractColor.b) / 3.0;<br />
if (Alpha > 1.0)<br />
Alpha = 1.0;<br />
if (Alpha < 0.0)<br />
Alpha = 0.0;<br />
<br />
refractAlpha = Alpha * 255.0;<br />
reflectAlpha = 255.0 - refractAlpha;<br />
refractAlpha /= 255.0;<br />
reflectAlpha /= 255.0;<br />
blendedColor = reflectColor * reflectAlpha + refractColor * refractAlpha;<br />
}<br />
<br />
gl_FragColor = blendedColor;<br />
}</glsl><br />
Das ist recht viel Quelltext auf einmal. Aber wir schauen ihn uns nun stückchenweise an. Die ersten Zeilen deklarieren die verwendeten Uniforms. Dann wird noch das texMat-Varying aus dem Vertexshader übernommen. Wie wir bereits vorhin gelernt haben, ist die Texturmatrix unerlässlich, um die Textur einfacher auf die Ebene zu projizieren. Im Hauptcode haben wir dann einen ganzen Haufen Variablendeklarationen. Hervor sticht offsetColor, für das zwei Texturzugriffe verwendet werden, um die Farbe aus der Bumpmap zu lesen. Zwei Zugriffe deshalb, um das Wasser ein wenig detaillierter zu machen. Danach wird offsetColor skaliert: Eine normale Bitmap hat nun einmal nicht die Möglichkeit, negative Werte zu speichern, aber es würde ohne diese sehr eintönig aussehen. Danach wird das Offset für Reflektion und Refraktion berechnet. Das läuft bei beiden ähnlich ab: Die Texturkoordinate wird mit der Matrix multipliziert und erst einmal in einer Variable gespeichert, dann wird der Wert aus der Map gelesen und das Alpha auf 1.0 gesetzt. <br />
<br />
[[Bild:Ld_watertutorial_step2.jpg|thumb|right|Der zweite Schritt: Die Shader-Wellen sind deutlich sichtbar (von littleDave)]]Weiter geht es mit dem Schalter zwischen erweitertem Blending und normalem Blending. Das erweiterte Blending versucht eine Wasseroberfläche von der Lichtdurchlässigkeit möglichst realistisch darzustellen, indem die Sichtbarkeit der Objekte unterhalb der Oberfläche, also dem Teil in der Refraktionsmap, der Helligkeit der Reflektion angepasst: Wenn man mit einer Lampe auf eine Wasseroberfläche leuchtet, kann man schlechter durch die von der Lampe getroffene Stelle blicken, weil die Reflektion der Lampe das, was darunter liegt, überstrahlt. Zu guter Letzt wird im Shader noch die berechnete Farbe an OpenGL zurückgegeben, sodass sie dann in den Framebuffer geschrieben werden kann. mappingRatio legt übrigens das Verhältnis zwischen Reflektion und Refraktion fest, falls erweitertes Blending deaktiviert ist (also extendedBlending = 0). Ein Wert von 1.0 würde bedeuten, dass nur die Refraktionsmap gerendert wird, ein Blending von 0.0 bedeutet, dass nur die Reflektionsmap durchkommt. Diese Multiplikationen da oben mache ich übrigens, um ein Problem mit der Genauigkeit des Floats im Shader zu umgehen. Rechts nochmal ein Bild zum aktuellen Status. Da erkennt man schon deutlich die Wellen und, wenn man genauer hinschaut, erkennt man auch eine unschöne Linie beim Übergang von der Wasserebene zum Objekt.<br />
<br />
= Feintuning =<br />
Wenn man jetzt einfach einen Würfel in die Wasserfläche setzt, der auf der Vorder- und der Rückseite verschiedene Farben hat, dann wird sichtbar, dass durch die Wellen unschöne Kanten entstehen, weil die Refraktions-/Reflektionsmap keine Objektgrenzen kennt. Doch dafür gibt es eine simple aber dennoch wirkungsvolle Methode, die ich jetzt beschreibe. Hierzu müssen wir den Code für das Rendern der Texturen leicht anpassen:<br />
<br />
<pascal> glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
glViewport(0, 0, WaterTexSize, WaterTexSize);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
DoRenderPass(True);<br />
<br />
if UseDepthShader then<br />
begin<br />
glColorMask(False, False, False, True);<br />
glClear(GL_DEPTH_BUFFER_BIT);<br />
<br />
glUseProgramObjectARB(DepthShader);<br />
glUniform1fARB(glGetUniformLocationARB(DepthShader, 'waterplaneZ'), 0.0);<br />
DoRenderPass(True);<br />
glUseProgramObjectARB(0);<br />
glColorMask(True, True, True, True);<br />
end;<br />
<br />
glBindTexture(GL_TEXTURE_2D, Reflect);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
if UseWaterShader then<br />
glClearColor(0.0, 0.0, 0.0, 0.0);<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
<br />
if UseDepthShader then<br />
begin<br />
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);<br />
glColorMask(True, True, True, True);<br />
<br />
glUseProgramObjectARB(DepthShader);<br />
DoRenderPass(False);<br />
glUseProgramObjectARB(0);<br />
<br />
glColorMask(True, True, True, False);<br />
glClear(GL_DEPTH_BUFFER_BIT);<br />
end;<br />
<br />
DoRenderPass(False);<br />
<br />
glColorMask(True, True, True, True); <br />
<br />
glBindTexture(GL_TEXTURE_2D, Refract);<br />
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, WaterTexSize, WaterTexSize);<br />
glBindTexture(GL_TEXTURE_2D, 0);<br />
<br />
// Hier bitte die eigenen Werte einsetzen<br />
glViewport(0, 0, Settings.ScreenWidth, Settings.ScreenHeight); <br />
<br />
glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT);<br />
<br />
glMatrixMode(GL_TEXTURE);<br />
glLoadIdentity;<br />
glTranslatef(0.5, 0.5, 0);<br />
glScalef(0.5, 0.5, 0);<br />
<br />
gluPerspective(PERSPECTIVE_FOV, Settings.ScreenWidth/Settings.ScreenHeight, 0.01, 100000);<br />
DoCamTranslate;<br />
DoCamRotate;<br />
glMatrixMode(GL_MODELVIEW);<br />
<br />
glBindTexture(GL_TEXTURE_2D, 0);</pascal><br />
Wieder ein riesiger Codeblock, aber das meiste kennen wir ja bereits. Das einzige, was dazugekommen ist, sind die if UseDepthShader-Blöcke. UseDepthShader ist mal wieder eine Variable, die Ihr ruhig deklarieren solltet, um die Verwendung des DepthShaders einzuschränken. DepthShader sollte wieder mal eine Variable für den Namen des Shaderprogramms sein. Ich missbrauche hier den Alpha-Kanal der Texturen für Tiefeninformationen. Dies ist insofern sinnvoll, dass man nicht extra eine Textur braucht und daher auch die Shader relativ einfach zu erweitern sind. Aber jetzt erst einmal zum Tiefenshader. Hier haben wir wieder einen recht unspektakulären Vertexshader:<br />
<br />
<glsl>varying vec4 vpos;<br />
varying vec4 ppos;<br />
<br />
void main(void)<br />
{<br />
vpos = gl_ModelViewMatrix * gl_Vertex;<br />
ppos = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_FrontColor = gl_Color;<br />
gl_ClipVertex = vpos;<br />
}</glsl><br />
Obwohl es eher wie ein normaler Shader aussieht, ist es doch interessanter als man glaubt. Jetzt kommt uns der Vorteil zugute, dass wir die Kamera in der Projektionsmatrix haben. Wir können einfach die Kameraoptionen außen vor lassen und nur die Operationen, die in der Modelview-Matrix stehen auf den Vertex anwenden. Das erleichtert das Errechnen des Z-Wertes des Vertex ungemein. Jetzt zum Fragmentshader:<br />
<br />
<glsl>uniform float waterplaneZ;<br />
<br />
varying vec4 vpos;<br />
varying vec4 ppos;<br />
<br />
void main(void)<br />
{<br />
float y, z;<br />
y = waterplaneZ - vpos.z;<br />
z = ppos.z;<br />
<br />
gl_FragColor = vec4(z, z, z, y);<br />
}</glsl><br />
Hier werden die im Vertexshader berechneten Werte verwendet, um die Höhe des Vertices in den Alphakanal zu schreiben. Angemerkt werden sollte, dass die RGB-Informationen dank des glColorMask in RenderWaterMap verworfen werden. WaterplaneZ ist übrigens der Z-Wert der Wasserebene, in unserem Beispiel also 0.0, wie aus dem obigen Code hervorgeht.<br />
<br />
Jetzt müssen wir nur noch den Wassershader so verändern, dass er die jetzt im Alphakanal vorhandenen Informationen auch sinnvoll nutzt. Der Vertexshader bleibt dabei, wie er ist, aber der Fragmentshader sieht jetzt so aus:<br />
<br />
<glsl>uniform sampler2D refractTex;<br />
uniform sampler2D reflectTex;<br />
uniform sampler2D bumpMap;<br />
<br />
uniform float refractRatio;<br />
<br />
uniform float reflectRatio;<br />
<br />
uniform float mappingRatio;<br />
<br />
uniform int extendedBlending;<br />
<br />
varying mat4 texMat;<br />
<br />
void main(void)<br />
{<br />
vec4 refractcoord;<br />
vec4 reflectcoord;<br />
vec4 offsetColor = (texture2D(bumpMap, vec2(gl_TexCoord[1])) + <br />
texture2D(bumpMap, vec2(gl_TexCoord[1]) * 4.0)) / 2.0;<br />
vec4 origOffset = offsetColor;<br />
vec4 color;<br />
vec4 reflectColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 refractColor = vec4(1.0, 1.0, 1.0, 1.0);<br />
vec4 blendedColor;<br />
<br />
offsetColor -= 0.5;<br />
offsetColor *= 2.0;<br />
<br />
refractcoord = texMat * gl_TexCoord[0];<br />
refractColor = texture2DProj(refractTex, refractcoord);<br />
refractcoord = gl_TexCoord[0];<br />
refractcoord.x += offsetColor[0] * refractColor[3] * refractRatio;<br />
refractcoord.z += offsetColor[1] * refractColor[3] * refractRatio;<br />
refractcoord = texMat * refractcoord;<br />
<br />
reflectcoord = texMat * gl_TexCoord[0];<br />
reflectColor = texture2DProj(reflectTex, reflectcoord);<br />
reflectcoord = gl_TexCoord[0];<br />
reflectcoord.x -= offsetColor[0] * reflectColor[3] * reflectRatio;<br />
reflectcoord.z -= offsetColor[1] * reflectColor[3] * reflectRatio;<br />
reflectcoord = texMat * reflectcoord;<br />
<br />
reflectColor = texture2DProj(reflectTex, reflectcoord);<br />
refractColor = texture2DProj(refractTex, refractcoord);<br />
<br />
reflectColor[3] = 1.0;<br />
refractColor[3] = 1.0;<br />
<br />
if (extendedBlending == 0)<br />
{<br />
float mappingRefract, mappingReflect;<br />
mappingRefract = mappingRatio * 255.0;<br />
mappingReflect = 255.0 - mappingRefract;<br />
mappingRefract /= 255.0;<br />
mappingReflect /= 255.0;<br />
blendedColor = refractColor * mappingRefract + reflectColor * mappingReflect;<br />
blendedColor.a = 1.0;<br />
}<br />
else<br />
{<br />
float Alpha, reflectAlpha, refractAlpha;<br />
Alpha = (refractColor.r + refractColor.g + refractColor.b) / 3.0;<br />
if (Alpha > 1.0)<br />
Alpha = 1.0;<br />
if (Alpha < 0.0)<br />
Alpha = 0.0;<br />
<br />
refractAlpha = Alpha * 255.0;<br />
reflectAlpha = 255.0 - refractAlpha;<br />
refractAlpha /= 255.0;<br />
reflectAlpha /= 255.0;<br />
blendedColor = reflectColor * reflectAlpha + refractColor * refractAlpha;<br />
}<br />
<br />
gl_FragColor = blendedColor;<br />
}</glsl><br />
<br />
[[Bild:Ld_watertutorial_step3.jpg|thumb|right|Der finale Wassereffekt mit Wellen und "Kantenerkennung" (von littleDave)]]Der neue Code multipliziert das Offset nochmal mit dem Alpha-Wert, in unserem Falle also die Distanz zwischen der Wasserebene und dem Pixel. Dadurch werden die Wellen je näher der Pixel an der Wasseroberfläche ist, immer niedriger. So werden an der direkten Kante zu dem Objekt, also da, wo der Alpha-Wert 0.0 ist, gar keine Wellen mehr erzeugt. Damit ist die nervige Kante weg. So sieht das ganze zum Schluss aus.<br />
<br />
= In the end... =<br />
Das war es eigentlich. Wenn alles so läuft, wie es soll, dann habt ihr jetzt eine schöne Ebene mit Wassereffekt. Die Shader sowie der andere Code dürfen natürlich beliebig weiterverwendet werden. Verbesserungsvorschläge, Kritik, Feedback jeglicher Art und Morddrohungen wie immer einfach an mich im Forum wenden.<br />
<br />
glEnd;<br />
<br />
Gruss [[Benutzer:Lord Horazont|Lord Horazont]]<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial Alphamasking]]|-}}</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_glsl&diff=22424Tutorial glsl2009-01-03T18:23:30Z<p>Ireyon: /* Shader benutzen */</p>
<hr />
<div>=Präambel=<br />
Ave und willkommen bei meiner "Einführung" in die recht frische und mit OpenGL1.5 eingeführte Shadersprache "glSlang". In diesem umfangreichen Dokument werde ich versuchen, sowohl auf die Nutzung (sprich das Laden und Anhängen von Shadern im Quellcode), als auch auf die Programmierung von Shadern selbst einzugehen, inklusive aller Sprachelemente der OpenGL Shadersprache. Es wird also auch recht viele Informationen zu der C-ähnlichen Programmstruktur und den von glSlang angebotenen Variablen und Attributen gehen. Am Ende dieser Einführung sollten alle die, die sich für das Thema interessieren, in der Lage sein, zumindest einfach Shader zu schreiben und auch in ihren Programmen zu nutzen. Ausserdem soll dieses Dokument gleichzeitig als ein deutsches "Pendant" zu den von 3DLabs veröffentlichten Shaderspezifikationen, und damit als alltägliches Nachschlagewerk, dienen.<br />
<br />
<br />
==Vorkenntnisse==<br />
Wie auch schon mein ARB_VP-Tutorial richtet sich auch diese Einführung aufgrund ihrer Thematik eher an die fortgeschritteneren GL-Programmierer und neben sehr guten GL-Kenntnissen sollten sich alle, die sich daran versuchen wollen, mit den technischen Hintergründen der GL, wie z.B. dem Aufbau der Renderpipeline auskennen. Weiterhin sind C-Kenntnisse absolut erforderlich, da die Shader ja in einer an ANSI-C angelehnten Syntax geschrieben werden. Auch Begriffsdefinitionen zu Vertex oder Fragment werden zum Verständis dieser Einführung benötigt. Wer also noch am Anfang seiner GL-Karriere steht, dem wird dieses Dokument nicht viel nützen. Ganz nebenbei solltet ihr auch noch eine gehörige Portion Zeit (am besten nen kompletten Nachmittag) mitbringen, denn die folgende Kost ist nicht nur umfangreich, sondern auch manchmal recht schwer verdaulich.<br />
<br />
<br />
<br />
----<br />
<br />
<br />
<br />
=Was ist glSlang?=<br />
Wie Eingangs kurz angesprochen handelt es sich bei glSlang um eine Shadersprache, also um eine Hochsprache, in der man die programmierbaren Teile aktueller Grafikbeschleuniger nach eigenem Belieben programmieren kann. Sie stellt quasi den Nachfolger zu den in Assembler geschriebenen Vertex- und Fragmentprogrammen ([[GL_ARB_Vertex_Program]]/[[GL_ARB_Fragment_Program]]) dar und basiert auf ANSI C, erweitert um Vektor- und Matrixtypen sowie einige C++-Mechanismen.<br />
<br />
Die in glSlang geschriebenen Programme nennen sich, angepasst an die Terminologie von RenderMan und DirectX, [[Shader]] (im Gegensatz zu "Programme" bei ARB_VP/FP) und werden entweder auf Vertexe (VertexShader) oder Fragmente (FragmentShader) angewendet, andere noch nicht programmierbare Teile der GL-Pipeline wie z.B. die Rasterisierung können momentan noch nicht über Shader beeinflusst werden.<br />
<br />
<br />
==Voraussetzungen==<br />
<br />
glSlang ist ein recht neues Feature, dass mit OpenGL1.5 eingeführt wurde, weshalb eine entsprechend moderne Grafikkarte (DX9-Generation) inklusive aktuellster Treiber von Nöten ist. <br />
''Aktueller Stand (November 2005) ist wie folgt :''<br />
<br />
[http://www.ati.com ATI] haben bereits seit fast 2 Jahren (Catalyst 3.10) glSlang-fähige Treiber, allerdings kommt es besonders mit neueren Treibern hier und da immernoch zu Fehlern (oder es werden gar neue Fehler eingführt) und ATI zeigt momentan kein sehr starkes Interesse am fixen dieser Fehler.<br />
<br />
[http://www.nvidia.com NVidia] haben sich etwas mehr Zeit gelassen, allerdings ist deren glSlang-Implementation inzwischen recht ausgereift. Bugs gibts allerdings trotzdem hier und da, aber NVidias Entwicklersupport ist da recht offen für Fehlerberichte. Die aktuellen Treiber der 80er Reihe sind daher für glSlang-Nutzer bestens geeignet.<br />
<br />
[http://www.3dlabs.com 3DLabs], die glSlang quasi erfunden haben, haben natürlich hervorragenden glSlang Support in ihren Treiber, allerdings sind deren Wildcat-Karten kaum verbreitet.<br />
<br />
Natürlich benötigt ihr auch einen passenden OpenGL-Header der die für glSlang nötigen Extensions und Funktionen exportiert. Ich verweise dazu auf unseren internen OpenGL-Header [[DGLOpenGL.pas]] der da einwandfrei seine Dienste verrichtet und auch in der Beispielanwendung Verwendung findet.<br />
<br />
==Neue Extensions==<br />
Die GL-Shadersprache "besteht" in ihrer aktuellen Version aus folgenden Extensions, fürs Verständnis wäre es nicht schlecht, wenn ihr euch zumindest die Einleitungen dazu durchlest :<br />
* [[GL_ARB_Shader_Objects]] ([http://oss.sgi.com/projects/ogl-sample/registry/ARB/shader_objects.txt Orginal Spezifikation])<br />
: Definiert die API-Aufrufe die zum Erstellen, Kompilieren, Linken, Anhängen und Aktivieren von Shader- und Programmobjekten nötig sind. <br />
* [[GL_ARB_Vertex_Shader]] ([http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_shader.txt Orginal Spezifikation])<br />
: Fügt der OpenGL Programmierbarkeit auf Vertexebene hinzu. <br />
* [[GL_ARB_Fragment_Shader]] ([http://oss.sgi.com/projects/ogl-sample/registry/ARB/fragment_shader.txt Orginal Spezifikation])<br />
: Fügt der OpenGL Programmierbarkeit auf Fragmentebene hinzu. <br />
* [[GL_ARB_Shading_Language_100]] ([http://oss.sgi.com/projects/ogl-sample/registry/ARB/shading_language_100.txt Orginal Spezifikation])<br />
: Gibt die unterstützte Version von glSlang an, momentan 1.00.<br />
<br />
<br />
==Objekte==<br />
Im Zuge der Vereinheitlichung der GL wird immer häufiger in Objekte gekapselt, deren API dann auch aneinander angelehnt ist. Ziel ist, dabei die Programmierung der GL uniform zu machen, so dass z.B. zwischen dem Erstellen und Verwalten eines Vertex-Buffer-Objektes oder eines Shader-Objektes kaum ein Unterschied besteht (demnächst kommen dann auch Pixel-Buffer-Objekte dazu). Mit glSlang wurden dann im Zuge dieser Aktion zwei neue Objekte eingeführt, deren Definition ihr euch unbedingt einprägen solltet :<br />
<br />
* '''Programmobjekt'''<br />
:Ein Objekt, an das die Shader später angebunden werden. Bietet Funktionalität zum Linken der Shader und prüft dabei die Kompatibilität zwischen Vertex- und Fragmentshader.<br />
<br />
* '''Shaderobjekt'''<br />
:Dieses Objekt verwaltet den Quellcodestring eines Shaders und ist entweder vom Typ '''GL_VERTEX_SHADER_ARB''' oder '''GL_FRAGMENT_SHADER_ARB'''.<br />
<br />
<br />
==Resourcen==<br />
Die Shadersprache ist keinesfalls final und es wurden bereits diverse Ausdrücke für zukünftige Verwendung reserviert, denn ein Ziel bei ihrer Entwicklung war es, sie so zukunftsorientiert zu gestalten, dass auch Grafikkarten der nächsten und übernächsten Generation voll ausgenutzt werden können. Damit einher geht die Tatsache, dass sich die Spezifikationen in Zukunft ändern/erweitern werden, weshalb man da immer einen Blick hineinwerfen sollte. Die Anlaufstelle dafür ist natürlich die [http://www.3dlabs.com/support/developer/ogl2/index.htm GL2-Seite von 3D-Labs], wo u.a. auch ein OGL2-SDK und diverse Whitepapers als PDFs angeboten werden, in denen auch stattgefundene Änderungen an glSlang dokumentiert sind.<br />
<br />
=glSlang im Programm=<br />
Bevor wir uns mit der Syntax von glSlang beschäftigen, zeige ich euch erstmal, wie ihr Shader in euer Programm einbindet und nutzt. Warum das zuerst? Ganz einfach deshalb, weil ihr dann das, was ihr im glSlang-Syntaxteil lernt, direkt in eurer Testanwendung verwenden könnt. Hoffe diese Entscheidung klingt logisch und findet Anklang.<br />
<br />
Zuerst benötigen wir natürlich unsere Objekte. Zum einen ein ''Programmobjekt'', an das unsere Shader gebunden werden, und zwei ''Shaderobjekte'', die den Quellcode unseres Vertex bzw. Fragment Shaders aufnehmen. Dazu wurde eigens der neue "Datentyp" {{INLINE_CODE|glHandleARB}} eingeführt, der ein Objekthandle repräsentiert. Wir deklarieren also wie folgt :<br />
<br />
ProgramObject : GLhandleARB;<br />
VertexShaderObject : GLhandleARB;<br />
FragmentShaderObject : GLhandleARB;<br />
<br />
<br />
Nach dieser Deklaration können wir dann damit beginnen unsere Objekte zu erstellen. Den Anfang macht das Programmobjekt :<br />
<br />
ProgramObject := glCreateProgramObjectARB;<br />
<br />
Die Funktion [[glCreateProgramObjectARB]] erstellt uns oben ein leeres Programmobjekt und gibt ein gültiges Handle darauf zurück.<br />
<br />
Weiter gehts mit der Erstellung unseres Vertex bzw. Fragment Shaders :<br />
<br />
VertexShaderObject := glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);<br />
FragmentShaderObject := glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);<br />
<br />
[[glCreateShaderObjectARB]] dient zur Generierung eines leeren Shaderobjektes. Momentan unterstützt diese Funktion VertexShader und FragmentShader.<br />
<br />
Nachdem wir nun also zwei gültige Shaderobjekte haben, wollen wir diese auch mit entsprechendem Quellcode versorgen :<br />
<br />
glShaderSourceARB(VertexShaderObject, 1, @ShaderText, @ShaderLength);<br />
glShaderSourceARB(FragmentShaderObject, 1, @ShaderText, @ShaderLength);<br />
<br />
Via [[glShaderSourceARB]] setzen wir den Quellcode eines Shaderobjektes ''komplett'' neu. Zum Laden des Quellcodes bietet sich unter Delphi übrigens eine TStringList geradezu an. Es sollte beachtet werden, dass der Quellcode zu diesem Zeitpunkt ''nicht geparst'' wird, also keine Fehleruntersuchung stattfindet.<br />
<br />
Der Quellcode wurde jetzt also an unsere Shaderobjekte gebunden und sollte dann natürlich auch noch kompiliert werden :<br />
<br />
glCompileShaderARB(VertexShaderObject);<br />
glCompileShaderARB(FragmentShaderObject);<br />
<br />
Der glSlang-Compiler des Treibers wird bei einem Aufruf von [[glCompileShaderARB]] versuchen, unsere Shader zu kompilieren. Sofern diese keine Fehler aufweisen, sollte dies auch erfolgreich sein. Wenn nicht, dann spuckt uns der ShaderKompiler je nach Treiber recht detaillierte Infos aus. Wie man an diese Infos kommt könnt ihr gleich nachlesen.<br />
<br />
Wenn unsere Shader dann kompiliert werden konnten, ist es Zeit, diese an unser anfangs erstelltes Programmobjekt anzuhängen :<br />
<br />
glAttachObjectARB(ProgramObject, VertexShaderObject);<br />
glAttachObjectARB(ProgramObject, FragmentShaderObject);<br />
<br />
<br />
Nachdem die Shaderobjekte nun an das Programmobjekt angehangen wurden, werden diese nicht mehr benötigt und ihre Resourcen können freigegeben werden :<br />
<br />
glDeleteObjectARB(VertexShaderObject);<br />
glDeleteObjectARB(FragmentShaderObject);<br />
<br />
<br />
Am Schluß müssen wir dann noch unsere ans Programmobjekt gebundenen Shader linken :<br />
<br />
glLinkProgramARB(ProgramObject);<br />
<br />
Während [[glCompileShaderARB]] unsere Shader auf syntaktische Fehler innerhalb ihres lokalen Raums geprüft hat, werden beim Linken durch [[glLinkProgramARB]] die angehangenen Shader zu einem ausführbaren Shader gelinkt. Folgende Bedingungen führen zu einem '''Linkerfehler''':<br />
<br />
* Die Zahl der von der Implementation unterstützten Attributvariablen wurde überschritten<br />
* Der Speicherplatz für Uniformvariablen wurde überschritten<br />
* Die Zahl der von der Implementation angebotenen Sampler wurde überschritten<br />
* Die main-Funktion fehlt<br />
* Die Liste der Varying-Variablen des Vertexshaders stimmt nicht mit der des Fragmentshaders überein<br />
* Funktions- oder Variablenname nicht gefunden<br />
* Eine gemeinsame Globale ist mit unterschiedlichen Werten oder Typen initialisiert worden<br />
* Zwei Sampler unterschiedlichen Typs zeigen auf die selbe Textureneinheit<br />
* Ein oder mehrere angehangene(r) Shader wurden nicht erfolgreich kompiliert<br />
<br />
Die Nutzung von glSlang im eigenen Programm ist wie oben erkennbar also nicht wirklich schwer und innerhalb kurzer Zeit realisiert. Natürlich ist es auch möglich z.B. nur einen VertexShader oder nur einen FragmentShader an ein Programmobjekt zu binden.<br />
<br />
<br />
==Fehlererkennung==<br />
Natürlich wird es ohne Fehlerausgabe recht schwer, etwaige Probleme in einem Vertex- oder Fragmentshader zu finden. Doch auch in diesem Bereich wurde glSlang recht gut durchdacht und es wurden zwei Funktionen eingeführt, welche im Zusammenspiel die Fehlersuche recht einfach machen, nämlich [[glGetInfoLogARB]] und [[glGetObjectParameterivARB]] mit dem Argument {{INLINE_CODE|GL_OBJECT_INFO_LOG_LENGTH_ARB}}. Erstere Funktion liefert uns einen Logstring, während uns letztere Funktion dessen Länge angibt. Der Logstring wird verändert, sobald ein Shader kompiliert oder ein Programm gelinkt wird.<br />
<br />
Um die Ausgabe dieses Logs so einfach wie möglich zu machen, bietet es sich an beide in einer einfach Funktion unterzubringen :<br />
<br />
<pascal>function glSlang_GetInfoLog(glObject : GLHandleARB) : String;<br />
var<br />
blen,slen : GLInt;<br />
InfoLog : PGLCharARB;<br />
begin<br />
glGetObjectParameterivARB(glObject, GL_OBJECT_INFO_LOG_LENGTH_ARB , @blen);<br />
if blen > 1 then<br />
begin<br />
GetMem(InfoLog, blen*SizeOf(GLCharARB));<br />
glGetInfoLogARB(glObject, blen, slen, InfoLog);<br />
Result := PChar(InfoLog);<br />
Dispose(InfoLog);<br />
end;<br />
end;</pascal><br />
<br />
<br />
Die Funktion ist recht leicht erklärt : Zuerst lassen wir uns über {{INLINE_CODE|glGetObjectParameterivARB}} mitteilen wie lang der aktuelle Infolog ist. Sollte dort tatsächlich etwas drinstehen (blen > 1), dann lassen wir uns dessen Inhalt via {{INLINE_CODE|glGetInfoLogARB}} in {{INLINE_CODE|InfoLog}} ausgeben und liefern diesen als Ergebnis zurück.<br />
<br />
Wie bereits gesagt wird nur nach dem Kompilieren eines Shaders bzw. dem Linken eines Programmobjektes ein Infolog erstellt. Es bietet sich dadurch an, direkt danach einen solchen Aufruf zu machen :<br />
<br />
glCompileShaderARB(VertexShaderObject);<br />
ShowMessage(glSlang_GetInfoLog(VertexShaderObject));<br />
<br />
Wenn unser Vertex Shader komplett fehlerfrei kompiliert werden konnte, dann sehen wir als Ergebnis nur einen leeren Dialog. Ist dies nicht der Fall, so werden wir vom Treiber mit recht detaillierten Fehlerinformationen "belohnt", z.B. so :<br />
<br />
[[Bild:GLSL_error_vshader.jpg|center]]<br />
<br />
Auch das Infolog nach dem Linken des Programmobjektes dürfte, selbst wenn keine Fehler vorkommen, recht interessant sein, das sieht dann nämlich so aus :<br />
<br />
[[Bild:GLSL info programobject.jpg|center]]<br />
<br />
Wie zu sehen, wird uns nach dem erfolgreichen Linken auch gesagt, ob und welcher Shader in Hardware bzw. Software läuft. Für Debuggingzwecke sicherlich eine mehr als brauchbare Information.<br />
<br />
<br />
==Shader benutzen==<br />
Um den Shader auch für die nächsten Polygone zu benutzen oder Uniformparameter übergeben zu können, ruft man die Funktion<br />
glUseProgramObjectARB(ProgramObject);<br />
um alle Shader zu deaktivieren, ruft man dieselbe Funktion mit dem Parameter 0.<br />
<br />
==Parameterübergabe==<br />
Uniformparameter (mehr dazu später) stellen die Schnittstelle zwischen eurem Programm und dem Shader dar, werden also genutzt um Daten aus dem Programm heraus an einen Shader zu übergeben. Zur Übergabe dieser Parameter bietet OpenGL diverse Funktionen, die alle Abkömmlinge von [[glUniformARB]] sind. Während mit {{INLINE_CODE|glUniform4fARB}} z.B. ein Vier-Komponentenvektor an das Programmobjekt übergeben wird, kann man mittels {{INLINE_CODE|glUniformMatrix4fvARB}} ganze Matrizen schnell und einfach übergeben. Ausserdem gibt es nun die Möglichkeit Uniformparameter direkt über ihren Namen, statt wie unter ARB_FP/VP über einen festen Index zu adressieren. Die Funktion [[glGetUniformLocationARB]] gibt anhand des übergebenen Parameternamens dessen Position zurück. Man kann also ganz einfach über den Namen drauf zugreifen :<br />
<br />
glUniform3fARB(glGetUniformLocationARB(ProgramObject, PGLCharARB('LightPosition')), LPos[0], LPos[1], LPos[2]);<br />
glUniform1iARB(glGetUniformLocationARB(ProgramObject, PGLCharARB('texSamplerTMU3')), 3);<br />
<br />
<br />
Wichtig ist hier, das man je nach Parametertyp auch die passende Anzahl von Argumenten übergibt. Also für einen 4-Komponenten Floatvektor {{INLINE_CODE|glUniform4fARB}} und für einen einfachen Integerwert (z.B. Textureinheit für einen Sampler) glUnifrom1iARB. Auch nicht vergessen dürft ihr, das die Namen der Parameter genauso wie im Shader geschrieben werden müssen, also Groß- und Kleinschreibung beachtet werden muß.<br />
<br />
=Die Shadersprache=<br />
<br />
Nachdem wir uns mit der Einbindung der glSlang-Shader in unser Programm beschäftigt haben, wollen wir uns in den folgenden Kapiteln um die Sprachelemente von glSlang kümmern. Wie schon gesagt basiert glSlang auf ANSI-C, wurde allerdings um speziell auf den Zielbereich angepasste Vektor- und Matrixtypen und einige C++-Features wie das freie deklarieren von Variablen an jeder Stelle und das Funktionsüberladen auf Basis des Argumenttyps erweitert. Wer sich ein wenig mit C/C++ auskennt sollte also in der nun folgenden Materie keine Probleme bekommen.<br />
<br />
'''Obligatorische Hinweise für verwöhnte Delphi-Nutzer : '''<br />
*Wie von C/C++ her gewohnt, spielt auch in glSlang die Groß- und Kleinschreibung eine wichtige Rolle, also bitte achtet darauf. gl_Position ist eine komplett andere Variable als z.B. gl_position.<br />
*Es findet keine automatische Typenkonvertierung statt. Das bedeutet also das float MyFloat = 1 ungültig ist und es in dem Falle float MyFloat = 1.0 heissen muss. Typecasts müssen also immer manuell stattfinden, z.B. MyFloat = float(MyInt).<br />
<br />
'''Kleine Programmstrukturkunde für C-Unkundige :'''<br><br />
Da sicherlich einige Delpher nie richtig was mit C gemacht haben, zeige ich mal anhand eines kleinen Beispieles (das auf keinen Fall nen brauchbaren Shader darstellt) den grundlegenden Aufbau eines glSlang-Shaders, der natürlich dem Aufbau eines C-Programmes stark ähnelt :<br />
<glsl><br />
uniform vec4 VariableA;<br />
float VariableB;<br />
vec3 VariableC;<br />
const float KonstanteA = 256.0;<br />
<br />
float MyFunction(vec4 ArgumentA)<br />
{<br />
float FunktionsVariableA = float(5.0);<br />
<br />
return float(ArgumentA * (FunktionsVariableA + KonstanteA));<br />
}<br />
<br />
// Ich bin ein Kommentar<br />
/* Und ich auch */<br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
}<br />
</glsl><br />
<br />
Sieht doch recht bekannt aus, unser Programmaufbau. Delphi und C haben ja so einige Grundlagen gleich, darunter auch der ungefähre Programmaufbau. Ausserhalb jeglicher Funktionen legen wir am Programmanfang unsere Variablen, Konstanten und Attribute fest, die dann ''global'' nutzbar sind, also in jeder Funktion.<br />
<br />
Darunter deklarieren wir dann eine kleine Funktion. Wie auch bei den Variablendeklarationen wird hier der Rückgabetyp nicht wie bei Pascal nach dem Funktionsnamen untergebracht, sondern davor. Innerhalb der Funktion können dann wieder Variablen deklariert werden, die dann allerdings ''lokal'', also nur in dieser Funktion nutzbar sind. Vorteil dieser Deklaration ist die Tatsache, dass je nach Grafikkarte nur bestimmt viele globale Variablen deklariert werden können. Wenn möglich sollte man also mit lokalen Vorlieb nehmen. Unsere Funktion gibt dann natürlich noch via return einen Wert zurück, ''was gemacht werden muss'', sofern man diese nicht als void deklariert hat (entspräche dann einer Prozedur in Pascal). Wird dies nicht getan, so spuckt der Compiler einen Fehler aus.<br />
<br />
Auch wichtig sind natürlich Kommentare. Erste Variante (Doppelslash) ist auch in der Pascalwelt verfügbar und kommentiert eine einzelne Zeile aus. Die Variante darunter kann man für Kommentarblöcke nutzen (/* .. */) und entspricht den Kommentaren in geschweiften Klammern in Delphi.<br />
<br />
Danach kommt dann die '''wichtigste Funktion''' des Shaders, nämlich '''main''', die in keinem Shader fehlen darf. Sie stellt quasi den Programmkörper dar und ist oft auch die einzige Funktion in einem Shader. Sie erhält weder ein Argument, noch gibt sie einen Wert zurück.<br />
<br />
Soviel also zum grundlegenden Aufbau eines Shader. Hoffe das jetzt alle die in C nicht so bewandert sind damit klar kommen, und dann bald ihre ersten glSlang-Shader schreiben können.<br />
<br />
<br />
==Datentypen==<br />
<br />
Obwohl einige Datentypen aus C übernommen wurden, sieht man der Typenliste an, das diese speziell auf den 3D-Bereich zugeschnitten wurde. Variablen müssen vor ihrer Nutzung eindeutig deklariert sein, Typecasting erfolgt über Konstruktoren (dazu später mehr). Folgende Datentypen stehen sowohl im Vertex- als auch Fragmentshader zur Verfügung :<br />
<br />
<div align="center"><br />
{|{{Prettytable_B1}}<br />
!Datentyp <br />
!Erklärung<br />
|-<br />
|void <br />
|Für Funktionen die keinen Wert zurückgeben<br />
|-<br />
|bool <br />
|Konditionaler Typ, entweder true (wahr) oder false (falsch)<br />
|-<br />
|int <br />
|Vorzeichenbehafteter Integerwert<br />
|-<br />
|float <br />
|Fließkommaskalar mit Singlegenauigkeit (32 Bit)<br />
|-<br />
|vec2 <br />
|2-Komponenten Fließkommavektor<br />
|-<br />
|vec3 <br />
|3-Komponenten Fließkommavektor<br />
|-<br />
|vec4 <br />
|4-Komponenten Fließkommavektor<br />
|-<br />
|bvec2 <br />
|2-Komponenten Booleanvektor<br />
|-<br />
|bvec3 <br />
|3-Komponenten Booleanvektor<br />
|-<br />
|bvec4 <br />
|4-Komponenten Booleanvektor<br />
|-<br />
|ivec2 <br />
|2-Komponenten Integervektor<br />
|-<br />
|ivec3 <br />
|3-Komponenten Integervektor<br />
|-<br />
|ivec4 <br />
|4-Komponenten Integervektor<br />
|-<br />
|mat2 <br />
|2x2 Fließkommamatrix<br />
|-<br />
|mat3 <br />
|3x3 Fließkommamatrix<br />
|-<br />
|mat4 <br />
|4x4 Fließkommamatrix<br />
|-<br />
|sampler1D <br />
|Zugriff auf 1D-Textur<br />
|-<br />
|sampler2D <br />
|Zugriff auf 2D-Textur<br />
|-<br />
|sampler3D <br />
|Zugriff auf 3D-Textur<br />
|-<br />
|samplerCube <br />
|Zugriff auf Cubemap<br />
|-<br />
|sampler1DShadow <br />
|Zugriff auf 1D-Tiefentextur mit Vergleichsoperation<br />
|-<br />
|sampler2DShadow <br />
|Zugriff auf 2D-Tiefentextur mit Vergleichsoperation<br />
|-<br />
|}<br />
</div><br />
Die sampler-Typen stellen eine besondere Klasse dar und werden im Kapitel 6.7 genauer erklärt, inklusive einiger Anwendungsbeispiele.<br />
<br />
<br />
===Arrays===<br />
<br />
Natürlich unterstützt glSlang auch Arrays, die wie in C deklariert werden und deren Index bei 0 beginnt. Folgendes Array im Shader :<br />
<glsl><br />
float temp[3];<br />
</glsl><br />
beginnt also bei Index 0 und endet bei Index 2. Im Gegensatz zu C lassen sich Arrays in glSlang allerdings ''nicht bei der Initialisierung vorbelegen''. Wenn ein Array als Parameter einer Funktion deklariert wird, so darf dieses keine Dimensionierung erhalten.<br />
<br />
<br />
===Strukturen===<br />
<br />
Neu ggü. ARB_FP/VP ist nun auch die Möglichkeit, Strukturen in einem Shader zu deklarieren. Vor allem die Übersicht komplexerer Shader kann dadurch stark verbessert werden. Strukturen werden wie gewohnt mit dem Schlüsselwort {{INLINE_CODE|struct}} eingeleitet und können dann zur Typisierung von Variablen genutzt werden. Folgendes Beispiel dürfte die Nutzung verdeutlichen :<br />
<glsl><br />
struct light<br />
{<br />
bool active;<br />
float intensity;<br />
vec3 position;<br />
vec3 color;<br />
};<br />
</glsl><br />
Im Shader können dann neue Variablen von diesem Typ ganz einfach deklariert werden :<br />
<glsl><br />
light LightSource[3];<br />
</glsl><br />
Der Zugriff auf die Elemente der Struktur erfolgt dann wie gewohnt über den Punkt :<br />
<glsl><br />
LightSource[3].position = vec3(1.0, 1.0, 5.0);<br />
</glsl><br />
<br />
<br />
==Typenqualifzierer==<br />
<br />
Zusätzlich zur Typendeklaration kann eine Variable noch einen Typenqualifizerer vorangestellt bekommen, der an den Anfang der Deklaration gehört.<br />
<br />
* '''const'''<br />
: Festgelegte (nur lesen) Konstante bzw. nur lesbarer Funktionsparameter.<br />
<br />
* '''uniform'''<br />
: Ein den ganzen Shader über gleichbleibender Wert, der eine Schnittstelle zwischen dem Shader und der OpenGL-Anwendung darstellt. Ein Uniformwert wird in der Hauptanwendung an den entsprechenden Shader übergeben und kann dort dann genutzt werden.<br />
<br />
* '''attribute'''<br />
: Nur lesbare Werte die eine Verbindung zwischen dem Shader und der OpenGL-VertexAPI darstellen (z.B. VertexParameter eines VertexArrays). Natürlich nur in einem Vertex Shader nutzbar.<br />
<br />
* '''varying'''<br />
: Stellt die Verbindung zwischen einem Vertex- und einem FragmentShader dar. Werden im VertexShader geschrieben und dann perspektivisch korrekt über die Primitive interpoliert, um dann im Fragment Shader gelesen werden zu können. Nutzbar sind hier nur die Typen float, vec2, vec3, vec4, mat2, mat3 und mat4, Strukturen und andere Datentypen können nicht varying sein. Die Namen einer varying-Variable müssen sowohl im VertexShader als auch im FragmentShader gleich sein.<br />
<br />
* '''in'''<br />
: Für Variablen die an eine Funktion übergeben und dort ausgelesen werden.<br />
<br />
* '''out'''<br />
: Für Variablen die von einer Funktion nach aussen zurückgegeben werden.<br />
<br />
* '''inout'''<br />
: Für Variablen die sowohl an eine Funktion übergeben als auch von dieser zurückgegeben werden.<br />
<br />
<br />
<br />
Um obige Auflistung nicht leer im Raum stehen zu lassen zeige ich ein paar Beispiele die hoffentlich zum Verständnis beitragen :<br />
<br />
===Beispiel A=== <br />
Vertexnormale soll an einen FragmenShader (interpoliert) übergeben werden :<br />
<br />
:Im VertexShader :<br />
<glsl><br />
varying vec3 VertexNormal;<br />
...<br />
VertexNormal = normalize(MV_IT * gl_Normal);<br />
</glsl><br />
:Im FragmentShader :<br />
<glsl><br />
varying vec3 VertexNormal;<br />
...<br />
TempVector = VertexNormal*...<br />
</glsl><br />
<br />
===Beispiel B=== <br />
Uniformparameter zur nachträglichen Farbänderung der Szene wird im Programm übergeben :<br />
<br />
:Im VertexShader :<br />
<glsl><br />
uniform vec4 GlobalColor;<br />
...<br />
gl_FrontColor = GlobalColor * gl_Color;<br />
</glsl><br />
:Im Programm :<br />
<br />
glUniform4fARB(glSlang_GetUniLoc(ProgramObject, 'GlobalColor'), Col[0], Col[1], Col[2], Col[3]);<br />
<br />
<br />
===Beispiel C=== <br />
Konstante zur festen Farbänderung :<br />
<br />
:Im VertexShader :<br />
<glsl><br />
const vec4 ColorBias = vec4(0.2, 0.3, 0.0, 0.0);<br />
...<br />
gl_FrontColor = ColorBias * gl_Color;<br />
</glsl><br />
==Konstruktoren==<br />
<br />
Um in einem Shader ''Vektoren'' oder ''Matrizen'' mit Werten zu belegen, gibt es sogenannte Konstruktoren (nicht zu verwechseln mit z.B. Klassenkonstruktoren unter Delphi), die im Endeffekt nichts anderes als Funktionen zur Vorbelegung von Vektoren oder Matrizen darstellen. Dabei trägt der Konstruktor den selben Namen wie die Typendeklaration, also lässt sich eine Variable vom Typ {{INLINE_CODE|vec4}} mit dem Konstruktor {{INLINE_CODE|vec4(float, float, float, float)}} initialisieren.<br />
<br />
Allerdings hat man sich recht viel Mühe bei dieser Konstruktorgeschichte gemacht, so dass man einen vec4 nicht unbedingt mit einem {{INLINE_CODE|vec4}}-Konstruktor vorbelegen muss, sondern es vielseitige Möglichkeiten gibt. Um dies zu verdeutlichen gibts ein paar Beispiele :<br />
<glsl><br />
vec4 Color = vec4(1.0, 0.0, 0.0, 0.0);<br />
vec4 Color = vec4(MyVec3, 1.0);<br />
vec4 Color = vec4(MyVec2_A, MyVec2_B);<br />
<br />
vec3 LVec = vec3(MyVec4);<br />
vec2 Tmp = vec2(MyVec3);<br />
</glsl><br />
<br />
Trotz der recht wenigen Beispiele sollte schnell erkennbar sein, das man hier wirklich sehr viele Kombinationsmöglichkeiten hat, die dann gültig sind ''wenn man mindestens auf die benötigte Anzahl der Argumente kommt''. Im vorletzten Beispiel wird z.B. ein 3-Komponentenvektor aus einem 4-Komponentenvektor initialisiert. Das erzeugt keinen Fehler, sondern führt dazu das {{INLINE_CODE|vec3.x, vec3.y, vec3.z}} aus MyVec4 übernommen werden und MyVec4.w einfach ignoriert wird.<br />
<br />
Das Umkehrbeispiel, also<br />
<glsl><br />
vec4 Color = vec4(MyVec3)<br />
</glsl><br />
funktioniert allerdings nicht, da hier die Zahl der benötigten Argumente nicht erreicht wird. In diesem Falle müsste es dann<br />
<glsl> <br />
vec4 Color = vec4(MyVec3, 0.0)<br />
</glsl><br />
heissen.<br />
<br />
Obiges gilt natürlich auch für ''Matrixkonstruktoren'', hier sind z.B. folgende Konstuktoren denkbar, obwohl eigentlich alle Möglichkeiten nutzbar sind, ''solange die benötigte Zahl an Argumenten erreicht wird'' :<br />
<glsl><br />
mat4 MyMatrix = mat4(MyVec4, MyVec4, MyVec4, MyVec4);<br />
mat2 MyMatrix = mat4(1.0, 0.0, 0.0, 0.0,<br />
0.0, 1.0, 0.0, 0.0,<br />
0.0, 0.0, 1.0, 0.0,<br />
0.0, 0.0, 0.0, 1.0);<br />
</glsl><br />
<br />
==Vektor- und Matrixkomponenten==<br />
<br />
Was natürlich in keiner Shadersprache fehlen darf, ist der leichte Zugriff auf die einzelnen Komponenten eines Vektors. glSlang bietet, je nach Anwendungsgebiet gleich drei Namensets für den Zugriff auf die Komponenten eines solchen Vektors, welches Set man nutzen will bleibt natürlich frei und ist unabhängig von der Deklaration eines Vektors. Man sollte nur darauf achten, beim gleichzeitigen Zugriff auf mehrere Komponenten im gleichen Namenset zu verbleiben :<br />
<br />
* {x, y, z, w}<br />
:Für den Zugriff auf Vektoren die Punkte, Normale oder sonstige Vertexdaten repräsentieren.<br />
<br />
* {r, g, b, a}<br />
:Für den Zugriff auf Vektoren die Farbwerte repräsentieren.<br />
<br />
* {s, t, p, q}<br />
:Für den Zugriff auf Vektoren die Texturkoordinaten repräsentieren.<br />
<br />
Ein paar Beispiele zur Unterstreichung des oben gesagten :<br />
<glsl><br />
v4.rgba = vec4(1.0, 0.0, 0.0, 0.0); // gültig<br />
v4.rgzw = vec4(1.0, 1.0, 1.0, 2.0); // Ungültig, da verschiedenen Namensets<br />
v2.rgb = vec3(1.0, 2.0, 1.0); // Ungültig, da vec2 nur r+g besitzt<br />
v2.xx = vec2(5.0, 3.0); // Ungültig, da 2 mal gleiche Komponente<br />
</glsl><br />
<br />
Auch der Zugriff auf die Komponenten einer Matrix geht leicht von der Hand. Namensets wie bei den Vektoren gibt es hier natürlich keine, aber folgende Beispiele sollen den Zugriff aufzeigen :<br />
<glsl><br />
MyMat4[2] = vec4(1.0); // Setzt die 3.Zeile der Matrix komplett auf 1.0<br />
MyMat4[3][3] = 3.5; // Setzt das Element unren rechts auf 3.5<br />
</glsl><br />
<br />
Ein Zugriff auf Matrixelemente ausserhalb ihrer Dimension (also z.B. MyMat4[4][4]) liefert unvorhersehabre Ergebnise, also sollte man auf diese Fälle prüfen. <br />
<br />
<br />
==Vektor- und Matrixoperationen==<br />
<br />
Wie von C gewohnt sind in glSlang so ziemlich alle Operatoren die man auf Matrizen oder Vektoren anwenden kann überladen, so das man nicht umständlich über selbstgeschriebene Funktionen kombinieren muss. Darüber hinaus ist es in den meisten Fällen auch möglich ohne Konvertierung Fließkommawerte mit kompletten Matrizen oder Vektoren zu kombinieren. Folgende Beispiele zeigen einige der vielfältigen Kombinationsmöglichkeiten auf :<br />
<glsl><br />
vec3 dest;<br />
vec3 source;<br />
float factor;<br />
<br />
vec3 dest = source + factor; <br />
<br />
// Ist gleich<br />
dest.x = source.x + factor;<br />
dest.y = source.y + factor;<br />
dest.z = source.z + factor;<br />
</glsl><br />
<br />
Matrix * Vektor ist auch ohne manuelle Konvertierung möglich :<br />
<glsl><br />
vec3 dest;<br />
vec3 source;<br />
mat3 MyMat;<br />
<br />
dest = source * MyMat; <br />
<br />
// Ist gleich<br />
dest.x = dot(source, MyMat[0]);<br />
dest.y = dot(source, MyMat[1]);<br />
dest.z = dot(source, MyMat[2]);<br />
</glsl><br />
<br />
Auch hier sind die Möglichkeiten fast unbeschränkt und zeigen wieder wie flexibel glSlang ausgelegt ist. <br />
<br />
==Operatoren==<br />
<br />
glSlang bietet (momentan) folgende Operatoren, die Liste ist nach ihrer Gewichtung sortiert (Anfang = höchste). Alle ''reservierten'' Operatoren werden erst in kommender Hardware/glSlang-Versionen nutzbar sein :<br />
<br />
<div align="center"><br />
{|{{Prettytable_B1}}<br />
!Operatorklasse <br />
!Operatoren <br />
!Assoziation<br />
|-<br />
|Gruppering <br />
|() <br />
| -<br />
|-<br />
|Arrayindizierung<br>Funktionsaufrufe und Konstruktoren<br>Strukturfeldwahl und Swizzle<br>Postinkrement und -dekrement<br> <br />
|[]<br>()<br>.<br>++ -- <br />
|Links n. Rechts<br />
|-<br />
|Prefixinkrement- und dekrement<br>Einheitlich (~ reserviert) <br />
| ++ --<br> + - ~ ! <br />
|Rechts n. Links<br />
|-<br />
|Mulitplikation (% reserviert) <br />
|* / % <br />
|Links n. Rechts<br />
|-<br />
|Additiv <br />
| + - <br />
|Links n. Rechts<br />
|-<br />
|Bitweises Verschieben (reserviert) <br />
|<< >> <br />
|Links n. Rechts<br />
|-<br />
|Relation <br />
|< > <= >= <br />
|Links n. Rechts<br />
|-<br />
|Vergleich <br />
|== != <br />
|Links n. Rechts<br />
|-<br />
|Bitweises AND (reserviert) <br />
|& <br />
|Links n. Rechts<br />
|-<br />
|Bitweises XOR (reserviert) <br />
|^ <br />
|Links n. Rechts<br />
|-<br />
|Bitweises OR (reserviert) <br />
| <nowiki>|</nowiki> <br />
|Links n. Rechts<br />
|-<br />
|Logisches AND <br />
|&& <br />
|Links n. Rechts<br />
|-<br />
|Logisches XOR <br />
|^^ <br />
|Links n. Rechts<br />
|-<br />
|Logisches OR <br />
| <nowiki>||</nowiki> <br />
|Links n. Rechts<br />
|-<br />
|Auswahl <br />
|?: <br />
|Rechts n. Links<br />
|-<br />
|Zuweisung<br>Arithmetrische Zuweisung<br>(Modulis, Shift und bitweise Op. reserviert) <br />
|<nowiki>=</nowiki><br> <nowiki>+= -= *= /= %=</nowiki> <br> <nowiki><<= >>= &= ^= |=</nowiki> <br />
|Rechts n. Links<br />
|-<br />
|Aufzählung <br />
|, <br />
|Links n. Rechts<br />
|-<br />
|}<br />
</div><br />
<br />
<br />
==Funktionen==<br />
<br />
Ein großer Vorteil von Hochsprachen ist u.A. die Möglichkeit oft genutzte Codeteile in Funktionen (bzw. auch Prozeduren unter Pascal) zu verpacken um so Flexibilität als auch Übersichtlichkeit zu steigern. Wer schonmal was in C geschrieben hat, der wird sich jetzt sicherlich kein Kopfzerbrechen machen müssen. Funktionen werden in glSlang genauso nach folgendem Prinzip deklariert :<br />
<glsl><br />
RückgabeTyp FunktionsName(Typ0 Argument0, Typ1, Argument1, ... , TypN, ArgumentN)<br />
{<br />
return RückgabeWert;<br />
}<br />
</glsl><br />
<br />
Funktionen die ''nichts zurückgeben'' müssen mit dem RückgabeTyp {{INLINE_CODE|void}} deklariert werden, ausserdem entfällt dann logischerweise das {{INLINE_CODE|return}}. Falls die Funktion eines ihrere Argumente nach aussen übergeben soll, muss dieses Argument mit dem Typenqualifizierer out (Siehe Kapitel 4.2) versehen werden. ''Arrays'' können nur als Eingabeargumente übergeben werden und dürfen nich dimensioniert als Argument verwendet werden, sondern müssen mit leeren Klammern argumentiert werden.<br />
Ein paar Beispiele :<br />
<glsl><br />
void MeineFunktion(float EingabeWert; out float AusgabeWert)<br />
{<br />
AusgabeWert = EingabeWert*MyConstValue;<br />
}<br />
</glsl><br />
<br />
Diese Funktion gibt ''nichts'' zurück, aber gibt EingabeWert*MyConstValue im Ausgabeargument AusgabeWert nach aussen.<br />
<glsl><br />
float MeineFunktion(float EingabeWert)<br />
{<br />
return EingabeWert*MyConstValue;<br />
}<br />
</glsl><br />
<br />
Bietet genau die selbe Funktionalität wie das Beispiel darüber. Allerdings wird hier der berechnete Wert als Ergebnis der Funktion zurückgeliefert.<br />
<glsl><br />
float VektorSumme(float v[])<br />
{<br />
return v[0]+v[1]+v[2]+v[3];<br />
}<br />
</glsl><br />
<br />
Wie bereits gesagt darf ein Array als Argument keine Dimensionierung enthalten. Wenn man der Funktion also ein Array übergibt, sollte man vorher drauf achten das es entsprechend der in der Funktion genutzten Indizes dimensioniert wurde.<br />
<br />
<br />
==if-Anweisung==<br />
<br />
Selektion über eine if-Anweisung darf auch in keiner Hochsprache fehlen. Genauso wie in C oder Delphi erwartet auch hier die If-Anweisung einen boolschen Ausdruck (Wahr oder Falsch) und wird dann ausgeführt (wahr) bzw. verzweigt auf ein (wenn vorhanden) else (falsch). Verschachtelung ist wie erwartet auch möglich.<br />
<br />
'''Hinweis : ''' <br />
Grafikkarten auf dem Stand des Shadermodells 2.0 (Radeon 9x00, Radeon X8x0, GeForceFX 5x00) unterstüzten im Fragmentshader kein Early-Out, was zur Folge hat das bei einer If-Anweisung immer alle Zweige ausgeführt werden. Am Ende wird dann aber nur ein Ergebnis geschrieben, die anderen verworfen. Auf solchen Karten bringen If-Anweisungen also im Normalfall keine Geschwindigkeitssteigerung, sondern oft eher das Gegenteil.<br />
Neuere SM3.0-Karten (Radeon X1x00, GeForce6x00 und höher) ist dass nicht mehr der Fall, da hier dynamische Verzweigungen und auch Early-Out von der Hardware implementiert werden.<br />
<br />
==Schleifen==<br />
<br />
Auch Schleifen, ein wichtiges Konzept jeder Hochsprache haben ihren Weg in glSlang gefunden. Unterstützt werden folgende Schleifentypen :<br />
<br />
* '''for'''-Schleife<br />
<glsl><br />
for (Startausdruck; Durchlaufbedingung; Wiederholungsausdruck;)<br />
{<br />
statement<br />
}<br />
</glsl><br />
<br />
* '''while'''-Schleife<br />
<glsl><br />
while (Durchlaufbedingung)<br />
{<br />
statement<br />
}<br />
</glsl><br />
<br />
* '''do'''-while-Schleife<br />
<glsl><br />
do<br />
{<br />
statement<br />
}<br />
while (Durchlaufbedingung)<br />
</glsl><br />
<br />
'''Hinweis :''' Grafikkarten auf dem Stand des Shadermodells 2.0 (Radeon 9x00, Radeon X8x0, GeForceFX 5x00) unterstüzten Schleifen nicht in Hardware. Schleifen werden dann beim Kompilieren vom Treiber entrollt, wodurch natürlich Shader mit weitaus mehr Instruktionen als erwartet generiert werden. Von daher sollte man auf solchen Karten möglichst auf Schleifen verzichten, oder diese nur recht kurz halten. Bei SM3.0-Karten (Radeon X1x00, GeForce6x00 und höher) ist dass nicht mehr der Fall.<br />
<br />
=Eingebaute Variablen, Attribute und Konstanten=<br />
Nachdem wir uns nun lange genug mit den minderinterssanten Elementen der glSlang-Syntax beschäftigt haben, gehts jetzt endlich an die wirklich interessanten Dinge. Wie schon ARB_VP/ARB_FP bringt auch glSlang jede Menge eingabauter Variablen, Attribute und Konstanten mit, deren Aliase sie recht leicht identifizierbar machen (ganz im Gegensatz zum Indexgewusel bei den DX-Shadern).<br />
<br />
<br />
==Variablen im Vertex Shader==<br />
Exklusiv im Vertex Shader stehen die folgenden Variablen zur Verfügung :<br />
<br />
* vec4 gl_Position muss geschrieben werden<br />
:Dieser Variable '''muss''' im Vertexshader ein Wert zugewiesen werden, wird dies nicht getan ist das Ergebnis (sprich die Position des Vertex) undefiniert. Vorgesehen ist diese Variable für die ''homogene Position des Vertex'' und wird u.a. zum Clipping und Culling verwendet. Sie darf natürlich auch (mehrfach) geschrieben und ausgelesen werden.<br />
<br />
* float gl_PointSize kann geschrieben werden<br />
:Diese Variable wurde dazu vorgesehen um dort im VertexShader die Punktgröße in Pixeln hineinzuschreiben.<br />
<br />
* vec4 gl_ClipVertex kann geschrieben werden<br />
:Falls genutzt, sollten hier die Vertexkoordinaten die im Zusammenhang mit benutzerdefinierten Clippingplanes genutzt werden abgelegt werden. Wichtig ist, das gl_ClipVertex im selben Koordinatenraum wie die Clippingplane definiert ist.<br />
<br />
==Attribute im Vertex Shader==<br />
<br />
Folgende Attribute stehen nur im Vertex Shader zur Verfügung und '''können nur gelesen werden''' :<br />
<br />
* vec4 gl_Color<br />
: Farbwert des Vertex.<br />
* vec4 gl_SecondaryColor<br />
:Sekundärer Farbwert des Vertex.<br />
* vec4 gl_Normal<br />
:Normale des Vertex.<br />
* vec4 gl_Vertex<br />
:Koordinaten des Vertex;<br />
* vec4 gl_MultiTexCoord0..7<br />
:Texturkoordinaten auf Textureinheit 0..7.<br />
* float gl_FogCoord<br />
:Nebelkoordinate des Vertex. <br />
<br />
<br />
==Variablen im Fragment Shader==<br />
<br />
Im Fragment Shader sind folgende Variablen exklusiv nutzbar :<br />
<br />
* vec4 gl_FragColor<br />
: Speichert den Farbwert des Fragmentes, der von folgenden Funktionen der festen Pipeline genutzt wird. Wird dieser Variable nichts zugewiesen, so ist ihr Inhalt undefiniert und darauf aufbauende Ergebnisse ebenfalls.<br />
<br />
* vec4 gl_FragData[0..15]<br />
: Ersetzt gl_FragColor bei der Verwendung von multiplen Rendertargets. <br />
<br />
* float gl_FragDepth<br />
: Durch schreiben dieser Variable kann man den von der festen Funktionspipeline ermittelten Tiefenwert überspringen, der mit {{INLINE_CODE|gl_FragCoord.z}} ausgelesen werden kann. Wird dieser Wert nicht geschrieben, nutzen folgende Funktionen der Pipeline den vorher fest berechneten Wert.<br />
<br />
* vec4 gl_FragCoord nur lesen<br />
: In dieser Variable ist die Position des Fragmentes relativ zur Fensterposition im Format x,y,z,1/w abgelegt, wobei z den von der festen Funktionspipeline berechneten Tiefenwert enthält.<br />
<br />
* bool gl_FrontFacing nur lesen<br />
: Gibt an ob das Fragment zu einer nach vorne zeigenden Primitive gehört (=true). <br />
<br />
<br />
Im Bezug auf {{INLINE_CODE|gl_FragColor}} und {{INLINE_CODE|gl_FragDepth}} sei noch anzumerken das diese ''nicht'' in den Wertebereich 0..1 gebracht werden müssen, da dies später durch die feste Funktionspipeline automatisch gemacht wird.<br />
<br />
==Eingebaute Varyings==<br />
<br />
Wie bereits in Kapitel 4.2 erwähnt, stellen Varyings eine Schnittstelle zwischen dem Vertex und dem Fragment Shader dar. Sie werden im Vertex Shader geschrieben und können dann im Fragment Shader ausgelesen werden, ohne das die folgenden Varyings dafür explizit deklariert werden müssen :<br />
<br />
* vec4 gl_FrontColor<br />
: Farbe der Vorderseite des Vertex.<br />
<br />
* vec4 gl_BackColor<br />
: Farbe der Rückseite des Vertex.<br />
<br />
* vec4 gl_FrontSecondaryColor<br />
: Sekundäre Farbe der Vorderseite des Vertex.<br />
<br />
* vec4 gl_BackSecondaryColor<br />
: Sekundäre Farbe der Rückseite des Vertex.<br />
<br />
* vec4 gl_TexCoord[x]<br />
: Texturkoordinaten des Vertex auf Textureinheit x, wobei x die von der Hardware zur Verfügung gestellte Zahl der Textureinheiten-1 nicht überschreiten darf.<br />
<br />
* float gl_FogFragCoord<br />
: Nebelkoordinate des Fragmentes. <br />
<br />
Die Varyings {{INLINE_CODE|gl_FrontColor, gl_FrontSecondaryColor, gl_BackColor}} und {{INLINE_CODE|gl_BackSecondaryColor}} können im FragmentShader nur unter den Aliases gl_Color bzw. gl_SecondaryColor gelesen werden. Welcher Wert des Vertex Shaders im Fragment Shader dort eingesetzt wird ist abhängig davon ob das Fragment zu einer nach vorne oder nach hinten zeigenden Primitive gehört.<br />
<br />
<br />
==Eingebaute Konstanten==<br />
Auch diverse Konstanten wurden definiert um darauf schnell im Shader zugreifen zu können. In den Klammern stehen die von einer GL-Implementation als Mindestanforderung anzubietenden Werte. Alle Konstanten sind sowohl im Vertex als auch im Fragment Shader abrufbar :<br />
<br />
: OpenGL 1.0/1.2 :<br />
* int gl_MaxLights (8)<br />
* int gl_MaxClipPlanes (6)<br />
* int gl_MaxTextureUnits (2)<br />
<br />
<br />
: ARB_Fragment_Program :<br />
* int gl_MaxTextureCoordsARB (2)<br />
<br />
<br />
: Vertex_Shader :<br />
* int gl_MaxVertexAttributesGL2 (16)<br />
* int gl_MaxVertexUniformFloatsGL2 (512)<br />
* int gl_MaxVaryingFloatsGL2 (32)<br />
* int gl_MaxVertexTextureUnitsGL2 (1)<br />
<br />
<br />
: Fragment_Shader :<br />
* int gl_MaxFragmentTextureUnitsGL2 (2)<br />
* int gl_MaxFragmentUniformFloatsGL2 (64)<br />
<br />
<br />
==Eingebaute Uniformvariablen==<br />
<br />
Um den Zugriff auf OpenGL-Staten zu vereinfachen wurden in glSlang diverse Uniformvariablen zur direkten Verwendung im Shader eingebaut. Wie gewohnt wurden auch hier sinnvolle Namen verwendet, so dass eine tiefere Erklärung unnötig sein dürfte :<br />
<br />
* mat4 gl_ModelViewMatrix<br />
* mat4 gl_ProjectionMatrix<br />
* mat4 gl_ModelViewProjectionMatrix<br />
* mat3 gl_NormalMatrix<br />
* mat4 gl_TextureMatrix[gl_MaxTextureCoordsARB]<br />
:{{INLINE_CODE|gl_NormalMatrix}} repräsentiert die inversen oberen 3x3 Werte der Modelansichtsmatrix. {{INLINE_CODE|gl_TextureMatrix[x]}} adressiert maximal Anzahl Textureinheiten-1-Texturmatrizen.<br />
<br />
* float gl_NormalScale<br />
: Gibt den unter OpenGL festgelegten Faktor zur Skalierung der Normalen zurück.<br />
<br />
* struct gl_DepthRangeParameters<br />
<glsl><br />
struct gl_DepthRangeParameters<br />
{<br />
float near;<br />
float far;<br />
float diff;<br />
};<br />
gl_DepthRangeParameters gl_DepthRange;<br />
</glsl><br />
: Clippingplanes : <br />
* vec4 gl_ClipPlane[gl_MaxClipPlanes]<br />
<br />
*struct gl_PointParameters<br />
<glsl><br />
struct gl_PointParameters<br />
{<br />
float size;<br />
float sizeMin;<br />
float sizeMax;<br />
float fadeThresholdSize;<br />
float distanceConstantAttenuation;<br />
float distanceLinearAttenuation;<br />
float distanceQuadraticAttenuation;<br />
};<br />
gl_PointParameters gl_Point;<br />
</glsl><br />
*struct gl_MaterialParameters<br />
<glsl><br />
struct gl_MaterialParameters<br />
{<br />
vec4 emission;<br />
vec4 ambient;<br />
vec4 diffuse;<br />
vec4 specular;<br />
float shininess;<br />
};<br />
gl_MaterialParameters gl_FrontMaterial;<br />
gl_MaterialParameters gl_BackMaterial;<br />
</glsl><br />
*struct gl_LightSourceParameters<br />
<glsl><br />
struct gl_LightSourceParameters<br />
{<br />
vec4 ambient;<br />
vec4 diffuse;<br />
vec4 specular;<br />
vec4 position;<br />
vec4 halfVector;<br />
vec3 spotDirection;<br />
float spotExponent;<br />
float spotCutoff;<br />
float spotCosCutoff;<br />
float constantAttenuation;<br />
float linearAttenuation;<br />
float quadraticAttenuation;<br />
};<br />
gl_LightSourceParameters gl_LightSource[gl_MaxLights];<br />
</glsl><br />
*struct gl_LightModelParameters<br />
<glsl><br />
struct gl_LightModelParameters<br />
{<br />
vec4 ambient;<br />
};<br />
gl_LightModelParameters gl_LightModel;<br />
</glsl><br />
*struct gl_LightModelProducts<br />
<glsl><br />
struct gl_LightModelProducts<br />
{<br />
vec4 sceneColor;<br />
};<br />
gl_LightModelProducts gl_FrontLightModelProduct;<br />
gl_LightModelProducts gl_BackLightModelProduct;<br />
</glsl><br />
*struct gl_LightProducts<br />
<glsl><br />
struct gl_LightProducts<br />
{<br />
vec4 ambient;<br />
vec4 diffuse;<br />
vec4 specular;<br />
};<br />
gl_LightProducts gl_FrontLightProduct[gl_MaxLights];<br />
gl_LightProducts gl_BackLightProduct[gl_MaxLights];<br />
</glsl><br />
* vec4 gl_TextureEnvColor[gl_MaxFragmentTextureUnitsGL2]<br />
* vec4 gl_EyePlaneS[gl_MaxTextureCoordsARB]<br />
* vec4 gl_EyePlaneT[gl_MaxTextureCoordsARB]<br />
* vec4 gl_EyePlaneR[gl_MaxTextureCoordsARB]<br />
* vec4 gl_EyePlaneQ[gl_MaxTextureCoordsARB]<br />
* vec4 gl_ObjectPlaneS[gl_MaxTextureCoordsARB]<br />
* vec4 gl_ObjectPlaneT[gl_MaxTextureCoordsARB]<br />
* vec4 gl_ObjectPlaneR[gl_MaxTextureCoordsARB]<br />
* vec4 gl_ObjectPlaneQ[gl_MaxTextureCoordsARB]<br />
<br />
*struct gl_FogParameters<br />
<glsl><br />
struct gl_FogParameters<br />
{<br />
vec4 color;<br />
float density;<br />
float start;<br />
float end;<br />
float scale;<br />
};<br />
gl_FogParameters gl_Fog;<br />
</glsl><br />
Diese recht umfangreiche GL-Stateliste sollte eigentlich jeden Bedarf decken und momentan gibts kaum einen OpenGL-Status den man so nicht in einem Shader abfragen bzw. nutzen kann.<br />
<br />
=Eingebaute Funktionen=<br />
glSlang ist mit diversen Skalar- und Vektorfunktionen ausgestattet, die teilweise (idealerweise) sogar direkt in der Hardware ausgeführt werden, weshalb einer fertigen Funktion ggü. gleichwertigen eigenen Berechnungen immer der Vorzug zu geben ist.<br />
{{Hinweis| ''genType'' kann vom Type float, vec2, vec3 oder vec4 sein, ''mat'' vom Typ mat2, mat3 oder mat4.}}<br />
<br />
<br />
==Trigonometrie und Winkel==<br />
Alle übergebenen Winkel sollten, soweit nicht anders vermerkt, in Radien angegeben werden.<br />
<br />
* genType radians (genType degrees)<br />
: Wandelt von Grad nach Radien. <br />
* genType degrees (genType radians)<br />
: Wandelt von Radien nach Grad.<br />
* genType sin (genType angle)<br />
: Gibt den Sinus von Angle zurück, wobei Angle in Radien angegeben wird.<br />
* genType cos (genType angle)<br />
: Gibt den Cosinus von Angle zurück, wobei Angle in Radien angegeben wird.<br />
* genType tan (genType angle)<br />
: Gibt den Tangens von Angle zurück, wobei Angle in Radien angegeben wird.<br />
* genType asin (genType x)<br />
: Liefert den Arcsinus von x zurück, also den Winkel dessen Sinus x ergeben würde.<br />
* genType acos (genType x)<br />
: Liefert den Arccosinus von x zurück, also den Winkel dessen Cosinus x ergeben würde.<br />
* genType atan (genType y, genType x)<br />
: Liefert den Winkel zurück, dessen Tangens x/y ergeben würde.<br />
* genType atan (genType y_over_x)<br />
: Liefert den Winkel zurück, dessen Tangens x über y ergeben würde.<br />
<br />
==Exponentiell==<br />
* genType pow (genType x, genType y)<br />
: Gibt x hoch y zurück.<br />
* genType exp2 (genType x)<br />
: Gibt 2 hoch x zurück.<br />
* genType log2 (genType x)<br />
: Gibt den Logarithmus zur Basis 2 von x zurück.<br />
* genType sqrt (genType x)<br />
: Gibt die Wurzel von x zurück.<br />
* genType inversesqrt (genType x)<br />
: Gibt die umgekehrte Wurzel von x zurück. <br />
<br />
<br />
==Standardfunktionen==<br />
* genType abs (genType x)<br />
: Liefert den absoluten Wert von x zurück.<br />
* genType sign (genType x)<br />
: Gibt -1.0 zurück, wenn x < 0.0, 0.0 wenn x = 0.0 und 1.0 wenn x > 0.0.<br />
* genType floor (genType x)<br />
: Gibt denn nächsten Integerwert zurück, der kleiner oder gleich x ist.<br />
* genType ceil (genType x)<br />
: Gibt den nächsten Integerwert zurück, der größer oder gleich x ist.<br />
* genType fract (genType x)<br />
: Gibt den Nachkommateil von x zurück.<br />
* genType mod (genType x, float y) <br />
* genType mod (genType x, genType y)<br />
: Gibt den Modulus zurück. (=x-y * floor(x/y)) <br />
* genType min (genType x, genType y) <br />
* genType min (genType x, float y)<br />
: Liefert y zurück wenn y < x, ansonsten x. <br />
* genType max (genType x, genType y) <br />
* genType max (genType x, float y)<br />
: Liefert y zurück wenn x < y, ansonsten x. <br />
* genType clamp (genType x, genType minVal, genType maxVal) <br />
* genType clamp (genType x, float minVal, float maxVal)<br />
: Zwängt x in den Bereich minVal..maxVal. <br />
* genType mix (genType x, genType y, genType a)<br />
* genType mix (genType x, genType y, float a)<br />
: Liefert den linearen Blend zwischen x und y zurück. (= x * (1-a) + y * a) <br />
* genType step (genType edge, genType x)<br />
* genType step (float edge, genType x)<br />
: Liefert 0.0 zurück, wenn x <= edge, ansonsten 1.0. <br />
* genType smoothstep (genType edge0, genType edge1, genType x)<br />
* genType smoothstep (float edge0, float edge1, genType x)<br />
: Liefert 0.0 zurück, wenn x <= edge und 1.0 wenn x >= edge. Dabei wird eine weiche Hermite Interpolation zwischen 0 und 1 durchgeführt. <br />
<br />
<br />
==Geometrie==<br />
* float length (genType x)<br />
: Gibt die Länge des Vektors x (= sqrt(x[0]² + x[1]² + ... + x[n]²) zurück. <br />
* float distance (genType p0, genType p1)<br />
: Gibt die Distanz zwischen den zwei Vektoren p0 un p1 (= length(p0-p1)) zurück. <br />
* float dot (genType x, genType y)<br />
: Gibt das Punktprodukt von x und y zurück (=x[0]*y[0] + x[1]*y[1] + ... + x[n]*y[n]). <br />
* vec3 cross (vec3 x, vec3 y)<br />
: Gibt das Kreuzprodukt von x und y zurück. <br />
* genType normalize (genType x)<br />
: Normalisiert den Vektor x auf die Länge 1. <br />
* vec4 ftransform()<br />
: Nur im Vertex Shader. Die Funktion stellt sicher, das das eingehende Vertex haargenau so transformiert wird wie in der festen Funktionspipeline. gl_Position = ftransform() wird dann also gebraucht, wenn in mehreren Durchgängen sowohl im Shader als auch in der festen Pipeline gerendert wird, um sicherzustellen das in beiden Fällen die gleiche Vertexposition herauskommt. <br />
* genType faceforward (genType N, genType I, genType Nref)<br />
: Gibt einen nach vorne zeigenden Vektor N zurück. (If dot(NRef, I) < 0 return N else return -N) <br />
* genType reflect (genType I, genType N)<br />
: Gibt den an der Flächenausrichtung N reflektierten Vektor I zurück. (=I-2 * dot(N,I) * N) <br />
<br />
<br />
==Matrixfunktionen==<br />
* mat matrixCompMult (mat x, mat y)<br />
: Multipliziert Matrix X mit Matrix Y komponentenweise. Um eine normale lineare Matrixmultiplikation durchzuführen, sollte der "*"-Operator genutzt werden. <br />
<br />
<br />
==Vektorvergleiche==<br />
Die meisten Vektorvergleichsfunktionen liefern als Ergebnis einen boolvektor zurück, da die Vergleiche per Komponente stattfinden. Wenn man also x = vec4(1.0, 3.0, 0.0, 0.0) mit y = vec4(2.0, 1.5, 1.5, 0.0) via lessThan(x, y) vergleicht, erhält man als Ergebnis bvec(true, false, true, false).<br />
<br />
* bvec lessThan (vec x, vec y)<br />
* bvec lessThan (ivec x, ivec y)<br />
: Gibt den komponentenweisen Vergleich x < y zurück. <br />
* bvec lessThanEqual (vec x, vec y)<br />
* bvec lessThanEqual (ivec x, ivec y)<br />
: Gibt den komponentenweisen Vergleich x <= y zurück. <br />
* bvec greaterThan (vec x, vec y)<br />
* bvec greaterThan (ivec x, ivec y)<br />
: Gibt den komponentenweisen Vergleich x > y zurück. <br />
* bvec greaterThanEqual (vec x, vec y)<br />
* bvec greaterThanEqual (ivec x, ivec y)<br />
: Gibt den komponentenweisen Vergleich x >= y zurück. <br />
* bvec equal (vec x, vec y)<br />
* bvec equal (ivec x, ivec y)<br />
* bvec equal (bvec x, bvec y)<br />
: Gibt den komponentenweisen Vergleich x == y zurück. <br />
* bvec notEqual (vec x, vec y)<br />
* bvec notEqual (ivec x, ivec y)<br />
* bvec notEqual (bvec x, bvec y)<br />
: Gibt den komponentenweisen Vergleich x != y zurück. <br />
* bool any (bvec x)<br />
: Liefert true zurück, wenn mindestens eine der Komponenten von x true ist.<br />
* bool all (bvec x)<br />
: Liefert true zurück, wenn alle Komponenten von x true sind. <br />
* bvec not (bvec x)<br />
: Liefert die logische Negation von x zurück. <br />
<br />
<br />
==Texturenzugriffe==<br />
<br />
Diese wichtige Funktionskategorie dient dazu, Werte aus einer an eine Textureinheit gebundenen Textur zu ermitteln. Die Texturenzugriffe können sowohl im Vertex (!) als auch im Fragment Shader ausgeführt werden, wobei der optionale Parameter bias im Vertex Shader ignoriert wird. Allerdings gibt es zusätzlich Funktionen die auf "Lod" enden und nur im Vertex Shader genutzt werden dürfen um eben dieses Manko zu umgehen. Funktionen mit dem Suffix "Proj" geben einen projizierten Texturenwert zurück.<br />
<br />
: '''1D-Texturen :'''<br />
* vec4 texture1D (sampler1D sampler, float coord [, float bias])<br />
* vec4 texture1DProj (sampler1D sampler, vec2 coord [, float bias])<br />
* vec4 texture1DProj (sampler1D sampler, vec4 coord [, float bias])<br />
: Nur im Vertex Shader :<br />
* vec4 texture1DLod (sampler1D sampler, float coord, float lod)<br />
* vec4 texture1DProjLod (sampler1D sampler, vec2 coord, float lod)<br />
* vec4 texture1DProjLod (sampler1D sampler, vec4 coord, float lod)<br />
<br />
<br />
: '''2D-Texturen :'''<br />
* vec4 texture2D (sampler2D sampler, vec2 coord [, float bias])<br />
* vec4 texture2DProj (sampler2D sampler, vec3 coord [, float bias])<br />
* vec4 texture2DProj (sampler2D sampler, vec4 coord [, float bias])<br />
: Nur im Vertex Shader : <br />
* vec4 texture2DLod (sampler2D sampler, vec2 coord, float lod)<br />
* vec4 texture2DProjLod (sampler2D sampler, vec3 coord, float lod)<br />
* vec4 texture2DProjLod (sampler2D sampler, vec4 coord, float lod)<br />
<br />
<br />
: '''3D-Texturen :'''<br />
* vec4 texture3D (sampler3D sampler, vec3 coord [, float bias])<br />
* vec4 texture3DProj (sampler3D sampler, vec4 coord [, float bias])<br />
: Nur im Vertex Shader : <br />
* vec4 texture3DLod (sampler3D sampler, vec3 coord, float lod)<br />
* vec4 texture3DProjLod (sampler3D sampler, vec4 coord, float lod)<br />
<br />
<br />
: '''Cubemap :'''<br />
* vec4 textureCube (samplerCube sampler, vec3 coord [, float bias])<br />
: Nur im Vertex Shader : <br />
*vec4 textureCubeLod (samplerCube sampler, vec3 coord, float lod)<br />
<br />
<br />
: '''Tiefentextur (Shadowmap) :'''<br />
* vec4 shadow1D (sampler1DShadow sampler, vec3 coord [, float bias])<br />
* vec4 shadow2D (sampler2DShadow sampler, vec3 coord [, float bias])<br />
* vec4 shadow1DProj (sampler1DShadow sampler, vec4 coord [, float bias])<br />
* vec4 shadow2DProj (sampler2DShadow sampler, vec4 coord [, float bias])<br />
: Nur im Vertex Shader :<br />
* vec4 shadow1DLod (sampler1DShadow sampler, vec3 coord, float lod)<br />
* vec4 shadow2DLod (sampler2DShadow sampler, vec3 coord, float lod)<br />
* vec4 shadow1DProjLod (sampler1DShadow sampler, vec4 coord, float lod)<br />
* vec4 shadow2DProjLod (sampler2DShadow sampler, vec4 coord, float lod)<br />
<br />
<br />
Wie bereits eingangs gesagt ist dieses Kapitel ein sehr wichtiges, denn eine 3D-Szene ohne Texturen ist heute kaum denkbar. Darüber hinaus lassen sich durch Texturenzugriffe recht viele interessante Sachen machen, z.B. ein einfacher Blurfilter oder das freie überblenden bestimmter Texturenteile. Deshalb führe ich hier kurz ein paar Beispiele an, welche die Nutzung dieser Funktionen verdeutlichen sollen :<br />
<br />
===Beispiel A=== <br />
Eine Textur gebunden die einfach ausgegeben werden soll<br />
<br />
''Im Vertex Shader'' :<br />
<glsl><br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
}<br />
</glsl> <br />
Der Vertex Shader ist recht minimal. Neben der homogenen Vertexposition leiten wir hier nur die im OpenGL-Programm angegebenen Texturkoordinaten weiter. ''Dies ist aber unbedingt nötig!'' Ohne die letzte Zeile hätten wir im Fragment Shader keine gültigen Texturkoordinaten auf TMU0, was in einer Fehldarstellung enden würde.<br />
<br />
''im Fragment Shader'' :<br />
<glsl><br />
uniform sampler2D texSampler;<br />
<br />
void main(void)<br />
{<br />
gl_FragColor = texture2D(texSampler, vec2(gl_TexCoord[0]));<br />
}<br />
</glsl><br />
Zuerst deklarieren wir hier einen 2D-Texturensampler, wichtig : '''Texturensampler müssen IMMER als uniform deklariert werden!''' In der Hauptfunktion weisen wir dann einfach den über die Funktion texture2D aus unserer gebundenen Textur ausgelesenen Farbwert, anhand der vom Vertex Shader übergebenen Texturkoordinaten, zu.<br />
<br />
===Beispiel B=== <br />
Zwei Texturen, jeweils auf TMU0 und TMU1. Fragmentfarbe soll eine Multiplikation der beiden Texturen darstellen.<br />
<br />
In diesem Beispielfall (der recht häufig vorkommt) müssen wir im Programm festlegen, ''welcher Sampler welche Textureinheit adressiert'', genau deshalb müssen die Texturensampler auch als uniform deklariert werden. Die Standardtextureneinheit eines Samplers ist TMU0, was in unserem Falle natürlich nicht brauchbar ist. Also müssen wir unserem zweiten Textursampler im Programm mitteilen das er seine Daten aus TMU1 beziehen soll :<br />
<br />
glUniform1iARB(glSlang_GetUniLoc(ProgramObject, 'texSamplerTMU1'), 1);<br />
<br />
Dies ist also unbedingt zu machen, sobald ein Texturensampler eine Textureinheit > GL_TEXTURE_0 adressieren will. Die Textureneinheit des Samplers lässt sich also nicht im Shader selbst festlegen. Der Fragment Shader ist nun allerdings schnell hergeleitet (Vertex Shader verändert sich nicht, da TMU1 die Texturkoordinaten auch von TMU0 bezieht) :<br />
<br />
<br />
im Fragment Shader :<br />
<glsl><br />
uniform sampler2D texSamplerTMU0;<br />
uniform sampler2D texSamplerTMU1;<br />
<br />
<br />
void main(void)<br />
{<br />
gl_FragColor = texture2D(texSamplerTMU0, vec2(gl_TexCoord[0])) *<br />
texture2D(texSamplerTMU1, vec2(gl_TexCoord[0]));<br />
}<br />
</glsl><br />
<br />
==Noisefunktionen==<br />
Sowohl im Vertex als auch im Fragment Shader lassen sich [[GLSL noise|Noisefunktionen]] nutzen, mit deren Hilfe sich eine gewisse "Zufälligkeit" simulieren lässt (wirklich zufällige Werte sind es natürlich nicht). Ein zurückgegebener Wert liegt dabei immer im Bereich [-1..1] und ist immer bei gleichem Eigabewert auch immer gleich. Die Verwendung empfiehlt sich derzeit allerdings eher nicht, da nicht alle aktuellen Treiber die Funktionen unterstützen und eine Noisetextur wahrscheinlich performanter ist.<br />
<br />
* float noise1 (genType x)<br />
* vec2 noise2 (genType x)<br />
* vec3 noise3 (genType x)<br />
* vec4 noise4 (genType x)<br />
<br />
==Discard==<br />
Eigentlich keine Funktion, sondern eine Abbruchbedingung '''nur im Fragment Shader'''. Das Schlüsselwort {{INLINE_CODE|discard}} verwirft das aktuell bearbeitete Fragment und beendet gleichzeitig den Shader. Es kann z.B. genutzt werden um Alphamasking manuell durchzuführen.<br />
Man sollte dabei jedoch beachten dass ein Großteil der aktuellen Hardware kein "early-out" (frühes Beenden) im Fragmentshader unterstützt. Wenn dort also ein {{INLINE_CODE|discard}} auftaucht, wird trotzdem auch der Code danach ausgeführt und einfach verworfen. Einen Geschwindigkeitsvorteil durch diesen Befehl wird man also erst auf neueren Karten feststellen, die dieses Faeature auch so unterstützen wie es angedacht war. <br />
<br />
<br />
=Beispielshader=<br />
Wen bis hierhin nicht der Mut verlassen hat, und wer aufmerksam gelesen hat, dürfte jetzt also zumindest in der Lage sein kleinere Shader in glSlang zu schreiben und diese auch im Programm zu nutzen. Ich habe im Themenbereich "glSlang" versucht alle Bereiche der Shadersprache selbst anzusprechen und hoffe das auch brauchbar rübergebracht zu haben. Um oben erlerntes (hoffe ich doch mal) nochmal zu vertiefen werde ich jetzt (wie ich das bereits bei meinem ARB_VP-Tutorial getan habe) einen simplen Beispielshader (Vertex und Fragment Shader) auseinanderpflücken um so u.a. auch die Programmstruktur für alle die in C nicht so bewandert sind zu erörtern.<br />
<br />
<br />
==Der Vertex Shader==<br />
<glsl><br />
uniform vec4 GlobalColor;<br />
<br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_FrontColor = gl_Color * GlobalColor;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
}<br />
</glsl><br />
<br />
Wie gesagt recht simpel. Angefangen wird mit der Deklaration einer globalen Uniformvariable namens {{INLINE_CODE|GlobalColor}}. Wie wir uns erinnern gibt der Typenqualifizierer uniform an, das wir den Wert dieser Variable (ein 4-Komponentenvektor, da Farbwerte aus R,G,B und A bestehen) in unserem Programm an den Shader übermitteln.<br />
<br />
Danach gehts ohne Umwege direkt in unsere Hauptfunktion, da wir im Vertex Shader keine anderen Funktionen benötigen. Dort berechnen wir zuerst die homogene Position unseres Vertex, die sich aus der eingehenden Vertexposition multipliziert mit der Modelansichtsmatrix ergibt. Wie schonmal gesagt '''muss diesem Wert etwas zugewiesen werden''', da sonst alle darauf aufbauenden Funktionen unvorhersehbare Ergebnisse liefern.<br />
Ausserdem wollen wir die Frontfarbe unseres Vertex jedesmal mit der im Programm übergebenen GlobalColor multiplizieren, so dass wir den Farbwert der gesamten Szene aus unserem Programm heraus manipulieren können. Zu guterletzt geben wir dann noch unsere aus der festen Funktionspipeline erhaltenen Texturkoordinaten auf Textureinheit 0 weiter. Wenn im Fragmentshader Texturkoordinaten verwendet werden, '''muss das getan werden'''. <br />
<br />
<br />
==Der Fragment Shader==<br />
<glsl><br />
uniform sampler2D Texture0;<br />
uniform sampler2D Texture1;<br />
uniform sampler2D Texture2;<br />
uniform sampler2D Texture3;<br />
<br />
void main(void)<br />
{<br />
vec2 TexCoord = vec2( gl_TexCoord[0] );<br />
vec4 RGB = texture2D( Texture0, TexCoord );<br />
<br />
gl_FragColor = texture2D(Texture1, TexCoord) * RGB.r +<br />
texture2D(Texture2, TexCoord) * RGB.g +<br />
texture2D(Texture3, TexCoord) * RGB.b;<br />
}<br />
</glsl><br />
<br />
Auch hier passiert nicht wirklich viel Großartiges. Wir deklarieren beim Shaderanfang zuerst vier Texturensampler, da wir insgesamt vier verschiedene Texturen im Shader auslesen wollen, eine Verlaufstextur und drei Oberflächentexturen. Auch hier sei wieder gesagt das man Sampler '''immer als uniform deklarieren muss'''. In der Hauptfunktion deklarieren wir dann einen Farbvektor, der auch direkt einen Farbwert aus Textureinheit 0 zugewiesen bekommt. Auf Textureinheit 0 haben wir ihm Hauptprogramm eine Verlaufstextur gebunden, die angibt wie die drei folgenden Texturen ineinander geblendet werden.<br />
Danach schreiben wir dann den Farbwert des Fragmentes, der '''im Fragment Shader ausgegeben werden muss'''. Der besteht wie einfach zu erkennen aus Farbwert von Textureinheit 1 * Rotwert von Textureinheit 0 + Farbwert von Textureinheit 2 * Grünwert von Textureinheit 0 + Farbwert von Textureinheit 3 * Blauwert von Textureinheit 0. So ist z.B. an Stellen an denen in der Verlaufstextur reines blau liegt nur die dritte Textur sichtbar.<br />
<br />
So viel also zu unserem kleinen Beispielshader. Er ist weder besonders toll noch besonders sinnvoll, sollte aber auch eher dazu dienen euch glSlang ein wenig zu veranschaulichen, was mir hoffentlich gelungen ist.<br />
<br />
Wenn ihr in den vorangegangenen Kapiteln zumindest ein wenig aufgepasst habt, dann könnt ihr euch vor eurem inneren Auge hoffentlich vortstellen was der Shader macht : Er blendet drei Texturen weich anhand der Verlaufstextur ineinander über. Sowas kann man z.B. für ein Terrain nutzen, um dieses anhand einer Farbtextur zu texturieren. Für alle, die damit Probleme haben hier zwei Bilder die den Shader veranschaulichen. Links die Verlaufstextur, die angibt wo welche Textur wie stark gewichtet wird und rechts dann das Ergebnis :<br />
<br />
<div align="center"> [[BILD:GLSL_sample_shader_a.jpg]] [[BILD:GLSL_sample_shader_b.jpg]]</div><br />
<br />
=Post Mortem=<br />
Das wars also, meine "Einführung" in die OpenGL Shader Sprache. Ich hoffe es hat euch nicht gelangweilt und auch die von mir zur Verfügung gestellten Informationen haben euch hoffentlich ausgereicht. Mit der Veröffentlichung dieser Einführung geht übrigens auch die Eröffnung eines Shaderforums hier auf der DGL einher, in der ihr dann also fleissig Fragen zum Thema stellen oder eure Shader präsentieren könnt. In diesem Post Mortem gehe ich jetzt noch kurz auf die Zukunft von glSlang ein und zeige ein paar Screenshots (damit die Augen entspannen können), bevor ihr euch dann selbst in die Shaderwelt stürzen könnt. <br />
<br />
<br />
=Screenshots=<br />
<br />
Um eure Augen ein wenig zu verwöhnen und zu zeigen was man mit glSlang alles machen, v.a. da man jetzt Shader schön lesbar in einer Hochsprache verfassen kann, mal ein paar Screens. Besonders der zweite Shot sieht animiert noch besser aus :<br />
<br />
<center>[[BILD:GLSL_sample_Kugel.jpg]] [[BILD:GLSL_sample_Alien.jpg]] </center><br />
<br />
Die Zahl möglicher Effekte ist bei einer so flexiblen Shadersprache natürlich nahezu unbegrenzt, und besonders auf kommender Hardware werden bisher ungesehen Effekte den Einzu in die Echtzeitgrafik finden. Man darf also mehr als gespannt sein.<br />
<br />
=Die Zukunft=<br />
Viele werden sich sicherlich fragen, warum sie z.B. statt ARB_VP/FP oder Nvidias cG denn überhaupt auf glSlang setzen sollen. Doch solche Zweifel dürften bei einem genauen Blick auf die neue Shadersprache schnell verworfen sein. Zum einen steckt hinter glSlang dank des ARBs fast die komplette 3D-Industrie und zum anderen hat man beim Entwurf der Shadersprache, wie z.B. an vielen reservierten Wörtern/Funktionen erkennbar versucht so weit wie möglich in die Zukunft zu planen. So sollen auch Karten der nächsten und übernächsten Generation mit glSlang ausnutzbar sein, und was danach kommt wird durch Spracherweiterungen erreicht. Sich also jetzt (besonders da es krachneu ist) mit glSlang zu befassen, um nicht ganz den Anschluss an kommende Entwicklungen im 3D-Bereich zu verlieren, ist der beste Weg.<br />
<br />
Also viel Spaß beim Experimentieren und Shaderschreiben! Und nicht vergessen : Wir wollen sehen was ihr so treibt,<br />
<br />
Euer<br />
:Sascha Willems<br />
<br />
<br />
<br />
{{TUTORIAL_NAVIGATION|-|[[Tutorial_glsl2]]}}<br />
[[Kategorie:Tutorial|GLSL]]</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_Vertexbufferobject&diff=22423Tutorial Vertexbufferobject2009-01-03T18:22:03Z<p>Ireyon: /* Weitere Vertexformate */</p>
<hr />
<div>=GL_ARB_Vertex_Buffer_Object=<br />
==Ave!==<br />
<br />
Und willkommen zu meinem zweiten GL_ARB-Extension Tutorial. Diesmal beschäftigen wir uns mit der recht neuen '''GL_ARB_Vertex_Buffer_Object'''-Extension, die vom ARB am 12.Februar 2003 fertiggestellt und vor kurzem auch als Corefeature in OpenGL 1.5 promoted wurde.<br />
<br />
VBOs (Vertex Buffer Object) sind quasi eine Erweiterung der recht weit verbreiteten Vertexarrays, die jedoch den großen Vorteil besitzen, dass ihre Vertexdaten serverseitig, also im schnellen VRAM der Grafikkarte, statt wie bei den VAs im Hauptspeicher, abgelegt werden. Das ist bei einer Displayliste zwar (fast) genauso der Fall, allerdings können die Daten nicht so einfach dynamisch geändert werden, was bei einem VBO aber bei Bedarf sehr einfach ist.<br />
<br />
Kurz gesagt vereint das VBO also die Flexibilität eines Vertexarrays mit der Geschwindigkeit einer Displayliste und eignet sich daher besonders für das Rendern aufwändiger Geometrie, egal ob statisch oder dynamisch.<br />
<br />
Eine weitere tolle Neuerung der VBO-API ist die Tatsache, dass man sich von OpenGL einen Pointer übergeben lassen kann, mit dessen Hilfe man Vertexdaten direkt in den VRAM schreiben kann. Dadurch spart man sich natürlich den Umweg über den Hauptspeicher und somit auch den erhöhten Speicherbedarf des Programmes beim Laden von Modellen o.&nbsp;Ä.<br />
<br />
VBOs bieten übrigens auch die Möglichkeit, sie je nach Anwendungsfall vom Grafikkartentreiber "optimieren" zu lassen. So legt man Vertexdaten, die im Nachhinein nicht mehr verändert werden sollenm am besten mit dem Schlüsselwort ''STATIC_DRAW_ARB'' ab, während dynamische Vertexdaten über ''DYNAMIC_DRAW_ARB'' abgelegt werden sollten. Doch dazu gibts später nähere Informationen.<br />
<br />
==Hardwareunterstützung==<br />
<br />
Da '''GL_ARB_Vertex_Buffer_Object''' eine noch recht junge Extension ist, siehts mit der Hardwareunterstützung besonders auf älteren 3D-Beschleunigern nicht so toll aus. Allerdings haben sowohl ATI als auch NVidia in ihre aktuellsten Treiber für ältere Beschleuniger Softwareemulationen dieser Extension eingebaut, hinter der wohl im Endeffekt ein simples Vertexarray seinen Dienst verrichtet. Von daher wirds auf älteren Karten keine Geschwindigkeitszuwächse ggü. Vertexarrays geben, aber man erspart sich das umständliche Schreiben mehrerer Renderpfade für verschiedene Grafikkartengenerationen. (Infos zur Unterstützung von '''GL_ARB_VBO''' gibts [http://www.delphi3d.net/hardware/extsupport.php?extension=GL_ARB_vertex_buffer_object hier]).<br />
<br />
==Erstellen des Vertexbufferobjects==<br />
<br />
Wie der Name vermuten lässt, handelt es sich beim VBO um ein Objekt, welches in Sachen Syntax an die anderen in OpenGL verfügbaren Objekte wie z.&nbsp;B. Texturenobjekte angelehnt ist. Das Erstellen eines VBOs geht daher genauso einfach von der Hand:<br />
<br />
<pascal><br />
glGenBuffersARB(1, @VBO);<br />
</pascal><br />
<br />
Nun sollten wir in VBO eine gültige ID zurückgeliefert bekommen haben, welche ein Vertex buffer object referenziert.<br />
<br />
Nachdem wir nun ein VBO erstellt haben, sollten wir dieses natürlich auch "aktivieren" (also binden), damit wir mit ihm arbeiten können:<br />
<br />
<pascal><br />
glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBO);<br />
glEnableClientState(GL_VERTEX_ARRAY);<br />
</pascal><br />
<br />
Wie erwähnt binden wir in der ersten Zeile unser frisch erstelltes VBO, während wir in der zweiten Zeile Vertexarrays auf der Clientseite aktivieren. Dies ist deshalb nötig, da wir das VBO später genauso zeichnen werden wie dies bei einem normalen VA der Fall wäre.<br />
<br />
==Übergabe der Vertexdaten (Variante 1: Ohne Umweg über den Hauptspeicher)==<br />
<br />
Wie in der Einleitung bereits erwähnt, bietet uns die VBO-API die Möglichkeit, unsere Vertexdaten ohne Umweg über den Hauptspeicher direkt in den Grafikkartenspeicher zu schreiben. Da dies eines der besten Features dieser neuen Extension ist, wollen wir uns auch zuerst mit dieser Übergabemethode beschäftigen.<br />
<br />
''Allerdings erstmal eine kleine Warnung vorweg: Pointer sind ein recht komplexes Thema, und ihre Nutzung erfordert einige Kenntnisse. Wer weder weiß, wie man damit umgeht, noch wie man sie einsetzt, sollte sich da zuerstmal schlau machen. Denn mit Pointer kann man ganz böse Sachen machen, wenn man sie nicht richtig nutzen kann!''<br />
<br />
Um unsere Vertexdaten als Pointer zu nutzen,müssen wir erstmal eine Vertexstruktur erstellen und auch einen passenden Pointertyp. Der Pointertyp ist zwar nicht zwingend (ein ^ täte es auch), aber macht die Sache übersichtlicher:<br />
<br />
<pascal><br />
PVertex = ^TVertex;<br />
TVertex = packed record<br />
S,T,X,Y,Z : TGLFloat;<br />
end;<br />
</pascal><br />
<br />
Sicher fragen sich einige von euch jetzt, warum unser TVertex gerade so aussieht (also zuerst S,T und dann der Rest). Das liegt daran, dass wir unser VBO später über die Funktion [[glInterleavedArrays]] rendern werden, welche wissen will, wie unsere Vertexdaten im Speicher abgelegt sind. Unsere Vertexstruktur entspricht dabei dem Format ''GL_T2F_V3F''. ''T2F'' entspricht 2 Floats für die Texturenkoordinaten (S&T), gefolgt von ''V3F'' für die drei Floatwerte die unsere Vertexkoordinaten repräsentieren. Wenn ihr ein anderes Vertexformat verwendet, müsst ihr euch auch ein passendes Vertexformat aus der Spezifikation heraussuchen.<br />
Ich verwende oben übrigens einen '''packed Record''' (auch wenn dies bei unserem Vertexformat nicht unbedingt ein Muss ist). Dies tue ich deshalb, da Delphi Werte bei einem normalen Record für einen beschleunigten Zugriff an einem (Double)Word-Raster ausrichtet und dies deshalb bei bestimmten Vertexformaten Probleme bereiten könnte.<br />
<br />
Hoffe mal das euch obiges klar ist,denn jetzt gehts ans Eingemachte. Wir kommen zum "schwersten" (ist ja ein relativer Begriff) Teil des Tutorials, nämlich der Übergabe der Vertexdaten an das VBO. Dazu müssen wir OpenGL zuerstmal mitteilen, wie groß unser VBO eigentlich sein soll,damit wir auch genug Platz im VRAM bekommen:<br />
<br />
<pascal><br />
glBufferDataARB(GL_ARRAY_BUFFER_ARB, VertexAnzahl*SizeOf(TVertex), nil, GL_STATIC_DRAW_ARB);<br />
</pascal><br />
<br />
Schauen wir uns also mal die Parameter von [[glBufferDataARB]] an. Zuerst müssen wir OpenGL mitteilen, was für einen Puffer wir erstellen wollen (in zukünftigen Versionen werden wohl noch andere folgen, z.&nbsp;B. Überbuffer). In unserem Falle also einen Arraypuffer. Parameter zwei übergibt dann den Speicherplatz, den wir reservieren wollen und ist recht selbsterklärend. Der dritte Parameter würde normalerweise einen Pointer auf unsere Vertexdaten enthalten. Da wir diese aber nicht im Hauptspeicher abgelegt haben, sondern über einen Pointer dort noch ablegen wollen, übergeben wir hier '''nil'''. OpenGL nimmt dies zur Kenntnis und spuckt dementsprechend auch keinen Fehler aus.<br />
<br />
Den letzten Parameter hab ich auch schon kurz in der Einführung angesprochen. Er teilt OpenGL mit, auf welchen Anwendungsfall unser VBO optimiert werden soll. Dabei gibt es folgende Anwendungsfälle,die ihr euch dann entsprechend eurer VBO-Verwendung aussuchen könnt:<br />
<br />
{|rules="all" border = "1"<br />
! STREAM_DRAW_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann eher selten als Quelle für OpenGL-Befehle genutzt.<br />
|-<br />
! STREAM_READ_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann selten von der Anwendung angefordert.<br />
|-<br />
! STREAM_COPY_ARB<br />
| Die Vertexdaten werden einmal durch Kopieren von der GL übergeben und dann eher selten als Quelle für OpenGL-Befehle genutzt.<br />
|-<br />
! STATIC_DRAW_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! STATIC_READ_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann recht oft von der Anwendung angefordert.<br />
|-<br />
! STATIC_COPY_ARB<br />
| Die Vertexdaten werden einmal durch Kopieren von der GL übergeben und dann recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! DYNAMIC_DRAW_ARB<br />
| Die Vertexdaten werden wiederholt angegeben (verändert) und recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! DYNAMIC_READ_ARB<br />
| Die Vertexdaten werden wiederholt durch das Auslesen von Daten durch OpenGL angegeben (verändert) und oft von der Anwendung angefordert.<br />
|-<br />
! DYNAMIC_COPY_ARB<br />
| Die Vertexdaten werden wiederholt durch das Auslesen von Daten durch OpenGL angegeben (verändert) und recht oft zum Rendern genutzt.<br />
|}<br />
<br />
Beachtet werden sollte, dass es sich bei obigen Anwendungsmuster nur um Hinweise (Hints) handelt, die es auch in anderen Bereichen von OpenGL gibt. Diese Hinweise sind allerdings nicht verbindlich und werden von Treiber zu Treiber unterschiedlich behandelt.<br />
<br />
Nachdem OpenGL nun weiss was für ein VBO wir genau haben wollen, brauchen wir letztendlich nur noch einen Pointer, der "auf" das VBO zeigt:<br />
<br />
<pascal><br />
VBOPointer := glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);<br />
</pascal><br />
<br />
Die Funktion [[glMapBufferARB]] liefert uns einen Pointer zurück, der den Speicherplatz des VBOs in den Adressraum des Clients "ummappt", also in den Hauptspeicher. Dadurch wird es dann möglich, über eine Adresse im Hauptspeicher des Rechners direkt in den Grafikkartenspeicher zu schreiben. Der zweite Parameter gibt übrigens an, das wir in das VBO schreiben wollen. Der einzige weitere zulässige Parameter ist hier '''GL_READ_ONLY_ARB''', welcher zum Auslesen des VBOs dient.<br />
<br />
Endlich haben wir einen Pointer auf unser inzwischen auch im VRAM der Grafikkarte richtig dimensioniertes Vertexbufferobject, und sind nun in der Lage dort unsere Vertexdaten abzulegen. Was jetzt folgt ist ein wenig Pseudo-Code der diesen Vorgang darstellt. Pseudo-Code deshalb, weil die Vertexdaten die ins VBO kommen ja in jeder Anwendung unterschiedlich sind, und man so auch selbst ein wenig denken muss:<br />
<br />
<pascal><br />
VertexDatenLaenge := 0;<br />
for i := 0 to High(MeineVertexDaten) do<br />
begin<br />
VertexPointer := VBOPointer;<br />
<br />
VertexPointer^.X := GeneriertesVertex.X;<br />
VertexPointer^.Y := GeneriertesVertex.Y;<br />
VertexPointer^.Z := GeneriertesVertex.Z;<br />
VertexPointer^.S := GeneriertesVertex.S;<br />
VertexPointer^.T := GeneriertesVertex.T;<br />
<br />
inc(Integer(VBOPointer), SizeOf(TVertex));<br />
<br />
inc(VertexDatenLaenge);<br />
end;<br />
</pascal><br />
<br />
Da sich dieses Tutorial (und v.&nbsp;a. diese Methode der Vertexdatenübergabe) an die fortgeschritteneren wendet, rede ich nicht lange um den heissen Brei herum. Wir durchlaufen also eine Schleife, in der wir unsere Vertexdaten dynamisch erstellen. Entweder in jedem Durchlauf, oder wir laden die Daten vor der Schleife z.&nbsp;B. aus einem 3D-Modell und entfernen sie danach aus dem Hauptspeicher. Erste Methode spart sowohl beim Programmstart, also auch während des Programmlaufes Arbeitsspeicher, während letztere dies nur während des Programmablaufs macht.<br />
<br />
Wir setzen die Adresse des Pointers der auf das momentan zu verändernde Vertex zeigt also auf die aktuelle Zeigerposition im VBO und schreiben dann unser Vertex direkt in den Grafikkartenspeicher. Ist dies getan, müssen wir die Adresse unseres VBOPointers natürlich um die Größe des gerade geschriebenen Vertexes erhöhen. Der Typecast auf ein Integer geschieht deshalb, weil man Pointer in Delphi nicht so einfach erhöhen oder verringern kann, Integers hingegen schon.<br />
<br />
Ich gehe mal davon aus,das meine Leserschaft mit obigem Codeschnippsel keinerlei Probleme hatte und bringe diese Methode der Übergabe von Vertexdaten an das VBO dann auch mit folgender Quellcodezeile zu Ende:<br />
<br />
<pascal><br />
glUnMapBufferARB(GL_ARRAY_BUFFER_ARB);<br />
</pascal><br />
<br />
Wie unschwer zu erraten geben wir hier unser VBO für den weiteren Zugriff wieder frei. Dies ist nötig um dies später rendern zu können.<br />
<br />
==Übergabe der Vertexdaten (Variante 2: Über den Hauptspeicher)==<br />
<br />
Wem obige Variante nicht zugesagt hat, weil sie ihm zu komplex war, oder weil er seine Vertexdaten aus diversen Gründen im Hauptspeicher braucht, dem wird in diesem Abschnitt geholfen.<br />
<br />
Das Übergeben der Vertexdaten an das VBO über den Hauptspeicher gestaltet sich nämlich sehr einfach:<br />
<br />
<pascal><br />
glBufferDataARB(GL_ARRAY_BUFFER_ARB, SizeOf(VertexDaten), @VertexDaten, GL_STATIC_DRAW_ARB);<br />
</pascal><br />
<br />
Sieht einfach aus,hört sich einfach an und ist auch einfach! Statt wie im letzten Kapitel für die Vertexdaten einen Zeiger auf '''nil''' zu übergeben, übergeben wir OpenGL jetzt einen direkten Zeiger auf die Vertexdaten die im Hauptspeicher abgelegt sind. Die Daten werden dann zum VBO hochgeladen, und können anschliessen auch wieder aus dem Hauptspeicher entfernt werden.<br />
<br />
==Rendern des VBOs==<br />
<br />
Kommen wir nun also schon zum Ende unseres VBO-Tutorials. Dank der Einfachheit dieser Extension gabs halt einfach nicht mehr zu erklären. Das Zeichnen gestaltet sich nämlich OpenGL-typisch mehr als einfach:<br />
<br />
<pascal><br />
glInterleavedArrays(GL_T2F_V3F, SizeOf(TVertex), nil);<br />
glDrawArrays(GL_QUADS, 0, VertexDatenLaenge);<br />
</pascal><br />
<br />
Schnell gezeichnet und schnell erklärt. Der erste Parameter gibt an, in welchem Format die Vertexdaten unseres VBOs abgelegt wurden (wir erinnern uns: '''GL_T2F_V3F'''), während der zweite Parameter den Speicherplatz, der zwischen zwei Vertices liegt, angibt. Würden wir ein einfaches Vertexarray zeichnen, so wäre der letzte Parameter ein Pointer auf die Vertexdaten im Hauptspeicher. Da wir allerdings vorher unser VBO gebunden haben, teilt hier ein '''nil''' mit, dass die vorher ins VBO geschriebenen Vertexdaten gerendert werden sollen.<br />
<br />
Mittels [[glDrawArrays]] sagen wir OpenGL dann noch, das es unsere VBO-Daten als Quads rendern soll.<br />
<br />
==Freigabe==<br />
<br />
Wie mit allen Objekten in der OpenGL ists auch keine schlechte Idee, unser VBO freizugeben,wenn es nicht mehr benötigt wird. Und obwohl beim Löschen des Renderkontextes solche Objekte vom Grafikkartentreiber freigegeben werden sollten, wäre es keine schlechte Idee des selbst zu erledigen,für den Fall das der Treiber da schlampig arbeitet:<br />
<br />
<pascal><br />
glDeleteBuffersARB(1, @VBO);<br />
</pascal><br />
<br />
==Weitere Vertexformate==<br />
Wem allein Texturkoordinaten und Vertices nicht reichen, sondern auch Farbangaben oder Vertexnormalen braucht, muss sich eines der folgenden Formate aussuchen. Die Konstantennamen sind logisch aufgebaut: ''GL_AngabeAnzahlTyp_AngabeAnzahlTyp...''<br />
Als Angabe gibt es '''V'''ertex, '''T'''exturkoordinaten, '''N'''ormalen und '''C'''olor-Werte, also Farben.<br />
Anzahl ist die Anzahl der Komponenten pro Angabe (V3 wären zum Bespiel die X, Y und Z-Koordinaten eines dreidimensionalen Vertex).<br />
Typ ist entweder '''F'''loat oder '''U'''nsigned '''Byte'''. Die Vertexdaten müssen in der richtigen Reihenfolge mit den richtigen Typen an das VBO übergeben werden.<br />
<br />
<pascal><br />
GL_V2F<br />
GL_V3F<br />
GL_C4UB_V2F<br />
GL_C4UB_V3F<br />
GL_C3F_V3F <br />
GL_N3F_V3F <br />
GL_C4F_N3F_V3F <br />
GL_T2F_V3F<br />
GL_T4F_V4F<br />
GL_T2F_C4UB_V3F<br />
GL_T2F_C3F_V3F<br />
GL_T2F_N3F_V3F // Wohl eines der sinnvollsten Vertexformate. 2 Floats für Texturkoordinaten, 3 Floats für den Normalenvektor und 3 Floats für den eigentlichen Vertex.<br />
GL_T2F_C4F_N3F_V3F<br />
GL_T4F_C4F_N3F_V4F<br />
</pascal><br />
<br />
Es ist sinnvoll, sich für jedes der Vertexformate, die man verwendet, einen Typen anzulegen, um später einfacher mit den VBOs arbeiten zu können.<br />
<br />
==Nachwort==<br />
<br />
Das wars auch schon. Wie zu erkennen ist das Vertexbufferobjekt mal wieder eine gut durchdachte, recht nützliche und zudem auch weit nutzbare Erweiterung, die OpenGL technisch wieder einen Schritt weitergebracht hat.<br />
<br />
Wer mehr wissen will, der sollte sich unbedingt mal die passende [http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt Spezifikation] ansehen.<br />
<br />
Hoffe das Tutorial hat euch soviel Spaß gemacht wie mir das Verfassen des selbigen und ich hoffe auf reges Feedback!<br />
<br />
Euer<br />
<br />
Sascha Willems (webmaster_at_delphigl.de)<br />
<br />
==Links==<br />
[http://developer.nvidia.com/attach/6427 Nvidia: Using Vertex Buffer Objects]<br />
<br />
[http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt Spezifikation]<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_Cubemap]]|[[Tutorial_Vertexprogramme]]}}<br />
<br />
[[Kategorie:Tutorial|Vertexbuffer]]</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_Vertexbufferobject&diff=22422Tutorial Vertexbufferobject2009-01-03T18:21:12Z<p>Ireyon: /* Rendern des VBOs */</p>
<hr />
<div>=GL_ARB_Vertex_Buffer_Object=<br />
==Ave!==<br />
<br />
Und willkommen zu meinem zweiten GL_ARB-Extension Tutorial. Diesmal beschäftigen wir uns mit der recht neuen '''GL_ARB_Vertex_Buffer_Object'''-Extension, die vom ARB am 12.Februar 2003 fertiggestellt und vor kurzem auch als Corefeature in OpenGL 1.5 promoted wurde.<br />
<br />
VBOs (Vertex Buffer Object) sind quasi eine Erweiterung der recht weit verbreiteten Vertexarrays, die jedoch den großen Vorteil besitzen, dass ihre Vertexdaten serverseitig, also im schnellen VRAM der Grafikkarte, statt wie bei den VAs im Hauptspeicher, abgelegt werden. Das ist bei einer Displayliste zwar (fast) genauso der Fall, allerdings können die Daten nicht so einfach dynamisch geändert werden, was bei einem VBO aber bei Bedarf sehr einfach ist.<br />
<br />
Kurz gesagt vereint das VBO also die Flexibilität eines Vertexarrays mit der Geschwindigkeit einer Displayliste und eignet sich daher besonders für das Rendern aufwändiger Geometrie, egal ob statisch oder dynamisch.<br />
<br />
Eine weitere tolle Neuerung der VBO-API ist die Tatsache, dass man sich von OpenGL einen Pointer übergeben lassen kann, mit dessen Hilfe man Vertexdaten direkt in den VRAM schreiben kann. Dadurch spart man sich natürlich den Umweg über den Hauptspeicher und somit auch den erhöhten Speicherbedarf des Programmes beim Laden von Modellen o.&nbsp;Ä.<br />
<br />
VBOs bieten übrigens auch die Möglichkeit, sie je nach Anwendungsfall vom Grafikkartentreiber "optimieren" zu lassen. So legt man Vertexdaten, die im Nachhinein nicht mehr verändert werden sollenm am besten mit dem Schlüsselwort ''STATIC_DRAW_ARB'' ab, während dynamische Vertexdaten über ''DYNAMIC_DRAW_ARB'' abgelegt werden sollten. Doch dazu gibts später nähere Informationen.<br />
<br />
==Hardwareunterstützung==<br />
<br />
Da '''GL_ARB_Vertex_Buffer_Object''' eine noch recht junge Extension ist, siehts mit der Hardwareunterstützung besonders auf älteren 3D-Beschleunigern nicht so toll aus. Allerdings haben sowohl ATI als auch NVidia in ihre aktuellsten Treiber für ältere Beschleuniger Softwareemulationen dieser Extension eingebaut, hinter der wohl im Endeffekt ein simples Vertexarray seinen Dienst verrichtet. Von daher wirds auf älteren Karten keine Geschwindigkeitszuwächse ggü. Vertexarrays geben, aber man erspart sich das umständliche Schreiben mehrerer Renderpfade für verschiedene Grafikkartengenerationen. (Infos zur Unterstützung von '''GL_ARB_VBO''' gibts [http://www.delphi3d.net/hardware/extsupport.php?extension=GL_ARB_vertex_buffer_object hier]).<br />
<br />
==Erstellen des Vertexbufferobjects==<br />
<br />
Wie der Name vermuten lässt, handelt es sich beim VBO um ein Objekt, welches in Sachen Syntax an die anderen in OpenGL verfügbaren Objekte wie z.&nbsp;B. Texturenobjekte angelehnt ist. Das Erstellen eines VBOs geht daher genauso einfach von der Hand:<br />
<br />
<pascal><br />
glGenBuffersARB(1, @VBO);<br />
</pascal><br />
<br />
Nun sollten wir in VBO eine gültige ID zurückgeliefert bekommen haben, welche ein Vertex buffer object referenziert.<br />
<br />
Nachdem wir nun ein VBO erstellt haben, sollten wir dieses natürlich auch "aktivieren" (also binden), damit wir mit ihm arbeiten können:<br />
<br />
<pascal><br />
glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBO);<br />
glEnableClientState(GL_VERTEX_ARRAY);<br />
</pascal><br />
<br />
Wie erwähnt binden wir in der ersten Zeile unser frisch erstelltes VBO, während wir in der zweiten Zeile Vertexarrays auf der Clientseite aktivieren. Dies ist deshalb nötig, da wir das VBO später genauso zeichnen werden wie dies bei einem normalen VA der Fall wäre.<br />
<br />
==Übergabe der Vertexdaten (Variante 1: Ohne Umweg über den Hauptspeicher)==<br />
<br />
Wie in der Einleitung bereits erwähnt, bietet uns die VBO-API die Möglichkeit, unsere Vertexdaten ohne Umweg über den Hauptspeicher direkt in den Grafikkartenspeicher zu schreiben. Da dies eines der besten Features dieser neuen Extension ist, wollen wir uns auch zuerst mit dieser Übergabemethode beschäftigen.<br />
<br />
''Allerdings erstmal eine kleine Warnung vorweg: Pointer sind ein recht komplexes Thema, und ihre Nutzung erfordert einige Kenntnisse. Wer weder weiß, wie man damit umgeht, noch wie man sie einsetzt, sollte sich da zuerstmal schlau machen. Denn mit Pointer kann man ganz böse Sachen machen, wenn man sie nicht richtig nutzen kann!''<br />
<br />
Um unsere Vertexdaten als Pointer zu nutzen,müssen wir erstmal eine Vertexstruktur erstellen und auch einen passenden Pointertyp. Der Pointertyp ist zwar nicht zwingend (ein ^ täte es auch), aber macht die Sache übersichtlicher:<br />
<br />
<pascal><br />
PVertex = ^TVertex;<br />
TVertex = packed record<br />
S,T,X,Y,Z : TGLFloat;<br />
end;<br />
</pascal><br />
<br />
Sicher fragen sich einige von euch jetzt, warum unser TVertex gerade so aussieht (also zuerst S,T und dann der Rest). Das liegt daran, dass wir unser VBO später über die Funktion [[glInterleavedArrays]] rendern werden, welche wissen will, wie unsere Vertexdaten im Speicher abgelegt sind. Unsere Vertexstruktur entspricht dabei dem Format ''GL_T2F_V3F''. ''T2F'' entspricht 2 Floats für die Texturenkoordinaten (S&T), gefolgt von ''V3F'' für die drei Floatwerte die unsere Vertexkoordinaten repräsentieren. Wenn ihr ein anderes Vertexformat verwendet, müsst ihr euch auch ein passendes Vertexformat aus der Spezifikation heraussuchen.<br />
Ich verwende oben übrigens einen '''packed Record''' (auch wenn dies bei unserem Vertexformat nicht unbedingt ein Muss ist). Dies tue ich deshalb, da Delphi Werte bei einem normalen Record für einen beschleunigten Zugriff an einem (Double)Word-Raster ausrichtet und dies deshalb bei bestimmten Vertexformaten Probleme bereiten könnte.<br />
<br />
Hoffe mal das euch obiges klar ist,denn jetzt gehts ans Eingemachte. Wir kommen zum "schwersten" (ist ja ein relativer Begriff) Teil des Tutorials, nämlich der Übergabe der Vertexdaten an das VBO. Dazu müssen wir OpenGL zuerstmal mitteilen, wie groß unser VBO eigentlich sein soll,damit wir auch genug Platz im VRAM bekommen:<br />
<br />
<pascal><br />
glBufferDataARB(GL_ARRAY_BUFFER_ARB, VertexAnzahl*SizeOf(TVertex), nil, GL_STATIC_DRAW_ARB);<br />
</pascal><br />
<br />
Schauen wir uns also mal die Parameter von [[glBufferDataARB]] an. Zuerst müssen wir OpenGL mitteilen, was für einen Puffer wir erstellen wollen (in zukünftigen Versionen werden wohl noch andere folgen, z.&nbsp;B. Überbuffer). In unserem Falle also einen Arraypuffer. Parameter zwei übergibt dann den Speicherplatz, den wir reservieren wollen und ist recht selbsterklärend. Der dritte Parameter würde normalerweise einen Pointer auf unsere Vertexdaten enthalten. Da wir diese aber nicht im Hauptspeicher abgelegt haben, sondern über einen Pointer dort noch ablegen wollen, übergeben wir hier '''nil'''. OpenGL nimmt dies zur Kenntnis und spuckt dementsprechend auch keinen Fehler aus.<br />
<br />
Den letzten Parameter hab ich auch schon kurz in der Einführung angesprochen. Er teilt OpenGL mit, auf welchen Anwendungsfall unser VBO optimiert werden soll. Dabei gibt es folgende Anwendungsfälle,die ihr euch dann entsprechend eurer VBO-Verwendung aussuchen könnt:<br />
<br />
{|rules="all" border = "1"<br />
! STREAM_DRAW_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann eher selten als Quelle für OpenGL-Befehle genutzt.<br />
|-<br />
! STREAM_READ_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann selten von der Anwendung angefordert.<br />
|-<br />
! STREAM_COPY_ARB<br />
| Die Vertexdaten werden einmal durch Kopieren von der GL übergeben und dann eher selten als Quelle für OpenGL-Befehle genutzt.<br />
|-<br />
! STATIC_DRAW_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! STATIC_READ_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann recht oft von der Anwendung angefordert.<br />
|-<br />
! STATIC_COPY_ARB<br />
| Die Vertexdaten werden einmal durch Kopieren von der GL übergeben und dann recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! DYNAMIC_DRAW_ARB<br />
| Die Vertexdaten werden wiederholt angegeben (verändert) und recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! DYNAMIC_READ_ARB<br />
| Die Vertexdaten werden wiederholt durch das Auslesen von Daten durch OpenGL angegeben (verändert) und oft von der Anwendung angefordert.<br />
|-<br />
! DYNAMIC_COPY_ARB<br />
| Die Vertexdaten werden wiederholt durch das Auslesen von Daten durch OpenGL angegeben (verändert) und recht oft zum Rendern genutzt.<br />
|}<br />
<br />
Beachtet werden sollte, dass es sich bei obigen Anwendungsmuster nur um Hinweise (Hints) handelt, die es auch in anderen Bereichen von OpenGL gibt. Diese Hinweise sind allerdings nicht verbindlich und werden von Treiber zu Treiber unterschiedlich behandelt.<br />
<br />
Nachdem OpenGL nun weiss was für ein VBO wir genau haben wollen, brauchen wir letztendlich nur noch einen Pointer, der "auf" das VBO zeigt:<br />
<br />
<pascal><br />
VBOPointer := glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);<br />
</pascal><br />
<br />
Die Funktion [[glMapBufferARB]] liefert uns einen Pointer zurück, der den Speicherplatz des VBOs in den Adressraum des Clients "ummappt", also in den Hauptspeicher. Dadurch wird es dann möglich, über eine Adresse im Hauptspeicher des Rechners direkt in den Grafikkartenspeicher zu schreiben. Der zweite Parameter gibt übrigens an, das wir in das VBO schreiben wollen. Der einzige weitere zulässige Parameter ist hier '''GL_READ_ONLY_ARB''', welcher zum Auslesen des VBOs dient.<br />
<br />
Endlich haben wir einen Pointer auf unser inzwischen auch im VRAM der Grafikkarte richtig dimensioniertes Vertexbufferobject, und sind nun in der Lage dort unsere Vertexdaten abzulegen. Was jetzt folgt ist ein wenig Pseudo-Code der diesen Vorgang darstellt. Pseudo-Code deshalb, weil die Vertexdaten die ins VBO kommen ja in jeder Anwendung unterschiedlich sind, und man so auch selbst ein wenig denken muss:<br />
<br />
<pascal><br />
VertexDatenLaenge := 0;<br />
for i := 0 to High(MeineVertexDaten) do<br />
begin<br />
VertexPointer := VBOPointer;<br />
<br />
VertexPointer^.X := GeneriertesVertex.X;<br />
VertexPointer^.Y := GeneriertesVertex.Y;<br />
VertexPointer^.Z := GeneriertesVertex.Z;<br />
VertexPointer^.S := GeneriertesVertex.S;<br />
VertexPointer^.T := GeneriertesVertex.T;<br />
<br />
inc(Integer(VBOPointer), SizeOf(TVertex));<br />
<br />
inc(VertexDatenLaenge);<br />
end;<br />
</pascal><br />
<br />
Da sich dieses Tutorial (und v.&nbsp;a. diese Methode der Vertexdatenübergabe) an die fortgeschritteneren wendet, rede ich nicht lange um den heissen Brei herum. Wir durchlaufen also eine Schleife, in der wir unsere Vertexdaten dynamisch erstellen. Entweder in jedem Durchlauf, oder wir laden die Daten vor der Schleife z.&nbsp;B. aus einem 3D-Modell und entfernen sie danach aus dem Hauptspeicher. Erste Methode spart sowohl beim Programmstart, also auch während des Programmlaufes Arbeitsspeicher, während letztere dies nur während des Programmablaufs macht.<br />
<br />
Wir setzen die Adresse des Pointers der auf das momentan zu verändernde Vertex zeigt also auf die aktuelle Zeigerposition im VBO und schreiben dann unser Vertex direkt in den Grafikkartenspeicher. Ist dies getan, müssen wir die Adresse unseres VBOPointers natürlich um die Größe des gerade geschriebenen Vertexes erhöhen. Der Typecast auf ein Integer geschieht deshalb, weil man Pointer in Delphi nicht so einfach erhöhen oder verringern kann, Integers hingegen schon.<br />
<br />
Ich gehe mal davon aus,das meine Leserschaft mit obigem Codeschnippsel keinerlei Probleme hatte und bringe diese Methode der Übergabe von Vertexdaten an das VBO dann auch mit folgender Quellcodezeile zu Ende:<br />
<br />
<pascal><br />
glUnMapBufferARB(GL_ARRAY_BUFFER_ARB);<br />
</pascal><br />
<br />
Wie unschwer zu erraten geben wir hier unser VBO für den weiteren Zugriff wieder frei. Dies ist nötig um dies später rendern zu können.<br />
<br />
==Übergabe der Vertexdaten (Variante 2: Über den Hauptspeicher)==<br />
<br />
Wem obige Variante nicht zugesagt hat, weil sie ihm zu komplex war, oder weil er seine Vertexdaten aus diversen Gründen im Hauptspeicher braucht, dem wird in diesem Abschnitt geholfen.<br />
<br />
Das Übergeben der Vertexdaten an das VBO über den Hauptspeicher gestaltet sich nämlich sehr einfach:<br />
<br />
<pascal><br />
glBufferDataARB(GL_ARRAY_BUFFER_ARB, SizeOf(VertexDaten), @VertexDaten, GL_STATIC_DRAW_ARB);<br />
</pascal><br />
<br />
Sieht einfach aus,hört sich einfach an und ist auch einfach! Statt wie im letzten Kapitel für die Vertexdaten einen Zeiger auf '''nil''' zu übergeben, übergeben wir OpenGL jetzt einen direkten Zeiger auf die Vertexdaten die im Hauptspeicher abgelegt sind. Die Daten werden dann zum VBO hochgeladen, und können anschliessen auch wieder aus dem Hauptspeicher entfernt werden.<br />
<br />
==Rendern des VBOs==<br />
<br />
Kommen wir nun also schon zum Ende unseres VBO-Tutorials. Dank der Einfachheit dieser Extension gabs halt einfach nicht mehr zu erklären. Das Zeichnen gestaltet sich nämlich OpenGL-typisch mehr als einfach:<br />
<br />
<pascal><br />
glInterleavedArrays(GL_T2F_V3F, SizeOf(TVertex), nil);<br />
glDrawArrays(GL_QUADS, 0, VertexDatenLaenge);<br />
</pascal><br />
<br />
Schnell gezeichnet und schnell erklärt. Der erste Parameter gibt an, in welchem Format die Vertexdaten unseres VBOs abgelegt wurden (wir erinnern uns: '''GL_T2F_V3F'''), während der zweite Parameter den Speicherplatz, der zwischen zwei Vertices liegt, angibt. Würden wir ein einfaches Vertexarray zeichnen, so wäre der letzte Parameter ein Pointer auf die Vertexdaten im Hauptspeicher. Da wir allerdings vorher unser VBO gebunden haben, teilt hier ein '''nil''' mit, dass die vorher ins VBO geschriebenen Vertexdaten gerendert werden sollen.<br />
<br />
Mittels [[glDrawArrays]] sagen wir OpenGL dann noch, das es unsere VBO-Daten als Quads rendern soll.<br />
<br />
==Freigabe==<br />
<br />
Wie mit allen Objekten in der OpenGL ists auch keine schlechte Idee, unser VBO freizugeben,wenn es nicht mehr benötigt wird. Und obwohl beim Löschen des Renderkontextes solche Objekte vom Grafikkartentreiber freigegeben werden sollten, wäre es keine schlechte Idee des selbst zu erledigen,für den Fall das der Treiber da schlampig arbeitet:<br />
<br />
<pascal><br />
glDeleteBuffersARB(1, @VBO);<br />
</pascal><br />
<br />
==Weitere Vertexformate==<br />
Wem allein Texturkoordinaten und Vertices nicht reichen, sondern auch Farbangaben oder Vertexnormalen braucht, muss sich eines der folgenden Formate aussuchen. Die Konstantennamen sind logisch aufgebaut: ''GL_AngabeAnzahlTyp_AngabeAnzahlTyp...''<br />
Als Angabe gibt es '''V'''ertex, '''T'''exturkoordinaten, '''N'''ormalen und '''C'''olor-Werte, also Farben.<br />
Anzahl ist die Anzahl der Komponenten pro Angabe (V3 wären zum Bespiel die X, Y und Z-Koordinaten eines dreidimensionalen Vertex).<br />
Typ ist entweder '''F'''loat oder '''U'''nsigned '''Byte'''. Die Vertexdaten müssen in der richtigen Reihenfolge mit den richtigen Typen an das VBO übergeben werden.<br />
<br />
<pascal><br />
GL_V2F<br />
GL_V3F<br />
GL_C4UB_V2F<br />
GL_C4UB_V3F<br />
GL_C3F_V3F <br />
GL_N3F_V3F <br />
GL_C4F_N3F_V3F <br />
GL_T2F_V3F<br />
GL_T4F_V4F<br />
GL_T2F_C4UB_V3F<br />
GL_T2F_C3F_V3F<br />
GL_T2F_N3F_V3F // Wohl eine der sinnvollsten Vertexformate. 2 Floats für Texturkoordinaten, 3 Floats für den Normalenvektor und 3 Floats für den eigentlichen Vertex.<br />
GL_T2F_C4F_N3F_V3F<br />
GL_T4F_C4F_N3F_V4F<br />
</pascal><br />
<br />
Es ist sinnvoll, sich für jedes der Vertexformate, die man verwendet, einen Typen anzulegen, um später einfacher mit den VBOs arbeiten zu können.<br />
<br />
==Nachwort==<br />
<br />
Das wars auch schon. Wie zu erkennen ist das Vertexbufferobjekt mal wieder eine gut durchdachte, recht nützliche und zudem auch weit nutzbare Erweiterung, die OpenGL technisch wieder einen Schritt weitergebracht hat.<br />
<br />
Wer mehr wissen will, der sollte sich unbedingt mal die passende [http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt Spezifikation] ansehen.<br />
<br />
Hoffe das Tutorial hat euch soviel Spaß gemacht wie mir das Verfassen des selbigen und ich hoffe auf reges Feedback!<br />
<br />
Euer<br />
<br />
Sascha Willems (webmaster_at_delphigl.de)<br />
<br />
==Links==<br />
[http://developer.nvidia.com/attach/6427 Nvidia: Using Vertex Buffer Objects]<br />
<br />
[http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt Spezifikation]<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_Cubemap]]|[[Tutorial_Vertexprogramme]]}}<br />
<br />
[[Kategorie:Tutorial|Vertexbuffer]]</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_Vertexbufferobject&diff=22421Tutorial Vertexbufferobject2009-01-03T18:19:50Z<p>Ireyon: /* GL_ARB_Vertex_Buffer_Object */</p>
<hr />
<div>=GL_ARB_Vertex_Buffer_Object=<br />
==Ave!==<br />
<br />
Und willkommen zu meinem zweiten GL_ARB-Extension Tutorial. Diesmal beschäftigen wir uns mit der recht neuen '''GL_ARB_Vertex_Buffer_Object'''-Extension, die vom ARB am 12.Februar 2003 fertiggestellt und vor kurzem auch als Corefeature in OpenGL 1.5 promoted wurde.<br />
<br />
VBOs (Vertex Buffer Object) sind quasi eine Erweiterung der recht weit verbreiteten Vertexarrays, die jedoch den großen Vorteil besitzen, dass ihre Vertexdaten serverseitig, also im schnellen VRAM der Grafikkarte, statt wie bei den VAs im Hauptspeicher, abgelegt werden. Das ist bei einer Displayliste zwar (fast) genauso der Fall, allerdings können die Daten nicht so einfach dynamisch geändert werden, was bei einem VBO aber bei Bedarf sehr einfach ist.<br />
<br />
Kurz gesagt vereint das VBO also die Flexibilität eines Vertexarrays mit der Geschwindigkeit einer Displayliste und eignet sich daher besonders für das Rendern aufwändiger Geometrie, egal ob statisch oder dynamisch.<br />
<br />
Eine weitere tolle Neuerung der VBO-API ist die Tatsache, dass man sich von OpenGL einen Pointer übergeben lassen kann, mit dessen Hilfe man Vertexdaten direkt in den VRAM schreiben kann. Dadurch spart man sich natürlich den Umweg über den Hauptspeicher und somit auch den erhöhten Speicherbedarf des Programmes beim Laden von Modellen o.&nbsp;Ä.<br />
<br />
VBOs bieten übrigens auch die Möglichkeit, sie je nach Anwendungsfall vom Grafikkartentreiber "optimieren" zu lassen. So legt man Vertexdaten, die im Nachhinein nicht mehr verändert werden sollenm am besten mit dem Schlüsselwort ''STATIC_DRAW_ARB'' ab, während dynamische Vertexdaten über ''DYNAMIC_DRAW_ARB'' abgelegt werden sollten. Doch dazu gibts später nähere Informationen.<br />
<br />
==Hardwareunterstützung==<br />
<br />
Da '''GL_ARB_Vertex_Buffer_Object''' eine noch recht junge Extension ist, siehts mit der Hardwareunterstützung besonders auf älteren 3D-Beschleunigern nicht so toll aus. Allerdings haben sowohl ATI als auch NVidia in ihre aktuellsten Treiber für ältere Beschleuniger Softwareemulationen dieser Extension eingebaut, hinter der wohl im Endeffekt ein simples Vertexarray seinen Dienst verrichtet. Von daher wirds auf älteren Karten keine Geschwindigkeitszuwächse ggü. Vertexarrays geben, aber man erspart sich das umständliche Schreiben mehrerer Renderpfade für verschiedene Grafikkartengenerationen. (Infos zur Unterstützung von '''GL_ARB_VBO''' gibts [http://www.delphi3d.net/hardware/extsupport.php?extension=GL_ARB_vertex_buffer_object hier]).<br />
<br />
==Erstellen des Vertexbufferobjects==<br />
<br />
Wie der Name vermuten lässt, handelt es sich beim VBO um ein Objekt, welches in Sachen Syntax an die anderen in OpenGL verfügbaren Objekte wie z.&nbsp;B. Texturenobjekte angelehnt ist. Das Erstellen eines VBOs geht daher genauso einfach von der Hand:<br />
<br />
<pascal><br />
glGenBuffersARB(1, @VBO);<br />
</pascal><br />
<br />
Nun sollten wir in VBO eine gültige ID zurückgeliefert bekommen haben, welche ein Vertex buffer object referenziert.<br />
<br />
Nachdem wir nun ein VBO erstellt haben, sollten wir dieses natürlich auch "aktivieren" (also binden), damit wir mit ihm arbeiten können:<br />
<br />
<pascal><br />
glBindBufferARB(GL_ARRAY_BUFFER_ARB, VBO);<br />
glEnableClientState(GL_VERTEX_ARRAY);<br />
</pascal><br />
<br />
Wie erwähnt binden wir in der ersten Zeile unser frisch erstelltes VBO, während wir in der zweiten Zeile Vertexarrays auf der Clientseite aktivieren. Dies ist deshalb nötig, da wir das VBO später genauso zeichnen werden wie dies bei einem normalen VA der Fall wäre.<br />
<br />
==Übergabe der Vertexdaten (Variante 1: Ohne Umweg über den Hauptspeicher)==<br />
<br />
Wie in der Einleitung bereits erwähnt, bietet uns die VBO-API die Möglichkeit, unsere Vertexdaten ohne Umweg über den Hauptspeicher direkt in den Grafikkartenspeicher zu schreiben. Da dies eines der besten Features dieser neuen Extension ist, wollen wir uns auch zuerst mit dieser Übergabemethode beschäftigen.<br />
<br />
''Allerdings erstmal eine kleine Warnung vorweg: Pointer sind ein recht komplexes Thema, und ihre Nutzung erfordert einige Kenntnisse. Wer weder weiß, wie man damit umgeht, noch wie man sie einsetzt, sollte sich da zuerstmal schlau machen. Denn mit Pointer kann man ganz böse Sachen machen, wenn man sie nicht richtig nutzen kann!''<br />
<br />
Um unsere Vertexdaten als Pointer zu nutzen,müssen wir erstmal eine Vertexstruktur erstellen und auch einen passenden Pointertyp. Der Pointertyp ist zwar nicht zwingend (ein ^ täte es auch), aber macht die Sache übersichtlicher:<br />
<br />
<pascal><br />
PVertex = ^TVertex;<br />
TVertex = packed record<br />
S,T,X,Y,Z : TGLFloat;<br />
end;<br />
</pascal><br />
<br />
Sicher fragen sich einige von euch jetzt, warum unser TVertex gerade so aussieht (also zuerst S,T und dann der Rest). Das liegt daran, dass wir unser VBO später über die Funktion [[glInterleavedArrays]] rendern werden, welche wissen will, wie unsere Vertexdaten im Speicher abgelegt sind. Unsere Vertexstruktur entspricht dabei dem Format ''GL_T2F_V3F''. ''T2F'' entspricht 2 Floats für die Texturenkoordinaten (S&T), gefolgt von ''V3F'' für die drei Floatwerte die unsere Vertexkoordinaten repräsentieren. Wenn ihr ein anderes Vertexformat verwendet, müsst ihr euch auch ein passendes Vertexformat aus der Spezifikation heraussuchen.<br />
Ich verwende oben übrigens einen '''packed Record''' (auch wenn dies bei unserem Vertexformat nicht unbedingt ein Muss ist). Dies tue ich deshalb, da Delphi Werte bei einem normalen Record für einen beschleunigten Zugriff an einem (Double)Word-Raster ausrichtet und dies deshalb bei bestimmten Vertexformaten Probleme bereiten könnte.<br />
<br />
Hoffe mal das euch obiges klar ist,denn jetzt gehts ans Eingemachte. Wir kommen zum "schwersten" (ist ja ein relativer Begriff) Teil des Tutorials, nämlich der Übergabe der Vertexdaten an das VBO. Dazu müssen wir OpenGL zuerstmal mitteilen, wie groß unser VBO eigentlich sein soll,damit wir auch genug Platz im VRAM bekommen:<br />
<br />
<pascal><br />
glBufferDataARB(GL_ARRAY_BUFFER_ARB, VertexAnzahl*SizeOf(TVertex), nil, GL_STATIC_DRAW_ARB);<br />
</pascal><br />
<br />
Schauen wir uns also mal die Parameter von [[glBufferDataARB]] an. Zuerst müssen wir OpenGL mitteilen, was für einen Puffer wir erstellen wollen (in zukünftigen Versionen werden wohl noch andere folgen, z.&nbsp;B. Überbuffer). In unserem Falle also einen Arraypuffer. Parameter zwei übergibt dann den Speicherplatz, den wir reservieren wollen und ist recht selbsterklärend. Der dritte Parameter würde normalerweise einen Pointer auf unsere Vertexdaten enthalten. Da wir diese aber nicht im Hauptspeicher abgelegt haben, sondern über einen Pointer dort noch ablegen wollen, übergeben wir hier '''nil'''. OpenGL nimmt dies zur Kenntnis und spuckt dementsprechend auch keinen Fehler aus.<br />
<br />
Den letzten Parameter hab ich auch schon kurz in der Einführung angesprochen. Er teilt OpenGL mit, auf welchen Anwendungsfall unser VBO optimiert werden soll. Dabei gibt es folgende Anwendungsfälle,die ihr euch dann entsprechend eurer VBO-Verwendung aussuchen könnt:<br />
<br />
{|rules="all" border = "1"<br />
! STREAM_DRAW_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann eher selten als Quelle für OpenGL-Befehle genutzt.<br />
|-<br />
! STREAM_READ_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann selten von der Anwendung angefordert.<br />
|-<br />
! STREAM_COPY_ARB<br />
| Die Vertexdaten werden einmal durch Kopieren von der GL übergeben und dann eher selten als Quelle für OpenGL-Befehle genutzt.<br />
|-<br />
! STATIC_DRAW_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! STATIC_READ_ARB<br />
| Die Vertexdaten werden einmal übergeben und dann recht oft von der Anwendung angefordert.<br />
|-<br />
! STATIC_COPY_ARB<br />
| Die Vertexdaten werden einmal durch Kopieren von der GL übergeben und dann recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! DYNAMIC_DRAW_ARB<br />
| Die Vertexdaten werden wiederholt angegeben (verändert) und recht oft von der Anwendung zum Rendern genutzt.<br />
|-<br />
! DYNAMIC_READ_ARB<br />
| Die Vertexdaten werden wiederholt durch das Auslesen von Daten durch OpenGL angegeben (verändert) und oft von der Anwendung angefordert.<br />
|-<br />
! DYNAMIC_COPY_ARB<br />
| Die Vertexdaten werden wiederholt durch das Auslesen von Daten durch OpenGL angegeben (verändert) und recht oft zum Rendern genutzt.<br />
|}<br />
<br />
Beachtet werden sollte, dass es sich bei obigen Anwendungsmuster nur um Hinweise (Hints) handelt, die es auch in anderen Bereichen von OpenGL gibt. Diese Hinweise sind allerdings nicht verbindlich und werden von Treiber zu Treiber unterschiedlich behandelt.<br />
<br />
Nachdem OpenGL nun weiss was für ein VBO wir genau haben wollen, brauchen wir letztendlich nur noch einen Pointer, der "auf" das VBO zeigt:<br />
<br />
<pascal><br />
VBOPointer := glMapBufferARB(GL_ARRAY_BUFFER_ARB, GL_WRITE_ONLY_ARB);<br />
</pascal><br />
<br />
Die Funktion [[glMapBufferARB]] liefert uns einen Pointer zurück, der den Speicherplatz des VBOs in den Adressraum des Clients "ummappt", also in den Hauptspeicher. Dadurch wird es dann möglich, über eine Adresse im Hauptspeicher des Rechners direkt in den Grafikkartenspeicher zu schreiben. Der zweite Parameter gibt übrigens an, das wir in das VBO schreiben wollen. Der einzige weitere zulässige Parameter ist hier '''GL_READ_ONLY_ARB''', welcher zum Auslesen des VBOs dient.<br />
<br />
Endlich haben wir einen Pointer auf unser inzwischen auch im VRAM der Grafikkarte richtig dimensioniertes Vertexbufferobject, und sind nun in der Lage dort unsere Vertexdaten abzulegen. Was jetzt folgt ist ein wenig Pseudo-Code der diesen Vorgang darstellt. Pseudo-Code deshalb, weil die Vertexdaten die ins VBO kommen ja in jeder Anwendung unterschiedlich sind, und man so auch selbst ein wenig denken muss:<br />
<br />
<pascal><br />
VertexDatenLaenge := 0;<br />
for i := 0 to High(MeineVertexDaten) do<br />
begin<br />
VertexPointer := VBOPointer;<br />
<br />
VertexPointer^.X := GeneriertesVertex.X;<br />
VertexPointer^.Y := GeneriertesVertex.Y;<br />
VertexPointer^.Z := GeneriertesVertex.Z;<br />
VertexPointer^.S := GeneriertesVertex.S;<br />
VertexPointer^.T := GeneriertesVertex.T;<br />
<br />
inc(Integer(VBOPointer), SizeOf(TVertex));<br />
<br />
inc(VertexDatenLaenge);<br />
end;<br />
</pascal><br />
<br />
Da sich dieses Tutorial (und v.&nbsp;a. diese Methode der Vertexdatenübergabe) an die fortgeschritteneren wendet, rede ich nicht lange um den heissen Brei herum. Wir durchlaufen also eine Schleife, in der wir unsere Vertexdaten dynamisch erstellen. Entweder in jedem Durchlauf, oder wir laden die Daten vor der Schleife z.&nbsp;B. aus einem 3D-Modell und entfernen sie danach aus dem Hauptspeicher. Erste Methode spart sowohl beim Programmstart, also auch während des Programmlaufes Arbeitsspeicher, während letztere dies nur während des Programmablaufs macht.<br />
<br />
Wir setzen die Adresse des Pointers der auf das momentan zu verändernde Vertex zeigt also auf die aktuelle Zeigerposition im VBO und schreiben dann unser Vertex direkt in den Grafikkartenspeicher. Ist dies getan, müssen wir die Adresse unseres VBOPointers natürlich um die Größe des gerade geschriebenen Vertexes erhöhen. Der Typecast auf ein Integer geschieht deshalb, weil man Pointer in Delphi nicht so einfach erhöhen oder verringern kann, Integers hingegen schon.<br />
<br />
Ich gehe mal davon aus,das meine Leserschaft mit obigem Codeschnippsel keinerlei Probleme hatte und bringe diese Methode der Übergabe von Vertexdaten an das VBO dann auch mit folgender Quellcodezeile zu Ende:<br />
<br />
<pascal><br />
glUnMapBufferARB(GL_ARRAY_BUFFER_ARB);<br />
</pascal><br />
<br />
Wie unschwer zu erraten geben wir hier unser VBO für den weiteren Zugriff wieder frei. Dies ist nötig um dies später rendern zu können.<br />
<br />
==Übergabe der Vertexdaten (Variante 2: Über den Hauptspeicher)==<br />
<br />
Wem obige Variante nicht zugesagt hat, weil sie ihm zu komplex war, oder weil er seine Vertexdaten aus diversen Gründen im Hauptspeicher braucht, dem wird in diesem Abschnitt geholfen.<br />
<br />
Das Übergeben der Vertexdaten an das VBO über den Hauptspeicher gestaltet sich nämlich sehr einfach:<br />
<br />
<pascal><br />
glBufferDataARB(GL_ARRAY_BUFFER_ARB, SizeOf(VertexDaten), @VertexDaten, GL_STATIC_DRAW_ARB);<br />
</pascal><br />
<br />
Sieht einfach aus,hört sich einfach an und ist auch einfach! Statt wie im letzten Kapitel für die Vertexdaten einen Zeiger auf '''nil''' zu übergeben, übergeben wir OpenGL jetzt einen direkten Zeiger auf die Vertexdaten die im Hauptspeicher abgelegt sind. Die Daten werden dann zum VBO hochgeladen, und können anschliessen auch wieder aus dem Hauptspeicher entfernt werden.<br />
<br />
==Rendern des VBOs==<br />
<br />
Kommen wir nun also schon zum Ende unseres VBO-Tutorials. Dank der Einfachheit dieser Extension gabs halt einfach nicht mehr zu erklären. Das Zeichnen gestaltet sich nämlich OpenGL-typisch mehr als einfach:<br />
<br />
<pascal><br />
glInterleavedArrays(GL_T2F_V3F, SizeOf(TVertex), nil);<br />
glDrawArrays(GL_QUADS, 0, VBOSize);<br />
</pascal><br />
<br />
Schnell gezeichnet und schnell erklärt. Der erste Parameter gibt an, in welchem Format die Vertexdaten unseres VBOs abgelegt wurden (wir erinnern uns: '''GL_T2F_V3F'''), während der zweite Parameter den Speicherplatz, der zwischen zwei Vertices liegt, angibt. Würden wir ein einfaches Vertexarray zeichnen, so wäre der letzte Parameter ein Pointer auf die Vertexdaten im Hauptspeicher. Da wir allerdings vorher unser VBO gebunden haben, teilt hier ein '''nil''' mit, dass die vorher ins VBO geschriebenen Vertexdaten gerendert werden sollen.<br />
<br />
Mittels [[glDrawArrays]] sagen wir OpenGL dann noch, das es unsere VBO-Daten als Quads rendern soll.<br />
<br />
==Freigabe==<br />
<br />
Wie mit allen Objekten in der OpenGL ists auch keine schlechte Idee, unser VBO freizugeben,wenn es nicht mehr benötigt wird. Und obwohl beim Löschen des Renderkontextes solche Objekte vom Grafikkartentreiber freigegeben werden sollten, wäre es keine schlechte Idee des selbst zu erledigen,für den Fall das der Treiber da schlampig arbeitet:<br />
<br />
<pascal><br />
glDeleteBuffersARB(1, @VBO);<br />
</pascal><br />
<br />
==Weitere Vertexformate==<br />
Wem allein Texturkoordinaten und Vertices nicht reichen, sondern auch Farbangaben oder Vertexnormalen braucht, muss sich eines der folgenden Formate aussuchen. Die Konstantennamen sind logisch aufgebaut: ''GL_AngabeAnzahlTyp_AngabeAnzahlTyp...''<br />
Als Angabe gibt es '''V'''ertex, '''T'''exturkoordinaten, '''N'''ormalen und '''C'''olor-Werte, also Farben.<br />
Anzahl ist die Anzahl der Komponenten pro Angabe (V3 wären zum Bespiel die X, Y und Z-Koordinaten eines dreidimensionalen Vertex).<br />
Typ ist entweder '''F'''loat oder '''U'''nsigned '''Byte'''. Die Vertexdaten müssen in der richtigen Reihenfolge mit den richtigen Typen an das VBO übergeben werden.<br />
<br />
<pascal><br />
GL_V2F<br />
GL_V3F<br />
GL_C4UB_V2F<br />
GL_C4UB_V3F<br />
GL_C3F_V3F <br />
GL_N3F_V3F <br />
GL_C4F_N3F_V3F <br />
GL_T2F_V3F<br />
GL_T4F_V4F<br />
GL_T2F_C4UB_V3F<br />
GL_T2F_C3F_V3F<br />
GL_T2F_N3F_V3F // Wohl eine der sinnvollsten Vertexformate. 2 Floats für Texturkoordinaten, 3 Floats für den Normalenvektor und 3 Floats für den eigentlichen Vertex.<br />
GL_T2F_C4F_N3F_V3F<br />
GL_T4F_C4F_N3F_V4F<br />
</pascal><br />
<br />
Es ist sinnvoll, sich für jedes der Vertexformate, die man verwendet, einen Typen anzulegen, um später einfacher mit den VBOs arbeiten zu können.<br />
<br />
==Nachwort==<br />
<br />
Das wars auch schon. Wie zu erkennen ist das Vertexbufferobjekt mal wieder eine gut durchdachte, recht nützliche und zudem auch weit nutzbare Erweiterung, die OpenGL technisch wieder einen Schritt weitergebracht hat.<br />
<br />
Wer mehr wissen will, der sollte sich unbedingt mal die passende [http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt Spezifikation] ansehen.<br />
<br />
Hoffe das Tutorial hat euch soviel Spaß gemacht wie mir das Verfassen des selbigen und ich hoffe auf reges Feedback!<br />
<br />
Euer<br />
<br />
Sascha Willems (webmaster_at_delphigl.de)<br />
<br />
==Links==<br />
[http://developer.nvidia.com/attach/6427 Nvidia: Using Vertex Buffer Objects]<br />
<br />
[http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_buffer_object.txt Spezifikation]<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial_Cubemap]]|[[Tutorial_Vertexprogramme]]}}<br />
<br />
[[Kategorie:Tutorial|Vertexbuffer]]</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_Frustum_Culling&diff=22232Tutorial Frustum Culling2008-11-25T21:09:04Z<p>Ireyon: /* Etwas Vorarbeit */</p>
<hr />
<div>= Frustum Culling =<br />
<br />
== Einleitung ==<br />
<br />
Wenn man dabei ist eine 3D-Engine auf eigene Faust zu programmieren, wird man irgendwann damit konfrontiert das mit steigender Szenenkomplexität alles langsamer wird, und dies obwohl immer nur ein Teil der Umgebung sichtbar ist.<br />
<br />
Moderne Grafikkarten besitzen einen Z-Puffer und andere Techniken um festzustellen, ob ein Pixel gesetzt wird oder nicht, aber bevor sie dies feststellen können, brauchen sie die Geometriedaten der Szene.Dann erst macht die Grafikkarte ihre Sichtbarkeitsoptimierungen und Tiefentests.<br />
Wenn ein Objekt hinter einem anderen liegt, oder ausserhalb des Sichtbereiches ist, wird die Grafikkarte so schlau sein und dies merken.Aber selbst wenn das Objekt dann nicht gezeichnet wird, drückt es die Geschwindigkeit, da dessen Geometriedaten erstmal über den Bus zur Grafikkarte gelangen müssen.<br />
<br />
Frustum Culling ist nun eine einfache Methode um VOR dem Senden der Geometriedaten festzustellen, ob ein Objekt im Sichtfeld liegt oder nicht.Dies spart jede Menge Bandbreite und bedeutet Arbeitsersparnis für den 3D-Beschleuniger.<br />
<br />
<br />
In diesem Tutorial werde ich deshalb zeigen wie man Frustum Culling implementiert und nutz.Es ist wirklich wenig Arbeit, aber eine sehr effiziente Art seine Rendergeschwindigkeit drastisch zu erhöhen.<br />
<br />
== Die TFrustum Klasse ==<br />
<br />
Wir werden das Frustum Culling mit Hilfe einer handlichen, TFrustum genannten Klasse implementieren um die Dinge etwas einfacher zu machen.Hier die Klassendeklaration :<br />
<br />
<pascal><br />
TFrustum = object<br />
Frustum : array[0..5,0..3] of Single;<br />
function IsPointWithin(const pX, pY, pZ : Single) : Boolean;<br />
function IsBoxWithin(const pX, pY, pZ, pB, pH, pT : Single) : Boolean;<br />
procedure Calculate;<br />
end;<br />
</pascal><br />
<br />
== Etwas Vorarbeit ==<br />
<br />
Als Erstes brauchen wir für unsere Klasse einige Konstanten, die uns das Leben deutlich einfacher machen. Im Grunde selbsterklärend:<br />
<br />
<pascal><br />
const<br />
Right = 0;<br />
Left = 1;<br />
Bottom = 2;<br />
Top = 3;<br />
Back = 4;<br />
Front = 5;<br />
A = 0;<br />
B = 1;<br />
C = 2;<br />
D = 3;<br />
</pascal><br />
<br />
Jetzt kommt Mathematik ins Spiel. Wir müssen noch eine Funktion definieren, in der wir die berechneten Planes des Frustums normalisieren:<br />
<br />
<pascal><br />
procedure NormalizePlane(var pFrustum: TFrustum; pPlane: Integer);<br />
var<br />
Magnitude: Single;<br />
begin<br />
Magnitude := Sqrt(Sqr(pFrustum.Frustum[pPlane][A]) + Sqr(pFrustum.Frustum[pPlane][B]) + Sqr(pFrustum.Frustum[pPlane][C]));<br />
pFrustum.Frustum[pPlane][A] := pFrustum.Frustum[pPlane][A] / Magnitude;<br />
pFrustum.Frustum[pPlane][B] := pFrustum.Frustum[pPlane][B] / Magnitude;<br />
pFrustum.Frustum[pPlane][C] := pFrustum.Frustum[pPlane][C] / Magnitude;<br />
pFrustum.Frustum[pPlane][D] := pFrustum.Frustum[pPlane][D] / Magnitude;<br />
end;<br />
</pascal><br />
<br />
== Das Frustum berechnen(procedure Calculate) ==<br />
<br />
Zu aller Erst der wichtigste Teil der TFrustum Klasse : Die Berechnung des Selbigen.Bevor wir also feststellen könne, ob ein Objekt im Frustum liegt oder nicht, müssen wir zuerst dessen 6 Flächen berechnen.<br />
<br />
<pascal><br />
procedure TFrustum.Calculate;<br />
var<br />
ProjM, ModM, Clip : array[0..15] of Single;<br />
begin<br />
glGetFloatv(GL_PROJECTION_MATRIX, @ProjM);<br />
glGetFloatv(GL_MODELVIEW_MATRIX, @ModM);<br />
Clip[ 0] := ModM[ 0]*ProjM[ 0] + ModM[ 1]*ProjM[ 4] +<br />
ModM[ 2]*ProjM[ 8] + ModM[ 3]*ProjM[12];<br />
Clip[ 1] := ModM[ 0]*ProjM[ 1] + ModM[ 1]*ProjM[ 5] +<br />
ModM[ 2]*ProjM[ 9] + ModM[ 3]*ProjM[13];<br />
Clip[ 2] := ModM[ 0]*ProjM[ 2] + ModM[ 1]*ProjM[ 6] +<br />
ModM[ 2]*ProjM[10] + ModM[ 3]*ProjM[14];<br />
Clip[ 3] := ModM[ 0]*ProjM[ 3] + ModM[ 1]*ProjM[ 7] +<br />
ModM[ 2]*ProjM[11] + ModM[ 3]*ProjM[15];<br />
Clip[ 4] := ModM[ 4]*ProjM[ 0] + ModM[ 5]*ProjM[ 4] +<br />
ModM[ 6]*ProjM[ 8] + ModM[ 7]*ProjM[12];<br />
Clip[ 5] := ModM[ 4]*ProjM[ 1] + ModM[ 5]*ProjM[ 5] +<br />
ModM[ 6]*ProjM[ 9] + ModM[ 7]*ProjM[13];<br />
Clip[ 6] := ModM[ 4]*ProjM[ 2] + ModM[ 5]*ProjM[ 6] +<br />
ModM[ 6]*ProjM[10] + ModM[ 7]*ProjM[14];<br />
Clip[ 7] := ModM[ 4]*ProjM[ 3] + ModM[ 5]*ProjM[ 7] +<br />
ModM[ 6]*ProjM[11] + ModM[ 7]*ProjM[15];<br />
Clip[ 8] := ModM[ 8]*ProjM[ 0] + ModM[ 9]*ProjM[ 4] +<br />
ModM[10]*ProjM[ 8] + ModM[11]*ProjM[12];<br />
Clip[ 9] := ModM[ 8]*ProjM[ 1] + ModM[ 9]*ProjM[ 5] +<br />
ModM[10]*ProjM[ 9] + ModM[11]*ProjM[13];<br />
Clip[10] := ModM[ 8]*ProjM[ 2] + ModM[ 9]*ProjM[ 6] +<br />
ModM[10]*ProjM[10] + ModM[11]*ProjM[14];<br />
Clip[11] := ModM[ 8]*ProjM[ 3] + ModM[ 9]*ProjM[ 7] +<br />
ModM[10]*ProjM[11] + ModM[11]*ProjM[15];<br />
Clip[12] := ModM[12]*ProjM[ 0] + ModM[13]*ProjM[ 4] +<br />
ModM[14]*ProjM[ 8] + ModM[15]*ProjM[12];<br />
Clip[13] := ModM[12]*ProjM[ 1] + ModM[13]*ProjM[ 5] +<br />
ModM[14]*ProjM[ 9] + ModM[15]*ProjM[13];<br />
Clip[14] := ModM[12]*ProjM[ 2] + ModM[13]*ProjM[ 6] +<br />
ModM[14]*ProjM[10] + ModM[15]*ProjM[14];<br />
Clip[15] := ModM[12]*ProjM[ 3] + ModM[13]*ProjM[ 7] +<br />
ModM[14]*ProjM[11] + ModM[15]*ProjM[15];<br />
{...}<br />
</pascal><br />
<br />
<br />
In Zeile [05] extrahieren wir die momentane Projektionsmatrix und speichern sie in einem 16 Single-Werte enthaltendem Array, selbiges tun wir dann in Zeile [06] mit der aktuellen Modelbetrachtungsmatrix.<br />
Diese beiden Matrizen werden dann in den Zeilen [07] bis [22] kombininert und werden als unsere Clippinmatrix gespeichert.<br />
<br />
Die folgenden Zeilen berechnen nun endlich die sechs Flächen unserer Frustumbox.Die Namen der Flächen wurden vorher in der globalen Konstantensektion dieser Unit deklariert.<br />
<br />
<pascal><br />
Frustum[Right][A] := clip[ 3] - clip[ 0];<br />
Frustum[Right][B] := clip[ 7] - clip[ 4];<br />
Frustum[Right][C] := clip[11] - clip[ 8];<br />
Frustum[Right][D] := clip[15] - clip[12];<br />
NormalizePlane(self, Right);<br />
<br />
Frustum[Left][A] := clip[ 3] + clip[ 0];<br />
Frustum[Left][B] := clip[ 7] + clip[ 4];<br />
Frustum[Left][C] := clip[11] + clip[ 8];<br />
Frustum[Left][D] := clip[15] + clip[12];<br />
NormalizePlane(self, Left);<br />
<br />
Frustum[Bottom][A] := clip[ 3] + clip[ 1];<br />
Frustum[Bottom][B] := clip[ 7] + clip[ 5];<br />
Frustum[Bottom][C] := clip[11] + clip[ 9];<br />
Frustum[Bottom][D] := clip[15] + clip[13];<br />
NormalizePlane(self, Bottom);<br />
<br />
Frustum[Top][A] := clip[ 3] - clip[ 1];<br />
Frustum[Top][B] := clip[ 7] - clip[ 5];<br />
Frustum[Top][C] := clip[11] - clip[ 9];<br />
Frustum[Top][D] := clip[15] - clip[13];<br />
NormalizePlane(self, Top);<br />
<br />
Frustum[Back][A] := clip[ 3] - clip[ 2];<br />
Frustum[Back][B] := clip[ 7] - clip[ 6];<br />
Frustum[Back][C] := clip[11] - clip[10];<br />
Frustum[Back][D] := clip[15] - clip[14];<br />
NormalizePlane(self, Back);<br />
<br />
Frustum[Front][A] := clip[ 3] + clip[ 2];<br />
Frustum[Front][B] := clip[ 7] + clip[ 6];<br />
Frustum[Front][C] := clip[11] + clip[10];<br />
Frustum[Front][D] := clip[15] + clip[14];<br />
NormalizePlane(self, Front);<br />
end;<br />
</pascal><br />
<br />
Diese Berechnungen speichern ihr Ergebnis im Frustum-Array unserer TFrustum Klasse.Die Berechnung des Frustums muss nach jeder Blickwinkeländerung erfolgen.Wenn man zu faul ist, dies zu erfassen, kann man das Frustum auch einfach in jedem Frame nach dem setzen des Blickwinkels berechnen.<br />
<br />
== Punkt im Frustum? ==<br />
<br />
Der einfachste Sichtbarkeitstest ist, festzustellen ob ein einzelner Punkt im Frustum liegt.Dies ist der Fall, wenn die Entfernung des Punktes zu allen sechs Flächen des Frustums positiv ist.(D.h. das der Punkt vor allen Flächen liegt.Ist die Entfernung negativ, so liegt er dahinter)<br />
<br />
Um dies zu berechnen, nutzen wir folgende einfache Formel :<br />
<br />
<pascal> Distance := (A*x) + (B*y) + (C*z) + D </pascal><br />
<br />
Die Funktion TFrustum.IsPointWithin(const pX, pY, pZ : Single) : Boolean durchläuft nun ganz einfache in einer Schleife alle sechs Flächen und berechnet die Entfernung des Punktes zu dieser Fläche.Wenn die Distanz negativ ist (der Punkt also dahinter liegt), liefert die Funktion False.<br />
<br />
<pascal><br />
function TFrustum.IsPointWithin(const pX, pY, pZ : Single) : Boolean;<br />
var<br />
i : Integer;<br />
begin<br />
Result := true;<br />
for i := 0 to 5 do<br />
if (Frustum[i][A]*pX + Frustum[i][B]*pY +<br />
Frustum[i][C]*pZ + Frustum[i][D]) <= 0 then<br />
begin<br />
Result := False;<br />
exit;<br />
end;<br />
end;<br />
</pascal><br />
<br />
== Kugel im Frustum? ==<br />
<br />
Nachdem wir nun feststellen können, ob ein Punkt im Frustum liegt, ist selbiges mit einer Kugel ein Leichtes.Wie man wissen sollte, lässt sich eine Kugel durch zwei Attribute darstellen : Ihren Ursprungspunkt und ihren Radius.<br />
Diese Funktion ersetzt also ganz einfach den Distanztest <=0 mit einem Distanztest gegen den negativen Radius der Kugel, also ziemlich einfach, oder?<br />
<br />
<pascal><br />
function TFrustum.IsSphereWithin(const pX, pY, pZ,<br />
pRadius : Single) : Boolean;<br />
var<br />
i : Integer;<br />
begin<br />
Result := true;<br />
for i := 0 to 5 do<br />
if (Frustum[i][A]*pX + Frustum[i][B]*pY +<br />
Frustum[i][C]*pZ + Frustum[i][D]) <= -pRadius then<br />
begin<br />
Result := False;<br />
exit;<br />
end;<br />
end;<br />
</pascal><br />
<br />
<br />
Dies ist der wohl meist genutzte Sichtbarkeitstest.Dies hat drei Gründe :<br />
Erstens : Dieser Test ist der schnellste, da nur sechs Berechnungen anfallen.Zweitens : Die Begrenzung der meisten Objekte lassen sich als Kugel beschreiben.Drittens : Für eine Kugel wird nur der Speicherplatz von vier Single-Werten benötigt.<br />
Wenn es also geht, sollte man diesen Test dem Quadertest vorziehen.<br />
<br />
<br />
== Quader im Frustum? ==<br />
<br />
Festzustellen, ob sich ein Quader im Frustum befindet ist nicht viel schwerer als dies mit einer Kugel ist.Wie bekannt, besteht ein Quader aus acht Ecken.Also durchläuft die Funktion eine Schleife durch alle sechs Frustumflächen und prüft ob eine der Ecken vor einer der Flächen liegt.Ist dies der Fall, wird die Schleife unterbrochen.<br />
<br />
<pascal><br />
function TFrustum.IsBoxWithin<br />
(const pX, pY, pZ, pB, pH, pT : Single) : Boolean;<br />
var<br />
i : Integer;<br />
begin<br />
Result := true;<br />
for i := 0 to 5 do<br />
begin<br />
if (Frustum[i][A]*(pX-pB) + Frustum[i][B]*(pY-pH) +<br />
Frustum[i][C]*(pZ-pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px+pB) + Frustum[i][B]*(py-pH) +<br />
Frustum[i][C]*(pz-pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px-pB) + Frustum[i][B]*(py+pH) +<br />
Frustum[i][C]*(pz-pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px+pB) + Frustum[i][B]*(py+pH) +<br />
Frustum[i][C]*(pz-pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px-pB) + Frustum[i][B]*(py-pH) +<br />
Frustum[i][C]*(pz+pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px+pB) + Frustum[i][B]*(py-pH) +<br />
Frustum[i][C]*(pz+pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px-pB) + Frustum[i][B]*(py+pH) +<br />
Frustum[i][C]*(pz+pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px+pB) + Frustum[i][B]*(py+pH) +<br />
Frustum[i][C]*(pz+pT) + Frustum[i][D]>0) then<br />
continue;<br />
Result := False;<br />
end;<br />
end;<br />
</pascal><br />
<br />
Wie vorher gesagt, benötigt dieser Test mehr Berechnungen als der Kugeltest(bis zu 48).Wenn man also eine Kugel statt eines Quaders als Begrenzung für ein Objekt nutzen kann, sollte man das tun.Dies spart jede Menge Berechnungen.<br />
<br />
== Das Beispielprogramm ==<br />
<br />
Das Beispielprogramm rendert jede Menge Kugeln und prüft deren Sichtbarkeit via Frustum Culling bevor diese zur Grafikkarte gesendet werden.Mit Hilfe des Buttons kann das Frustum Culling ein- bzw. ausgeschaltet werden, um den Geschwindigkeitsunterschied leichter zu erkennen.<br />
<br />
[[Bild:Tutorial_Frustum_Culling01.jpg]]<br />
<br />
*[http://www.delphigl.de/files/frustumcull.zip Das Frustum-Culling Demo (inklusive Quellcode) herunterladen]<br />
<br />
<br />
<br />
Autor: [[Benutzer:Sascha_Willems|Sascha Willems]]</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_Frustum_Culling&diff=22231Tutorial Frustum Culling2008-11-25T21:07:50Z<p>Ireyon: Fehlende Funktionen und Konstanten ergänzt, die die meisten Anfänger wohl noch brauchen.</p>
<hr />
<div>= Frustum Culling =<br />
<br />
== Einleitung ==<br />
<br />
Wenn man dabei ist eine 3D-Engine auf eigene Faust zu programmieren, wird man irgendwann damit konfrontiert das mit steigender Szenenkomplexität alles langsamer wird, und dies obwohl immer nur ein Teil der Umgebung sichtbar ist.<br />
<br />
Moderne Grafikkarten besitzen einen Z-Puffer und andere Techniken um festzustellen, ob ein Pixel gesetzt wird oder nicht, aber bevor sie dies feststellen können, brauchen sie die Geometriedaten der Szene.Dann erst macht die Grafikkarte ihre Sichtbarkeitsoptimierungen und Tiefentests.<br />
Wenn ein Objekt hinter einem anderen liegt, oder ausserhalb des Sichtbereiches ist, wird die Grafikkarte so schlau sein und dies merken.Aber selbst wenn das Objekt dann nicht gezeichnet wird, drückt es die Geschwindigkeit, da dessen Geometriedaten erstmal über den Bus zur Grafikkarte gelangen müssen.<br />
<br />
Frustum Culling ist nun eine einfache Methode um VOR dem Senden der Geometriedaten festzustellen, ob ein Objekt im Sichtfeld liegt oder nicht.Dies spart jede Menge Bandbreite und bedeutet Arbeitsersparnis für den 3D-Beschleuniger.<br />
<br />
<br />
In diesem Tutorial werde ich deshalb zeigen wie man Frustum Culling implementiert und nutz.Es ist wirklich wenig Arbeit, aber eine sehr effiziente Art seine Rendergeschwindigkeit drastisch zu erhöhen.<br />
<br />
== Die TFrustum Klasse ==<br />
<br />
Wir werden das Frustum Culling mit Hilfe einer handlichen, TFrustum genannten Klasse implementieren um die Dinge etwas einfacher zu machen.Hier die Klassendeklaration :<br />
<br />
<pascal><br />
TFrustum = object<br />
Frustum : array[0..5,0..3] of Single;<br />
function IsPointWithin(const pX, pY, pZ : Single) : Boolean;<br />
function IsBoxWithin(const pX, pY, pZ, pB, pH, pT : Single) : Boolean;<br />
procedure Calculate;<br />
end;<br />
</pascal><br />
<br />
== Etwas Vorarbeit ==<br />
<br />
Als Erstes brauchen wir für unsere Klasse einige Konstanten, die uns das Leben deutlich einfacher machen.<br />
<br />
<pascal><br />
const<br />
Right = 0;<br />
Left = 1;<br />
Bottom = 2;<br />
Top = 3;<br />
Back = 4;<br />
Front = 5;<br />
A = 0;<br />
B = 1;<br />
C = 2;<br />
D = 3;<br />
</pascal><br />
<br />
Jetzt kommt Mathematik ins Spiel. Wir müssen noch eine Funktion definieren, in der wir die berechneten Planes des Frustums normalisieren:<br />
<br />
<pascal><br />
procedure NormalizePlane(var pFrustum: TFrustum; pPlane: Integer);<br />
var<br />
Magnitude: Single;<br />
begin<br />
Magnitude := Sqrt(Sqr(pFrustum.Frustum[pPlane][A]) + Sqr(pFrustum.Frustum[pPlane][B]) + Sqr(pFrustum.Frustum[pPlane][C]));<br />
pFrustum.Frustum[pPlane][A] := pFrustum.Frustum[pPlane][A] / Magnitude;<br />
pFrustum.Frustum[pPlane][B] := pFrustum.Frustum[pPlane][B] / Magnitude;<br />
pFrustum.Frustum[pPlane][C] := pFrustum.Frustum[pPlane][C] / Magnitude;<br />
pFrustum.Frustum[pPlane][D] := pFrustum.Frustum[pPlane][D] / Magnitude;<br />
end;<br />
</pascal><br />
<br />
== Das Frustum berechnen(procedure Calculate) ==<br />
<br />
Zu aller Erst der wichtigste Teil der TFrustum Klasse : Die Berechnung des Selbigen.Bevor wir also feststellen könne, ob ein Objekt im Frustum liegt oder nicht, müssen wir zuerst dessen 6 Flächen berechnen.<br />
<br />
<pascal><br />
procedure TFrustum.Calculate;<br />
var<br />
ProjM, ModM, Clip : array[0..15] of Single;<br />
begin<br />
glGetFloatv(GL_PROJECTION_MATRIX, @ProjM);<br />
glGetFloatv(GL_MODELVIEW_MATRIX, @ModM);<br />
Clip[ 0] := ModM[ 0]*ProjM[ 0] + ModM[ 1]*ProjM[ 4] +<br />
ModM[ 2]*ProjM[ 8] + ModM[ 3]*ProjM[12];<br />
Clip[ 1] := ModM[ 0]*ProjM[ 1] + ModM[ 1]*ProjM[ 5] +<br />
ModM[ 2]*ProjM[ 9] + ModM[ 3]*ProjM[13];<br />
Clip[ 2] := ModM[ 0]*ProjM[ 2] + ModM[ 1]*ProjM[ 6] +<br />
ModM[ 2]*ProjM[10] + ModM[ 3]*ProjM[14];<br />
Clip[ 3] := ModM[ 0]*ProjM[ 3] + ModM[ 1]*ProjM[ 7] +<br />
ModM[ 2]*ProjM[11] + ModM[ 3]*ProjM[15];<br />
Clip[ 4] := ModM[ 4]*ProjM[ 0] + ModM[ 5]*ProjM[ 4] +<br />
ModM[ 6]*ProjM[ 8] + ModM[ 7]*ProjM[12];<br />
Clip[ 5] := ModM[ 4]*ProjM[ 1] + ModM[ 5]*ProjM[ 5] +<br />
ModM[ 6]*ProjM[ 9] + ModM[ 7]*ProjM[13];<br />
Clip[ 6] := ModM[ 4]*ProjM[ 2] + ModM[ 5]*ProjM[ 6] +<br />
ModM[ 6]*ProjM[10] + ModM[ 7]*ProjM[14];<br />
Clip[ 7] := ModM[ 4]*ProjM[ 3] + ModM[ 5]*ProjM[ 7] +<br />
ModM[ 6]*ProjM[11] + ModM[ 7]*ProjM[15];<br />
Clip[ 8] := ModM[ 8]*ProjM[ 0] + ModM[ 9]*ProjM[ 4] +<br />
ModM[10]*ProjM[ 8] + ModM[11]*ProjM[12];<br />
Clip[ 9] := ModM[ 8]*ProjM[ 1] + ModM[ 9]*ProjM[ 5] +<br />
ModM[10]*ProjM[ 9] + ModM[11]*ProjM[13];<br />
Clip[10] := ModM[ 8]*ProjM[ 2] + ModM[ 9]*ProjM[ 6] +<br />
ModM[10]*ProjM[10] + ModM[11]*ProjM[14];<br />
Clip[11] := ModM[ 8]*ProjM[ 3] + ModM[ 9]*ProjM[ 7] +<br />
ModM[10]*ProjM[11] + ModM[11]*ProjM[15];<br />
Clip[12] := ModM[12]*ProjM[ 0] + ModM[13]*ProjM[ 4] +<br />
ModM[14]*ProjM[ 8] + ModM[15]*ProjM[12];<br />
Clip[13] := ModM[12]*ProjM[ 1] + ModM[13]*ProjM[ 5] +<br />
ModM[14]*ProjM[ 9] + ModM[15]*ProjM[13];<br />
Clip[14] := ModM[12]*ProjM[ 2] + ModM[13]*ProjM[ 6] +<br />
ModM[14]*ProjM[10] + ModM[15]*ProjM[14];<br />
Clip[15] := ModM[12]*ProjM[ 3] + ModM[13]*ProjM[ 7] +<br />
ModM[14]*ProjM[11] + ModM[15]*ProjM[15];<br />
{...}<br />
</pascal><br />
<br />
<br />
In Zeile [05] extrahieren wir die momentane Projektionsmatrix und speichern sie in einem 16 Single-Werte enthaltendem Array, selbiges tun wir dann in Zeile [06] mit der aktuellen Modelbetrachtungsmatrix.<br />
Diese beiden Matrizen werden dann in den Zeilen [07] bis [22] kombininert und werden als unsere Clippinmatrix gespeichert.<br />
<br />
Die folgenden Zeilen berechnen nun endlich die sechs Flächen unserer Frustumbox.Die Namen der Flächen wurden vorher in der globalen Konstantensektion dieser Unit deklariert.<br />
<br />
<pascal><br />
Frustum[Right][A] := clip[ 3] - clip[ 0];<br />
Frustum[Right][B] := clip[ 7] - clip[ 4];<br />
Frustum[Right][C] := clip[11] - clip[ 8];<br />
Frustum[Right][D] := clip[15] - clip[12];<br />
NormalizePlane(self, Right);<br />
<br />
Frustum[Left][A] := clip[ 3] + clip[ 0];<br />
Frustum[Left][B] := clip[ 7] + clip[ 4];<br />
Frustum[Left][C] := clip[11] + clip[ 8];<br />
Frustum[Left][D] := clip[15] + clip[12];<br />
NormalizePlane(self, Left);<br />
<br />
Frustum[Bottom][A] := clip[ 3] + clip[ 1];<br />
Frustum[Bottom][B] := clip[ 7] + clip[ 5];<br />
Frustum[Bottom][C] := clip[11] + clip[ 9];<br />
Frustum[Bottom][D] := clip[15] + clip[13];<br />
NormalizePlane(self, Bottom);<br />
<br />
Frustum[Top][A] := clip[ 3] - clip[ 1];<br />
Frustum[Top][B] := clip[ 7] - clip[ 5];<br />
Frustum[Top][C] := clip[11] - clip[ 9];<br />
Frustum[Top][D] := clip[15] - clip[13];<br />
NormalizePlane(self, Top);<br />
<br />
Frustum[Back][A] := clip[ 3] - clip[ 2];<br />
Frustum[Back][B] := clip[ 7] - clip[ 6];<br />
Frustum[Back][C] := clip[11] - clip[10];<br />
Frustum[Back][D] := clip[15] - clip[14];<br />
NormalizePlane(self, Back);<br />
<br />
Frustum[Front][A] := clip[ 3] + clip[ 2];<br />
Frustum[Front][B] := clip[ 7] + clip[ 6];<br />
Frustum[Front][C] := clip[11] + clip[10];<br />
Frustum[Front][D] := clip[15] + clip[14];<br />
NormalizePlane(self, Front);<br />
end;<br />
</pascal><br />
<br />
Diese Berechnungen speichern ihr Ergebnis im Frustum-Array unserer TFrustum Klasse.Die Berechnung des Frustums muss nach jeder Blickwinkeländerung erfolgen.Wenn man zu faul ist, dies zu erfassen, kann man das Frustum auch einfach in jedem Frame nach dem setzen des Blickwinkels berechnen.<br />
<br />
== Punkt im Frustum? ==<br />
<br />
Der einfachste Sichtbarkeitstest ist, festzustellen ob ein einzelner Punkt im Frustum liegt.Dies ist der Fall, wenn die Entfernung des Punktes zu allen sechs Flächen des Frustums positiv ist.(D.h. das der Punkt vor allen Flächen liegt.Ist die Entfernung negativ, so liegt er dahinter)<br />
<br />
Um dies zu berechnen, nutzen wir folgende einfache Formel :<br />
<br />
<pascal> Distance := (A*x) + (B*y) + (C*z) + D </pascal><br />
<br />
Die Funktion TFrustum.IsPointWithin(const pX, pY, pZ : Single) : Boolean durchläuft nun ganz einfache in einer Schleife alle sechs Flächen und berechnet die Entfernung des Punktes zu dieser Fläche.Wenn die Distanz negativ ist (der Punkt also dahinter liegt), liefert die Funktion False.<br />
<br />
<pascal><br />
function TFrustum.IsPointWithin(const pX, pY, pZ : Single) : Boolean;<br />
var<br />
i : Integer;<br />
begin<br />
Result := true;<br />
for i := 0 to 5 do<br />
if (Frustum[i][A]*pX + Frustum[i][B]*pY +<br />
Frustum[i][C]*pZ + Frustum[i][D]) <= 0 then<br />
begin<br />
Result := False;<br />
exit;<br />
end;<br />
end;<br />
</pascal><br />
<br />
== Kugel im Frustum? ==<br />
<br />
Nachdem wir nun feststellen können, ob ein Punkt im Frustum liegt, ist selbiges mit einer Kugel ein Leichtes.Wie man wissen sollte, lässt sich eine Kugel durch zwei Attribute darstellen : Ihren Ursprungspunkt und ihren Radius.<br />
Diese Funktion ersetzt also ganz einfach den Distanztest <=0 mit einem Distanztest gegen den negativen Radius der Kugel, also ziemlich einfach, oder?<br />
<br />
<pascal><br />
function TFrustum.IsSphereWithin(const pX, pY, pZ,<br />
pRadius : Single) : Boolean;<br />
var<br />
i : Integer;<br />
begin<br />
Result := true;<br />
for i := 0 to 5 do<br />
if (Frustum[i][A]*pX + Frustum[i][B]*pY +<br />
Frustum[i][C]*pZ + Frustum[i][D]) <= -pRadius then<br />
begin<br />
Result := False;<br />
exit;<br />
end;<br />
end;<br />
</pascal><br />
<br />
<br />
Dies ist der wohl meist genutzte Sichtbarkeitstest.Dies hat drei Gründe :<br />
Erstens : Dieser Test ist der schnellste, da nur sechs Berechnungen anfallen.Zweitens : Die Begrenzung der meisten Objekte lassen sich als Kugel beschreiben.Drittens : Für eine Kugel wird nur der Speicherplatz von vier Single-Werten benötigt.<br />
Wenn es also geht, sollte man diesen Test dem Quadertest vorziehen.<br />
<br />
<br />
== Quader im Frustum? ==<br />
<br />
Festzustellen, ob sich ein Quader im Frustum befindet ist nicht viel schwerer als dies mit einer Kugel ist.Wie bekannt, besteht ein Quader aus acht Ecken.Also durchläuft die Funktion eine Schleife durch alle sechs Frustumflächen und prüft ob eine der Ecken vor einer der Flächen liegt.Ist dies der Fall, wird die Schleife unterbrochen.<br />
<br />
<pascal><br />
function TFrustum.IsBoxWithin<br />
(const pX, pY, pZ, pB, pH, pT : Single) : Boolean;<br />
var<br />
i : Integer;<br />
begin<br />
Result := true;<br />
for i := 0 to 5 do<br />
begin<br />
if (Frustum[i][A]*(pX-pB) + Frustum[i][B]*(pY-pH) +<br />
Frustum[i][C]*(pZ-pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px+pB) + Frustum[i][B]*(py-pH) +<br />
Frustum[i][C]*(pz-pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px-pB) + Frustum[i][B]*(py+pH) +<br />
Frustum[i][C]*(pz-pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px+pB) + Frustum[i][B]*(py+pH) +<br />
Frustum[i][C]*(pz-pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px-pB) + Frustum[i][B]*(py-pH) +<br />
Frustum[i][C]*(pz+pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px+pB) + Frustum[i][B]*(py-pH) +<br />
Frustum[i][C]*(pz+pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px-pB) + Frustum[i][B]*(py+pH) +<br />
Frustum[i][C]*(pz+pT) + Frustum[i][D]>0) then<br />
continue;<br />
if (Frustum[i][A]*(px+pB) + Frustum[i][B]*(py+pH) +<br />
Frustum[i][C]*(pz+pT) + Frustum[i][D]>0) then<br />
continue;<br />
Result := False;<br />
end;<br />
end;<br />
</pascal><br />
<br />
Wie vorher gesagt, benötigt dieser Test mehr Berechnungen als der Kugeltest(bis zu 48).Wenn man also eine Kugel statt eines Quaders als Begrenzung für ein Objekt nutzen kann, sollte man das tun.Dies spart jede Menge Berechnungen.<br />
<br />
== Das Beispielprogramm ==<br />
<br />
Das Beispielprogramm rendert jede Menge Kugeln und prüft deren Sichtbarkeit via Frustum Culling bevor diese zur Grafikkarte gesendet werden.Mit Hilfe des Buttons kann das Frustum Culling ein- bzw. ausgeschaltet werden, um den Geschwindigkeitsunterschied leichter zu erkennen.<br />
<br />
[[Bild:Tutorial_Frustum_Culling01.jpg]]<br />
<br />
*[http://www.delphigl.de/files/frustumcull.zip Das Frustum-Culling Demo (inklusive Quellcode) herunterladen]<br />
<br />
<br />
<br />
Autor: [[Benutzer:Sascha_Willems|Sascha Willems]]</div>Ireyonhttps://wiki.delphigl.com/index.php?title=shader_PerPixelLighting&diff=22230shader PerPixelLighting2008-11-24T16:10:21Z<p>Ireyon: /* Per Pixel-Beleuchtung */</p>
<hr />
<div>=Per Pixel-Beleuchtung=<br />
Zurück zur [[Shadersammlung]]<br />
{|{{Prettytable_B1}} width=100%<br />
!width=60%|Beschreibung<br />
!width=20%|Autor<br />
!width=20%|Version<br />
|-<br />
|Beleuchtet Primitiven per Pixel anstatt per Vertex, wie es OpenGLs Standardbeleuchtung macht <br />
|Ireyon<br />
|1.0<br />
|}<br />
<br />
==Bilder==<br />
<br />
{|<br />
|[[Bild:shader_ppl.png|thumb|framed|Deutlich sichtbarer Reflexionspunkt]]<br />
|}<br />
<br />
==Beschreibung==<br />
Dieser Shader berechnet im Gegensatz zur Standard-OpenGL-Beleuchtung, die per Vertex berechnet, die Fragmentfarbe per Pixel. Anders als der im zweiten [[Tutorial_glsl2|GLSL]]-Tutorial veröffentlichte Per-Pixel-Beleuchtungsshader berücksichtigt dieser allerdings auch Texturen und Materialien. Die Rendergeschwindigkeit könnte bei älteren Grafikkarten stark sinken.<br />
<br />
==Besondere Vorraussetzungen==<br />
Licht- und Materialinformationen werden wie beim normale OpenGL-Licht übergeben, vor der Verwendung dieses Shaders sollte es aber mit {{INLINE_CODE|glDisable(GL_LIGHTING);}} ausgeschaltet werden. <br />
<br />
==Code==<br />
Vertexshader:<br />
<cpp>varying vec3 v;<br />
varying vec3 lightvec;<br />
varying vec3 normal;<br />
varying vec4 FrontColor;<br />
<br />
void main(void) {<br />
normal = normalize(gl_NormalMatrix * gl_Normal);<br />
v = vec3(gl_ModelViewMatrix * gl_Vertex);<br />
lightvec = normalize(gl_LightSource[0].position.xyz - v);<br />
<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
FrontColor = gl_Color;<br />
<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
}</cpp><br />
<br />
Fragmentshader:<br />
<cpp>varying vec3 v;<br />
varying vec3 lightvec;<br />
varying vec3 normal;<br />
varying vec4 FrontColor;<br />
<br />
uniform sampler2D Texture0;<br />
<br />
void main(void) {<br />
vec3 Eye = normalize(-v);<br />
vec3 Reflected = normalize(reflect( -lightvec, normal)); <br />
<br />
vec4 IAmbient = gl_LightSource[0].ambient * gl_FrontMaterial.ambient;<br />
vec4 IDiffuse = gl_LightSource[0].diffuse * max(dot(normal, lightvec), 0.0) * gl_FrontMaterial.diffuse;<br />
vec4 ISpecular = gl_LightSource[0].specular * pow(max(dot(Reflected, Eye), 0.0), gl_FrontMaterial.shininess) * gl_FrontMaterial.specular;<br />
<br />
gl_FragColor = vec4((gl_FrontLightModelProduct.sceneColor + IAmbient + IDiffuse) * texture2D(Texture0, vec2(gl_TexCoord[0])) + ISpecular);<br />
}</cpp></div>Ireyonhttps://wiki.delphigl.com/index.php?title=Shadersammlung&diff=22229Shadersammlung2008-11-23T20:12:54Z<p>Ireyon: /* GLSL-Shader */</p>
<hr />
<div>Die DGL Shadersammlung soll euch helfen Shader für OpenGL zu finden. <br />
<br />
=Organisation=<br />
==Aufbau==<br />
Für den Anfang wird die Shadersammlung nicht gegliedert. Mit wachsender Anzahl der verlinkten Shader wird sich dies ändern.<br />
==Vorlage==<br />
Falls ihr einen Shader bereitstellen wollt sind drei Dinge nötig:<br />
# Kopiert die nachfolgende Vorlage ans Ende der Shaderliste und tragt die passenden Daten ein.<br />
# Erstellt einen neuen Shaderartikel den ihr in der eingefügten Zeile verlinkt. Benutzt dazu die [[Shaderartikelvorlage]]. Shaderartikel müssen mit '''shader_''' benannt sein. ''z.B. shader_Wasser2d'' für einen 2D Wassershader.<br />
# Da Shader zu 90% optische Effekte bewirken, sollte (mindestens) ein aussagekräftiges Beispielbild im Shaderartikel hinterlegt werden. Das Bild muss dann auch hier in der Liste verlinkt werden.<br />
<br />
Fügt folgenden Code ans Ende der Shaderliste an:<br />
!<nowiki>[[shader_HierNameEintragen]]</nowiki><br />
!HierNameEintragen<br />
!Autorname<br />
!Was macht der Shader.<nowiki><br></nowiki><br />
''What does the shader do.''*<br />
!<nowiki>[[Bild:Beispielbild.jpg|150px|150px]]</nowiki><br />
<br />
''* Die englische Beschreibung ist freiwillig. Hilft aber bestimmt den Shader zu verbreiten. Die deutsche Beschreibung ist Pflicht.''<br />
<br />
<br />
Für euren neuen Shaderartikel sollte diese [[Shaderartikelvorlage]] benutzt werden. (Vorlage aufrufen, bearbeiten klicken, Alles kopieren und in euren neuen Shaderartikel einfügen.)<br />
<br />
=Shader=<br />
== ARB-Shader ==<br />
{|{{Prettytable_B1}} width=100%<br />
!width=20%|Link<br />
!width=20%|Shadername<br />
!width=20%|Autor<br />
!width=40%|Kurzbeschreibung<br />
!width=150px|Bild<br />
|-<br />
![[shader_surface_scattering(ARB)|shader_surface_scattering]]<br />
!surface scattering<br />
!dj3hut1 <br />
!Bestimmt die Distanz, die Licht durch ein Material zurücklegt.<br />
<br><br />
''Calculates the distance of light through a material.''<br />
![[Bild:Scattering_s.jpg|144px|150px]]<br />
|}<br />
== GLSL-Shader ==<br />
{|{{Prettytable_B1}} width=100%<br />
!width=20%|Link<br />
!width=20%|Shadername<br />
!width=20%|Autor<br />
!width=40%|Kurzbeschreibung<br />
!width=150px|Bild<br />
|-<br />
![[shader_verysimple]]<br />
!verysimple<br />
(standard texture binding)<br />
!damadmax <br />
!Ein einfacher Shader, welcher eine Textur an Vertices bindet. Dieser Shader baut damit das standard Texturierungsverhalten der fixen Pipeline nach.<br><br />
''A very simple shader. It only binds a texture on some vertices, thus this shader simulates the standard behavior of a part of the fixed pipeline.''<br />
!''Kein Bild vorhanden''<br />
|-<br />
![[shader_sepia]]<br />
!Sepia<br />
!Markus<br />
!Post-Processing-Shader, welcher einen Sepia-Farbfilter auf die Szene anwendet.<br><br />
''Post-Processing-Shader which applies a sepia color filter to the scene.'' <br />
![[Bild:shader_sepia_nachher.jpg|150px|150px]]<br />
|-<br />
![[shader_radial_blur]]<br />
!Radial Blur<br />
!Markus<br />
!Post-Processing-Shader, welcher die Szene ausgehend vom Mittelpunkt verwischt.<br><br />
''Post-Processing-Shader which applies a radial blur to the scene.'' <br />
![[Bild:shader_radial_blur_nachher.jpg|150px|150px]]<br />
|-<br />
![[shader_PerPixelLighting]]<br />
!Per Pixel-Beleuchtung<br />
!Ireyon<br />
!Shader, der Per Pixel beleuchtet<br><br />
''Shader which calculates lighting per pixel''<br />
![[Bild:shader_ppl.png|150px|150px]]<br />
|}<br />
<br />
== Cg-Shader ==<br />
{|{{Prettytable_B1}} width=100%<br />
!width=20%|Link<br />
!width=20%|Shadername<br />
!width=20%|Autor<br />
!width=40%|Kurzbeschreibung<br />
!width=150px|Bild<br />
|-<br />
![[shader_diffuse_bumpmapping(Cg)|diffuse_bumpmapping]]<br />
!diffuse_bumpmapping<br />
!igel457<br />
!Wendet diffuses Bumpmapping auf eine beliebige Oberfläche an.<br><br />
''Applies diffuse bumpmapping to a surface.''<br />
![[Bild:shader_diffuse_bumpmapping_cg.jpg|150px|150px]]<br />
|}</div>Ireyonhttps://wiki.delphigl.com/index.php?title=shader_PerPixelLighting&diff=22228shader PerPixelLighting2008-11-23T20:08:37Z<p>Ireyon: Die Seite wurde neu angelegt: =Per Pixel-Beleuchtung= Zurück zur Shadersammlung {|{{Prettytable_B1}} width=100% !width=60%|Beschreibung !width=20%|Autor !width=20%|Version |- |Beleuchtet Primit...</p>
<hr />
<div>=Per Pixel-Beleuchtung=<br />
Zurück zur [[Shadersammlung]]<br />
{|{{Prettytable_B1}} width=100%<br />
!width=60%|Beschreibung<br />
!width=20%|Autor<br />
!width=20%|Version<br />
|-<br />
|Beleuchtet Primitiven per Pixel <br />
|Ireyon<br />
|1.0<br />
|}<br />
<br />
==Bilder==<br />
<br />
{|<br />
|[[Bild:shader_ppl.png|thumb|framed|Deutlich sichtbarer Reflexionspunkt]]<br />
|}<br />
<br />
==Beschreibung==<br />
Dieser Shader berechnet im Gegensatz zur Standard-OpenGL-Beleuchtung, die per Vertex berechnet, die Fragmentfarbe per Pixel. Anders als der im zweiten [[Tutorial_glsl2|GLSL]]-Tutorial veröffentlichte Per-Pixel-Beleuchtungsshader berücksichtigt dieser allerdings auch Texturen und Materialien. Die Rendergeschwindigkeit könnte bei älteren Grafikkarten stark sinken.<br />
<br />
==Besondere Vorraussetzungen==<br />
Licht- und Materialinformationen werden wie beim normale OpenGL-Licht übergeben, vor der Verwendung dieses Shaders sollte es aber mit {{INLINE_CODE|glDisable(GL_LIGHTING);}} ausgeschaltet werden. <br />
<br />
==Code==<br />
Vertexshader:<br />
<cpp>varying vec3 v;<br />
varying vec3 lightvec;<br />
varying vec3 normal;<br />
varying vec4 FrontColor;<br />
<br />
void main(void) {<br />
normal = normalize(gl_NormalMatrix * gl_Normal);<br />
v = vec3(gl_ModelViewMatrix * gl_Vertex);<br />
lightvec = normalize(gl_LightSource[0].position.xyz - v);<br />
<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
FrontColor = gl_Color;<br />
<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
}</cpp><br />
<br />
Fragmentshader:<br />
<cpp>varying vec3 v;<br />
varying vec3 lightvec;<br />
varying vec3 normal;<br />
varying vec4 FrontColor;<br />
<br />
uniform sampler2D Texture0;<br />
<br />
void main(void) {<br />
vec3 Eye = normalize(-v);<br />
vec3 Reflected = normalize(reflect( -lightvec, normal)); <br />
<br />
vec4 IAmbient = gl_LightSource[0].ambient * gl_FrontMaterial.ambient;<br />
vec4 IDiffuse = gl_LightSource[0].diffuse * max(dot(normal, lightvec), 0.0) * gl_FrontMaterial.diffuse;<br />
vec4 ISpecular = gl_LightSource[0].specular * pow(max(dot(Reflected, Eye), 0.0), gl_FrontMaterial.shininess) * gl_FrontMaterial.specular;<br />
<br />
gl_FragColor = vec4((gl_FrontLightModelProduct.sceneColor + IAmbient + IDiffuse) * texture2D(Texture0, vec2(gl_TexCoord[0])) + ISpecular);<br />
}</cpp></div>Ireyonhttps://wiki.delphigl.com/index.php?title=Datei:shader_ppl.png&diff=22227Datei:shader ppl.png2008-11-23T20:02:37Z<p>Ireyon: hat eine neue Version von „Bild:shader ppl.png“ hochgeladen: Per-Pixel-Lighting an einer texturierten GLUSphrere</p>
<hr />
<div>Dieses Bild demonstriert Per-Pixel-Lighting anhand einer GLU-Sphere.</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Datei:shader_ppl.png&diff=22226Datei:shader ppl.png2008-11-23T19:49:02Z<p>Ireyon: hat eine neue Version von „Bild:shader ppl.png“ hochgeladen</p>
<hr />
<div>Dieses Bild demonstriert Per-Pixel-Lighting anhand einer GLU-Sphere.</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Datei:shader_ppl.png&diff=22225Datei:shader ppl.png2008-11-23T19:46:40Z<p>Ireyon: hat eine neue Version von „Bild:shader ppl.png“ hochgeladen: zurückgesetzt auf die Version vom 23. November 2008, 19:36 Uhr</p>
<hr />
<div>Dieses Bild demonstriert Per-Pixel-Lighting anhand einer GLU-Sphere.</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Datei:shader_ppl.png&diff=22224Datei:shader ppl.png2008-11-23T19:41:53Z<p>Ireyon: hat eine neue Version von „Bild:shader ppl.png“ hochgeladen</p>
<hr />
<div>Dieses Bild demonstriert Per-Pixel-Lighting anhand einer GLU-Sphere.</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Datei:shader_ppl.png&diff=22223Datei:shader ppl.png2008-11-23T19:36:52Z<p>Ireyon: Dieses Bild demonstriert Per-Pixel-Lighting anhand einer GLU-Sphere.</p>
<hr />
<div>Dieses Bild demonstriert Per-Pixel-Lighting anhand einer GLU-Sphere.</div>Ireyonhttps://wiki.delphigl.com/index.php?title=Tutorial_glsl&diff=22222Tutorial glsl2008-11-23T19:02:28Z<p>Ireyon: </p>
<hr />
<div>=Präambel=<br />
Ave und willkommen bei meiner "Einführung" in die recht frische und mit OpenGL1.5 eingeführte Shadersprache "glSlang". In diesem umfangreichen Dokument werde ich versuchen, sowohl auf die Nutzung (sprich das Laden und Anhängen von Shadern im Quellcode), als auch auf die Programmierung von Shadern selbst einzugehen, inklusive aller Sprachelemente der OpenGL Shadersprache. Es wird also auch recht viele Informationen zu der C-ähnlichen Programmstruktur und den von glSlang angebotenen Variablen und Attributen gehen. Am Ende dieser Einführung sollten alle die, die sich für das Thema interessieren, in der Lage sein, zumindest einfach Shader zu schreiben und auch in ihren Programmen zu nutzen. Ausserdem soll dieses Dokument gleichzeitig als ein deutsches "Pendant" zu den von 3DLabs veröffentlichten Shaderspezifikationen, und damit als alltägliches Nachschlagewerk, dienen.<br />
<br />
<br />
==Vorkenntnisse==<br />
Wie auch schon mein ARB_VP-Tutorial richtet sich auch diese Einführung aufgrund ihrer Thematik eher an die fortgeschritteneren GL-Programmierer und neben sehr guten GL-Kenntnissen sollten sich alle, die sich daran versuchen wollen, mit den technischen Hintergründen der GL, wie z.B. dem Aufbau der Renderpipeline auskennen. Weiterhin sind C-Kenntnisse absolut erforderlich, da die Shader ja in einer an ANSI-C angelehnten Syntax geschrieben werden. Auch Begriffsdefinitionen zu Vertex oder Fragment werden zum Verständis dieser Einführung benötigt. Wer also noch am Anfang seiner GL-Karriere steht, dem wird dieses Dokument nicht viel nützen. Ganz nebenbei solltet ihr auch noch eine gehörige Portion Zeit (am besten nen kompletten Nachmittag) mitbringen, denn die folgende Kost ist nicht nur umfangreich, sondern auch manchmal recht schwer verdaulich.<br />
<br />
<br />
<br />
----<br />
<br />
<br />
<br />
=Was ist glSlang?=<br />
Wie Eingangs kurz angesprochen handelt es sich bei glSlang um eine Shadersprache, also um eine Hochsprache, in der man die programmierbaren Teile aktueller Grafikbeschleuniger nach eigenem Belieben programmieren kann. Sie stellt quasi den Nachfolger zu den in Assembler geschriebenen Vertex- und Fragmentprogrammen ([[GL_ARB_Vertex_Program]]/[[GL_ARB_Fragment_Program]]) dar und basiert auf ANSI C, erweitert um Vektor- und Matrixtypen sowie einige C++-Mechanismen.<br />
<br />
Die in glSlang geschriebenen Programme nennen sich, angepasst an die Terminologie von RenderMan und DirectX, [[Shader]] (im Gegensatz zu "Programme" bei ARB_VP/FP) und werden entweder auf Vertexe (VertexShader) oder Fragmente (FragmentShader) angewendet, andere noch nicht programmierbare Teile der GL-Pipeline wie z.B. die Rasterisierung können momentan noch nicht über Shader beeinflusst werden.<br />
<br />
<br />
==Voraussetzungen==<br />
<br />
glSlang ist ein recht neues Feature, dass mit OpenGL1.5 eingeführt wurde, weshalb eine entsprechend moderne Grafikkarte (DX9-Generation) inklusive aktuellster Treiber von Nöten ist. <br />
''Aktueller Stand (November 2005) ist wie folgt :''<br />
<br />
[http://www.ati.com ATI] haben bereits seit fast 2 Jahren (Catalyst 3.10) glSlang-fähige Treiber, allerdings kommt es besonders mit neueren Treibern hier und da immernoch zu Fehlern (oder es werden gar neue Fehler eingführt) und ATI zeigt momentan kein sehr starkes Interesse am fixen dieser Fehler.<br />
<br />
[http://www.nvidia.com NVidia] haben sich etwas mehr Zeit gelassen, allerdings ist deren glSlang-Implementation inzwischen recht ausgereift. Bugs gibts allerdings trotzdem hier und da, aber NVidias Entwicklersupport ist da recht offen für Fehlerberichte. Die aktuellen Treiber der 80er Reihe sind daher für glSlang-Nutzer bestens geeignet.<br />
<br />
[http://www.3dlabs.com 3DLabs], die glSlang quasi erfunden haben, haben natürlich hervorragenden glSlang Support in ihren Treiber, allerdings sind deren Wildcat-Karten kaum verbreitet.<br />
<br />
Natürlich benötigt ihr auch einen passenden OpenGL-Header der die für glSlang nötigen Extensions und Funktionen exportiert. Ich verweise dazu auf unseren internen OpenGL-Header [[DGLOpenGL.pas]] der da einwandfrei seine Dienste verrichtet und auch in der Beispielanwendung Verwendung findet.<br />
<br />
==Neue Extensions==<br />
Die GL-Shadersprache "besteht" in ihrer aktuellen Version aus folgenden Extensions, fürs Verständnis wäre es nicht schlecht, wenn ihr euch zumindest die Einleitungen dazu durchlest :<br />
* [[GL_ARB_Shader_Objects]] ([http://oss.sgi.com/projects/ogl-sample/registry/ARB/shader_objects.txt Orginal Spezifikation])<br />
: Definiert die API-Aufrufe die zum Erstellen, Kompilieren, Linken, Anhängen und Aktivieren von Shader- und Programmobjekten nötig sind. <br />
* [[GL_ARB_Vertex_Shader]] ([http://oss.sgi.com/projects/ogl-sample/registry/ARB/vertex_shader.txt Orginal Spezifikation])<br />
: Fügt der OpenGL Programmierbarkeit auf Vertexebene hinzu. <br />
* [[GL_ARB_Fragment_Shader]] ([http://oss.sgi.com/projects/ogl-sample/registry/ARB/fragment_shader.txt Orginal Spezifikation])<br />
: Fügt der OpenGL Programmierbarkeit auf Fragmentebene hinzu. <br />
* [[GL_ARB_Shading_Language_100]] ([http://oss.sgi.com/projects/ogl-sample/registry/ARB/shading_language_100.txt Orginal Spezifikation])<br />
: Gibt die unterstützte Version von glSlang an, momentan 1.00.<br />
<br />
<br />
==Objekte==<br />
Im Zuge der Vereinheitlichung der GL wird immer häufiger in Objekte gekapselt, deren API dann auch aneinander angelehnt ist. Ziel ist, dabei die Programmierung der GL uniform zu machen, so dass z.B. zwischen dem Erstellen und Verwalten eines Vertex-Buffer-Objektes oder eines Shader-Objektes kaum ein Unterschied besteht (demnächst kommen dann auch Pixel-Buffer-Objekte dazu). Mit glSlang wurden dann im Zuge dieser Aktion zwei neue Objekte eingeführt, deren Definition ihr euch unbedingt einprägen solltet :<br />
<br />
* '''Programmobjekt'''<br />
:Ein Objekt, an das die Shader später angebunden werden. Bietet Funktionalität zum Linken der Shader und prüft dabei die Kompatibilität zwischen Vertex- und Fragmentshader.<br />
<br />
* '''Shaderobjekt'''<br />
:Dieses Objekt verwaltet den Quellcodestring eines Shaders und ist entweder vom Typ '''GL_VERTEX_SHADER_ARB''' oder '''GL_FRAGMENT_SHADER_ARB'''.<br />
<br />
<br />
==Resourcen==<br />
Die Shadersprache ist keinesfalls final und es wurden bereits diverse Ausdrücke für zukünftige Verwendung reserviert, denn ein Ziel bei ihrer Entwicklung war es, sie so zukunftsorientiert zu gestalten, dass auch Grafikkarten der nächsten und übernächsten Generation voll ausgenutzt werden können. Damit einher geht die Tatsache, dass sich die Spezifikationen in Zukunft ändern/erweitern werden, weshalb man da immer einen Blick hineinwerfen sollte. Die Anlaufstelle dafür ist natürlich die [http://www.3dlabs.com/support/developer/ogl2/index.htm GL2-Seite von 3D-Labs], wo u.a. auch ein OGL2-SDK und diverse Whitepapers als PDFs angeboten werden, in denen auch stattgefundene Änderungen an glSlang dokumentiert sind.<br />
<br />
=glSlang im Programm=<br />
Bevor wir uns mit der Syntax von glSlang beschäftigen, zeige ich euch erstmal, wie ihr Shader in euer Programm einbindet und nutzt. Warum das zuerst? Ganz einfach deshalb, weil ihr dann das, was ihr im glSlang-Syntaxteil lernt, direkt in eurer Testanwendung verwenden könnt. Hoffe diese Entscheidung klingt logisch und findet Anklang.<br />
<br />
Zuerst benötigen wir natürlich unsere Objekte. Zum einen ein ''Programmobjekt'', an das unsere Shader gebunden werden, und zwei ''Shaderobjekte'', die den Quellcode unseres Vertex bzw. Fragment Shaders aufnehmen. Dazu wurde eigens der neue "Datentyp" {{INLINE_CODE|glHandleARB}} eingeführt, der ein Objekthandle repräsentiert. Wir deklarieren also wie folgt :<br />
<br />
ProgramObject : GLhandleARB;<br />
VertexShaderObject : GLhandleARB;<br />
FragmentShaderObject : GLhandleARB;<br />
<br />
<br />
Nach dieser Deklaration können wir dann damit beginnen unsere Objekte zu erstellen. Den Anfang macht das Programmobjekt :<br />
<br />
ProgramObject := glCreateProgramObjectARB;<br />
<br />
Die Funktion [[glCreateProgramObjectARB]] erstellt uns oben ein leeres Programmobjekt und gibt ein gültiges Handle darauf zurück.<br />
<br />
Weiter gehts mit der Erstellung unseres Vertex bzw. Fragment Shaders :<br />
<br />
VertexShaderObject := glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB);<br />
FragmentShaderObject := glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);<br />
<br />
[[glCreateShaderObjectARB]] dient zur Generierung eines leeren Shaderobjektes. Momentan unterstützt diese Funktion VertexShader und FragmentShader.<br />
<br />
Nachdem wir nun also zwei gültige Shaderobjekte haben, wollen wir diese auch mit entsprechendem Quellcode versorgen :<br />
<br />
glShaderSourceARB(VertexShaderObject, 1, @ShaderText, @ShaderLength);<br />
glShaderSourceARB(FragmentShaderObject, 1, @ShaderText, @ShaderLength);<br />
<br />
Via [[glShaderSourceARB]] setzen wir den Quellcode eines Shaderobjektes ''komplett'' neu. Zum Laden des Quellcodes bietet sich unter Delphi übrigens eine TStringList geradezu an. Es sollte beachtet werden, dass der Quellcode zu diesem Zeitpunkt ''nicht geparst'' wird, also keine Fehleruntersuchung stattfindet.<br />
<br />
Der Quellcode wurde jetzt also an unsere Shaderobjekte gebunden und sollte dann natürlich auch noch kompiliert werden :<br />
<br />
glCompileShaderARB(VertexShaderObject);<br />
glCompileShaderARB(FragmentShaderObject);<br />
<br />
Der glSlang-Compiler des Treibers wird bei einem Aufruf von [[glCompileShaderARB]] versuchen, unsere Shader zu kompilieren. Sofern diese keine Fehler aufweisen, sollte dies auch erfolgreich sein. Wenn nicht, dann spuckt uns der ShaderKompiler je nach Treiber recht detaillierte Infos aus. Wie man an diese Infos kommt könnt ihr gleich nachlesen.<br />
<br />
Wenn unsere Shader dann kompiliert werden konnten, ist es Zeit, diese an unser anfangs erstelltes Programmobjekt anzuhängen :<br />
<br />
glAttachObjectARB(ProgramObject, VertexShaderObject);<br />
glAttachObjectARB(ProgramObject, FragmentShaderObject);<br />
<br />
<br />
Nachdem die Shaderobjekte nun an das Programmobjekt angehangen wurden, werden diese nicht mehr benötigt und ihre Resourcen können freigegeben werden :<br />
<br />
glDeleteObjectARB(VertexShaderObject);<br />
glDeleteObjectARB(FragmentShaderObject);<br />
<br />
<br />
Am Schluß müssen wir dann noch unsere ans Programmobjekt gebundenen Shader linken :<br />
<br />
glLinkProgramARB(ProgramObject);<br />
<br />
Während [[glCompileShaderARB]] unsere Shader auf syntaktische Fehler innerhalb ihres lokalen Raums geprüft hat, werden beim Linken durch [[glLinkProgramARB]] die angehangenen Shader zu einem ausführbaren Shader gelinkt. Folgende Bedingungen führen zu einem '''Linkerfehler''':<br />
<br />
* Die Zahl der von der Implementation unterstützten Attributvariablen wurde überschritten<br />
* Der Speicherplatz für Uniformvariablen wurde überschritten<br />
* Die Zahl der von der Implementation angebotenen Sampler wurde überschritten<br />
* Die main-Funktion fehlt<br />
* Die Liste der Varying-Variablen des Vertexshaders stimmt nicht mit der des Fragmentshaders überein<br />
* Funktions- oder Variablenname nicht gefunden<br />
* Eine gemeinsame Globale ist mit unterschiedlichen Werten oder Typen initialisiert worden<br />
* Zwei Sampler unterschiedlichen Typs zeigen auf die selbe Textureneinheit<br />
* Ein oder mehrere angehangene(r) Shader wurden nicht erfolgreich kompiliert<br />
<br />
Die Nutzung von glSlang im eigenen Programm ist wie oben erkennbar also nicht wirklich schwer und innerhalb kurzer Zeit realisiert. Natürlich ist es auch möglich z.B. nur einen VertexShader oder nur einen FragmentShader an ein Programmobjekt zu binden.<br />
<br />
<br />
==Fehlererkennung==<br />
Natürlich wird es ohne Fehlerausgabe recht schwer, etwaige Probleme in einem Vertex- oder Fragmentshader zu finden. Doch auch in diesem Bereich wurde glSlang recht gut durchdacht und es wurden zwei Funktionen eingeführt, welche im Zusammenspiel die Fehlersuche recht einfach machen, nämlich [[glGetInfoLogARB]] und [[glGetObjectParameterivARB]] mit dem Argument {{INLINE_CODE|GL_OBJECT_INFO_LOG_LENGTH_ARB}}. Erstere Funktion liefert uns einen Logstring, während uns letztere Funktion dessen Länge angibt. Der Logstring wird verändert, sobald ein Shader kompiliert oder ein Programm gelinkt wird.<br />
<br />
Um die Ausgabe dieses Logs so einfach wie möglich zu machen, bietet es sich an beide in einer einfach Funktion unterzubringen :<br />
<br />
<pascal>function glSlang_GetInfoLog(glObject : GLHandleARB) : String;<br />
var<br />
blen,slen : GLInt;<br />
InfoLog : PGLCharARB;<br />
begin<br />
glGetObjectParameterivARB(glObject, GL_OBJECT_INFO_LOG_LENGTH_ARB , @blen);<br />
if blen > 1 then<br />
begin<br />
GetMem(InfoLog, blen*SizeOf(GLCharARB));<br />
glGetInfoLogARB(glObject, blen, slen, InfoLog);<br />
Result := PChar(InfoLog);<br />
Dispose(InfoLog);<br />
end;<br />
end;</pascal><br />
<br />
<br />
Die Funktion ist recht leicht erklärt : Zuerst lassen wir uns über {{INLINE_CODE|glGetObjectParameterivARB}} mitteilen wie lang der aktuelle Infolog ist. Sollte dort tatsächlich etwas drinstehen (blen > 1), dann lassen wir uns dessen Inhalt via {{INLINE_CODE|glGetInfoLogARB}} in {{INLINE_CODE|InfoLog}} ausgeben und liefern diesen als Ergebnis zurück.<br />
<br />
Wie bereits gesagt wird nur nach dem Kompilieren eines Shaders bzw. dem Linken eines Programmobjektes ein Infolog erstellt. Es bietet sich dadurch an, direkt danach einen solchen Aufruf zu machen :<br />
<br />
glCompileShaderARB(VertexShaderObject);<br />
ShowMessage(glSlang_GetInfoLog(VertexShaderObject));<br />
<br />
Wenn unser Vertex Shader komplett fehlerfrei kompiliert werden konnte, dann sehen wir als Ergebnis nur einen leeren Dialog. Ist dies nicht der Fall, so werden wir vom Treiber mit recht detaillierten Fehlerinformationen "belohnt", z.B. so :<br />
<br />
[[Bild:GLSL_error_vshader.jpg|center]]<br />
<br />
Auch das Infolog nach dem Linken des Programmobjektes dürfte, selbst wenn keine Fehler vorkommen, recht interessant sein, das sieht dann nämlich so aus :<br />
<br />
[[Bild:GLSL info programobject.jpg|center]]<br />
<br />
Wie zu sehen, wird uns nach dem erfolgreichen Linken auch gesagt, ob und welcher Shader in Hardware bzw. Software läuft. Für Debuggingzwecke sicherlich eine mehr als brauchbare Information.<br />
<br />
<br />
==Shader benutzen==<br />
Um den Shader auch für die nächsten Polygone zu benutzen, ruft man die Funktion<br />
glUseProgramObjectARB(ProgramObject);<br />
um alle Shader zu deaktivieren, ruft man dieselbe Funktion mit dem Parameter 0.<br />
<br />
==Parameterübergabe==<br />
Uniformparameter (mehr dazu später) stellen die Schnittstelle zwischen eurem Programm und dem Shader dar, werden also genutzt um Daten aus dem Programm heraus an einen Shader zu übergeben. Zur Übergabe dieser Parameter bietet OpenGL diverse Funktionen, die alle Abkömmlinge von [[glUniformARB]] sind. Während mit {{INLINE_CODE|glUniform4fARB}} z.B. ein Vier-Komponentenvektor an das Programmobjekt übergeben wird, kann man mittels {{INLINE_CODE|glUniformMatrix4fvARB}} ganze Matrizen schnell und einfach übergeben. Ausserdem gibt es nun die Möglichkeit Uniformparameter direkt über ihren Namen, statt wie unter ARB_FP/VP über einen festen Index zu adressieren. Die Funktion [[glGetUniformLocationARB]] gibt anhand des übergebenen Parameternamens dessen Position zurück. Man kann also ganz einfach über den Namen drauf zugreifen :<br />
<br />
glUniform3fARB(glGetUniformLocationARB(ProgramObject, PGLCharARB('LightPosition')), LPos[0], LPos[1], LPos[2]);<br />
glUniform1iARB(glGetUniformLocationARB(ProgramObject, PGLCharARB('texSamplerTMU3')), 3);<br />
<br />
<br />
Wichtig ist hier, das man je nach Parametertyp auch die passende Anzahl von Argumenten übergibt. Also für einen 4-Komponenten Floatvektor {{INLINE_CODE|glUniform4fARB}} und für einen einfachen Integerwert (z.B. Textureinheit für einen Sampler) glUnifrom1iARB. Auch nicht vergessen dürft ihr, das die Namen der Parameter genauso wie im Shader geschrieben werden müssen, also Groß- und Kleinschreibung beachtet werden muß.<br />
<br />
=Die Shadersprache=<br />
<br />
Nachdem wir uns mit der Einbindung der glSlang-Shader in unser Programm beschäftigt haben, wollen wir uns in den folgenden Kapiteln um die Sprachelemente von glSlang kümmern. Wie schon gesagt basiert glSlang auf ANSI-C, wurde allerdings um speziell auf den Zielbereich angepasste Vektor- und Matrixtypen und einige C++-Features wie das freie deklarieren von Variablen an jeder Stelle und das Funktionsüberladen auf Basis des Argumenttyps erweitert. Wer sich ein wenig mit C/C++ auskennt sollte also in der nun folgenden Materie keine Probleme bekommen.<br />
<br />
'''Obligatorische Hinweise für verwöhnte Delphi-Nutzer : '''<br />
*Wie von C/C++ her gewohnt, spielt auch in glSlang die Groß- und Kleinschreibung eine wichtige Rolle, also bitte achtet darauf. gl_Position ist eine komplett andere Variable als z.B. gl_position.<br />
*Es findet keine automatische Typenkonvertierung statt. Das bedeutet also das float MyFloat = 1 ungültig ist und es in dem Falle float MyFloat = 1.0 heissen muss. Typecasts müssen also immer manuell stattfinden, z.B. MyFloat = float(MyInt).<br />
<br />
'''Kleine Programmstrukturkunde für C-Unkundige :'''<br><br />
Da sicherlich einige Delpher nie richtig was mit C gemacht haben, zeige ich mal anhand eines kleinen Beispieles (das auf keinen Fall nen brauchbaren Shader darstellt) den grundlegenden Aufbau eines glSlang-Shaders, der natürlich dem Aufbau eines C-Programmes stark ähnelt :<br />
<glsl><br />
uniform vec4 VariableA;<br />
float VariableB;<br />
vec3 VariableC;<br />
const float KonstanteA = 256.0;<br />
<br />
float MyFunction(vec4 ArgumentA)<br />
{<br />
float FunktionsVariableA = float(5.0);<br />
<br />
return float(ArgumentA * (FunktionsVariableA + KonstanteA));<br />
}<br />
<br />
// Ich bin ein Kommentar<br />
/* Und ich auch */<br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
}<br />
</glsl><br />
<br />
Sieht doch recht bekannt aus, unser Programmaufbau. Delphi und C haben ja so einige Grundlagen gleich, darunter auch der ungefähre Programmaufbau. Ausserhalb jeglicher Funktionen legen wir am Programmanfang unsere Variablen, Konstanten und Attribute fest, die dann ''global'' nutzbar sind, also in jeder Funktion.<br />
<br />
Darunter deklarieren wir dann eine kleine Funktion. Wie auch bei den Variablendeklarationen wird hier der Rückgabetyp nicht wie bei Pascal nach dem Funktionsnamen untergebracht, sondern davor. Innerhalb der Funktion können dann wieder Variablen deklariert werden, die dann allerdings ''lokal'', also nur in dieser Funktion nutzbar sind. Vorteil dieser Deklaration ist die Tatsache, dass je nach Grafikkarte nur bestimmt viele globale Variablen deklariert werden können. Wenn möglich sollte man also mit lokalen Vorlieb nehmen. Unsere Funktion gibt dann natürlich noch via return einen Wert zurück, ''was gemacht werden muss'', sofern man diese nicht als void deklariert hat (entspräche dann einer Prozedur in Pascal). Wird dies nicht getan, so spuckt der Compiler einen Fehler aus.<br />
<br />
Auch wichtig sind natürlich Kommentare. Erste Variante (Doppelslash) ist auch in der Pascalwelt verfügbar und kommentiert eine einzelne Zeile aus. Die Variante darunter kann man für Kommentarblöcke nutzen (/* .. */) und entspricht den Kommentaren in geschweiften Klammern in Delphi.<br />
<br />
Danach kommt dann die '''wichtigste Funktion''' des Shaders, nämlich '''main''', die in keinem Shader fehlen darf. Sie stellt quasi den Programmkörper dar und ist oft auch die einzige Funktion in einem Shader. Sie erhält weder ein Argument, noch gibt sie einen Wert zurück.<br />
<br />
Soviel also zum grundlegenden Aufbau eines Shader. Hoffe das jetzt alle die in C nicht so bewandert sind damit klar kommen, und dann bald ihre ersten glSlang-Shader schreiben können.<br />
<br />
<br />
==Datentypen==<br />
<br />
Obwohl einige Datentypen aus C übernommen wurden, sieht man der Typenliste an, das diese speziell auf den 3D-Bereich zugeschnitten wurde. Variablen müssen vor ihrer Nutzung eindeutig deklariert sein, Typecasting erfolgt über Konstruktoren (dazu später mehr). Folgende Datentypen stehen sowohl im Vertex- als auch Fragmentshader zur Verfügung :<br />
<br />
<div align="center"><br />
{|{{Prettytable_B1}}<br />
!Datentyp <br />
!Erklärung<br />
|-<br />
|void <br />
|Für Funktionen die keinen Wert zurückgeben<br />
|-<br />
|bool <br />
|Konditionaler Typ, entweder true (wahr) oder false (falsch)<br />
|-<br />
|int <br />
|Vorzeichenbehafteter Integerwert<br />
|-<br />
|float <br />
|Fließkommaskalar mit Singlegenauigkeit (32 Bit)<br />
|-<br />
|vec2 <br />
|2-Komponenten Fließkommavektor<br />
|-<br />
|vec3 <br />
|3-Komponenten Fließkommavektor<br />
|-<br />
|vec4 <br />
|4-Komponenten Fließkommavektor<br />
|-<br />
|bvec2 <br />
|2-Komponenten Booleanvektor<br />
|-<br />
|bvec3 <br />
|3-Komponenten Booleanvektor<br />
|-<br />
|bvec4 <br />
|4-Komponenten Booleanvektor<br />
|-<br />
|ivec2 <br />
|2-Komponenten Integervektor<br />
|-<br />
|ivec3 <br />
|3-Komponenten Integervektor<br />
|-<br />
|ivec4 <br />
|4-Komponenten Integervektor<br />
|-<br />
|mat2 <br />
|2x2 Fließkommamatrix<br />
|-<br />
|mat3 <br />
|3x3 Fließkommamatrix<br />
|-<br />
|mat4 <br />
|4x4 Fließkommamatrix<br />
|-<br />
|sampler1D <br />
|Zugriff auf 1D-Textur<br />
|-<br />
|sampler2D <br />
|Zugriff auf 2D-Textur<br />
|-<br />
|sampler3D <br />
|Zugriff auf 3D-Textur<br />
|-<br />
|samplerCube <br />
|Zugriff auf Cubemap<br />
|-<br />
|sampler1DShadow <br />
|Zugriff auf 1D-Tiefentextur mit Vergleichsoperation<br />
|-<br />
|sampler2DShadow <br />
|Zugriff auf 2D-Tiefentextur mit Vergleichsoperation<br />
|-<br />
|}<br />
</div><br />
Die sampler-Typen stellen eine besondere Klasse dar und werden im Kapitel 6.7 genauer erklärt, inklusive einiger Anwendungsbeispiele.<br />
<br />
<br />
===Arrays===<br />
<br />
Natürlich unterstützt glSlang auch Arrays, die wie in C deklariert werden und deren Index bei 0 beginnt. Folgendes Array im Shader :<br />
<glsl><br />
float temp[3];<br />
</glsl><br />
beginnt also bei Index 0 und endet bei Index 2. Im Gegensatz zu C lassen sich Arrays in glSlang allerdings ''nicht bei der Initialisierung vorbelegen''. Wenn ein Array als Parameter einer Funktion deklariert wird, so darf dieses keine Dimensionierung erhalten.<br />
<br />
<br />
===Strukturen===<br />
<br />
Neu ggü. ARB_FP/VP ist nun auch die Möglichkeit, Strukturen in einem Shader zu deklarieren. Vor allem die Übersicht komplexerer Shader kann dadurch stark verbessert werden. Strukturen werden wie gewohnt mit dem Schlüsselwort {{INLINE_CODE|struct}} eingeleitet und können dann zur Typisierung von Variablen genutzt werden. Folgendes Beispiel dürfte die Nutzung verdeutlichen :<br />
<glsl><br />
struct light<br />
{<br />
bool active;<br />
float intensity;<br />
vec3 position;<br />
vec3 color;<br />
};<br />
</glsl><br />
Im Shader können dann neue Variablen von diesem Typ ganz einfach deklariert werden :<br />
<glsl><br />
light LightSource[3];<br />
</glsl><br />
Der Zugriff auf die Elemente der Struktur erfolgt dann wie gewohnt über den Punkt :<br />
<glsl><br />
LightSource[3].position = vec3(1.0, 1.0, 5.0);<br />
</glsl><br />
<br />
<br />
==Typenqualifzierer==<br />
<br />
Zusätzlich zur Typendeklaration kann eine Variable noch einen Typenqualifizerer vorangestellt bekommen, der an den Anfang der Deklaration gehört.<br />
<br />
* '''const'''<br />
: Festgelegte (nur lesen) Konstante bzw. nur lesbarer Funktionsparameter.<br />
<br />
* '''uniform'''<br />
: Ein den ganzen Shader über gleichbleibender Wert, der eine Schnittstelle zwischen dem Shader und der OpenGL-Anwendung darstellt. Ein Uniformwert wird in der Hauptanwendung an den entsprechenden Shader übergeben und kann dort dann genutzt werden.<br />
<br />
* '''attribute'''<br />
: Nur lesbare Werte die eine Verbindung zwischen dem Shader und der OpenGL-VertexAPI darstellen (z.B. VertexParameter eines VertexArrays). Natürlich nur in einem Vertex Shader nutzbar.<br />
<br />
* '''varying'''<br />
: Stellt die Verbindung zwischen einem Vertex- und einem FragmentShader dar. Werden im VertexShader geschrieben und dann perspektivisch korrekt über die Primitive interpoliert, um dann im Fragment Shader gelesen werden zu können. Nutzbar sind hier nur die Typen float, vec2, vec3, vec4, mat2, mat3 und mat4, Strukturen und andere Datentypen können nicht varying sein. Die Namen einer varying-Variable müssen sowohl im VertexShader als auch im FragmentShader gleich sein.<br />
<br />
* '''in'''<br />
: Für Variablen die an eine Funktion übergeben und dort ausgelesen werden.<br />
<br />
* '''out'''<br />
: Für Variablen die von einer Funktion nach aussen zurückgegeben werden.<br />
<br />
* '''inout'''<br />
: Für Variablen die sowohl an eine Funktion übergeben als auch von dieser zurückgegeben werden.<br />
<br />
<br />
<br />
Um obige Auflistung nicht leer im Raum stehen zu lassen zeige ich ein paar Beispiele die hoffentlich zum Verständnis beitragen :<br />
<br />
===Beispiel A=== <br />
Vertexnormale soll an einen FragmenShader (interpoliert) übergeben werden :<br />
<br />
:Im VertexShader :<br />
<glsl><br />
varying vec3 VertexNormal;<br />
...<br />
VertexNormal = normalize(MV_IT * gl_Normal);<br />
</glsl><br />
:Im FragmentShader :<br />
<glsl><br />
varying vec3 VertexNormal;<br />
...<br />
TempVector = VertexNormal*...<br />
</glsl><br />
<br />
===Beispiel B=== <br />
Uniformparameter zur nachträglichen Farbänderung der Szene wird im Programm übergeben :<br />
<br />
:Im VertexShader :<br />
<glsl><br />
uniform vec4 GlobalColor;<br />
...<br />
gl_FrontColor = GlobalColor * gl_Color;<br />
</glsl><br />
:Im Programm :<br />
<br />
glUniform4fARB(glSlang_GetUniLoc(ProgramObject, 'GlobalColor'), Col[0], Col[1], Col[2], Col[3]);<br />
<br />
<br />
===Beispiel C=== <br />
Konstante zur festen Farbänderung :<br />
<br />
:Im VertexShader :<br />
<glsl><br />
const vec4 ColorBias = vec4(0.2, 0.3, 0.0, 0.0);<br />
...<br />
gl_FrontColor = ColorBias * gl_Color;<br />
</glsl><br />
==Konstruktoren==<br />
<br />
Um in einem Shader ''Vektoren'' oder ''Matrizen'' mit Werten zu belegen, gibt es sogenannte Konstruktoren (nicht zu verwechseln mit z.B. Klassenkonstruktoren unter Delphi), die im Endeffekt nichts anderes als Funktionen zur Vorbelegung von Vektoren oder Matrizen darstellen. Dabei trägt der Konstruktor den selben Namen wie die Typendeklaration, also lässt sich eine Variable vom Typ {{INLINE_CODE|vec4}} mit dem Konstruktor {{INLINE_CODE|vec4(float, float, float, float)}} initialisieren.<br />
<br />
Allerdings hat man sich recht viel Mühe bei dieser Konstruktorgeschichte gemacht, so dass man einen vec4 nicht unbedingt mit einem {{INLINE_CODE|vec4}}-Konstruktor vorbelegen muss, sondern es vielseitige Möglichkeiten gibt. Um dies zu verdeutlichen gibts ein paar Beispiele :<br />
<glsl><br />
vec4 Color = vec4(1.0, 0.0, 0.0, 0.0);<br />
vec4 Color = vec4(MyVec3, 1.0);<br />
vec4 Color = vec4(MyVec2_A, MyVec2_B);<br />
<br />
vec3 LVec = vec3(MyVec4);<br />
vec2 Tmp = vec2(MyVec3);<br />
</glsl><br />
<br />
Trotz der recht wenigen Beispiele sollte schnell erkennbar sein, das man hier wirklich sehr viele Kombinationsmöglichkeiten hat, die dann gültig sind ''wenn man mindestens auf die benötigte Anzahl der Argumente kommt''. Im vorletzten Beispiel wird z.B. ein 3-Komponentenvektor aus einem 4-Komponentenvektor initialisiert. Das erzeugt keinen Fehler, sondern führt dazu das {{INLINE_CODE|vec3.x, vec3.y, vec3.z}} aus MyVec4 übernommen werden und MyVec4.w einfach ignoriert wird.<br />
<br />
Das Umkehrbeispiel, also<br />
<glsl><br />
vec4 Color = vec4(MyVec3)<br />
</glsl><br />
funktioniert allerdings nicht, da hier die Zahl der benötigten Argumente nicht erreicht wird. In diesem Falle müsste es dann<br />
<glsl> <br />
vec4 Color = vec4(MyVec3, 0.0)<br />
</glsl><br />
heissen.<br />
<br />
Obiges gilt natürlich auch für ''Matrixkonstruktoren'', hier sind z.B. folgende Konstuktoren denkbar, obwohl eigentlich alle Möglichkeiten nutzbar sind, ''solange die benötigte Zahl an Argumenten erreicht wird'' :<br />
<glsl><br />
mat4 MyMatrix = mat4(MyVec4, MyVec4, MyVec4, MyVec4);<br />
mat2 MyMatrix = mat4(1.0, 0.0, 0.0, 0.0,<br />
0.0, 1.0, 0.0, 0.0,<br />
0.0, 0.0, 1.0, 0.0,<br />
0.0, 0.0, 0.0, 1.0);<br />
</glsl><br />
<br />
==Vektor- und Matrixkomponenten==<br />
<br />
Was natürlich in keiner Shadersprache fehlen darf, ist der leichte Zugriff auf die einzelnen Komponenten eines Vektors. glSlang bietet, je nach Anwendungsgebiet gleich drei Namensets für den Zugriff auf die Komponenten eines solchen Vektors, welches Set man nutzen will bleibt natürlich frei und ist unabhängig von der Deklaration eines Vektors. Man sollte nur darauf achten, beim gleichzeitigen Zugriff auf mehrere Komponenten im gleichen Namenset zu verbleiben :<br />
<br />
* {x, y, z, w}<br />
:Für den Zugriff auf Vektoren die Punkte, Normale oder sonstige Vertexdaten repräsentieren.<br />
<br />
* {r, g, b, a}<br />
:Für den Zugriff auf Vektoren die Farbwerte repräsentieren.<br />
<br />
* {s, t, p, q}<br />
:Für den Zugriff auf Vektoren die Texturkoordinaten repräsentieren.<br />
<br />
Ein paar Beispiele zur Unterstreichung des oben gesagten :<br />
<glsl><br />
v4.rgba = vec4(1.0, 0.0, 0.0, 0.0); // gültig<br />
v4.rgzw = vec4(1.0, 1.0, 1.0, 2.0); // Ungültig, da verschiedenen Namensets<br />
v2.rgb = vec3(1.0, 2.0, 1.0); // Ungültig, da vec2 nur r+g besitzt<br />
v2.xx = vec2(5.0, 3.0); // Ungültig, da 2 mal gleiche Komponente<br />
</glsl><br />
<br />
Auch der Zugriff auf die Komponenten einer Matrix geht leicht von der Hand. Namensets wie bei den Vektoren gibt es hier natürlich keine, aber folgende Beispiele sollen den Zugriff aufzeigen :<br />
<glsl><br />
MyMat4[2] = vec4(1.0); // Setzt die 3.Zeile der Matrix komplett auf 1.0<br />
MyMat4[3][3] = 3.5; // Setzt das Element unren rechts auf 3.5<br />
</glsl><br />
<br />
Ein Zugriff auf Matrixelemente ausserhalb ihrer Dimension (also z.B. MyMat4[4][4]) liefert unvorhersehabre Ergebnise, also sollte man auf diese Fälle prüfen. <br />
<br />
<br />
==Vektor- und Matrixoperationen==<br />
<br />
Wie von C gewohnt sind in glSlang so ziemlich alle Operatoren die man auf Matrizen oder Vektoren anwenden kann überladen, so das man nicht umständlich über selbstgeschriebene Funktionen kombinieren muss. Darüber hinaus ist es in den meisten Fällen auch möglich ohne Konvertierung Fließkommawerte mit kompletten Matrizen oder Vektoren zu kombinieren. Folgende Beispiele zeigen einige der vielfältigen Kombinationsmöglichkeiten auf :<br />
<glsl><br />
vec3 dest;<br />
vec3 source;<br />
float factor;<br />
<br />
vec3 dest = source + factor; <br />
<br />
// Ist gleich<br />
dest.x = source.x + factor;<br />
dest.y = source.y + factor;<br />
dest.z = source.z + factor;<br />
</glsl><br />
<br />
Matrix * Vektor ist auch ohne manuelle Konvertierung möglich :<br />
<glsl><br />
vec3 dest;<br />
vec3 source;<br />
mat3 MyMat;<br />
<br />
dest = source * MyMat; <br />
<br />
// Ist gleich<br />
dest.x = dot(source, MyMat[0]);<br />
dest.y = dot(source, MyMat[1]);<br />
dest.z = dot(source, MyMat[2]);<br />
</glsl><br />
<br />
Auch hier sind die Möglichkeiten fast unbeschränkt und zeigen wieder wie flexibel glSlang ausgelegt ist. <br />
<br />
==Operatoren==<br />
<br />
glSlang bietet (momentan) folgende Operatoren, die Liste ist nach ihrer Gewichtung sortiert (Anfang = höchste). Alle ''reservierten'' Operatoren werden erst in kommender Hardware/glSlang-Versionen nutzbar sein :<br />
<br />
<div align="center"><br />
{|{{Prettytable_B1}}<br />
!Operatorklasse <br />
!Operatoren <br />
!Assoziation<br />
|-<br />
|Gruppering <br />
|() <br />
| -<br />
|-<br />
|Arrayindizierung<br>Funktionsaufrufe und Konstruktoren<br>Strukturfeldwahl und Swizzle<br>Postinkrement und -dekrement<br> <br />
|[]<br>()<br>.<br>++ -- <br />
|Links n. Rechts<br />
|-<br />
|Prefixinkrement- und dekrement<br>Einheitlich (~ reserviert) <br />
| ++ --<br> + - ~ ! <br />
|Rechts n. Links<br />
|-<br />
|Mulitplikation (% reserviert) <br />
|* / % <br />
|Links n. Rechts<br />
|-<br />
|Additiv <br />
| + - <br />
|Links n. Rechts<br />
|-<br />
|Bitweises Verschieben (reserviert) <br />
|<< >> <br />
|Links n. Rechts<br />
|-<br />
|Relation <br />
|< > <= >= <br />
|Links n. Rechts<br />
|-<br />
|Vergleich <br />
|== != <br />
|Links n. Rechts<br />
|-<br />
|Bitweises AND (reserviert) <br />
|& <br />
|Links n. Rechts<br />
|-<br />
|Bitweises XOR (reserviert) <br />
|^ <br />
|Links n. Rechts<br />
|-<br />
|Bitweises OR (reserviert) <br />
| <nowiki>|</nowiki> <br />
|Links n. Rechts<br />
|-<br />
|Logisches AND <br />
|&& <br />
|Links n. Rechts<br />
|-<br />
|Logisches XOR <br />
|^^ <br />
|Links n. Rechts<br />
|-<br />
|Logisches OR <br />
| <nowiki>||</nowiki> <br />
|Links n. Rechts<br />
|-<br />
|Auswahl <br />
|?: <br />
|Rechts n. Links<br />
|-<br />
|Zuweisung<br>Arithmetrische Zuweisung<br>(Modulis, Shift und bitweise Op. reserviert) <br />
|<nowiki>=</nowiki><br> <nowiki>+= -= *= /= %=</nowiki> <br> <nowiki><<= >>= &= ^= |=</nowiki> <br />
|Rechts n. Links<br />
|-<br />
|Aufzählung <br />
|, <br />
|Links n. Rechts<br />
|-<br />
|}<br />
</div><br />
<br />
<br />
==Funktionen==<br />
<br />
Ein großer Vorteil von Hochsprachen ist u.A. die Möglichkeit oft genutzte Codeteile in Funktionen (bzw. auch Prozeduren unter Pascal) zu verpacken um so Flexibilität als auch Übersichtlichkeit zu steigern. Wer schonmal was in C geschrieben hat, der wird sich jetzt sicherlich kein Kopfzerbrechen machen müssen. Funktionen werden in glSlang genauso nach folgendem Prinzip deklariert :<br />
<glsl><br />
RückgabeTyp FunktionsName(Typ0 Argument0, Typ1, Argument1, ... , TypN, ArgumentN)<br />
{<br />
return RückgabeWert;<br />
}<br />
</glsl><br />
<br />
Funktionen die ''nichts zurückgeben'' müssen mit dem RückgabeTyp {{INLINE_CODE|void}} deklariert werden, ausserdem entfällt dann logischerweise das {{INLINE_CODE|return}}. Falls die Funktion eines ihrere Argumente nach aussen übergeben soll, muss dieses Argument mit dem Typenqualifizierer out (Siehe Kapitel 4.2) versehen werden. ''Arrays'' können nur als Eingabeargumente übergeben werden und dürfen nich dimensioniert als Argument verwendet werden, sondern müssen mit leeren Klammern argumentiert werden.<br />
Ein paar Beispiele :<br />
<glsl><br />
void MeineFunktion(float EingabeWert; out float AusgabeWert)<br />
{<br />
AusgabeWert = EingabeWert*MyConstValue;<br />
}<br />
</glsl><br />
<br />
Diese Funktion gibt ''nichts'' zurück, aber gibt EingabeWert*MyConstValue im Ausgabeargument AusgabeWert nach aussen.<br />
<glsl><br />
float MeineFunktion(float EingabeWert)<br />
{<br />
return EingabeWert*MyConstValue;<br />
}<br />
</glsl><br />
<br />
Bietet genau die selbe Funktionalität wie das Beispiel darüber. Allerdings wird hier der berechnete Wert als Ergebnis der Funktion zurückgeliefert.<br />
<glsl><br />
float VektorSumme(float v[])<br />
{<br />
return v[0]+v[1]+v[2]+v[3];<br />
}<br />
</glsl><br />
<br />
Wie bereits gesagt darf ein Array als Argument keine Dimensionierung enthalten. Wenn man der Funktion also ein Array übergibt, sollte man vorher drauf achten das es entsprechend der in der Funktion genutzten Indizes dimensioniert wurde.<br />
<br />
<br />
==if-Anweisung==<br />
<br />
Selektion über eine if-Anweisung darf auch in keiner Hochsprache fehlen. Genauso wie in C oder Delphi erwartet auch hier die If-Anweisung einen boolschen Ausdruck (Wahr oder Falsch) und wird dann ausgeführt (wahr) bzw. verzweigt auf ein (wenn vorhanden) else (falsch). Verschachtelung ist wie erwartet auch möglich.<br />
<br />
'''Hinweis : ''' <br />
Grafikkarten auf dem Stand des Shadermodells 2.0 (Radeon 9x00, Radeon X8x0, GeForceFX 5x00) unterstüzten im Fragmentshader kein Early-Out, was zur Folge hat das bei einer If-Anweisung immer alle Zweige ausgeführt werden. Am Ende wird dann aber nur ein Ergebnis geschrieben, die anderen verworfen. Auf solchen Karten bringen If-Anweisungen also im Normalfall keine Geschwindigkeitssteigerung, sondern oft eher das Gegenteil.<br />
Neuere SM3.0-Karten (Radeon X1x00, GeForce6x00 und höher) ist dass nicht mehr der Fall, da hier dynamische Verzweigungen und auch Early-Out von der Hardware implementiert werden.<br />
<br />
==Schleifen==<br />
<br />
Auch Schleifen, ein wichtiges Konzept jeder Hochsprache haben ihren Weg in glSlang gefunden. Unterstützt werden folgende Schleifentypen :<br />
<br />
* '''for'''-Schleife<br />
<glsl><br />
for (Startausdruck; Durchlaufbedingung; Wiederholungsausdruck;)<br />
{<br />
statement<br />
}<br />
</glsl><br />
<br />
* '''while'''-Schleife<br />
<glsl><br />
while (Durchlaufbedingung)<br />
{<br />
statement<br />
}<br />
</glsl><br />
<br />
* '''do'''-while-Schleife<br />
<glsl><br />
do<br />
{<br />
statement<br />
}<br />
while (Durchlaufbedingung)<br />
</glsl><br />
<br />
'''Hinweis :''' Grafikkarten auf dem Stand des Shadermodells 2.0 (Radeon 9x00, Radeon X8x0, GeForceFX 5x00) unterstüzten Schleifen nicht in Hardware. Schleifen werden dann beim Kompilieren vom Treiber entrollt, wodurch natürlich Shader mit weitaus mehr Instruktionen als erwartet generiert werden. Von daher sollte man auf solchen Karten möglichst auf Schleifen verzichten, oder diese nur recht kurz halten. Bei SM3.0-Karten (Radeon X1x00, GeForce6x00 und höher) ist dass nicht mehr der Fall.<br />
<br />
=Eingebaute Variablen, Attribute und Konstanten=<br />
Nachdem wir uns nun lange genug mit den minderinterssanten Elementen der glSlang-Syntax beschäftigt haben, gehts jetzt endlich an die wirklich interessanten Dinge. Wie schon ARB_VP/ARB_FP bringt auch glSlang jede Menge eingabauter Variablen, Attribute und Konstanten mit, deren Aliase sie recht leicht identifizierbar machen (ganz im Gegensatz zum Indexgewusel bei den DX-Shadern).<br />
<br />
<br />
==Variablen im Vertex Shader==<br />
Exklusiv im Vertex Shader stehen die folgenden Variablen zur Verfügung :<br />
<br />
* vec4 gl_Position muss geschrieben werden<br />
:Dieser Variable '''muss''' im Vertexshader ein Wert zugewiesen werden, wird dies nicht getan ist das Ergebnis (sprich die Position des Vertex) undefiniert. Vorgesehen ist diese Variable für die ''homogene Position des Vertex'' und wird u.a. zum Clipping und Culling verwendet. Sie darf natürlich auch (mehrfach) geschrieben und ausgelesen werden.<br />
<br />
* float gl_PointSize kann geschrieben werden<br />
:Diese Variable wurde dazu vorgesehen um dort im VertexShader die Punktgröße in Pixeln hineinzuschreiben.<br />
<br />
* vec4 gl_ClipVertex kann geschrieben werden<br />
:Falls genutzt, sollten hier die Vertexkoordinaten die im Zusammenhang mit benutzerdefinierten Clippingplanes genutzt werden abgelegt werden. Wichtig ist, das gl_ClipVertex im selben Koordinatenraum wie die Clippingplane definiert ist.<br />
<br />
==Attribute im Vertex Shader==<br />
<br />
Folgende Attribute stehen nur im Vertex Shader zur Verfügung und '''können nur gelesen werden''' :<br />
<br />
* vec4 gl_Color<br />
: Farbwert des Vertex.<br />
* vec4 gl_SecondaryColor<br />
:Sekundärer Farbwert des Vertex.<br />
* vec4 gl_Normal<br />
:Normale des Vertex.<br />
* vec4 gl_Vertex<br />
:Koordinaten des Vertex;<br />
* vec4 gl_MultiTexCoord0..7<br />
:Texturkoordinaten auf Textureinheit 0..7.<br />
* float gl_FogCoord<br />
:Nebelkoordinate des Vertex. <br />
<br />
<br />
==Variablen im Fragment Shader==<br />
<br />
Im Fragment Shader sind folgende Variablen exklusiv nutzbar :<br />
<br />
* vec4 gl_FragColor<br />
: Speichert den Farbwert des Fragmentes, der von folgenden Funktionen der festen Pipeline genutzt wird. Wird dieser Variable nichts zugewiesen, so ist ihr Inhalt undefiniert und darauf aufbauende Ergebnisse ebenfalls.<br />
<br />
* vec4 gl_FragData[0..15]<br />
: Ersetzt gl_FragColor bei der Verwendung von multiplen Rendertargets. <br />
<br />
* float gl_FragDepth<br />
: Durch schreiben dieser Variable kann man den von der festen Funktionspipeline ermittelten Tiefenwert überspringen, der mit {{INLINE_CODE|gl_FragCoord.z}} ausgelesen werden kann. Wird dieser Wert nicht geschrieben, nutzen folgende Funktionen der Pipeline den vorher fest berechneten Wert.<br />
<br />
* vec4 gl_FragCoord nur lesen<br />
: In dieser Variable ist die Position des Fragmentes relativ zur Fensterposition im Format x,y,z,1/w abgelegt, wobei z den von der festen Funktionspipeline berechneten Tiefenwert enthält.<br />
<br />
* bool gl_FrontFacing nur lesen<br />
: Gibt an ob das Fragment zu einer nach vorne zeigenden Primitive gehört (=true). <br />
<br />
<br />
Im Bezug auf {{INLINE_CODE|gl_FragColor}} und {{INLINE_CODE|gl_FragDepth}} sei noch anzumerken das diese ''nicht'' in den Wertebereich 0..1 gebracht werden müssen, da dies später durch die feste Funktionspipeline automatisch gemacht wird.<br />
<br />
==Eingebaute Varyings==<br />
<br />
Wie bereits in Kapitel 4.2 erwähnt, stellen Varyings eine Schnittstelle zwischen dem Vertex und dem Fragment Shader dar. Sie werden im Vertex Shader geschrieben und können dann im Fragment Shader ausgelesen werden, ohne das die folgenden Varyings dafür explizit deklariert werden müssen :<br />
<br />
* vec4 gl_FrontColor<br />
: Farbe der Vorderseite des Vertex.<br />
<br />
* vec4 gl_BackColor<br />
: Farbe der Rückseite des Vertex.<br />
<br />
* vec4 gl_FrontSecondaryColor<br />
: Sekundäre Farbe der Vorderseite des Vertex.<br />
<br />
* vec4 gl_BackSecondaryColor<br />
: Sekundäre Farbe der Rückseite des Vertex.<br />
<br />
* vec4 gl_TexCoord[x]<br />
: Texturkoordinaten des Vertex auf Textureinheit x, wobei x die von der Hardware zur Verfügung gestellte Zahl der Textureinheiten-1 nicht überschreiten darf.<br />
<br />
* float gl_FogFragCoord<br />
: Nebelkoordinate des Fragmentes. <br />
<br />
Die Varyings {{INLINE_CODE|gl_FrontColor, gl_FrontSecondaryColor, gl_BackColor}} und {{INLINE_CODE|gl_BackSecondaryColor}} können im FragmentShader nur unter den Aliases gl_Color bzw. gl_SecondaryColor gelesen werden. Welcher Wert des Vertex Shaders im Fragment Shader dort eingesetzt wird ist abhängig davon ob das Fragment zu einer nach vorne oder nach hinten zeigenden Primitive gehört.<br />
<br />
<br />
==Eingebaute Konstanten==<br />
Auch diverse Konstanten wurden definiert um darauf schnell im Shader zugreifen zu können. In den Klammern stehen die von einer GL-Implementation als Mindestanforderung anzubietenden Werte. Alle Konstanten sind sowohl im Vertex als auch im Fragment Shader abrufbar :<br />
<br />
: OpenGL 1.0/1.2 :<br />
* int gl_MaxLights (8)<br />
* int gl_MaxClipPlanes (6)<br />
* int gl_MaxTextureUnits (2)<br />
<br />
<br />
: ARB_Fragment_Program :<br />
* int gl_MaxTextureCoordsARB (2)<br />
<br />
<br />
: Vertex_Shader :<br />
* int gl_MaxVertexAttributesGL2 (16)<br />
* int gl_MaxVertexUniformFloatsGL2 (512)<br />
* int gl_MaxVaryingFloatsGL2 (32)<br />
* int gl_MaxVertexTextureUnitsGL2 (1)<br />
<br />
<br />
: Fragment_Shader :<br />
* int gl_MaxFragmentTextureUnitsGL2 (2)<br />
* int gl_MaxFragmentUniformFloatsGL2 (64)<br />
<br />
<br />
==Eingebaute Uniformvariablen==<br />
<br />
Um den Zugriff auf OpenGL-Staten zu vereinfachen wurden in glSlang diverse Uniformvariablen zur direkten Verwendung im Shader eingebaut. Wie gewohnt wurden auch hier sinnvolle Namen verwendet, so dass eine tiefere Erklärung unnötig sein dürfte :<br />
<br />
* mat4 gl_ModelViewMatrix<br />
* mat4 gl_ProjectionMatrix<br />
* mat4 gl_ModelViewProjectionMatrix<br />
* mat3 gl_NormalMatrix<br />
* mat4 gl_TextureMatrix[gl_MaxTextureCoordsARB]<br />
:{{INLINE_CODE|gl_NormalMatrix}} repräsentiert die inversen oberen 3x3 Werte der Modelansichtsmatrix. {{INLINE_CODE|gl_TextureMatrix[x]}} adressiert maximal Anzahl Textureinheiten-1-Texturmatrizen.<br />
<br />
* float gl_NormalScale<br />
: Gibt den unter OpenGL festgelegten Faktor zur Skalierung der Normalen zurück.<br />
<br />
* struct gl_DepthRangeParameters<br />
<glsl><br />
struct gl_DepthRangeParameters<br />
{<br />
float near;<br />
float far;<br />
float diff;<br />
};<br />
gl_DepthRangeParameters gl_DepthRange;<br />
</glsl><br />
: Clippingplanes : <br />
* vec4 gl_ClipPlane[gl_MaxClipPlanes]<br />
<br />
*struct gl_PointParameters<br />
<glsl><br />
struct gl_PointParameters<br />
{<br />
float size;<br />
float sizeMin;<br />
float sizeMax;<br />
float fadeThresholdSize;<br />
float distanceConstantAttenuation;<br />
float distanceLinearAttenuation;<br />
float distanceQuadraticAttenuation;<br />
};<br />
gl_PointParameters gl_Point;<br />
</glsl><br />
*struct gl_MaterialParameters<br />
<glsl><br />
struct gl_MaterialParameters<br />
{<br />
vec4 emission;<br />
vec4 ambient;<br />
vec4 diffuse;<br />
vec4 specular;<br />
float shininess;<br />
};<br />
gl_MaterialParameters gl_FrontMaterial;<br />
gl_MaterialParameters gl_BackMaterial;<br />
</glsl><br />
*struct gl_LightSourceParameters<br />
<glsl><br />
struct gl_LightSourceParameters<br />
{<br />
vec4 ambient;<br />
vec4 diffuse;<br />
vec4 specular;<br />
vec4 position;<br />
vec4 halfVector;<br />
vec3 spotDirection;<br />
float spotExponent;<br />
float spotCutoff;<br />
float spotCosCutoff;<br />
float constantAttenuation;<br />
float linearAttenuation;<br />
float quadraticAttenuation;<br />
};<br />
gl_LightSourceParameters gl_LightSource[gl_MaxLights];<br />
</glsl><br />
*struct gl_LightModelParameters<br />
<glsl><br />
struct gl_LightModelParameters<br />
{<br />
vec4 ambient;<br />
};<br />
gl_LightModelParameters gl_LightModel;<br />
</glsl><br />
*struct gl_LightModelProducts<br />
<glsl><br />
struct gl_LightModelProducts<br />
{<br />
vec4 sceneColor;<br />
};<br />
gl_LightModelProducts gl_FrontLightModelProduct;<br />
gl_LightModelProducts gl_BackLightModelProduct;<br />
</glsl><br />
*struct gl_LightProducts<br />
<glsl><br />
struct gl_LightProducts<br />
{<br />
vec4 ambient;<br />
vec4 diffuse;<br />
vec4 specular;<br />
};<br />
gl_LightProducts gl_FrontLightProduct[gl_MaxLights];<br />
gl_LightProducts gl_BackLightProduct[gl_MaxLights];<br />
</glsl><br />
* vec4 gl_TextureEnvColor[gl_MaxFragmentTextureUnitsGL2]<br />
* vec4 gl_EyePlaneS[gl_MaxTextureCoordsARB]<br />
* vec4 gl_EyePlaneT[gl_MaxTextureCoordsARB]<br />
* vec4 gl_EyePlaneR[gl_MaxTextureCoordsARB]<br />
* vec4 gl_EyePlaneQ[gl_MaxTextureCoordsARB]<br />
* vec4 gl_ObjectPlaneS[gl_MaxTextureCoordsARB]<br />
* vec4 gl_ObjectPlaneT[gl_MaxTextureCoordsARB]<br />
* vec4 gl_ObjectPlaneR[gl_MaxTextureCoordsARB]<br />
* vec4 gl_ObjectPlaneQ[gl_MaxTextureCoordsARB]<br />
<br />
*struct gl_FogParameters<br />
<glsl><br />
struct gl_FogParameters<br />
{<br />
vec4 color;<br />
float density;<br />
float start;<br />
float end;<br />
float scale;<br />
};<br />
gl_FogParameters gl_Fog;<br />
</glsl><br />
Diese recht umfangreiche GL-Stateliste sollte eigentlich jeden Bedarf decken und momentan gibts kaum einen OpenGL-Status den man so nicht in einem Shader abfragen bzw. nutzen kann.<br />
<br />
=Eingebaute Funktionen=<br />
glSlang ist mit diversen Skalar- und Vektorfunktionen ausgestattet, die teilweise (idealerweise) sogar direkt in der Hardware ausgeführt werden, weshalb einer fertigen Funktion ggü. gleichwertigen eigenen Berechnungen immer der Vorzug zu geben ist.<br />
{{Hinweis| ''genType'' kann vom Type float, vec2, vec3 oder vec4 sein, ''mat'' vom Typ mat2, mat3 oder mat4.}}<br />
<br />
<br />
==Trigonometrie und Winkel==<br />
Alle übergebenen Winkel sollten, soweit nicht anders vermerkt, in Radien angegeben werden.<br />
<br />
* genType radians (genType degrees)<br />
: Wandelt von Grad nach Radien. <br />
* genType degrees (genType radians)<br />
: Wandelt von Radien nach Grad.<br />
* genType sin (genType angle)<br />
: Gibt den Sinus von Angle zurück, wobei Angle in Radien angegeben wird.<br />
* genType cos (genType angle)<br />
: Gibt den Cosinus von Angle zurück, wobei Angle in Radien angegeben wird.<br />
* genType tan (genType angle)<br />
: Gibt den Tangens von Angle zurück, wobei Angle in Radien angegeben wird.<br />
* genType asin (genType x)<br />
: Liefert den Arcsinus von x zurück, also den Winkel dessen Sinus x ergeben würde.<br />
* genType acos (genType x)<br />
: Liefert den Arccosinus von x zurück, also den Winkel dessen Cosinus x ergeben würde.<br />
* genType atan (genType y, genType x)<br />
: Liefert den Winkel zurück, dessen Tangens x/y ergeben würde.<br />
* genType atan (genType y_over_x)<br />
: Liefert den Winkel zurück, dessen Tangens x über y ergeben würde.<br />
<br />
==Exponentiell==<br />
* genType pow (genType x, genType y)<br />
: Gibt x hoch y zurück.<br />
* genType exp2 (genType x)<br />
: Gibt 2 hoch x zurück.<br />
* genType log2 (genType x)<br />
: Gibt den Logarithmus zur Basis 2 von x zurück.<br />
* genType sqrt (genType x)<br />
: Gibt die Wurzel von x zurück.<br />
* genType inversesqrt (genType x)<br />
: Gibt die umgekehrte Wurzel von x zurück. <br />
<br />
<br />
==Standardfunktionen==<br />
* genType abs (genType x)<br />
: Liefert den absoluten Wert von x zurück.<br />
* genType sign (genType x)<br />
: Gibt -1.0 zurück, wenn x < 0.0, 0.0 wenn x = 0.0 und 1.0 wenn x > 0.0.<br />
* genType floor (genType x)<br />
: Gibt denn nächsten Integerwert zurück, der kleiner oder gleich x ist.<br />
* genType ceil (genType x)<br />
: Gibt den nächsten Integerwert zurück, der größer oder gleich x ist.<br />
* genType fract (genType x)<br />
: Gibt den Nachkommateil von x zurück.<br />
* genType mod (genType x, float y) <br />
* genType mod (genType x, genType y)<br />
: Gibt den Modulus zurück. (=x-y * floor(x/y)) <br />
* genType min (genType x, genType y) <br />
* genType min (genType x, float y)<br />
: Liefert y zurück wenn y < x, ansonsten x. <br />
* genType max (genType x, genType y) <br />
* genType max (genType x, float y)<br />
: Liefert y zurück wenn x < y, ansonsten x. <br />
* genType clamp (genType x, genType minVal, genType maxVal) <br />
* genType clamp (genType x, float minVal, float maxVal)<br />
: Zwängt x in den Bereich minVal..maxVal. <br />
* genType mix (genType x, genType y, genType a)<br />
* genType mix (genType x, genType y, float a)<br />
: Liefert den linearen Blend zwischen x und y zurück. (= x * (1-a) + y * a) <br />
* genType step (genType edge, genType x)<br />
* genType step (float edge, genType x)<br />
: Liefert 0.0 zurück, wenn x <= edge, ansonsten 1.0. <br />
* genType smoothstep (genType edge0, genType edge1, genType x)<br />
* genType smoothstep (float edge0, float edge1, genType x)<br />
: Liefert 0.0 zurück, wenn x <= edge und 1.0 wenn x >= edge. Dabei wird eine weiche Hermite Interpolation zwischen 0 und 1 durchgeführt. <br />
<br />
<br />
==Geometrie==<br />
* float length (genType x)<br />
: Gibt die Länge des Vektors x (= sqrt(x[0]² + x[1]² + ... + x[n]²) zurück. <br />
* float distance (genType p0, genType p1)<br />
: Gibt die Distanz zwischen den zwei Vektoren p0 un p1 (= length(p0-p1)) zurück. <br />
* float dot (genType x, genType y)<br />
: Gibt das Punktprodukt von x und y zurück (=x[0]*y[0] + x[1]*y[1] + ... + x[n]*y[n]). <br />
* vec3 cross (vec3 x, vec3 y)<br />
: Gibt das Kreuzprodukt von x und y zurück. <br />
* genType normalize (genType x)<br />
: Normalisiert den Vektor x auf die Länge 1. <br />
* vec4 ftransform()<br />
: Nur im Vertex Shader. Die Funktion stellt sicher, das das eingehende Vertex haargenau so transformiert wird wie in der festen Funktionspipeline. gl_Position = ftransform() wird dann also gebraucht, wenn in mehreren Durchgängen sowohl im Shader als auch in der festen Pipeline gerendert wird, um sicherzustellen das in beiden Fällen die gleiche Vertexposition herauskommt. <br />
* genType faceforward (genType N, genType I, genType Nref)<br />
: Gibt einen nach vorne zeigenden Vektor N zurück. (If dot(NRef, I) < 0 return N else return -N) <br />
* genType reflect (genType I, genType N)<br />
: Gibt den an der Flächenausrichtung N reflektierten Vektor I zurück. (=I-2 * dot(N,I) * N) <br />
<br />
<br />
==Matrixfunktionen==<br />
* mat matrixCompMult (mat x, mat y)<br />
: Multipliziert Matrix X mit Matrix Y komponentenweise. Um eine normale lineare Matrixmultiplikation durchzuführen, sollte der "*"-Operator genutzt werden. <br />
<br />
<br />
==Vektorvergleiche==<br />
Die meisten Vektorvergleichsfunktionen liefern als Ergebnis einen boolvektor zurück, da die Vergleiche per Komponente stattfinden. Wenn man also x = vec4(1.0, 3.0, 0.0, 0.0) mit y = vec4(2.0, 1.5, 1.5, 0.0) via lessThan(x, y) vergleicht, erhält man als Ergebnis bvec(true, false, true, false).<br />
<br />
* bvec lessThan (vec x, vec y)<br />
* bvec lessThan (ivec x, ivec y)<br />
: Gibt den komponentenweisen Vergleich x < y zurück. <br />
* bvec lessThanEqual (vec x, vec y)<br />
* bvec lessThanEqual (ivec x, ivec y)<br />
: Gibt den komponentenweisen Vergleich x <= y zurück. <br />
* bvec greaterThan (vec x, vec y)<br />
* bvec greaterThan (ivec x, ivec y)<br />
: Gibt den komponentenweisen Vergleich x > y zurück. <br />
* bvec greaterThanEqual (vec x, vec y)<br />
* bvec greaterThanEqual (ivec x, ivec y)<br />
: Gibt den komponentenweisen Vergleich x >= y zurück. <br />
* bvec equal (vec x, vec y)<br />
* bvec equal (ivec x, ivec y)<br />
* bvec equal (bvec x, bvec y)<br />
: Gibt den komponentenweisen Vergleich x == y zurück. <br />
* bvec notEqual (vec x, vec y)<br />
* bvec notEqual (ivec x, ivec y)<br />
* bvec notEqual (bvec x, bvec y)<br />
: Gibt den komponentenweisen Vergleich x != y zurück. <br />
* bool any (bvec x)<br />
: Liefert true zurück, wenn mindestens eine der Komponenten von x true ist.<br />
* bool all (bvec x)<br />
: Liefert true zurück, wenn alle Komponenten von x true sind. <br />
* bvec not (bvec x)<br />
: Liefert die logische Negation von x zurück. <br />
<br />
<br />
==Texturenzugriffe==<br />
<br />
Diese wichtige Funktionskategorie dient dazu, Werte aus einer an eine Textureinheit gebundenen Textur zu ermitteln. Die Texturenzugriffe können sowohl im Vertex (!) als auch im Fragment Shader ausgeführt werden, wobei der optionale Parameter bias im Vertex Shader ignoriert wird. Allerdings gibt es zusätzlich Funktionen die auf "Lod" enden und nur im Vertex Shader genutzt werden dürfen um eben dieses Manko zu umgehen. Funktionen mit dem Suffix "Proj" geben einen projizierten Texturenwert zurück.<br />
<br />
: '''1D-Texturen :'''<br />
* vec4 texture1D (sampler1D sampler, float coord [, float bias])<br />
* vec4 texture1DProj (sampler1D sampler, vec2 coord [, float bias])<br />
* vec4 texture1DProj (sampler1D sampler, vec4 coord [, float bias])<br />
: Nur im Vertex Shader :<br />
* vec4 texture1DLod (sampler1D sampler, float coord, float lod)<br />
* vec4 texture1DProjLod (sampler1D sampler, vec2 coord, float lod)<br />
* vec4 texture1DProjLod (sampler1D sampler, vec4 coord, float lod)<br />
<br />
<br />
: '''2D-Texturen :'''<br />
* vec4 texture2D (sampler2D sampler, vec2 coord [, float bias])<br />
* vec4 texture2DProj (sampler2D sampler, vec3 coord [, float bias])<br />
* vec4 texture2DProj (sampler2D sampler, vec4 coord [, float bias])<br />
: Nur im Vertex Shader : <br />
* vec4 texture2DLod (sampler2D sampler, vec2 coord, float lod)<br />
* vec4 texture2DProjLod (sampler2D sampler, vec3 coord, float lod)<br />
* vec4 texture2DProjLod (sampler2D sampler, vec4 coord, float lod)<br />
<br />
<br />
: '''3D-Texturen :'''<br />
* vec4 texture3D (sampler3D sampler, vec3 coord [, float bias])<br />
* vec4 texture3DProj (sampler3D sampler, vec4 coord [, float bias])<br />
: Nur im Vertex Shader : <br />
* vec4 texture3DLod (sampler3D sampler, vec3 coord, float lod)<br />
* vec4 texture3DProjLod (sampler3D sampler, vec4 coord, float lod)<br />
<br />
<br />
: '''Cubemap :'''<br />
* vec4 textureCube (samplerCube sampler, vec3 coord [, float bias])<br />
: Nur im Vertex Shader : <br />
*vec4 textureCubeLod (samplerCube sampler, vec3 coord, float lod)<br />
<br />
<br />
: '''Tiefentextur (Shadowmap) :'''<br />
* vec4 shadow1D (sampler1DShadow sampler, vec3 coord [, float bias])<br />
* vec4 shadow2D (sampler2DShadow sampler, vec3 coord [, float bias])<br />
* vec4 shadow1DProj (sampler1DShadow sampler, vec4 coord [, float bias])<br />
* vec4 shadow2DProj (sampler2DShadow sampler, vec4 coord [, float bias])<br />
: Nur im Vertex Shader :<br />
* vec4 shadow1DLod (sampler1DShadow sampler, vec3 coord, float lod)<br />
* vec4 shadow2DLod (sampler2DShadow sampler, vec3 coord, float lod)<br />
* vec4 shadow1DProjLod (sampler1DShadow sampler, vec4 coord, float lod)<br />
* vec4 shadow2DProjLod (sampler2DShadow sampler, vec4 coord, float lod)<br />
<br />
<br />
Wie bereits eingangs gesagt ist dieses Kapitel ein sehr wichtiges, denn eine 3D-Szene ohne Texturen ist heute kaum denkbar. Darüber hinaus lassen sich durch Texturenzugriffe recht viele interessante Sachen machen, z.B. ein einfacher Blurfilter oder das freie überblenden bestimmter Texturenteile. Deshalb führe ich hier kurz ein paar Beispiele an, welche die Nutzung dieser Funktionen verdeutlichen sollen :<br />
<br />
===Beispiel A=== <br />
Eine Textur gebunden die einfach ausgegeben werden soll<br />
<br />
''Im Vertex Shader'' :<br />
<glsl><br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
}<br />
</glsl> <br />
Der Vertex Shader ist recht minimal. Neben der homogenen Vertexposition leiten wir hier nur die im OpenGL-Programm angegebenen Texturkoordinaten weiter. ''Dies ist aber unbedingt nötig!'' Ohne die letzte Zeile hätten wir im Fragment Shader keine gültigen Texturkoordinaten auf TMU0, was in einer Fehldarstellung enden würde.<br />
<br />
''im Fragment Shader'' :<br />
<glsl><br />
uniform sampler2D texSampler;<br />
<br />
void main(void)<br />
{<br />
gl_FragColor = texture2D(texSampler, vec2(gl_TexCoord[0]));<br />
}<br />
</glsl><br />
Zuerst deklarieren wir hier einen 2D-Texturensampler, wichtig : '''Texturensampler müssen IMMER als uniform deklariert werden!''' In der Hauptfunktion weisen wir dann einfach den über die Funktion texture2D aus unserer gebundenen Textur ausgelesenen Farbwert, anhand der vom Vertex Shader übergebenen Texturkoordinaten, zu.<br />
<br />
===Beispiel B=== <br />
Zwei Texturen, jeweils auf TMU0 und TMU1. Fragmentfarbe soll eine Multiplikation der beiden Texturen darstellen.<br />
<br />
In diesem Beispielfall (der recht häufig vorkommt) müssen wir im Programm festlegen, ''welcher Sampler welche Textureinheit adressiert'', genau deshalb müssen die Texturensampler auch als uniform deklariert werden. Die Standardtextureneinheit eines Samplers ist TMU0, was in unserem Falle natürlich nicht brauchbar ist. Also müssen wir unserem zweiten Textursampler im Programm mitteilen das er seine Daten aus TMU1 beziehen soll :<br />
<br />
glUniform1iARB(glSlang_GetUniLoc(ProgramObject, 'texSamplerTMU1'), 1);<br />
<br />
Dies ist also unbedingt zu machen, sobald ein Texturensampler eine Textureinheit > GL_TEXTURE_0 adressieren will. Die Textureneinheit des Samplers lässt sich also nicht im Shader selbst festlegen. Der Fragment Shader ist nun allerdings schnell hergeleitet (Vertex Shader verändert sich nicht, da TMU1 die Texturkoordinaten auch von TMU0 bezieht) :<br />
<br />
<br />
im Fragment Shader :<br />
<glsl><br />
uniform sampler2D texSamplerTMU0;<br />
uniform sampler2D texSamplerTMU1;<br />
<br />
<br />
void main(void)<br />
{<br />
gl_FragColor = texture2D(texSamplerTMU0, vec2(gl_TexCoord[0])) *<br />
texture2D(texSamplerTMU1, vec2(gl_TexCoord[0]));<br />
}<br />
</glsl><br />
<br />
==Noisefunktionen==<br />
Sowohl im Vertex als auch im Fragment Shader lassen sich [[GLSL noise|Noisefunktionen]] nutzen, mit deren Hilfe sich eine gewisse "Zufälligkeit" simulieren lässt (wirklich zufällige Werte sind es natürlich nicht). Ein zurückgegebener Wert liegt dabei immer im Bereich [-1..1] und ist immer bei gleichem Eigabewert auch immer gleich. Die Verwendung empfiehlt sich derzeit allerdings eher nicht, da nicht alle aktuellen Treiber die Funktionen unterstützen und eine Noisetextur wahrscheinlich performanter ist.<br />
<br />
* float noise1 (genType x)<br />
* vec2 noise2 (genType x)<br />
* vec3 noise3 (genType x)<br />
* vec4 noise4 (genType x)<br />
<br />
==Discard==<br />
Eigentlich keine Funktion, sondern eine Abbruchbedingung '''nur im Fragment Shader'''. Das Schlüsselwort {{INLINE_CODE|discard}} verwirft das aktuell bearbeitete Fragment und beendet gleichzeitig den Shader. Es kann z.B. genutzt werden um Alphamasking manuell durchzuführen.<br />
Man sollte dabei jedoch beachten dass ein Großteil der aktuellen Hardware kein "early-out" (frühes Beenden) im Fragmentshader unterstützt. Wenn dort also ein {{INLINE_CODE|discard}} auftaucht, wird trotzdem auch der Code danach ausgeführt und einfach verworfen. Einen Geschwindigkeitsvorteil durch diesen Befehl wird man also erst auf neueren Karten feststellen, die dieses Faeature auch so unterstützen wie es angedacht war. <br />
<br />
<br />
=Beispielshader=<br />
Wen bis hierhin nicht der Mut verlassen hat, und wer aufmerksam gelesen hat, dürfte jetzt also zumindest in der Lage sein kleinere Shader in glSlang zu schreiben und diese auch im Programm zu nutzen. Ich habe im Themenbereich "glSlang" versucht alle Bereiche der Shadersprache selbst anzusprechen und hoffe das auch brauchbar rübergebracht zu haben. Um oben erlerntes (hoffe ich doch mal) nochmal zu vertiefen werde ich jetzt (wie ich das bereits bei meinem ARB_VP-Tutorial getan habe) einen simplen Beispielshader (Vertex und Fragment Shader) auseinanderpflücken um so u.a. auch die Programmstruktur für alle die in C nicht so bewandert sind zu erörtern.<br />
<br />
<br />
==Der Vertex Shader==<br />
<glsl><br />
uniform vec4 GlobalColor;<br />
<br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_FrontColor = gl_Color * GlobalColor;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
}<br />
</glsl><br />
<br />
Wie gesagt recht simpel. Angefangen wird mit der Deklaration einer globalen Uniformvariable namens {{INLINE_CODE|GlobalColor}}. Wie wir uns erinnern gibt der Typenqualifizierer uniform an, das wir den Wert dieser Variable (ein 4-Komponentenvektor, da Farbwerte aus R,G,B und A bestehen) in unserem Programm an den Shader übermitteln.<br />
<br />
Danach gehts ohne Umwege direkt in unsere Hauptfunktion, da wir im Vertex Shader keine anderen Funktionen benötigen. Dort berechnen wir zuerst die homogene Position unseres Vertex, die sich aus der eingehenden Vertexposition multipliziert mit der Modelansichtsmatrix ergibt. Wie schonmal gesagt '''muss diesem Wert etwas zugewiesen werden''', da sonst alle darauf aufbauenden Funktionen unvorhersehbare Ergebnisse liefern.<br />
Ausserdem wollen wir die Frontfarbe unseres Vertex jedesmal mit der im Programm übergebenen GlobalColor multiplizieren, so dass wir den Farbwert der gesamten Szene aus unserem Programm heraus manipulieren können. Zu guterletzt geben wir dann noch unsere aus der festen Funktionspipeline erhaltenen Texturkoordinaten auf Textureinheit 0 weiter. Wenn im Fragmentshader Texturkoordinaten verwendet werden, '''muss das getan werden'''. <br />
<br />
<br />
==Der Fragment Shader==<br />
<glsl><br />
uniform sampler2D Texture0;<br />
uniform sampler2D Texture1;<br />
uniform sampler2D Texture2;<br />
uniform sampler2D Texture3;<br />
<br />
void main(void)<br />
{<br />
vec2 TexCoord = vec2( gl_TexCoord[0] );<br />
vec4 RGB = texture2D( Texture0, TexCoord );<br />
<br />
gl_FragColor = texture2D(Texture1, TexCoord) * RGB.r +<br />
texture2D(Texture2, TexCoord) * RGB.g +<br />
texture2D(Texture3, TexCoord) * RGB.b;<br />
}<br />
</glsl><br />
<br />
Auch hier passiert nicht wirklich viel Großartiges. Wir deklarieren beim Shaderanfang zuerst vier Texturensampler, da wir insgesamt vier verschiedene Texturen im Shader auslesen wollen, eine Verlaufstextur und drei Oberflächentexturen. Auch hier sei wieder gesagt das man Sampler '''immer als uniform deklarieren muss'''. In der Hauptfunktion deklarieren wir dann einen Farbvektor, der auch direkt einen Farbwert aus Textureinheit 0 zugewiesen bekommt. Auf Textureinheit 0 haben wir ihm Hauptprogramm eine Verlaufstextur gebunden, die angibt wie die drei folgenden Texturen ineinander geblendet werden.<br />
Danach schreiben wir dann den Farbwert des Fragmentes, der '''im Fragment Shader ausgegeben werden muss'''. Der besteht wie einfach zu erkennen aus Farbwert von Textureinheit 1 * Rotwert von Textureinheit 0 + Farbwert von Textureinheit 2 * Grünwert von Textureinheit 0 + Farbwert von Textureinheit 3 * Blauwert von Textureinheit 0. So ist z.B. an Stellen an denen in der Verlaufstextur reines blau liegt nur die dritte Textur sichtbar.<br />
<br />
So viel also zu unserem kleinen Beispielshader. Er ist weder besonders toll noch besonders sinnvoll, sollte aber auch eher dazu dienen euch glSlang ein wenig zu veranschaulichen, was mir hoffentlich gelungen ist.<br />
<br />
Wenn ihr in den vorangegangenen Kapiteln zumindest ein wenig aufgepasst habt, dann könnt ihr euch vor eurem inneren Auge hoffentlich vortstellen was der Shader macht : Er blendet drei Texturen weich anhand der Verlaufstextur ineinander über. Sowas kann man z.B. für ein Terrain nutzen, um dieses anhand einer Farbtextur zu texturieren. Für alle, die damit Probleme haben hier zwei Bilder die den Shader veranschaulichen. Links die Verlaufstextur, die angibt wo welche Textur wie stark gewichtet wird und rechts dann das Ergebnis :<br />
<br />
<div align="center"> [[BILD:GLSL_sample_shader_a.jpg]] [[BILD:GLSL_sample_shader_b.jpg]]</div><br />
<br />
=Post Mortem=<br />
Das wars also, meine "Einführung" in die OpenGL Shader Sprache. Ich hoffe es hat euch nicht gelangweilt und auch die von mir zur Verfügung gestellten Informationen haben euch hoffentlich ausgereicht. Mit der Veröffentlichung dieser Einführung geht übrigens auch die Eröffnung eines Shaderforums hier auf der DGL einher, in der ihr dann also fleissig Fragen zum Thema stellen oder eure Shader präsentieren könnt. In diesem Post Mortem gehe ich jetzt noch kurz auf die Zukunft von glSlang ein und zeige ein paar Screenshots (damit die Augen entspannen können), bevor ihr euch dann selbst in die Shaderwelt stürzen könnt. <br />
<br />
<br />
=Screenshots=<br />
<br />
Um eure Augen ein wenig zu verwöhnen und zu zeigen was man mit glSlang alles machen, v.a. da man jetzt Shader schön lesbar in einer Hochsprache verfassen kann, mal ein paar Screens. Besonders der zweite Shot sieht animiert noch besser aus :<br />
<br />
<center>[[BILD:GLSL_sample_Kugel.jpg]] [[BILD:GLSL_sample_Alien.jpg]] </center><br />
<br />
Die Zahl möglicher Effekte ist bei einer so flexiblen Shadersprache natürlich nahezu unbegrenzt, und besonders auf kommender Hardware werden bisher ungesehen Effekte den Einzu in die Echtzeitgrafik finden. Man darf also mehr als gespannt sein.<br />
<br />
=Die Zukunft=<br />
Viele werden sich sicherlich fragen, warum sie z.B. statt ARB_VP/FP oder Nvidias cG denn überhaupt auf glSlang setzen sollen. Doch solche Zweifel dürften bei einem genauen Blick auf die neue Shadersprache schnell verworfen sein. Zum einen steckt hinter glSlang dank des ARBs fast die komplette 3D-Industrie und zum anderen hat man beim Entwurf der Shadersprache, wie z.B. an vielen reservierten Wörtern/Funktionen erkennbar versucht so weit wie möglich in die Zukunft zu planen. So sollen auch Karten der nächsten und übernächsten Generation mit glSlang ausnutzbar sein, und was danach kommt wird durch Spracherweiterungen erreicht. Sich also jetzt (besonders da es krachneu ist) mit glSlang zu befassen, um nicht ganz den Anschluss an kommende Entwicklungen im 3D-Bereich zu verlieren, ist der beste Weg.<br />
<br />
Also viel Spaß beim Experimentieren und Shaderschreiben! Und nicht vergessen : Wir wollen sehen was ihr so treibt,<br />
<br />
Euer<br />
:Sascha Willems<br />
<br />
<br />
<br />
{{TUTORIAL_NAVIGATION|-|[[Tutorial_glsl2]]}}<br />
[[Kategorie:Tutorial|GLSL]]</div>Ireyon