https://wiki.delphigl.com/api.php?action=feedcontributions&user=Lord+Horazont&feedformat=atomDGL Wiki - Benutzerbeiträge [de]2024-03-28T23:24:00ZBenutzerbeiträgeMediaWiki 1.27.4https://wiki.delphigl.com/index.php?title=DGL-Chat&diff=24252DGL-Chat2009-10-26T19:55:56Z<p>Lord Horazont: /* IRC Chat Client */ Linkkorrektor</p>
<hr />
<div>= DelphiGL-Chat =<br />
<br />
Die DGL Community bietet neben der Website, dem Forum und dem Wiki auch ein interaktiven Chat über IRC an.<br />
Den Chat findet man im [http://www.euirc.net/de/ euIRCnet] unter '''#delphigl'''.<br />
In diesen Artikel erfahren Sie, welche Clients es gibt und wie man in diesen Chatroom hinein kommt.<br />
<br />
== IRC Chat Client ==<br />
IRC Clients gibt es wie Sand am Meer aber dementsprechend sind auch die Unterschiede.<br />
Da es sich um einen einfachen Zugang handeln soll, ist ein IRC Chatprogram am geeignesten.<br />
Ein Java Applet (wie es z.B. DelphiGL.com unter [http://chat.delphigl.com/ http://chat.delphigl.com/] zur Verfügung stellt) oder ein Webserver basierter Chat sind langsamer und bieten nicht den Funktionsumfang.<br />
<br />
Unter Windows:<br />
*[http://www.xchat.org/ XChat]<br />
*[http://www.trillian-messenger.de/ Trillian]<br />
*[http://www.mirc.de/default.php mIRC]<br />
*[http://www.miranda-im.org/ Miranda]<br />
*[http://www.hydrairc.com/ HydraIRC]<br />
*[http://www.pidgin.im/ Pidgin]<br />
<br />
Unter Linux:<br />
*[http://www.xchat.org/ XChat]<br />
*[http://www.pidgin.im/ Pidgin]<br />
<br />
Ratsam ist XChat zu verwenden, da dieser nicht nur unter Windows,Linux und Mac funktioniert, sondern auch OpenSource und auf dem neusten Stand ist. X-Chat kann unter Windows auch als Precompiled Version geladen werden aber oft sind diese dann kostenpflichtig. Darum ist eine selber compilieren oft praktischer, da es stabiler, kostenlos und oft auch schneller ist. Dies liegt daran, dass das Compilat auf das eigene System vorher angepasst wird.<br />
<br />
{{Hinweis|Im Channel solltet Ihr eine der folgenden Personen sehen: [[Benutzer:TAK2004|TAK2004]], [[Benutzer:Frase|Frase]], [[Benutzer:Lord Horazont|Horazont]] oder [[Benutzer:I0n0s|i0n0s]].<br />
Diese Personen sind Inventar und im Normalfall ist immer einer dieser Personen da.}}<br />
<br />
== XChat ==<br />
<br />
=== Installation von XChat ===<br />
----<br />
<br />
==== Compilieren ====<br />
----<br />
<br />
Die Source lässt sich unter dem obengenannten Link laden.<br />
<br />
#Shell öffnen<br />
#cd /...Ordner wo das Sourcepackage liegt.<br />
#Archive entpacken:<br> tar -xzvf xchat-versionsnummer.tar.gz <br> oder <br> tar -xjvf xchat-versionsnummer.tar.bz2<br />
#cd /...Ordner wo die Source entpackt wurde.../<br />
#Konfigurationsscript ausführen: <br> ./configure<br />
#das Programm compilieren lassen <br> make<br />
#zu root wechseln für eine systemweite Installation <br> su<br />
#sichergehen das man im Sourceordner ist<br />
#xchat installieren <br> make install <br />
<br />
==== Binary ====<br />
----<br />
<br />
Die binär Datei lässt sich unter dem oben genannten Link laden und mit simplen Klicks unter Windows installieren.<br />
Die Linux Variante kann man als RPM-Datei laden, diese ist eine Linux Installationsdatei und muss mit dem Programm rpm installiert werden.<br />
<br />
==== rpm ====<br />
----<br />
<br />
#Shell öffnen<br />
#den Ordner mit der rpm öffnen <br> cd /...ordner wo die rpm liegt.../<br />
#das rpm installieren <br> rpm -i xchat-versionsnummer<br />
<br />
Es gibt noch eine bequemere Variante, und zwar über ein Packagemanger X-Chat zu beziehen.<br />
<br />
===== apt-get =====<br />
----<br />
<br />
#Shell öffnen<br />
#zu Root wechseln <br> su -<br />
#apt-get aktualisieren <br> apt-get update<br />
#xchat suchen <br> apt-cache search xchat <br> xchat - A popular and easy to use graphical IRC (chat) client <br><br />
#das Ergebnis was gefunden wurde installieren <br> apt-get install xchat<br />
<br />
===== yum =====<br />
----<br />
<br />
#Shell öffnen<br />
#zu Root wechseln <br> su -<br />
#xchat suchen <br> yum search xchat <br> <b>xchat</b>.x86_64 : A popular and easy to use graphical IRC (chat) client <br><br />
#das Ergebnis was gefunden wurde installieren <br> yum install xchat<br />
<br />
=== Konfiguration von XChat ===<br />
----<br />
<br />
#X-Chat starten<br />
#oben im Menü X-Chat->Serverliste auswählen<br />
#die einzelnen Nicks eintragen falls die vorigen schon belegt sind<br />
#Hinzufügen drücken und ein Namen auswählen wie "dglchat_server"<br />
#Editieren<br />
#in der Liste ganz oben alle bis auf einen Eintrag "Entfernen" <br />
#den letzten Eintrag "Editieren" und "#irc.euirc.net" eintragen<br />
#ein Häkchen bei "Automatisches Verbinden mit diesem Netzwerk beim Start"<br />
#bei "Zu betretende Channels:" "#delphigl" eintragen<br />
#bei "Zeichensatz:" "ISO-8859-2 (Central Europe)" auswählen (dies hat den Grund X-Chat läuft standardmässig mit UTF aber mIRC und andere IRC Clients nutzen noch veraltete Zeichensätze und dann würden umlaute nicht korrekt interpretiert werden)<br />
#fenster schließen<br />
#bei "Keine Serverliste beim Start" ein Häkchen machen,"Verbinde" drücken und schließen<br />
<br />
== Miranda ==<br />
<br />
=== Installation von Miranda ===<br />
----<br />
Die Installation sollte ansich selbsterklärend sein.<br />
Wichtig ist es nur das IRC-PlugIn mitzuinstallieren.<br />
<br />
=== Konfiguration von Miranda ===<br />
----<br />
In Miranda-Optionen unter Network sollten folgende Einträge vorhanden sein:<br />
*IRC<br />
*IRC Advanced<br />
*IRC DCC'n CTCP<br />
Wenn nicht sollte unter Plugins kontrolliert werden ob irc.dll ausgewählt ist und läuft.<br />
<br><br><br />
Gehen wir nun in den Tab IRC:<br />
Unter '''Default network''' wählt ihr euIRCnet: Random server.<br />
{{Hinweis|Wenn man mehrere Netzwerke besuchen will reicht es aus die irc.dll und irc_servers.ini zu kopieren}}<br />
Nick ist euere Nickname. Alternative nick ein alternativer Nick falls der erste schon verwendet wird.<br />
Unter '''Other''' sollte noch 'Rejoin channel if kicked' deaktiviert werden.<br><br />
<br />
Um einen Channel zu joinen klickt man auf das Miranda Icon in der Menuleiste, wählt 'IRC->Join a Channel' aus.<br />
Dort gibt ihr dann #delphigl ein.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=DelphiGL&diff=24238DelphiGL2009-10-24T12:25:16Z<p>Lord Horazont: /* Historie im Überblick */ Bin ich eine Last? ;) (Load Horazont -> Lord Horazont)</p>
<hr />
<div>{|<br />
|Dieser Artikel beschäftigt sich mit [http://DelphiGL.com DelphiGL.com].<br> <br />
DelphiGL ist die Website welche dieses Wiki betreibt und das angeschlossene Forum ist der feste Hafen für deutschsprachige OpenGL-Programmierer im sturmumtosten GrafikAPI-Ozean.<br />
<br />
Informationen zum Team findet ihr im Artikel [[Team]].<br />
|<div align="right">[http://delphiGL.com http://delphigl.com/gfx/banner/banner256x64.gif]</div><br />
<small>Falls Ihr auf DelphiGL.com oder das DGL-Wiki verlinken wollt, findet ihr die nötigen Banner '''[[DelphiGL_Banner|hier]]'''.</small><br />
|-<br />
|}<br />
<br />
<br />
==Mitgliederentwicklung==<br />
[[Bild:DGL_Mitgliederzahlen.jpg|center|framed|<br />
Monatliches Wachstum der DGL Community (Angemeldete User im Forum):<br><br />
- insgesamt: 19,83 Mitglieder.<br><br />
- letzten 12 Monate: 19,41 Mitglieder.<br><br />
- letzten 6 Monate: 17,16 Mitglieder.]]<br />
<br />
<br />
Für Liebhaber von Statistiken: <br />
<div align="center"><br />
{|{{Prettytable_B1}}<br />
|+Halbjährliche Mitgliederentwicklung<br />
!Zeitraum<br />
!durchschnittlicher Zuwachs<br />
|-<br />
|01.06.02 - 01.11.02<br />
|13<br />
|-<br />
|01.12.02 - 01.05.03<br />
|13.5<br />
|-<br />
|01.06.03 - 01.11.03<br />
|17.66<br />
|-<br />
|01.12.03 - 01.05.04<br />
|16<br />
|-<br />
|01.06.04 - 01.11.04<br />
|18<br />
|-<br />
|01.12.04 - 01.05.05<br />
|19.5<br />
|-<br />
|01.06.05 - 01.11.05<br />
|19.83<br />
|-<br />
|01.12.05 - 01.05.06<br />
|31<br />
|-<br />
|01.06.05 - 01.11.06<br />
|21.67<br />
|-<br />
|01.12.06 - 01.05.07<br />
|26.83<br />
|-<br />
|01.06.07 - 01.11.07<br />
|21.16<br />
|-<br />
|01.12.07 - 01.05.08<br />
|21.33<br />
|-<br />
|01.06.08 - 01.11.08<br />
|15.83<br />
|-<br />
|01.12.08 - 01.05.09<br />
|21,67<br />
|-<br />
|}<br />
</div><br />
<br />
==Geschichte von DelphiGL.com==<br />
===Die frühsten Anfänge===<br />
Blickt man insbesondere vor das Jahr 2000 war die Grafikprogrammierung unter Delphi eine ziemlich frustrierende Angelegenheit. Nur sehr wenige Quellen waren überhaupt verfügbar, ganz zu schweigen von notwendigen Headern. Ein erste Etappe dieser Durststrecke war vermutlich das Erscheinen von DelphiX, dass sich binnen kurzer Zeit einer sehr großen Popularität erfreute. Der gemeinsame Treffpunkt hierfür war das alte Forum auf www.neobrothers.de. Prinzipiell machte die Community bis zu diesem Zeitpunkt nichts anderes als langsam zu wachsen und alle Varianten der Grafikprogrammierung durchzugehen. Vermutlich gab es damals kaum jemanden der nicht DelphiX, WdirectX oder diverse andere Header durchprobierte.<br />
<br />
Anfang 2002 kam es dann im Neobrothers-Forum zu einer Umfrage, ob man nicht ein Unterforum für OpenGL einrichten sollte. Zu diesem Zeitpunkt war mir (Phobeus) der Begriff OpenGL noch kaum ein Begriff. Da ich zu diesem Zeitpunkt jedoch zunehmend unzufriedener mit Direct3D wurde (Ständige Wechsel der API, zuviel Code für zu wenig Ergebnis etc.) riskierte ich einen Blick auf OpenGL und begann, mich damit auseinander zu setzen. Die Seite erster Wahl war damals für den OpenGL-Programmierer definitiv NeHe und erlaubte es mir, mich relativ schnell in OpenGL einzuarbeiten. Das Ergebnis beeindruckte mich: Wenige Wochen und man konnte fast das gleiche machen wie mit Direct3D, allerdings oftmals mit erheblich weniger Code und alleine die Tatsache, dass man mit ein paar glVertex-Aufrufen schnell Figuren bilden konnte, beeindruckte mich sehr.<br />
<br />
Zeitgleich nahm die Umfrage im Neobrothers-Forum ein Ende und mit nur wenigen Stimmen Vorsprung siegte die Direct3D-Fraktion und es wurde abgelehnt ein entsprechendes Unterforum einzurichten. Ich selbst war zu diesem Zeitpunkt über das Ergebnis ziemlich enttäuscht. Ebenso ein weiteres Mitglied aus dem Forum namens Sebo, den man streng genommen als geistigen Vater von DGL nennen muss.<br />
<br />
Ich selbst besaß damals ausreichend Webspace und unterhielt dort eine kleine Seite für einige Projekte meinerseits. Er fragte mich, ob man nicht dort dann ein Unterforum einrichten könnte. Ich selbst war ziemlich skeptisch darüber, da ich nicht glaubte, dass ein kleines Unterforum in meinem „Gästebuch“ wirklich Interessierte anlocken könnte. Ich wollte Nägel mit Köpfen machen und es wurde mir hier erstmals bewußt, dass ich unlängst OpenGL als meine künftige API gewählt habe und definitiv nicht mehr zu Direct3D zurück wollte. Nach einer kurzen Sammlung von Ideen, Plänen und dem Entschluss auch Tutorials anbieten zu wollen, ging am 1. April 2002 die Webseite http://dgl.thechaoscompany.net/ (nicht mehr im Besitz) ans Netz. Inhalt war eine kleinere rote Webseite (http://www.phobeus.de/hosting/olddgl/) mit einem Forum auf einem Lycos-Account. Das Projekt wurde DGL genannt (ist Jemandem schon aufgefallen wie unsinnig der Name ist? Deutsche/Delphi Gomputer/Graphic Libary/Community? *sg) und wurde allgemein mit einem herzhaften Lachen aufgenommen. Die Tatsache, dass ein Direct3D-Programmierer plante, mit einer neuen OpenGL-Seite am ersten April ans Netz zu gehen, schien etwas belustigendes zu haben.<br />
<br />
Was man über die Grafik-Community von Delphi im Jahre 2002 wissen sollte ist, dass Neobrothers.de ein zentrales Forum war in dem sich die Leute zum Sprechen trafen. Die Projekte selbst waren jedoch meist dezentral und jeder Entwickler / jedes Team werkelte im Stillen an seiner Arbeit herum. In der Tat beobachtete ich damals, dass viele Leute mehr Arbeit in Ihre Webseiten steckten als in die eigentliche Programmierung. DGL sollte anderes sein und ein wirkliches Portal sein. Warum muss jeder eine Webseite haben, die nur spärlich verlinkt ist, wenn es doch einen zentralen Ort geben kann an dem man das Wissen einfach zusammenträgt?<br />
<br />
Bereits Ende April hatte DGL rund 20 Besucher täglich und lag damit weit über dem, was wir anfangs geschätzt hatten. Die Folge davon war, dass das Forum auf Grund des „hohen“ Traffic oftmals abends bereits gar nicht mehr erreichbar war. PHP-Webspace war damals noch recht teuer. Also kam es u.a. dazu, dass das Forum von DGL zwischenzeitlich bei Untergrund-Spiele (dem alten unter Krawall-Hosting), quellcodes.de und einem Cobal-Server lag. Ganz vereinzelt findet man noch in sehr alten Dateien unsere alte URL: http://dgl.quellcodes.de/. Keine dieser Seiten existiert heute noch so.<br />
<br />
Bereits im Mai 2002 bekam ich eine ziemlich ernüchternde Mail von einem Entwickler, der mir bis dahin in der Szene nicht bekannt war: Lithander. Er ließ sich über das schreckliche Design der roten Seite aus und dass man da doch sicherlich etwas besser machen könnte. Tja... dauerte keinen Tag, da hatte er einen neuen Job und es entstand das Design von DGL, das bis heute auf dem Server liegt – wenngleich die Technik dahinter mehrfach überarbeitet wurde. Kurze Zeit darauf erhielt ich eine E-Mail von Delphic, der mir anbot ein Tutorial über Landschafts-Rendering zu schreiben. Kaum zu glauben, aber ich habe damals (auf Grund bisheriger Erfahrungen) das ganze für einen schlechten Scherz gehalten und bin umso mehr überrascht gewesen, als plötzlich wirklich ein Tutorial bei mir im Postfach lag. Bereits zu dieser Zeit war Sebo nicht mehr wirklich für DGL aktiv (deswegen findet man auch kaum etwas von ihm), allerdings wurden Lithander und Delphic ins Team aufgenommen.<br />
<br />
Von da an wuchs DGL mit einer ziemlichen Geschwindigkeit. Ich selbst kann nur darüber schmunzeln, dass wir einst mit 10 MB Traffic am Tag Probleme hatten, ein Jahr später wurde bereits die 1,5 GB-Grenze gesprengt. Inzwischen kommt das gesamte DGL-Netz auf knapp 20 GB Traffic. Oftmals blicke ich mit einer Träne im Auge zurück auf das alte DGL, das sehr familiär gewesen ist – bei dem sich die Mitglieder fast alle mehr oder minder noch persönlich kannten. Dies ist inzwischen wohl kaum mehr möglich und der logistische Aufwand für die Community ist erheblich gestiegen. Dennoch ist es schön zu sehen, dass auch noch bei einer solch großen Seite sehr viele Leute da sind, die ebenso wie zur Gründungszeit bereit sind etwas für die Community beizutragen.<br />
<br />
DGL ist eine Community die aus einer Laune heraus entstanden ist, als April-Scherz abgetan wurde und dennoch etwas sinnvolles wurde ;)<br />
<br />
===Legendäre Threads===<br />
<br />
Im DGL Forum wurden bereits viele interessante Themen diskutiert. Diese wurden im Artikel [[BestOfDGL]] gesammelt. Ein Blick hinein lohnt sich sicherlich.<br />
<br />
===Aprilscherze===<br />
DGL selbst wurde ursprünglich selbst als Aprilscherz angesehen. Mittlerweile weiß man, dass es mehr war als ein Scherz.<br />
Die Aprilscherze behalten aber ihren Stellenwert in der Community. Zwischen 0.00 und 1.00 Uhr erscheint an jedem 1. April ein erschreckender Newsbeitrag von Phobeus, der den Untergang des Abendlandes, zumindest aber sämtlicher Menschenrechte prophezeit bzw. als bereits vollzogen darstellt. Die News wird immer mit vielen Links zu echten Ereignissen und Schlagwörtern wie "CIA", "NSA", "EU-Kommission", aber immer auch mit Bezug auf die OpenGL-Entwicklung ("GL_ARB_QAIDA") gewürzt, so dass es einem zuerst beim Lesen, und dann nach der Auflösung und Präsentation der erschreckend harten Fakten kalt den Rücken herunterläuft.<br />
<br />
====Bisherige April-News====<br />
1. April 2009: [http://www.delphigl.com/forum/viewtopic.php?t=8322 DGL kooperiert mit der Bundeswehr]<br />
: Phobeus wurde von einem Generalmajor angesprochen der Bundeswehr beim Schreiben einer deutschen Version von "America's Army" zu helfen. Arbeitstitel sollte sein "N.A.T.O: Opfer am Hidukusch".<br />
<br />
1. April 2008: [http://www.delphigl.com/forum/viewtopic.php?t=7374 Breaking News: OpenGL heißt nun FGF] <br />
:Phobeus verkündet die tolle Nachricht, dass sich Microsoft entschlossen habe DirectX mit OpenGL zusammen zu führen.<br />
<br />
1. April 2007: [http://www.delphigl.com/forum/viewtopic.php?t=6495 Investment opportunity für DGL] <br />
:Phobeus entschließt sich DGL kostenpflichtig zu machen. Denn "Es kann [...] nicht sein, dass ich für meine Ausbildung bezahlen muss und gleichzeitig frei Wissen weitergebe".<br />
<br />
1. April 2006: [http://www.delphigl.com/forum/viewtopic.php?t=5398 Sicherheitsrichtlinie im Schnellverfahren] <br />
:Die NSA platziert Backdoors in fast allen Systemen, darunter die OpenGL-Extension "GL_ARB_QAIDA" - alle persönlichen Daten sollen im Klartext zu entsprechenden Stellen weitergeleitet werden...<br />
<br />
1. April 2005: [http://www.delphigl.com/forum/viewtopic.php?t=3993 Ein schwarzer Tag...] <br />
:Softwarepatente machen fast alle Internetangebote von Open-Source-Gruppen illegal. Erste Homepages gehen bereits vom Netz.<br />
<br />
1. April 2003: [http://www.delphigl.com/forum/viewtopic.php?t=1745 Phobeus erklärt Rücktritt] <br />
:Die Seite fabriziert zuviel Stress für Phobeus. Deshalb erklärte er seinen Rücktritt als Admin und wollte sich von nun an seiner neuen Leidenschaft "Datenbanken" zuwenden und dafür eine neue Comunity gründen: Die "Deutsche Datenbank-Programmierer Community" DDPC.<br />
<br />
===Die Community aktuell===<br />
Seit Mitte 2005 kam neben '''OpenGL''' vor allem '''SDL''' als Thema in der DGL Community auf. Plattformunabhängige Programmierung wurde dadurch zunehmend häufiger ein Ziel der DGLer, was im Start der Übersetzung der [[SDL-Funktionsübersicht|SDL-Dokumentation im DGL-Wiki]] gipfelte.<br />
<br />
Des weiteren entwickelte sich DGL von einer "Randgruppen-Randgruppe" (Zitat: Phobeus) hin zu einem OpenGL-Forum, unabhängig welcher Sprache man den Vorzug gibt. Zwar sind die '''Delphi'''- bzw. '''FPC'''-Programmierer immer noch in der Überzahl (ca.60% Jan.2009), aber auch '''C++'''ler, '''C#'''- und '''Java'''-Programmierer sieht man häufiger. Neben der Vielfalt an Programmiersprachen herrscht auch eine große "kulturelle" Vielfalt. Neben 13jährigen Schülern, Mathematikstudenten und Berufsinformatikern aus Deutschland findet man auch Mitglieder aus dem deutsch und nichtdeutschsprachigen Ausland.<br />
<br />
2009 wurden erstmals die [[DGL_Award|DelphiGL Awards]] verliehen, um die Mitglieder auszuzeichnen, welche besonders gute Projekte, Projektpflege oder Communityarbeit geleistet haben.<br />
<br />
<br />
DGL ist aber nicht nur als Forum aktiv. Zur Zeit bestreitet DelphiGL.com 3 Großprojekte:<br />
*'''[[Hauptseite|DGL-Wiki]]'''<br />
:Das DGL-Wiki ist das wohl '''größte Projekt''' der DGL, denn es bündelt das Wissen vieler OpenGLer und stellt es in deutscher Sprache Interessierten zur Verfügung. (Sie befinden sich gerade im DGL-Wiki ;-)) Neben diesem altruistischen Effekt hilft das Wiki auch, die Wartbarkeit zu erhöhen. Folge dessen ist, dass Fehler, die von unseren Mitgliedern gefunden werden, sofort berichtigt werden können.<br />
<br />
:Im DGL-Wiki findet man unsere [[Tutorial]]s, [[OpenGL-Funktionsübersicht|OpenGL Funktionen]], [[Hintergrundwissen]], [[Link]]s und vieles vieles mehr. Auch eine Sammlung mit [[Shadersammlung|Shadern]] und [[Materialsammlung|Materialien]] ist vorhanden.<br />
<br />
*'''[[DGLSDK]]'''<br />
:Der DGLSDK ist das '''hilfreichste Projekt''' der DGL-Comunity. Bereits 2003 war eine erste Version verfügbar, entwickelte sich aber nicht wie erhofft. Seit 2005 ist nun eine verbesserte und aktualisierte Version verfügbar, die Einsteigern in die OpenGL-Programmierung mit Delphi bzw. FPC alle nötigen Werkzeuge nicht nur an die Hand gibt, sondern auch gleich noch einrichtet. Mit dabei ist unter anderem der [[DGLOpenGL.pas|DGL-OpenGL Header]] welcher dem Delphi eigenen Header unbedingt vorzuziehen ist.<br />
<br />
*'''[[DGLOpenGL.pas]]'''<br />
:Das '''nützlichste Projekt''' und gleichzeitig unser Aushängeschild ist der DGL OpenGL Header. Dieser ist innerhalb der Delphiszene für seine Aktualität und Nutzerfreundlichkeit bekannt. Seit Jahren bietet der DGL Header die Funktionen, die der original Delphi Header von Borland nie bieten konnte. Insbesondere unser Mitglied '''Lossy eX''' hat sich neben Anderen um die Weiterentwicklung und Pflege verdient gemacht.<br />
<br />
===Rund um DelphiGL===<br />
<br />
====DGL-Chat====<br />
Schnelle Hilfe und ab und an auch Themenabende gibt es im DGL-Chat. Datailierte Informationen wo ihr ihn findet stehen im Artikel '''[[DGL-Chat]]'''. Für alle Chat unerprobten welche keinen IRC-Client auf ihrem Rechner haben, bietet DelphiGL.com auch einen browsergestützten Chatclient an.<br />
<br />
====Projekteecke====<br />
Was herauskommen kann, wenn die Mitglieder von DelphiGL.com in die Tasten hauen, kann in der [[DGL Projekte|'''DGL Projekteecke''']] betrachtet werden. Auch wenn die Übersicht nur einen Teil der entstandenen Arbeiten zeigt, sind doch einige recht schöne Ergebnisse dabei.<br />
<br />
====DelpiGL kämpft gegen AIDS und Krebs====<br />
DelphiGL hilft mit beim [http://www.worldcommunitygrid.org/ World Community Grid]. Diese Organisation stellt eine Software bereit mit der man seine ungenutzte Rechenzeit der Wissenschaft spenden kann. Das Programm läuft im Hintergrund und berechnet z.B. Wirkstoffmolekülstrukturen für AIDS-Medikamente. Das beste aber ist: euer Rechner läuft dadurch nicht langsammer, denn es wird nur ungenutzte Rechenzeit benutzt. Ladet euch doch einfach auch das Tool herunter.<br />
<br />
Nachdem ihr euch beim WCG registriert habt könnt ihr einem Team beitreten. Als DelphiGL-Supporter seid ihr in unserem Team (TeamID: "DGL") natürlich herzlich willkommen. Wer sich für die '''Teamstatistik''' interessiert kann diese [http://www.worldcommunitygrid.org/team/viewTeamInfo.do?teamId=81JW9RQQGQ1 hier] finden.<br />
<br />
Mittlerweile wird DGL beim World Community Grid als "Partner" geführt.<br />
<br />
===Historie im Überblick===<br />
Die nachfolgende Tabelle versucht, aus den Newsmeldungen die im Forum von DelphiGL.com zu finden sind, zumindest ausschnittsweise die Geschichte von DelphiGL.com zu rekonstruieren.<br />
<br />
[[Bild:DGL_Map.jpg|right|framed|Die [http://www.frappr.com/delphigl '''Original DGL-Map'''] gibt euch einen Überblick, wo die DGL-Mitglieder wohnen.]]<br />
[[Bild:Verteilung.png|right|thumb|Die Verteilung der DGL Mitglieder laut einer Forenumfrage im Herbst 2009.]]<br />
<br />
<br />
<div align="center"><br />
{|{{Prettytable_B1}} <br />
!Datum||Mitglieder<br> bis ''Datum''||Veränderungen/Ereignisse <br> bis ''Datum''<br />
|- align = "center"<br />
|01.04.2002||0||DelphiGL wird gegründet<br />
|- align = "center"<br />
|01.06.2002||13||Forum geht online<br />
|- align = "center"<br />
|01.07.2002||22||erste OpenGL <-> Direct3D Schlammschlacht<br />
|- align = "center"<br />
|01.08.2002||31||Member Forum<br><br />
Erste Idee Page ins englische zu übersetzen (per McClaw)<br />
|- align = "center"<br />
|01.09.2002||42||Erster Umzug von DGL<br><br />
Spruch des Tages<br />
|- align = "center"<br />
|01.10.2002||58||SchodMC wird Mod<br><br />
DGL-Upload-Center<br><br />
Poll<br><br />
Recent Uploads<br><br />
Featured Sites<br><br />
Bild der Woche<br><br />
Snippets<br />
|- align = "center"<br />
|01.11.2002||78||TexturenTutorial<br><br />
Foren FAQ<br />
<br>Poll Archiv<br />
|- align = "center"<br />
|01.12.2002||87||DelphiGL.com Domain<br> <br />
Kommentare zu Newsmeldungen<br />
|- align = "center"<br />
|01.01.2003||106||Neues DGL Logo<br><br />
'''''100 Mitglieder-Marke erreicht'''''<br />
|- align = "center"<br />
|01.02.2003||123||Umzug auf neuen Server<br><br />
Sascha Willems (SonOfSatan / SOS) wird Mod<br><br />
Tutorials als CHM-Version<br />
|- align = "center"<br />
|01.03.2003||136||<br />
|- align = "center"<br />
|01.04.2003||147||Bomberman Tutorial 2<br />
|- align = "center"<br />
|01.05.2003||159||Projekteforum eingerichtet<br />
<br>Phobeus stellt Firefox vor (damals Firebird)<br />
|- align = "center"<br />
|01.06.2003||175||<br />
|- align = "center"<br />
|01.07.2003||196||<br />
|- align = "center"<br />
|01.08.2003||212||IE erkennt DGL als nicht vertrauensvoll ;-)<br><br />
Flooding durch MS-Bots<br />
|- align = "center"<br />
|01.09.2003||223||Ausfall der Domain<br />
|- align = "center"<br />
|01.10.2003||245||<br />
|- align = "center"<br />
|01.11.2003||265||<br />
|- align = "center"<br />
|01.12.2003||277||Erster IRC-Chat<br />
|- align = "center"<br />
|01.01.2004||296||<br />
|- align = "center"<br />
|01.02.2004||308||<br />
|- align = "center"<br />
|01.03.2004||323||<br />
|- align = "center"<br />
|01.04.2004||347||Borland Developer Network und <br />
Code Central News<br><br />
W32.Beagle.J@MM Wurm nimmt DGL-Identität an.<br />
|- align = "center"<br />
|01.05.2004||361||<br />
|- align = "center"<br />
|01.06.2004||378||dglOpenGL.pas - Version 1.4<br />
|- align = "center"<br />
|01.07.2004||403||'''DGL Wiki gestartet'''<br />
|- align = "center"<br />
|01.08.2004||421||Atom Feed<br />
|- align = "center"<br />
|01.09.2004||438||dglOpenGL.pas - Version 1.4B<br>'''DGL Wiki hat 100 Artikel'''<br />
|- align = "center"<br />
|01.10.2004||451||dglOpenGL.pas goes FreePascal<br />
|- align = "center"<br />
|01.11.2004||469||<br />
|- align = "center"<br />
|01.12.2004||493||.Net Forum<br />
|- align = "center"<br />
|01.01.2005||512||dglOpenGL.pas - Version 1.6<br><br />
'''''500 Mitglieder-Marke erreicht'''''<br />
|- align = "center"<br />
|01.02.2005||531||dglOpenGL.pas - Version 1.7<br>'''DGL Wiki hat 250 Artikel'''<br />
|- align = "center"<br />
|01.03.2005||552||<br />
|- align = "center"<br />
|01.04.2005||570||Sascha Willems (>4000 Beiträge) verabschiedet sich aus dem Forum.<br />
|- align = "center"<br />
|01.05.2005||586||<br />
|- align = "center"<br />
|01.06.2005||604||<br />
|- align = "center"<br />
|01.07.2005||617||DGLSDK 2005.1<br />
|- align = "center"<br />
|01.08.2005||635||<br />
|- align = "center"<br />
|01.09.2005||661||Quake3Delphi<br />
|- align = "center"<br />
|01.10.2005||677||Diskussion zum Thema "Windows Vista & OpenGL"<br />
|- align = "center"<br />
|01.11.2005||705||<br />
|- align = "center"<br />
|01.12.2005||729|| '''DGL Wiki hat 500 Artikel'''<br />
|- align = "center"<br />
|01.01.2006||767|| '''DGL Wiki Phasen 1 u. 2 abgeschlossen'''<br> Weihnachtsaktion: Portierung der DGL-Tutorials ins Wiki <br> DGL-Mitglieder starten DGL-Map bei "Frappr.com"<br />
|- align = "center"<br />
|01.02.2006||797||<br />
|- align = "center"<br />
|01.03.2006||832|| Borland will sich von seiner IDE-Sparte trennen<br />
|- align = "center"<br />
|01.04.2006||863|| Windows Vista soll nun doch OpenGL voll unterstützen<br>'''Wiki Phase III abgeschlossen''' <br> '''Wiki Phase V vorzeitig abgeschlossen'''<br />
|- align = "center"<br />
|01.05.2006||891|| SGI beantragt Gläubigerschutz<br />
|- align = "center"<br />
|01.06.2006||912|| Neuer DGL Poll<br />
|- align = "center"<br />
|01.07.2006||933|| DGLSDK 2006.1<br />
|- align = "center"<br />
|01.08.2006||950|| Serverumzug (größte Wartungsarbeiten seit Gründung von DGL)<br />
|- align = "center"<br />
|01.09.2006||977|| Änderungen im DelphiGL Team<br />
|- align = "center"<br />
|01.10.2006||991|| Turbodelphi erscheint<br />
|- align = "center"<br />
|01.11.2006||1021|| DGL wird Partner des World Community Grids<br><br />
'''''1000 Mitglieder-Marke erreicht'''''<br />
|- align = "center"<br />
|01.12.2006||1045||<br />
|- align = "center"<br />
|01.01.2007||1067|| Tutorial Raytracing Grundlagen<br />
|- align = "center"<br />
|01.02.2007||1106|| Tutorial Raytracing Grundlagen 2 <br> DOS Angriff auf DelphiGL<br>'''Wiki Phase VI vorzeitig abgeschlossen'''<br />
|- align = "center"<br />
|01.03.2007||1131|| SDL_Rwops Tutorial<br />
|- align = "center"<br />
|01.04.2007||1164|| [[DGL_Projekte]]<br />
|- align = "center"<br />
|01.05.2007||1182|| DGLOpenGL.pas V 2.1<br>[[BestOfDGL]]<br />
|- align = "center"<br />
|01.06.2007||1201|| <br />
|- align = "center"<br />
|01.07.2007||1223||<br />
|- align = "center"<br />
|01.08.2007||1248||"Spekulationen über OpenGL3.0<br>Tutorial_Softwareentwicklung3"<br />
|- align = "center"<br />
|01.09.2007||1261||<br />
|- align = "center"<br />
|01.10.2007||1284||'''DGL Wiki hat 750 Artikel'''<br>Tutorial_Kollisionserkennung<br>Tutorial Framebufferobject<br />
|- align = "center"<br />
|01.11.2007||1309||Forenstruktur überarbeitet<br>Tutorial Wassereffekt<br />
|- align = "center"<br />
|01.12.2007||1330||<br />
|- align = "center"<br />
|01.01.2008||1347||<br />
|- align = "center"<br />
|01.02.2008||1372||<br />
|- align = "center"<br />
|01.03.2008||1393||DGL Suche für Firefox<br>1. Kollisionstutorial<br />
|- align = "center"<br />
|01.04.2008||1411||Bundesverfassungsgericht erklärt Sicherheit gespeicherter Daten zum Grundrecht<br />
|- align = "center"<br />
|01.05.2008||1437||2. Kollisionstutorial<br>OpenAL Übersicht offiziell als Wikiteil vorgestellt<br>UTF8 Umstellung des Forums gescheitert<br />
|- align = "center"<br />
|01.06.2008||1452||Borland verkauft seine Entwicklungstools (CodeGear)<br />
|- align = "center"<br />
|01.07.2008||1466||3. Kollisionstutorial<br />
|- align = "center"<br />
|01.08.2008||1479||<br />
|- align = "center"<br />
|01.09.2008||1499||Erste Lebenszeichen von OpenGL3.0<br>Shadersammlung im Wiki<br />
|- align = "center"<br />
|01.10.2008||1514||SGI stellt OpenGL Quellcode unter echte OSS Lizenz<br />
|- align = "center"<br />
|01.11.2008||1532||<br />
|- align = "center"<br />
|01.12.2008||1546||<br />
|- align = "center"<br />
|01.01.2009||1574||Start der Überarbeitung/Aktualisierung der Einsteigertutorials<br />
|- align = "center"<br />
|01.02.2009||1602||Erstmalige Verleihung der [[DGL_Award]]s<br />
dglOpenGL.pas mit GL3.0 Unterstützung<br />
|- align = "center"<br />
|01.03.2009||1617||<br />
|- align = "center"<br />
|01.04.2009||1639||'''DGL Wiki hat 1000 Artikel'''<br />
|- align = "center"<br />
|01.05.2009||1662||Quasi OpenGL-Erfinder SGI ist Pleite<br />
|- align = "center"<br />
|01.06.2009||1679||Providerinterner Umzug von DGL<br />
|- align = "center"<br />
|01.07.2009||1700||Offtopic ist nicht mehr öffentlich<br />
|- align = "center"<br />
|01.08 2009||1737||1. [[DGL Treffen]] - "Nerds on Tour in Dänemark"<br />
|- align = "center"<br />
|01.09.2009||1754||Team verändert sich. Lord Horazont kommt dazu - KidPaddle, La Boda, LarsMiddendorf, Magellan, sniper, lithander, SchodMC, Fiji-Fighter und Mars gehen. (Viele waren nicht mehr aktiv.)<br><br />
Erster Virus der geziehlt Delphi IDEs befällt.<br><br />
Erste offizielle DGL Shirts verfügbar<br />
|- align = "center"<br />
|01.10.2009||1765||WebGL - OpenGL im Browser wird vorgestellt<br>dglOpenGL.pas mit GL3.2 Unterstützung<br>CodeGear stellt die kostenlosen TURBO-Delphis ein<br />
|}<br />
</div><br />
<br />
==Links==<br />
[[DGLOpenGL.pas|Link zum OpenGL Header von DelphiGL.]]<br />
<br />
[http://www.delphiGL.com http://www.DelphiGL.com - Heimat der DGLer]<br />
<br />
[http://www.frappr.com/delphigl Link zur DGL_Map]<br />
<br />
[[DGL Projekte|Link auf die DGL Projekteecke]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Tutorial_glsl&diff=24193Tutorial glsl2009-10-13T20:30:34Z<p>Lord Horazont: /* Schleifen */ Fehler in der forschleife</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 />
<source lang="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;</source><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 />
<source lang="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 />
</source><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 />
<source lang="glsl"><br />
float temp[3];<br />
</source><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 />
<source lang="glsl"><br />
struct light<br />
{<br />
bool active;<br />
float intensity;<br />
vec3 position;<br />
vec3 color;<br />
};<br />
</source><br />
Im Shader können dann neue Variablen von diesem Typ ganz einfach deklariert werden :<br />
<source lang="glsl"><br />
light LightSource[3];<br />
</source><br />
Der Zugriff auf die Elemente der Struktur erfolgt dann wie gewohnt über den Punkt :<br />
<source lang="glsl"><br />
LightSource[3].position = vec3(1.0, 1.0, 5.0);<br />
</source><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 />
<source lang="glsl"><br />
varying vec3 VertexNormal;<br />
...<br />
VertexNormal = normalize(MV_IT * gl_Normal);<br />
</source><br />
:Im FragmentShader :<br />
<source lang="glsl"><br />
varying vec3 VertexNormal;<br />
...<br />
TempVector = VertexNormal*...<br />
</source><br />
<br />
===Beispiel B=== <br />
Uniformparameter zur nachträglichen Farbänderung der Szene wird im Programm übergeben :<br />
<br />
:Im VertexShader :<br />
<source lang="glsl"><br />
uniform vec4 GlobalColor;<br />
...<br />
gl_FrontColor = GlobalColor * gl_Color;<br />
</source><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 />
<source lang="glsl"><br />
const vec4 ColorBias = vec4(0.2, 0.3, 0.0, 0.0);<br />
...<br />
gl_FrontColor = ColorBias * gl_Color;<br />
</source><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 />
<source lang="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 />
</source><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 />
<source lang="glsl"><br />
vec4 Color = vec4(MyVec3)<br />
</source><br />
funktioniert allerdings nicht, da hier die Zahl der benötigten Argumente nicht erreicht wird. In diesem Falle müsste es dann<br />
<source lang="glsl"> <br />
vec4 Color = vec4(MyVec3, 0.0)<br />
</source><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 />
<source lang="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 />
</source><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 />
<source lang="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 />
</source><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 />
<source lang="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 />
</source><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 />
<source lang="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 />
</source><br />
<br />
Matrix * Vektor ist auch ohne manuelle Konvertierung möglich :<br />
<source lang="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 />
</source><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 />
<source lang="glsl"><br />
RückgabeTyp FunktionsName(Typ0 Argument0, Typ1, Argument1, ... , TypN, ArgumentN)<br />
{<br />
return RückgabeWert;<br />
}<br />
</source><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 />
<source lang="glsl"><br />
void MeineFunktion(float EingabeWert; out float AusgabeWert)<br />
{<br />
AusgabeWert = EingabeWert*MyConstValue;<br />
}<br />
</source><br />
<br />
Diese Funktion gibt ''nichts'' zurück, aber gibt EingabeWert*MyConstValue im Ausgabeargument AusgabeWert nach aussen.<br />
<source lang="glsl"><br />
float MeineFunktion(float EingabeWert)<br />
{<br />
return EingabeWert*MyConstValue;<br />
}<br />
</source><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 />
<source lang="glsl"><br />
float VektorSumme(float v[])<br />
{<br />
return v[0]+v[1]+v[2]+v[3];<br />
}<br />
</source><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 />
<source lang="glsl"><br />
for (Startausdruck; Durchlaufbedingung; Wiederholungsausdruck)<br />
{<br />
statement<br />
}<br />
</source><br />
<br />
* '''while'''-Schleife<br />
<source lang="glsl"><br />
while (Durchlaufbedingung)<br />
{<br />
statement<br />
}<br />
</source><br />
<br />
* '''do'''-while-Schleife<br />
<source lang="glsl"><br />
do<br />
{<br />
statement<br />
}<br />
while (Durchlaufbedingung)<br />
</source><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 />
:{{INLINE_CODE|gl_NormalMatrix}} repräsentiert die invertierten und anschließend transponierten oberen 3x3 Werte der {{INLINE_CODE|gl_ModelViewMatrix}}.<br />
* mat4 gl_TextureMatrix[gl_MaxTextureCoordsARB]<br />
<br />
* float gl_NormalScale<br />
: Gibt den unter OpenGL festgelegten Faktor zur Skalierung der Normalen zurück.<br />
<br />
* struct gl_DepthRangeParameters<br />
<source lang="glsl"><br />
struct gl_DepthRangeParameters<br />
{<br />
float near;<br />
float far;<br />
float diff;<br />
};<br />
gl_DepthRangeParameters gl_DepthRange;<br />
</source><br />
: Clippingplanes : <br />
* vec4 gl_ClipPlane[gl_MaxClipPlanes]<br />
<br />
*struct gl_PointParameters<br />
<source lang="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 />
</source><br />
*struct gl_MaterialParameters<br />
<source lang="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 />
</source><br />
*struct gl_LightSourceParameters<br />
<source lang="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 />
</source><br />
*struct gl_LightModelParameters<br />
<source lang="glsl"><br />
struct gl_LightModelParameters<br />
{<br />
vec4 ambient;<br />
};<br />
gl_LightModelParameters gl_LightModel;<br />
</source><br />
*struct gl_LightModelProducts<br />
<source lang="glsl"><br />
struct gl_LightModelProducts<br />
{<br />
vec4 sceneColor;<br />
};<br />
gl_LightModelProducts gl_FrontLightModelProduct;<br />
gl_LightModelProducts gl_BackLightModelProduct;<br />
</source><br />
*struct gl_LightProducts<br />
<source lang="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 />
</source><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 />
<source lang="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 />
</source><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 />
<source lang="glsl"><br />
void main(void)<br />
{<br />
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;<br />
gl_TexCoord[0] = gl_MultiTexCoord0;<br />
}<br />
</source> <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 />
<source lang="glsl"><br />
uniform sampler2D texSampler;<br />
<br />
void main(void)<br />
{<br />
gl_FragColor = texture2D(texSampler, vec2(gl_TexCoord[0]));<br />
}<br />
</source><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 />
<source lang="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 />
</source><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 />
<source lang="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 />
</source><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 />
<source lang="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 />
</source><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>Lord Horazonthttps://wiki.delphigl.com/index.php?title=glColorPointer&diff=24192glColorPointer2009-10-10T17:21:54Z<p>Lord Horazont: /* Beschreibung */ Absatz über den Pointer-Parameter wenn es sich um VBOs handelt hinzugefügt nach: http://www.opengl.org/sdk/docs/man/xhtml/glColorPointer.xml</p>
<hr />
<div>= glColorPointer =<br />
<br />
<br />
<br />
== Name ==<br />
'''glColorPointer''' - definiert ein Feld das Farben enthält.<br />
<br />
<br />
<br />
== Delphi-Spezifikation ==<br />
procedure '''glColorPointer'''(''size'': TGLint; ''_type'': TGLenum; ''stride'': TGLsizei; const ''_pointer'': PGLvoid);<br />
<br />
<br />
<br />
<br />
== Parameter ==<br />
<br />
{| border="1" rules="all"<br />
! ''size'' <br />
| Gibt an, aus wievielen Komponenten die Farbe besteht. (Muss 3 oder 4 sein)<br />
|-<br />
! ''_type'' <br />
| Bestimmt den Datentyp jeder Farbcomponente im Feld. Als Symbolische Konstanten werden '''GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT''' und '''GL_DOUBLE''' akzeptiert.<br />
|-<br />
! ''stride'' <br />
| bestimmt den Byteabstand zwischen aufeinenderfolgenden Farben. Wenn stride 0(Standard) ist, wird angenommen, dass die Farben in dem Array dicht gepackt liegen.<br />
|-<br />
! ''_pointer'' <br />
| Bestimmt einen Zeiger auf die erste Farbkomponente, der ersten Farbe im Feld.<br />
|}<br />
<br />
<br />
<br />
<br />
== Beschreibung == <br />
'''glColorPointer''' bestimmt den Ort und das Datenformat eines Farbkomponenten-Arrays, das zum Rendern benutzt werden soll. ''size'' bestimmt die Anzahl an Komponenten pro Farbe und muss 3 oder 4 sein. ''_type'' bestimmt den Datentyp jeder Farbkomponente. ''stride'' legt den Abstand zwischen den Farben fest, was ermöglicht, dass Vertices und Attribute im einem einzelnen oder aber verschiedenen Arrays liegen.(Einzelspeicherung kann in einigen Implemetationen effizienter sein; siehe [[glInterleavedArrays]])<br />
<br />
Wenn ein Buffer Object für GL_ARRAY_BUFFER gebunden ist (siehe [[glBindBuffer]]) während glColorPointer aufgerufen wird, wird ''pointer'' als ein Byte-Offset vom Beginn zum ersten Color-Element im Puffer behandelt. Weiterhin wird die Bindung des Buffer Objects (GL_ARRAY_BUFFER_BINDING) als Farb-Vertex-Array Client-Side Zustand (GL_COLOR_ARRAY_BUFFER_BINDING) gespeichert.<br />
<br />
Wenn ein Farb-Array festgelegt wird, werden ''size'', ''_type'', ''stride'' und ''_pointer'' als klientseitiger Status gespeichert.<br />
<br />
Um das Farb-Array ein- und auszuschalten, können [[glEnableClientState]] und [[glDisableClientState]] mit dem Argument '''GL_COLOR_ARRAY''' aufgerufen werden. Wenn aktiviert, wird das Farb-Array bei Aufrufen von [[glDrawArrays]], [[glDrawElements]] und [[glArrayElement]] benutzt.<br />
<br />
== Hinweise ==<br />
'''glColorPointer''' ist erst ab OpenGL Version 1.1 oder später verfügbar.<br />
<br />
Im Initialisierungszustand ist das Farbfeld deaktiviert und wird von [[glArrayElement]], [[glDrawArrays]] bzw. [[glDrawElements]] nicht benutzt, wenn diese Befehle aufgerufen werden.<br />
<br />
Ein Aufruf von '''glColorPointer''' innerhalb eines [[glBegin]]-[[glEnd]] Blocks ist nicht erlaubt.<br />
Trotzdem kann es sein, dass kein Fehler generiert wird. Wenn dies geschieht, ist die Operation undefiniert.<br />
<br />
'''glColorPointer''' ist normalerweise auf der Klientseite implementiert.<br />
<br />
Farbfeld Parameter werden klientseitig gespeichert und können deshalb nicht mit [[glPushAttrib]] und [[glPopAttrib]] gespeichert bzw. wiederhergestellt werden. Benutzen Sie stattdessen [[glPushClientAttrib]] und [[glPopClientAttrib]].<br />
<br />
<br />
<br />
== Fehlermeldungen ==<br />
'''GL_INVALID_VALUE''' wird generiert wenn ''size'' nicht 3 oder 4 ist.<br />
<br />
'''GL_INVALID_ENUM''' wird generiert wenn ''_type'' ein ungültiger Wert zugewiesen wird.<br />
<br />
'''GL_INVALID_VALUE''' wird generiert wenn ''stride'' negativ ist.<br />
<br />
<br />
<br />
== Zugehörige Wertrückgaben ==<br />
[[glIsEnabled]] mit dem Token '''GL_COLOR_ARRAY'''<br />
<br />
[[glGet]] mit Token [[glGet#GL_COLOR_ARRAY_SIZE|GL_COLOR_ARRAY_SIZE]]<br />
<br />
[[glGet]] mit Token [[glGet#GL_COLOR_ARRAY_TYPE|GL_COLOR_ARRAY_TYPE]]<br />
<br />
[[glGet]] mit Token [[glGet#GL_COLOR_ARRAY_STRIDE|GL_COLOR_ARRAY_STRIDE]]<br />
<br />
[[glGetPointerv]] mit dem Token '''GL_COLOR_ARRAY_POINTER'''<br />
<br />
<br />
<br />
== Siehe auch ==<br />
[[glArrayElement]], [[glDrawArrays]], [[glDrawElements]], [[glEdgeFlagPointer]], [[glEnable]], [[glGetPointerv]], [[glIndexPointer]], [[glInterleavedArrays]], [[glNormalPointer]], [[glPopClientAttrib]], [[glPushClientAttrib]], [[glTexCoordPointer]], [[glVertexPointer]]<br />
<br />
[[Kategorie:GL|ColorPointer]]<br />
[[Kategorie:GL1.1|ColorPointer]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=glTexEnvf&diff=24187glTexEnvf2009-10-03T08:57:09Z<p>Lord Horazont: Weiterleitung zu glTexEnv</p>
<hr />
<div>#REDIRECT [[glTexEnv]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=glTexEnvi&diff=24186glTexEnvi2009-10-03T08:56:43Z<p>Lord Horazont: Weiterleitung zu glTexEnv</p>
<hr />
<div>#REDIRECT [[glTexEnv]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Team&diff=24169Team2009-08-27T15:54:55Z<p>Lord Horazont: E-Mail-Adresse ergänzt</p>
<hr />
<div>= Vorwort =<br />
<br />
Auf dieser Seite befindet sich eine Auflistung der Personen, die momentan aktiv daran arbeiten, dass DGL Euch wie gewohnt im Netz mit Informationen versorgt. Bitte beachtet, dass die meisten der Personen nur einen begrenzten Vorrat an Zeit haben. Überlegt daher bitte sorgsam, an wen Ihr Euch wendet.<br />
<br />
Im unteren Teil dieser Seite sind die verschiedenen Bereiche von DGL und deren Betreuer aufgelistet. Bitte wendet auch an den passenden Ansprechpartner.<br />
<br />
Diese Seite ersetzt nicht das '''[http://www.delphigl.com/launcher.php?em=impressum IMPRESSUM]'''.<br />
<br />
= Das Team im Ganzen =<br />
In diesen Abschnitt ist aufgelistet '''wer''' bei [[DelphiGL]] Verantwortung für einen Teil übernommen hat, und '''was''' genau er macht.<br />
== Moderatoren ==<br />
<div align="center"><br />
{|{{Prettytable_B1}} width="80%"<br />
!width="20%"|Forumsname<br />
!width="30%"|Email<br />
!width="50%"|Aufgabenbereich<br />
|-<br />
|[[Benutzer:Phobeus|Phobeus]] <br />
|{{eMail|Phobeus|delphigl.com}}<br />
|<br />
*Technische Pflege des Wikis (Wartung/Updates)<br />
*Betreuung der Hardware<br />
*Freischalten von Usern für das Forum<br />
*Freischalten von Usern für das Wiki<br />
*''News eintragen''<br />
*''allgemeine inhaltliche Kontrolle''<br />
*"der Boss"<br />
|-<br />
|[[Benutzer:Flash|Flash]] <br />
|{{eMail|Flash|delphigl.com}}<br />
|<br />
*Inhaltliche Pflege/Koordination des Wikis<br />
*Community-Aktionen<br />
*Tutorial Lektor<br />
*DGL-Poll pflegen<br />
*News eintragen<br />
*allgemeine inhaltliche Kontrolle im Forum<br />
|-<br />
|[[Benutzer:Frase|Frase]] <br />
|{{eMail|Frase|delphigl.com}}<br />
|<br />
*Technische Unterstützung für Phobeus<br />
*Tutorial Lektor<br />
*News eintragen<br />
*allgemeine inhaltliche Kontrolle im Forum<br />
|-<br />
|[[Benutzer:Delphic|Delphic]] <br />
|{{eMail|delphic|delphigl.com}}<br />
|<br />
*Freischalten von Usern für das Forum<br />
*News eintragen<br />
*allgemeine inhaltliche Kontrolle<br />
|-<br />
|[[Benutzer:Lossy eX|Lossy eX]] <br />
|{{eMail|Lossy|delphigl.com}}<br />
|<br />
*dglOpenGL.pas<br />
*Tutorial Lektor<br />
*News eintragen<br />
*allgemeine inhaltliche Kontrolle<br />
|-<br />
|[[Benutzer:Flo|Flo]]<br />
|{{eMail|Flo|DelphiGL.com}}<br />
|<br />
*DGL-Wiki-Bot Manager<br />
*Freischalten von Usern für das Wiki<br />
*Tutorial Lektor<br />
*News eintragen<br />
*allgemeine inhaltliche Kontrolle<br />
|-<br />
|[[Benutzer:I0n0s|i0n0s]] <br />
|{{eMail|i0n0s|DelphiGL.com}}<br />
|<br />
*DGLSDK<br />
*Freischalten von Usern für das Forum<br />
*Tutorial Lektor<br />
*News eintragen<br />
*allgemeine inhaltliche Kontrolle<br />
|-<br />
|[[Benutzer:Sascha Willems|Sascha Willems]] <br />
|{{eMail|webmaster|delphigl.de}}<br />
|<br />
*News eintragen<br />
*allgemeine inhaltliche Kontrolle<br />
|-<br />
|[[Benutzer:Lord_Horazont|Lord Horazont]] <br />
|{{eMail|lord_horazont|DelphiGL.com}}<br />
|<br />
*SVN/WebSVN Betreuung<br />
*News eintragen<br />
*allgemeine inhaltliche Kontrolle<br />
|-<br />
|}<br />
</div><br />
<br />
= Gruppen =<br />
In diesem Teil findet ihr detailierte Informationen, wer für welchen Bereich zuständig ist.<br />
== DGLSDK ==<br />
<div align="center"><br />
{|{{Prettytable_B1}} width="80%"<br />
!width="20%"|Forumsname<br />
!width="30%"|Email<br />
!width="50%"|Aufgabenbereich<br />
|-<br />
|[[Benutzer:I0n0s|i0n0s]] <br />
|{{eMail|i0n0s|DelphiGL.com}}<br />
|<br />
|-<br />
|}<br />
</div><br />
<br />
== DGLOpenGL.pas ==<br />
<div align="center"><br />
{|{{Prettytable_B1}} width="80%"<br />
!width="20%"|Forumsname<br />
!width="30%"|Email<br />
!width="50%"|Aufgabenbereich<br />
|-<br />
|[[Benutzer:Lossy eX|Lossy eX]] <br />
|{{eMail|Lossy|delphigl.com}}<br />
|<br />
*Weiterentwicklung, Debugging<br />
|-<br />
|}<br />
</div><br />
<br />
== Webseite ==<br />
<div align="center"><br />
{|{{Prettytable_B1}} width="80%"<br />
!width="20%"|Forumsname<br />
!width="30%"|Email<br />
!width="50%"|Aufgabenbereich<br />
|-<br />
|[[Benutzer:Phobeus|Phobeus]] <br />
|{{eMail|Phobeus|delphigl.com}}<br />
|<br />
*Technische Pflege des Seite (Wartung/Updates)<br />
*Coding (Integrieren von Neuerungen)<br />
|-<br />
|[[Benutzer:Lord_Horazont|Lord Horazont]]<br />
| -<br />
|<br />
*PHP<br />
|-<br />
|[[Benutzer:Flash|Flash]] <br />
|{{eMail|Flash|delphigl.com}}<br />
|<br />
*DGL-Poll pflegen<br />
|-<br />
|Alle Anderen <br />
|(siehe oben)<br />
|<br />
*News<br />
*Sorgen für angemessene Umgangsformen<br />
|-<br />
|}<br />
</div><br />
<br />
== Wiki ==<br />
<div align="center"><br />
{|{{Prettytable_B1}} width="80%"<br />
!width="20%"|Forumsname<br />
!width="30%"|Email<br />
!width="50%"|Aufgabenbereich<br />
|-<br />
|[[Benutzer:Flash|Flash]] <br />
|{{eMail|Flash|delphigl.com}}<br />
|<br />
*Inhaltliche Pflege des Wikis<br />
*Koordinierung<br />
|-<br />
|[[Benutzer:Phobeus|Phobeus]] <br />
|{{eMail|Phobeus|delphigl.com}}<br />
|<br />
*Technische Pflege des Wikis (Wartung/Updates)<br />
*Betreuung der Hardware<br />
|-<br />
|[[Benutzer:I0n0s|i0n0s]] <br />
|{{eMail|i0n0s|DelphiGL.com}}<br />
|<br />
*Filesektion<br />
|-<br />
|[[Benutzer:Lord_Horazont|Lord Horazont]]<br />
|{{eMail|lord_horazont|DelphiGL.com}}<br />
|<br />
*Filesektion (SVN Betreuung)<br />
|-<br />
|}</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=DGL_Treffen/2009&diff=24164DGL Treffen/20092009-08-23T19:29:33Z<p>Lord Horazont: /* Tagebuch */ Bilder hinzugefügt.</p>
<hr />
<div>=1. DGL Treffen in Hejlsminde/Dänemark=<br />
{|{{Prettytable_B1}}<br />
!Ort<br />
|[http://www.cofman.de/detail.php?houseid=29152 Ferienhaus] in [http://maps.google.de/?ie=UTF8&ll=55.376697,9.609175&spn=0.009778,0.027595&t=h&z=15 Hejlsminde], Hejls, Ostseeküste Dänemark<br />
|-<br />
!Zeit<br />
|25.07.09 - 01.08.09<br />
|-<br />
!Kosten p.P.<br />
|<br />
155 EUR Unterkunft<br />
50 EUR Verpflegung<br />
5 EUR Kaution<br />
40-100 EUR Anreise<br />
--------------------<br />
250-300EUR pro Person<br />
|-<br />
!Teilnehmer<br />
|Flash, Frase, i0n0s, Lord Horazont, Phobeus<br />
|}<br />
<br />
==Organisation==<br />
Die Organisation würde über diese [[DGL_Treffen/2009/Organisation|Wiki-Seite]] und diesem [http://www.delphigl.com/forum/viewtopic.php?t=8130 Thread im DGL Forum] organisiert.<br />
Ursprünglich sollte ein Ferienhaus in Deutschland gemietet werden. Allerdings waren alle Ferienhäuser die in Frage kamen (6Personen, mind. 4 Schlafzimmer, strandnah) alle bereits belegt.<br />
Flash fand dann in Dänemark passende Häuser, und da die Zeit drängte wurde ein passendes Haus von Flash angemietet.<br />
<br />
==Tagebuch==<br />
{|{{Prettytable_B1}}<br />
!Tag<br />
!Bericht<br />
|-<br />
!Sa 25.07.2009<br />
|Frase und I0n0s sowie Flash reisen mit dem PKW Richtung Hamburg. Auch ein Kamikaze-Vogel konnte uns (insbesondere Flash) nicht vom Urlaub mehr abhalten. Lord Horazont kommt mit dem Zug. Gegen 13Uhr wird Lord Horazont von Phobeus am Hamburg Bahnhof abgeholt. Da die Südfraktion (Frase&I0n0s) im Stau steht entschließt sich Flash direkt zu Phobeus zu fahren um von dort aus zu Planen wie es weiter geht. Die Zeit ist recht fortgeschritten (da die Nord-Ost-Gruppe dank eines fehlgeleiteten Navis/Fahrers einen Abstecher durch den Elbtunnel gemacht hat), deshalb beschließt Flash voraus zu fahren um den Schlüssel abzuholen, während sich Phobeus, Lord Horanzont, Frase und I0n0s in Flensburg zum Einkaufen zusammen rotten.<br />
Flash nimmt das Haus um 19Uhr in Besitz und erkundet den Weg zum Strand, bevor die Anderen um 20Uhr in Hejlsminde ankommen. <br />
Die nächste Stunde wird damit zugebracht die Elektronik aufzubauen, mit der Folge, dass in der gesamten Woche am einzigen echten Tisch im Haus niemals gegessen werden konnte - dort standen die Rechner. ;)<br />
Am Abend zauberte dann i0n0s ein feurig scharfes und komplett handgemachtes Chilli Con Carne auf den Tisch.<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Belagerung.jpg|thumb|none|200px|Die Belagerung des Esstisches durch DelphiGL hat begonnen!]]<br />
|[[Bild:Chilli_Con_Carne.jpg|thumb|none|200px|Äußerst lecker und aus i0n0s Kochkünsten entsprungen.]]<br />
|[[Bild:Toter_Nummer_1.jpg|thumb|none|200px|Dieses Opfer wurde zwischen Osten und Westen Deutschlands "aufgegabelt".]]<br />
|}<br />
|-<br />
!So 26.07.2009<br />
|Heute gings das erste mal zum "Frühsport" an den Strand. Dort stellte sich allerdings heraus, dass es an diesem Strand ein elementares Problem gab: Das Wasser reichte trotz wagemutigem hinauswaten, selbst bei den Kleineren, gerade mal bis zum Bauch. Das Problem wurde pragmatisch durch hinsetzen gelöst.<br />
<br />
Danach gab es "Frühtagessen" - quasi ein Frühstück zur Mittgagszeit.<br />
Starcraft wurde im Anschluss bei allen installiert und ausführlich gespielt. Phobeus entdeckte dabei seine lange unterdrückten Hang zur Expansion und Verschleimung weiter Landteile (Zerg) und i0n0s spielte den Protoss Executor. Anfangs wurden einige kurze Spiele gegen den Rechner gemacht, bevor man sich in 2 Teams (i0n0s+Flash sowie Phobeus+Frase+Lord Horazont) aufteilte um gegeneinander zu spielen. <br />
Erschütternde Szenen (im Wahrsten Sinne des Wortes) ergaben sich, als Frase und Phobeus die "Stepmania" Tanzmatten klar zum Gefecht machten.<br />
Am Abend machte Flash "Pfannenpizzas". Der Teig dieser enthielt als Überraschung den letzten Rest der Chillis vom Vortag, was Frase Tränen (der Freude natürlich) in die Augen trieb.<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Stepmania.JPG|thumb|none|200px|Die erste Welle der Stepmania-Attacke.]]<br />
|}<br />
|-<br />
!Mo 27.07.2009<br />
|Trotz wenig tollem Wetter wurde erneut der Strand in Angriff genommen. <br />
Danach wurde ein Expeditionsteam zu den Warenhäusern der Einheimischen geschickt um dort unsere Reserven zu ergänzen. Erschreckt mussten wir feststellen, dass die dänischen Preise Deutschland wie einen großen DutyFreeShop aussehen lassen (1Tüte Chips ca.4EUR!). Wir kaufen nur das nötigste... Fast... den die Neugier trieb uns zum Kauf seltsamer Frühstücks-Cerialien: wir nannten es "Zuckerbrot"! Man nehme Pumpernikel-Brot, zerkleinere dies und erstelle eine Mischung mit ca. 40% Zucker. Das Ganze wird dann wie Müsli gegessen. Strange, durchaus, aber interessant. Und enthält überraschenderweise weniger Kalorien als Müsli. <br />
Flash machte sich auf ins Novasol Büro um dort um Hilfe zu bitten um den Boiler im Haus zu aktivieren. 2Tage lang kalt Duschen war nur für die angenehm, welche gerade aus der noch kälteren Ostsee gestiegen waren. Auch wurde angesprochen, dass der Grillrost zu klein für den vorhandenen Grill war. Einige Stunden später kam ein Techniker vorbei der, nachdem er eine Holzblende im Bad entfernt hatte, uns den Schalter für den Boiler zeigen konnte. Dem Thema Grill stand er hilflos gegenüber. Dumm war nur, dass er seinen Schlüssel bei uns vergaß.<br />
Danach gab's wieder Starcraft. Lord Horazont, Frase und i0n0s streuten dazwischen auch gern die ein oder andere Runde Diablo II ein.<br />
Am Abend wurde gegrillt. Dank Improvisation konnte das Problem eines zu kleinen Grillrostes für den riesigen Grill aber gelöst werden. Ebenfalls gelöst wurde, wie man mit einem Fegefeuer gleichen Grill, Baguettes rösten kann ohne sie komplett in Holzkohle zu verwandeln. Nicht zu lösen war, dass die Steaks die uns verkauft wurden zu ca. 30% aus Fettschwarte bestand.<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Zuckerbrot_und_Müsli.jpg|thumb|none|200px|Unser Frühstück =)]]<br />
|[[Bild:Fegefeuer.jpg|thumb|none|200px|Wenn der Grill ''zu'' heiß ist.]]<br />
|-<br />
|[[Bild:Grillversuche.jpg|thumb|none|200px|Die ersten Grillversuche von DGL, nachdem der Grill gebändigt wurde.]]<br />
|[[Bild:Guitar_Hero.jpg|thumb|none|200px|DGL's Guitar Hero]]<br />
|}<br />
|-<br />
!Di 28.07.2009<br />
|Erste Ausfallerscheinungen am Morgen sorgten dafür, dass Phobeus und Flash allein in die Fluten stiegen. Dies war auch der Tag an dem alle anderen Teilnehmer das erste mal in Kontakt mit den Tanzmatten kamen. Flash und i0n0s "battelten" sich auf Einsteigernivau während Phobeus und "Grandmaster Frase" selbiges auf fortgeschrittenem Niveau taten. Lord Horazont versuchte seine "Mattenzeit" möglichst klein zu halten. Seine Droge hieß erschreckenderweise "Neverball"! <br />
Im weiteren Tagesverlauf wurde neben dem obligatorischen Starcraft Matches noch Frases Waffeleisen scharf gemacht. Definitiv eine gute Entscheidung! Lecker!<br />
Am Abend gab es Spagetti mit i0n0s mediterraner Soße (Tomaten, Oliven, Kapern, Zwiebeln).<br />
Nach dem Essen kam der mitgebrachte Beamer samt Leinwand zum Einsatz. Es wurden DVDs geschaut. Zuerst einen Anime "NGE Evangelion 1.01" anschließend "The Butterfly Effect".<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Stepmania.jpg|thumb|none|200px|Die zweite Welle der Stepmania-Attacke.]]<br />
|}<br />
|-<br />
!Mi 29.07.2009<br />
|Wiederum mussten Flash und Phobeus allein die Flagge am Strand hochhalten und die Krebse und Quallen mit ihrem Astralkörpern vertreiben. Vervollkommned wurde der Frühsport durch eine einstündige Stepmania-Session noch vor dem Frühstück. Nach dem Frühstück klopfte es plötzlich an der Tür und die Postfrau verlangte nach einem Herrn "Florian Fleischer", für den sie ein Paket dabei hatte. Der Namensmix der an der T-Shirt-Organisation beteiligten Personen war der Brüller und passte zum gesamten Unternehmen "T-Shirt-Beschaffung".<br />
Nachdem die neuen T-Shirts verteilt und alles fachgerecht dokumentiert war, wurde nach einem WLAN gesucht um doch noch Verstärkung in Form von Aya zu bekommen. Das WLAN wurde gefunden, Aya aber nicht erreicht. <br />
Einige wenige Partien Starcraft, diesmal mit wechselnden Teams, wurden noch vor dem Abendessen gespielt. Am Abend stellten wir fest, dass die Vorräte zur Neige gehen werden. Eine ordentliche Portion Eierkuchen (Pfannkuchen) konnte aber noch erzeugt werden. Anschließend wurde begonnen alle NGE DVDs durchzuschauen um die, von den Anwesenden Fachleuten als "verstümmelt" bewertete, "Evangelion 1.01" Version zu erklären.<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Florian_Fleischer.jpg|thumb|none|200px|...]]<br />
|[[Bild:T-Shirts.jpg|thumb|none|200px|Die offiziellen DGL-Shirts, während der Entpackvorgang läuft.]]<br />
|[[Bild:Pfannenkuchen.jpg|thumb|none|200px|Die Eierkuchen ( = Pfannenkuchen), die uns über den Tag gebracht haben.]]<br />
|}<br />
|-<br />
!Do 30.07.2009<br />
|Das Wetter war diesmal noch schlechter als sonst, deshalb blieb das Baden aus. Dafür wurde nach fachmännischer Kostenkalkulation beschlossen, dass es sinnvoll wäre nach Deutschland einkaufen zu fahren. Phobeus und i0n0s bildeten das Expeditionsteam. Während dessen machten sich Flash, Frase und Lord Horazont auf zum Novasol Büro um den Hausmeisterschlüssel abzugeben, das Thema Grill noch einmal zur Sprache zu bringen und das Geld für gekaufte Geschirrspühltabs zurück zu holen.<br />
Als Ergebnis des Vorhabens durfte Lord Horazont einen neuen Grill zurück ins Ferienhaus tragen.<br />
Bis das Expeditionsteam wieder zurück war wurde Starcraft gespielt. Diesmal als Team gegen ein KI-Team.<br />
Nach der Rückkehr des Expeditionsteams wurden die Nahrungsmittel verstaut und der Stauraum knapp. Man fühlte sich wie die Made im Speck beim Anblick von Steaks, Würstchen, Knabberkram und Marshmellow.<br />
Danach wurde der Beamer erneut scharf gemacht und alle Anwesenden präsentierten ihre aktuellen Projekte, welche von den Übrigen dann kritisch unter die Lupe genommen wurden.<br />
Am Abend wurde dann der neue Grill aufgebaut und startklar gemacht. Auch einsetzender Nieselregen konnte uns nicht von der (überdachten) Terrasse vertreiben. Flash und i0n0s schnippelten während dessen leckere Salate zusammen, so dass der abendlichen Völlerei mit Fleisch und Bier nichts mehr im Wege stand. Nachdem alle satt und zufrieden waren, wurden die nächsten NGE DVDs in Angriff genommen. Auch technische Schwierigkeiten (Linux-Nutzer sind einem erhöhten Druck ausgesetzt, wenn etwas nicht sofort beim ersten Mal funktioniert und andere Linuxnutzer im Publikum gute Ratschläge geben ;) - und an Fachpersonal mangelte es nicht...) konnten dies nicht verhindern.<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Aufräumaktion_1.jpg|thumb|none|200px|Ein ...]]<br />
|[[Bild:Aufräumaktion_2.jpg|thumb|none|200px| ... Kick ... ]]<br />
|-<br />
|[[Bild:Aufräumaktion_3.jpg|thumb|none|200px| ... im ... ]]<br />
|[[Bild:Aufräumaktion_4.jpg|thumb|none|200px| ... Real-Life.]]<br />
|-<br />
|[[Bild:Die_tragende_Rolle.jpg|thumb|none|200px|Und die tragende Rolle des Webmasters ^.^]]<br />
|}<br />
|-<br />
!Fr 31.07.2009<br />
|Da das Wetter wieder besser wurde, bekamen Phobeus und Flash beim Baden Unterstützung von Frase. Dieser machte im Wasser auch gleich Bekanntschaft mit listigen kleinen Krabben die eine Vorliebe für ihn zu haben schienen. Nach dem Frühstück, bei dem das, mittlerweile zur Delikatesse erklärte, dänische Zuckerbrot zur Neige ging, wurde versucht Patrizier II auf allen Rechnern zu installieren. Leider war es nicht möglich dies bei Phobeus zu erreichen, deshalb schlossen sich Frase, Flash und Phobeus zusammen, um dem Rechner in Starcraft zu zeigen wo der Hammer hängt. Die Folge waren epische Schlachten die in einem 3v4 Match gipfelten, welches nach 75Minuten und dem kompletten abernten der gesamten Map, mit einem Sieg für die 3 DGLer endete. Die euphorisierten Frase und Flash begannen sogleich damit "Apfelwaffeln für Alle" zuzubereiten. Versuche, einige Räume für die morgige Abreise vorzubereiten, endeten vor dem Rechner und erneuten Starcraft Partien. Am Abend gab es dann endlich HotDogs. Die Kalkulation sah vor, dass jede Person 5-6 Hotdogs verspeissen würde. Die Realität zeigte, dass 3 bei allen mehr als genug war. Den Abschluss bildeten dann die letzten NGE DVDs bevor alle bei Gitarrenklängen von Flash, totmüde ins Bett fielen<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Killerkrabbe.jpg|thumb|none|200px|Die Krabbe des Schreckens in der kalt-finsteren See des Ostens.]]<br />
|[[Bild:Patrizier_II.jpg|thumb|none|200px|Herrschaftsübernahme in der Hanse durch Jonas und Jonas.]]<br />
|[[Bild:Überlebenswaage.jpg|thumb|none|200px|DGL-ler wissen sich auch weit ab von zivilisatorischen Einrichtungen wie Waagen zu helfen.]]<br />
|}<br />
|-<br />
!Sa 01.08.2009<br />
|Der Tag begann unerfreulich früh. Bereits 8Uhr wurde gefrühstückt um rechtzeitig bis 10Uhr den Schlüssel abgeben zu können. Es zeigte sich, dass 5 Personen in der Lage sind innerhalb 1,5h ein Haus übergabefähig zu reinigen. Nachdem die Reste aufgeteilt und die letzten Abschiedsfotos geschossen waren, wurde die Rückreise in Angriff genommen. Trotz gekonntem Navigieren war es jedoch nicht möglich alle Staus und Behinderungen im Umfeld von Hamburg zu umfahren. Auf der Fahrt nach Hannover schoss Lord Horazont noch ein Foto eines polnischen Kennzeichens mit dem passenden Kennzeichen: '''DGL'''. Damit dies gelang, muss Flash kurz die Warnblinkanlage setzen um den Polen zum überholen zu bringen. Dieses Manöver versetzte wiederum Frase und i0n0s in Aufregung, da sie dachten Flash würde mit technischen Problemen kämpfen. Aber zum Glück haben wir alle Handys...<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Fensterputzen.jpg|thumb|none|200px|Aufräumen. Spuren vernichten.]]<br />
|[[Bild:Abschiedsfoto.jpg|thumb|none|200px|Und noch ein Foto zum Schluss.]]<br />
|[[Bild:DGL.jpg|thumb|none|200px|Das (Kenn-)Zeichen auf der Heimfahrt.]]<br />
|}<br />
|-<br />
|colspan="2"|Und zum Schluss noch ein paar Bilder, die sich nicht (mehr) einordnen ließen.<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Meow_mit_Wollknäuel.jpg|thumb|none|200px|Gerade für diejenigen, die sich ab und zu im IRC rumtreiben :)]]<br />
|[[Bild:Papa_Schlumpf.jpg|thumb|none|200px|Papa Schlumpf ^.^]]<br />
|[[Bild:Panorama_Haus.jpg|thumb|none|200px|Und ein Panorama vom Haus.]]<br />
|}<br />
|}</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Panorama_Haus.jpg&diff=24163Datei:Panorama Haus.jpg2009-08-23T19:27:49Z<p>Lord Horazont: Das Panorama des Hauses, in dem die DGL-Treffen-Gruppe eine Woche verweilte.</p>
<hr />
<div>Das Panorama des Hauses, in dem die [[DGL-Treffen|DGL-Treffen-Gruppe]] eine Woche verweilte.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Papa_Schlumpf.jpg&diff=24162Datei:Papa Schlumpf.jpg2009-08-23T19:26:30Z<p>Lord Horazont: Der Papa Schlumpf des DGL.</p>
<hr />
<div>Der Papa Schlumpf des DGL.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Meow_mit_Wollkn%C3%A4uel.jpg&diff=24161Datei:Meow mit Wollknäuel.jpg2009-08-23T19:25:37Z<p>Lord Horazont: Gerade für diejenigen, die sich ab und zu im IRC rumtreiben :)</p>
<hr />
<div>Gerade für diejenigen, die sich ab und zu im IRC rumtreiben :)</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Abschiedsfoto.jpg&diff=24160Datei:Abschiedsfoto.jpg2009-08-23T19:23:55Z<p>Lord Horazont: Abschiedsfoto</p>
<hr />
<div>Abschiedsfoto</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:DGL.jpg&diff=24159Datei:DGL.jpg2009-08-23T19:23:35Z<p>Lord Horazont: Das (Kenn-)Zeichen auf der Heimfahrt.</p>
<hr />
<div>Das (Kenn-)Zeichen auf der Heimfahrt.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Fensterputzen.jpg&diff=24158Datei:Fensterputzen.jpg2009-08-23T19:22:38Z<p>Lord Horazont: Aufräumen. Spuren vernichten.</p>
<hr />
<div>Aufräumen. Spuren vernichten.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:%C3%9Cberlebenswaage.jpg&diff=24157Datei:Überlebenswaage.jpg2009-08-23T19:21:13Z<p>Lord Horazont: DGL-ler wissen sich auch weit ab von zivilisatorischen Einrichtungen wie Waagen zu helfen.</p>
<hr />
<div>DGL-ler wissen sich auch weit ab von zivilisatorischen Einrichtungen wie Waagen zu helfen.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Patrizier_II.jpg&diff=24156Datei:Patrizier II.jpg2009-08-23T19:20:28Z<p>Lord Horazont: Herrschaftsübernahme in der Hanse durch Jonas und Jonas.</p>
<hr />
<div>Herrschaftsübernahme in der Hanse durch Jonas und Jonas.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Killerkrabbe.jpg&diff=24155Datei:Killerkrabbe.jpg2009-08-23T19:19:35Z<p>Lord Horazont: Die Krabbe des Schreckens in der kalt-finsteren See des Ostens.</p>
<hr />
<div>Die Krabbe des Schreckens in der kalt-finsteren See des Ostens.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Die_tragende_Rolle.jpg&diff=24154Datei:Die tragende Rolle.jpg2009-08-23T19:16:52Z<p>Lord Horazont: Die tragende Rolle des Webmasters ^.^</p>
<hr />
<div>Die tragende Rolle des Webmasters ^.^</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Aufr%C3%A4umaktion_4.jpg&diff=24153Datei:Aufräumaktion 4.jpg2009-08-23T19:16:30Z<p>Lord Horazont: zurück</p>
<hr />
<div>[[Bild:Aufräumaktion_3.jpg|thumb|none|200px|zurück]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Aufr%C3%A4umaktion_3.jpg&diff=24152Datei:Aufräumaktion 3.jpg2009-08-23T19:16:07Z<p>Lord Horazont: zurück
weiter</p>
<hr />
<div>[[Bild:Aufräumaktion_2.jpg|thumb|none|200px|zurück]]<br />
[[Bild:Aufräumaktion_4.jpg|thumb|none|200px|weiter]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Aufr%C3%A4umaktion_2.jpg&diff=24151Datei:Aufräumaktion 2.jpg2009-08-23T19:15:34Z<p>Lord Horazont: zurück
weiter</p>
<hr />
<div>[[Bild:Aufräumaktion_1.jpg|thumb|none|200px|zurück]]<br />
[[Bild:Aufräumaktion_3.jpg|thumb|none|200px|weiter]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Aufr%C3%A4umaktion_1.jpg&diff=24150Datei:Aufräumaktion 1.jpg2009-08-23T19:14:44Z<p>Lord Horazont: </p>
<hr />
<div>So in der Art sieht ein Ban in Real-Life aus.<br />
[[Bild:Aufräumaktion_2.jpg|thumb|none|200px|weiter]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Aufr%C3%A4umaktion_1.jpg&diff=24149Datei:Aufräumaktion 1.jpg2009-08-23T19:14:09Z<p>Lord Horazont: So in der Art sieht ein Ban in Real-Life aus.
200px</p>
<hr />
<div>So in der Art sieht ein Ban in Real-Life aus.<br />
[[Bild:Aufräumaktion_2.jpg|thumb|none|200px]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:T-Shirts.jpg&diff=24148Datei:T-Shirts.jpg2009-08-23T19:12:00Z<p>Lord Horazont: Die offiziellen DGL-Shirts direkt nach dem Auspacken.</p>
<hr />
<div>Die offiziellen DGL-Shirts direkt nach dem Auspacken.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Pfannenkuchen.jpg&diff=24147Datei:Pfannenkuchen.jpg2009-08-23T19:11:20Z<p>Lord Horazont: Kevin's delikate Pfannenkuchen (auch bekannt als Eierkuchen).</p>
<hr />
<div>Kevin's delikate Pfannenkuchen (auch bekannt als Eierkuchen).</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Florian_Fleischer.jpg&diff=24146Datei:Florian Fleischer.jpg2009-08-23T19:10:04Z<p>Lord Horazont: Der Name sagt alles.</p>
<hr />
<div>Der Name sagt alles.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Stepmania.jpg&diff=24145Datei:Stepmania.jpg2009-08-23T19:08:53Z<p>Lord Horazont: Stepmania-Attacke die zweite.</p>
<hr />
<div>Stepmania-Attacke die zweite.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Zuckerbrot_und_M%C3%BCsli.jpg&diff=24144Datei:Zuckerbrot und Müsli.jpg2009-08-23T19:08:15Z<p>Lord Horazont: Frühstück</p>
<hr />
<div>Frühstück</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Guitar_Hero.jpg&diff=24143Datei:Guitar Hero.jpg2009-08-23T19:07:30Z<p>Lord Horazont: Unser Guitar Hero!</p>
<hr />
<div>Unser Guitar Hero!</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Grillversuche.jpg&diff=24142Datei:Grillversuche.jpg2009-08-23T19:06:44Z<p>Lord Horazont: Die Grillversuche von DGL.</p>
<hr />
<div>Die Grillversuche von DGL.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Fegefeuer.jpg&diff=24141Datei:Fegefeuer.jpg2009-08-23T19:06:01Z<p>Lord Horazont: Wenn aus dem Grill das Höllenfeuer wird.</p>
<hr />
<div>Wenn aus dem Grill das Höllenfeuer wird.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Stepmania.JPG&diff=24140Datei:Stepmania.JPG2009-08-23T19:05:09Z<p>Lord Horazont: Die erste Welle der Stepmania-Attacke.</p>
<hr />
<div>Die erste Welle der Stepmania-Attacke.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=DGL_Treffen/2009&diff=24138DGL Treffen/20092009-08-23T18:52:13Z<p>Lord Horazont: /* Tagebuch */</p>
<hr />
<div>=1. DGL Treffen in Hejlsminde/Dänemark=<br />
{|{{Prettytable_B1}}<br />
!Ort<br />
|[http://www.cofman.de/detail.php?houseid=29152 Ferienhaus] in [http://maps.google.de/?ie=UTF8&ll=55.376697,9.609175&spn=0.009778,0.027595&t=h&z=15 Hejlsminde], Hejls, Ostseeküste Dänemark<br />
|-<br />
!Zeit<br />
|25.07.09 - 01.08.09<br />
|-<br />
!Kosten p.P.<br />
|<br />
155 EUR Unterkunft<br />
50 EUR Verpflegung<br />
5 EUR Kaution<br />
40-100 EUR Anreise<br />
--------------------<br />
250-300EUR pro Person<br />
|-<br />
!Teilnehmer<br />
|Flash, Frase, i0n0s, Lord Horazont, Phobeus<br />
|}<br />
<br />
==Organisation==<br />
Die Organisation würde über diese [[DGL_Treffen/2009/Organisation|Wiki-Seite]] und diesem [http://www.delphigl.com/forum/viewtopic.php?t=8130 Thread im DGL Forum] organisiert.<br />
Ursprünglich sollte ein Ferienhaus in Deutschland gemietet werden. Allerdings waren alle Ferienhäuser die in Frage kamen (6Personen, mind. 4 Schlafzimmer, strandnah) alle bereits belegt.<br />
Flash fand dann in Dänemark passende Häuser, und da die Zeit drängte wurde ein passendes Haus von Flash angemietet.<br />
<br />
==Tagebuch==<br />
{|{{Prettytable_B1}}<br />
!Tag<br />
!Bericht<br />
|-<br />
!Sa 25.07.2009<br />
|Frase und I0n0s sowie Flash reisen mit dem PKW Richtung Hamburg. Auch ein Kamikaze-Vogel konnte uns (insbesondere Flash) nicht vom Urlaub mehr abhalten. Lord Horazont kommt mit dem Zug. Gegen 13Uhr wird Lord Horazont von Phobeus am Hamburg Bahnhof abgeholt. Da die Südfraktion (Frase&I0n0s) im Stau steht entschließt sich Flash direkt zu Phobeus zu fahren um von dort aus zu Planen wie es weiter geht. Die Zeit ist recht fortgeschritten (da die Nord-Ost-Gruppe dank eines fehlgeleiteten Navis/Fahrers einen Abstecher durch den Elbtunnel gemacht hat), deshalb beschließt Flash voraus zu fahren um den Schlüssel abzuholen, während sich Phobeus, Lord Horanzont, Frase und I0n0s in Flensburg zum Einkaufen zusammen rotten.<br />
Flash nimmt das Haus um 19Uhr in Besitz und erkundet den Weg zum Strand, bevor die Anderen um 20Uhr in Hejlsminde ankommen. <br />
Die nächste Stunde wird damit zugebracht die Elektronik aufzubauen, mit der Folge, dass in der gesamten Woche am einzigen echten Tisch im Haus niemals gegessen werden konnte - dort standen die Rechner. ;)<br />
Am Abend zauberte dann i0n0s ein feurig scharfes und komplett handgemachtes Chilli Con Carne auf den Tisch.<br />
|-<br />
|colspan="2"|<br />
{|style="background-color: inherit"<br />
|[[Bild:Belagerung.jpg|thumb|none|200px|Die Belagrung des Esstisches durch DelphiGL hat begonnen!]]<br />
|[[Bild:Chilli_Con_Carne.jpg|thumb|none|200px|Äußerst lecker und aus i0n0s Kochkünsten entsprungen.]]<br />
|[[Bild:Toter_Nummer_1.jpg|thumb|none|200px|Dieses Opfer wurde zwischen Osten und Westen Deutschlands "aufgegabelt".]]<br />
|}<br />
|-<br />
!So 26.07.2009<br />
|Heute gings das erste mal zum "Frühsport" an den Strand. Dort stellte sich allerdings heraus, dass es an diesem Strand ein elementares Problem gab: Das Wasser reichte trotz wagemutigem hinauswaten, selbst bei den Kleineren, gerade mal bis zum Bauch. Das Problem wurde pragmatisch durch hinsetzen gelöst.<br />
<br />
Danach gab es "Frühtagessen" - quasi ein Frühstück zur Mittgagszeit.<br />
Starcraft wurde im Anschluss bei allen installiert und ausführlich gespielt. Phobeus entdeckte dabei seine lange unterdrückten Hang zur Expansion und Verschleimung weiter Landteile (Zerg) und i0n0s spielte den Protoss Executor. Anfangs wurden einige kurze Spiele gegen den Rechner gemacht, bevor man sich in 2 Teams (i0n0s+Flash sowie Phobeus+Frase+Lord Horazont) aufteilte um gegeneinander zu spielen. <br />
Erschütternde Szenen (im Wahrsten Sinne des Wortes) ergaben sich, als Frase und Phobeus die "Stepmania" Tanzmatten klar zum Gefecht machten.<br />
Am Abend machte Flash "Pfannenpizzas". Der Teig dieser enthielt als Überraschung den letzten Rest der Chillis vom Vortag, was Frase Tränen (der Freude natürlich) in die Augen trieb.<br />
|-<br />
|colspan="2"|''Bilder Tag 2''<br />
|-<br />
!Mo 27.07.2009<br />
|Trotz wenig tollem Wetter wurde erneut der Strand in Angriff genommen. <br />
Danach wurde ein Expeditionsteam zu den Warenhäusern der Einheimischen geschickt um dort unsere Reserven zu ergänzen. Erschreckt mussten wir feststellen, dass die dänischen Preise Deutschland wie einen großen DutyFreeShop aussehen lassen (1Tüte Chips ca.4EUR!). Wir kaufen nur das nötigste... Fast... den die Neugier trieb uns zum Kauf seltsamer Frühstücks-Cerialien: wir nannten es "Zuckerbrot"! Man nehme Pumpernikel-Brot, zerkleinere dies und erstelle eine Mischung mit ca. 40% Zucker. Das Ganze wird dann wie Müsli gegessen. Strange, durchaus, aber interessant. Und enthält überraschenderweise weniger Kalorien als Müsli. <br />
Flash machte sich auf ins Novasol Büro um dort um Hilfe zu bitten um den Boiler im Haus zu aktivieren. 2Tage lang kalt Duschen war nur für die angenehm, welche gerade aus der noch kälteren Ostsee gestiegen waren. Auch wurde angesprochen, dass der Grillrost zu klein für den vorhandenen Grill war. Einige Stunden später kam ein Techniker vorbei der, nachdem er eine Holzblende im Bad entfernt hatte, uns den Schalter für den Boiler zeigen konnte. Dem Thema Grill stand er hilflos gegenüber. Dumm war nur, dass er seinen Schlüssel bei uns vergaß.<br />
Danach gab's wieder Starcraft. Lord Horazont, Frase und i0n0s streuten dazwischen auch gern die ein oder andere Runde Diablo II ein.<br />
Am Abend wurde gegrillt. Dank Improvisation konnte das Problem eines zu kleinen Grillrostes für den riesigen Grill aber gelöst werden. Ebenfalls gelöst wurde, wie man mit einem Fegefeuer gleichen Grill, Baguettes rösten kann ohne sie komplett in Holzkohle zu verwandeln. Nicht zu lösen war, dass die Steaks die uns verkauft wurden zu ca. 30% aus Fettschwarte bestand.<br />
|-<br />
|colspan="2"|''Bilder Tag 3''<br />
|-<br />
!Di 28.07.2009<br />
|Erste Ausfallerscheinungen am Morgen sorgten dafür, dass Phobeus und Flash allein in die Fluten stiegen. Dies war auch der Tag an dem alle anderen Teilnehmer das erste mal in Kontakt mit den Tanzmatten kamen. Flash und i0n0s "battelten" sich auf Einsteigernivau während Phobeus und "Grandmaster Frase" selbiges auf fortgeschrittenem Niveau taten. Lord Horazont versuchte seine "Mattenzeit" möglichst klein zu halten. Seine Droge hieß erschreckenderweise "Neverball"! <br />
Im weiteren Tagesverlauf wurde neben dem obligatorischen Starcraft Matches noch Frases Waffeleisen scharf gemacht. Definitiv eine gute Entscheidung! Lecker!<br />
Am Abend gab es Spagetti mit i0n0s mediterraner Soße (Tomaten, Oliven, Kapern, Zwiebeln).<br />
Nach dem Essen kam der mitgebrachte Beamer samt Leinwand zum Einsatz. Es wurden DVDs geschaut. Zuerst einen Anime "NGE Evangelion 1.01" anschließend "The Butterfly Effect".<br />
|-<br />
|colspan="2"|''Bilder Tag 4''<br />
|-<br />
!Mi 29.07.2009<br />
|Wiederum mussten Flash und Phobeus allein die Flagge am Strand hochhalten und die Krebse und Quallen mit ihrem Astralkörpern vertreiben. Vervollkommned wurde der Frühsport durch eine einstündige Stepmania-Session noch vor dem Frühstück. Nach dem Frühstück klopfte es plötzlich an der Tür und die Postfrau verlangte nach einem Herrn "Florian Fleischer", für den sie ein Paket dabei hatte. Der Namensmix der an der T-Shirt-Organisation beteiligten Personen war der Brüller und passte zum gesamten Unternehmen "T-Shirt-Beschaffung".<br />
Nachdem die neuen T-Shirts verteilt und alles fachgerecht dokumentiert war, wurde nach einem WLAN gesucht um doch noch Verstärkung in Form von Aya zu bekommen. Das WLAN wurde gefunden, Aya aber nicht erreicht. <br />
Einige wenige Partien Starcraft, diesmal mit wechselnden Teams, wurden noch vor dem Abendessen gespielt. Am Abend stellten wir fest, dass die Vorräte zur Neige gehen werden. Eine ordentliche Portion Eierkuchen (Pfannkuchen) konnte aber noch erzeugt werden. Anschließend wurde begonnen alle NGE DVDs durchzuschauen um die, von den Anwesenden Fachleuten als "verstümmelt" bewertete, "Evangelion 1.01" Version zu erklären.<br />
|-<br />
|colspan="2"|''Bilder Tag 5''<br />
|-<br />
!Do 30.07.2009<br />
|Das Wetter war diesmal noch schlechter als sonst, deshalb blieb das Baden aus. Dafür wurde nach fachmännischer Kostenkalkulation beschlossen, dass es sinnvoll wäre nach Deutschland einkaufen zu fahren. Phobeus und i0n0s bildeten das Expeditionsteam. Während dessen machten sich Flash, Frase und Lord Horazont auf zum Novasol Büro um den Hausmeisterschlüssel abzugeben, das Thema Grill noch einmal zur Sprache zu bringen und das Geld für gekaufte Geschirrspühltabs zurück zu holen.<br />
Als Ergebnis des Vorhabens durfte Lord Horazont einen neuen Grill zurück ins Ferienhaus tragen.<br />
Bis das Expeditionsteam wieder zurück war wurde Starcraft gespielt. Diesmal als Team gegen ein KI-Team.<br />
Nach der Rückkehr des Expeditionsteams wurden die Nahrungsmittel verstaut und der Stauraum knapp. Man fühlte sich wie die Made im Speck beim Anblick von Steaks, Würstchen, Knabberkram und Marshmellow.<br />
Danach wurde der Beamer erneut scharf gemacht und alle Anwesenden präsentierten ihre aktuellen Projekte, welche von den Übrigen dann kritisch unter die Lupe genommen wurden.<br />
Am Abend wurde dann der neue Grill aufgebaut und startklar gemacht. Auch einsetzender Nieselregen konnte uns nicht von der (überdachten) Terrasse vertreiben. Flash und i0n0s schnippelten während dessen leckere Salate zusammen, so dass der abendlichen Völlerei mit Fleisch und Bier nichts mehr im Wege stand. Nachdem alle satt und zufrieden waren, wurden die nächsten NGE DVDs in Angriff genommen. Auch technische Schwierigkeiten (Linux-Nutzer sind einem erhöhten Druck ausgesetzt, wenn etwas nicht sofort beim ersten Mal funktioniert und andere Linuxnutzer im Publikum gute Ratschläge geben ;) - und an Fachpersonal mangelte es nicht...) konnten dies nicht verhindern.<br />
|-<br />
|colspan="2"|''Bilder Tag 6''<br />
|-<br />
!Fr 31.07.2009<br />
|Da das Wetter wieder besser wurde, bekamen Phobeus und Flash beim Baden Unterstützung von Frase. Dieser machte im Wasser auch gleich Bekanntschaft mit listigen kleinen Krabben die eine Vorliebe für ihn zu haben schienen. Nach dem Frühstück, bei dem das, mittlerweile zur Delikatesse erklärte, dänische Zuckerbrot zur Neige ging, wurde versucht Patrizier II auf allen Rechnern zu installieren. Leider war es nicht möglich dies bei Phobeus zu erreichen, deshalb schlossen sich Frase, Flash und Phobeus zusammen, um dem Rechner in Starcraft zu zeigen wo der Hammer hängt. Die Folge waren epische Schlachten die in einem 3v4 Match gipfelten, welches nach 75Minuten und dem kompletten abernten der gesamten Map, mit einem Sieg für die 3 DGLer endete. Die euphorisierten Frase und Flash begannen sogleich damit "Apfelwaffeln für Alle" zuzubereiten. Versuche, einige Räume für die morgige Abreise vorzubereiten, endeten vor dem Rechner und erneuten Starcraft Partien. Am Abend gab es dann endlich HotDogs. Die Kalkulation sah vor, dass jede Person 5-6 Hotdogs verspeissen würde. Die Realität zeigte, dass 3 bei allen mehr als genug war. Den Abschluss bildeten dann die letzten NGE DVDs bevor alle bei Gitarrenklängen von Flash, totmüde ins Bett fielen<br />
|-<br />
|colspan="2"|''Bilder Tag 7''<br />
|-<br />
!Sa 01.08.2009<br />
|Der Tag begann unerfreulich früh. Bereits 8Uhr wurde gefrühstückt um rechtzeitig bis 10Uhr den Schlüssel abgeben zu können. Es zeigte sich, dass 5 Personen in der Lage sind innerhalb 1,5h ein Haus übergabefähig zu reinigen. Nachdem die Reste aufgeteilt und die letzten Abschiedsfotos geschossen waren, wurde die Rückreise in Angriff genommen. Trotz gekonntem Navigieren war es jedoch nicht möglich alle Staus und Behinderungen im Umfeld von Hamburg zu umfahren. Auf der Fahrt nach Hannover schoss Lord Horazont noch ein Foto eines polnischen Kennzeichens mit dem passenden Kennzeichen: '''DGL'''. Damit dies gelang, muss Flash kurz die Warnblinkanlage setzen um den Polen zum überholen zu bringen. Dieses Manöver versetzte wiederum Frase und i0n0s in Aufregung, da sie dachten Flash würde mit technischen Problemen kämpfen. Aber zum Glück haben wir alle Handys...<br />
|-<br />
|colspan="2"|''Bilder Tag 8''<br />
|}</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Toter_Nummer_1.jpg&diff=24137Datei:Toter Nummer 1.jpg2009-08-23T18:39:02Z<p>Lord Horazont: Dieses Opfer wurde irgendwo zwischen Osten und Westen Deutschlands aufgegabelt.</p>
<hr />
<div>Dieses Opfer wurde irgendwo zwischen Osten und Westen Deutschlands aufgegabelt.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Chilli_Con_Carne.jpg&diff=24136Datei:Chilli Con Carne.jpg2009-08-23T18:37:26Z<p>Lord Horazont: Lecker Chilli Con Carne made by i0n0s.</p>
<hr />
<div>Lecker Chilli Con Carne made by i0n0s.</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Datei:Belagerung.jpg&diff=24135Datei:Belagerung.jpg2009-08-23T18:36:36Z<p>Lord Horazont: Die Belagerung des Esstisches durch DelphiGL hat begonnen!</p>
<hr />
<div>Die Belagerung des Esstisches durch DelphiGL hat begonnen!</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Tutorial_SDL_RWops&diff=24134Tutorial SDL RWops2009-08-21T18:49:02Z<p>Lord Horazont: /* RWops aus Datei */ Update. Die Beschreibungen der Modi waren ziehmlich falsch.</p>
<hr />
<div>= Einleitung =<br />
Aye und willkommen an Bord zu meinem ersten Tutorial. Dies beschäftigt sich im Gegensatz zu den wohl überwiegenden Erwartungen in dieser Community nicht mit OpenGL sondern mit [[SDL]]. Diejenigen, die nicht wissen, was SDL ist, sollten sich erst darüber informieren und das ein oder andere damit ausprobieren. Einige Grundkenntnisse in SDL sollten schon vorhanden sein. Aber jetzt los.<br />
<br />
<br />
= RWops? =<br />
Wer schon einmal mit SDL und Dateien gearbeitet hat, der kennt bestimmt den [[SDL_RWops]]-Record. In ihm ist alles, was man zum Lesen und Schreiben der Datei braucht gespeichert. Hier noch einmal die Deklarationen aus der SDL.pas: <br />
<source lang="pascal">type <br />
TStdio = record<br />
autoclose: Integer;<br />
fp: Pointer;<br />
end;<br />
<br />
TMem = record<br />
base: PUInt8;<br />
here: PUInt8;<br />
stop: PUInt8;<br />
end;<br />
<br />
TUnknown = record<br />
data1: Pointer;<br />
end;<br />
<br />
TSDL_RWops = record<br />
seek: TSeek;<br />
read: TRead;<br />
write: TWrite;<br />
close: TClose;<br />
type_: UInt32;<br />
case Integer of<br />
0: (stdio: TStdio);<br />
1: (mem: TMem);<br />
2: (unknown: TUnknown);<br />
end;<br />
end;</source><br />
Der Record besteht also aus vier Pointern auf jeweils eine Prozedur, aus einem Integer der für die Typenangabe wichtig ist und einem dynamischen Teil, der später Pointer auf die Daten enthält (dazu später mehr).<br />
<br />
<br />
= Handling =<br />
Die Daten im RWops kann man eigentlich ganz einfach manipulieren und auslesen. Ähnlich wie bei den normalen Delphi-Streams hat man hier Funktionen zum Lesen und Schreiben sowie zum Setzen der Position innerhalb der Datenquelle zur Verfügung. Dadurch, dass die Funktionen als Variablen innerhalb des RWops liegen, kann man so ziemlich alles mit ihnen machen. SDL bietet von Haus aus zwei Möglichkeiten, RWops zu erstellen. <br />
<br />
<br />
== RWops aus Datei ==<br />
<source lang="pascal">function SDL_RWFromFile(filename, mode: PChar): PSDL_RWops;</source><br />
Die erste und wohl am meisten benutzte Methode ist das Erstellen eines RWops aus einer Datei. Hierbei wird die Funktion [[SDL_RWFromFile]] verwendet, die mit zwei relativ simplen Parametern auskommt. Der erste ist (wie könnte es anders sein) der Dateiname als PChar (Man kann einfach einen normalen String nehmen und diesen mit PChar() auf PChar casten oder einen konstanten Wert verwenden). Hinweis: Ich empfehle absolute Pfade zu verwenden, (also mit Laufwerk und allem) da es sonst erstens unter einigen Betriebssystemen zu Problemen kommen könnte und zweitens kann sich der aktuelle Pfad, also der von dem aus die Relativen berechnet werden sich jeder Zeit ändern, wodurch dann die Dateien nicht mehr gefunden werden würden. Der zweite Parameter enthält den Zugriffsmodus auf die Datei. Das kennt man ja eigentlich schon von den TFileStreams, die ja auch wissen müssen, was man mit der Datei machen will. Hier gibt es sechs Möglichkeiten:<br />
#r Die Datei wird einfach nur zum Lesen geöffnet und muss existieren.<br />
#w Die Datei wird erzeugt. Wenn sie bereits existiert, wird die Datei überschrieben und als neue, leere Datei behandelt.<br />
#a Die Datei wird zum Schreiben geöffnet aber die Daten werden an das Ende der Datei angehängt. Die Datei wird, wenn nötig, erstellt.<br />
#r+ Wie ''r'', aber zusätzlich Schreibzugriff.<br />
#w+ Wie ''w'', aber zusätzlich Lesezugriff.<br />
#a+ Wie ''a'', aber auch Lesezugriff. Schreibvorgänge werden immer am Ende der Datei durchgeführt. Zum Lesen kann an eine beliebige Stelle gesprungen werden, zum Schreiben wird die Position aber immer wieder ans Ende gesetzt.<br />
Simpel, oder? Was man jetzt noch machen kann (und sollte) ist festzulegen, wie die Datei geöffnet wird. Es gibt hier noch einmal zwei Möglichkeiten. Die Erste ist der Textmodus. Das Lesen aus der Datei wird abgebrochen, sobald das Steuerzeichen ^Z erreicht ist. Der Textmodus ist Standard, kann aber durch das Anhängen eines 't' an den Modus explizit festgelegt werden. Das Zweite ist der Binärmodus. Das Lesen wird beendet, sobald das „logische“ Ende der Datei erreicht ist und wird durch ein 'b' am Ende des Modus festgelegt.<br />
<br />
== RWops aus Pointern ==<br />
<source lang="pascal">function SDL_RWFromMem(mem: Pointer; size: Integer): PSDL_RWops;</source><br />
Die zweite Möglichkeit, direkt über SDL RWops zu erstellen ist, der Funktion [[SDL_RWFromMem]] einen Pointer zu übergeben. Das erlaubt dann den Zugriff auf den Speicherbereich über die komfortablen SDL_RWops. Das spart das ganze Rumgerechne mit Pointern, was durchaus gerne mal zu Fehlern führt. Auch diese Funktion erwartet zwei Parameter. Als erstes übergibt man einen Pointer auf den Speicherbereich, auf den man später mit dem SDL_RWops zugreifen möchte. Der zweite Parameter muss die Größe des Speicherbereiches in Bytes enthalten (da kann auch die Funktion SizeOf() helfen, die die Größe einer Variable oder eines Typs in Bytes zurückgibt – aber Achtung, bei Strings und dynamischen Arrays muss man Length() verwenden und ggf. noch mit der Größe der einzelnen Array-Elemente multiplizieren)<br />
<br />
== Lesezugriff ==<br />
Aus den RWops muss man nicht direkt über die im dynamischen Teil enthaltenen Pointer zugreifen. Dafür gibt es (wer hätte das gedacht) die Funktion auf die in der Read-Eigenschaft gezeigt wird. Die kann man eigentlich genauso wie die Delphi Stream-Methoden verwenden mit dem kleinen Unterschied, dass man noch einen Zeiger auf den RWops übergeben muss. Das ist nötig, weil es sich ja um einen Record handelt. Wäre TSDL_RWops eine Klasse, so könnten die Methoden über ein Self auf die Pointer zugreifen, so muss man den Kontext mit übergeben. Als zweiten Parameter wird ein Pointer auf den Speicherbereich erwartet, in den später die gelesenen Daten geschrieben werden sollen. Für die, die nicht gerne mit Pointern arbeiten: Man kann auch einfach ein Array oder sonst eine normale Zielvariable verwenden und auf die dann mit einem @ zeigen, indem man es einfach vor den Variablennamen setzt. Der dritte Parameter (size) sollte die Größe eines Datenblocks enthalten. Ein Datenblock wäre für das byteweise Auslesen immer 1. Wenn man aber immer einen Integer, der ja 4 Byte groß ist, erhalten will, dann sollte man dort eine 4 übergeben. Die Anzahl an Bytes, die bei size übergeben wird, wird immer versucht zu lesen. Wenn also eine 15 Byte große Datei gelesen wird, dann werden die letzten 3 Byte nicht mit eingelesen, da man mit ihnen keinen Block voll bekommt. (Auch hier kann man wieder gut SizeOf() bzw. Length() verwenden) Zu guter Letzt: maxnum. Maxnum legt fest, wie viele Blöcke maximal gelesen werden können. Will man also (um beim obigen Beispiel zu bleiben) zwei Integer einlesen, so wird man bei size die Größe des Integers, also 4 und bei maxnum die Anzahl der Integer, also 2 angeben. Man könnte natürlich genausogut bei size 1 und bei maxnum 8 übergeben, aber so kann man sicher sein, dass immer ein kompletter Integer eingelesen wird, nicht nur ein halber. Dies kann später bei eigenen RWops (etwa für Netzwerkübertragung) sehr hilfreich sein. Die Funktion gibt die Anzahl der gelesenen Blocks zurück, nicht die Anzahl der Bytes! Will man also die Anzahl der gelesenen Bytes erhalten, muss man den Rückgabewert noch mit size multiplizieren. Zum Schluss noch ein Beispiel zum Einlesen einer Zahl aus einer binären Datei:<br />
<source lang="pascal">var<br />
F: PSDL_RWops;<br />
Number: Integer;<br />
begin<br />
F := SDL_RWFromFile(PChar('datei.bin'), 'rb'); // datei.bin wird im binären Lesemodus geöffnet<br />
F^.read(F, @Number, 4, 1); // Lesen von 1 Integer (4 Bytes) in die Variable Number aus F<br />
SDL_RWClose(F); // Freigeben des Speichers für den RWops<br />
<br />
WriteLn(Number); // Ausgabe in die Konsole (natürlich nur bei einer Konsolenanwendung möglich<br />
end;</source><br />
Was übrigens auch geht ist, die Funktion [[SDL_RWRead]] zu verwenden. Dieser muss man dann die gleichen Parameter übergeben wie der Funktion im SDL_RWops, aber man spart sich das Dereferenzieren des RWops. Diese sogenannten Makros gibt es auch für [[SDL_RWWrite]], [[SDL_RWSeek]] und [[SDL_RWClose]].<br />
<br />
== Schreibzugriff ==<br />
Das Schreiben in RWops verläuft ähnlich wie das Lesen und wiederum ähnlich wie bei den Delphi Streams. Die Funktion erwartet vier Parameter. Der erste ist wie schon bei der Lesefunktion der Pointer auf den RWops. Der zweite ist der Speicherbereich, von dem aus die Daten in den RWops geschrieben werden. Auch hier könnte man wieder einen @-Pointer, der auf eine lokale oder globale Variable zeigt, verwenden. Size ist hier wie schon beim Lesen die Blockgröße und num die Anzahl der Blocks, die geschrieben werden sollen. Die Funktion gibt die Anzahl der geschrieben Blocks zurück, wiederum nicht die Anzahl der Bytes (außer natürlich, size ist 1, dann würde ein Block einem Byte entsprechen). Es kann passieren, dass die Funktion einen Wert zurückgibt, der kleiner als num ist. In diesem Falle ist irgendein Fehler aufgetreten (z.B. ein Out of Memory). Hier wieder ein kleines Beispiel wie man in eine Datei schreiben würde (diesmal ein String):<br />
<source lang="pascal">var<br />
F: PSDL_RWops;<br />
S: String;<br />
begin<br />
F := SDL_RWFromFile(PChar('datei.txt'), 'wb'); // datei.txt wird im binären Schreibmodus geöffnet, obwohl Text geschrieben wird.<br />
// Bei RWops ist das imho besser<br />
S := 'Hallo Welt!';<br />
F^.write(F, @S[1], 1, Length(S)); // Schreiben des Strings in die Datei. <br />
// Wichtig: Strings beginnen an der 1. Position, daher muss auch der Pointer auf diese zeigen<br />
SDL_RWClose(F); // Schließen der Datei<br />
end;</source><br />
Wie schon in den Kommentaren erwähnt, sollte man die Datei trotz der Absicht, Text zu schreiben im Binärmodus öffnen. Und nicht vergessen, dass Strings immer an der Array-Position 1 beginnen, nicht wie so ziehmlich alles andere bei 0. Dies kann böse Access Violation-Fehler verursachen.<br />
<br />
== Position ändern ==<br />
Für das, was man in normalen Streams mit Stream.Position := x; macht, braucht man bei RWops den Befehl seek, der wie die anderen im RWops vorhanden ist. Der erste Parameter ist, wie bei allen anderen auch, der RWops selber. Mit dem dritten Parameter legt man fest, wie der zweite behandelt wird. Dafür gibt es drei Werte (man muss sie selbst deklarieren, da sie aus der stdio.h von C kommen):<br />
<source lang="pascal">const<br />
SEEK_SET = 0;<br />
SEEK_CUR = 1;<br />
SEEK_END = 2;</source><br />
Wenn jetzt der dritte Parameter gleich SEEK_SET ist, dann ist Offset im Prinzip die Position vom Anfang des RWops an. SEEK_CUR ändert die Position relativ zur aktuellen, SEEK_END relativ zum Ende. Offset kann auch negative Werte annehmen, sodass man in Verbindung mit SEEK_CUR innerhalb des RWops zurück gehen könnte.<br />
{{Hinweis|Mindestens unter Windows scheint nur SEEK_SET bei whence, dem dritten Parameter, bei Dateien zu funktionieren. Bei den beiden anderen kommt es zwar zu keiner Fehlermeldung aber sie werden behandelt wie SEEK_SET.}}<br />
<br />
= Erweiterungen =<br />
„Das ist ja alles schön und gut. Aber man könnte ja auch normale Streams verwenden, um Dateien einzulesen.“ werden jetzt einige Leser einwenden. Aber wer z.B. Bilder mit SDL laden will, kann dies nicht über normale Streams tun. Man kann zwar direkt aus der Datei laden, aber was will man machen, wenn man ein eigenes [[VFS]] schreibt und die Daten im Speicher hält? Es wäre da alles andere als effektiv, die einmal mühselig aus dem Archiv geholten Daten erst auf die Festplatte in eine Datei zu schreiben um sie dann von SDL auslesen zu lassen. Statt dessen könnte man SDL einfach einen RWops auf den Speicherbereich erstellen lassen (siehe SDL_RWFromMem) und dann über beispielsweise IMG_LoadRW ein Bild laden lassen. Aber ehrlich gesagt habe ich schlechte Erfahrungen mit dem Auslesen von Daten aus Dateien mit RWops gemacht, irgendwie war mir das ganze zu spartanisch und zu Anti-OOP. Dazu kam das oben genannte Problem, dass man nicht anständig in der Datei navigieren kann und dass man die aktuelle Position nicht auslesen kann. Also habe ich das VFS für mein Projekt unüberlegter Weise mit Streams geschrieben. Jetzt stand ich vor dem Problem mit SDL-Funktionen Bilder zu laden (ja, ich bin faul, ich hätte mir auch einen Loader schreiben können). Nach kurzem Überlegen kam mir in den Sinn dass RWops ja durch die Möglichkeit, eigene Funktionen einzusetzen, sehr flexibel sind. Jetzt kommen wir zu dem dynamischen Teil des Records. Dynamischer Record bedeutet, dass alle Teile, die in einer solchen Case-Anweisung liegen, den gleichen Speicherbereich belegen. Also kann man immer nur eines der drei Teile, die im TSDL_RWops deklariert sind verwenden.<br />
#'''TStdio'''<br/>TStdio stammt von C ab. Eigentlich enthält der Pointer eine FILE-Variable. Die sind in C das, was in Pascal ein File of Byte ist, also der direkte Dateizugriff. Dieser Typ wird eigentlich nur von SDL selbst benutzt, zumindest fällt mir nichts anderes ein.<br />
#'''TMem'''<br/>TMem enthält informationen wenn man einen RWops aus einem Speicherbereich erstellt (siehe SDL_RWFromMem) und wird meiner Meinung nach auch nur intern benutzt.<br />
#'''TUnknown'''<br/>Auf den ersten Blick langweilig. Er enthält nur einen Pointer. Aber man denke drüber nach, was man mit diesem Pointer alles anstellen kann.<br />
Um mein kleines Stream zu RWops Problem zu lösen habe ich mir vier Funktionen geschrieben, die allesamt den Unknown-Teil des Records benutzen. Diese Funktionen casten sich aus dem Pointer einen TStream und führen dort die jeweiligen Operationen aus. Read führt die Read-Methode des Streams aus, Write die Write-Methode. Seek setzt die Position korrekt anhand der Parameter. Close gibt den Speicher des Streams frei und setzt den Pointer in Unknown auf nil. Dann habe ich mir noch zwei kleine Procedures geschrieben, die mir aus einem Stream einen RWops machen und aus einem RWops einen Stream. Bei ersterer wurden nur die gerade erwähnten Standardfunktionen zu einer frischen Rwops-Variable zugewiesen und der Unknown-Pointer durch den Stream ersetzt. Die andere Funktion war etwas komplizierter, da man ja nicht unbedingt weiss, ob einem ein „eigener“ RWops übergeben wurde oder einer, der von SDL erstellt wurde. Also habe ich innerhalb dieser Funktion alle Daten des RWops über dessen Read-Funktion in einen in den Parametern übergebenen Stream kopiert. Das stellt außerdem sicher, dass selbst nach dem Freigeben des RWops die Daten im erstellten Stream erhalten bleiben. Hier ein paar Codes:<br />
<source lang="pascal">function StreamRWSeek( context: PSDL_RWops; offset: Integer; <br />
whence: Integer ): Integer; {$IFNDEF ___GPC___}cdecl;{$ENDIF}<br />
var<br />
Stream: TStream;<br />
begin<br />
Stream := Tstream(context^.unknown.data1); // Den Stream aus dem RWops holen<br />
if Stream = nil then<br />
begin<br />
Result := -1;<br />
Exit;<br />
end;<br />
case whence of<br />
SEEK_SET: Stream.Position := offset; // Anhand des Whence-Parameters die neue Streamposition setzen<br />
SEEK_CUR: Stream.Position := Stream.Position + offset;<br />
SEEK_END: Stream.Position := Stream.Size + offset;<br />
end;<br />
Result := Stream.Position; // Die neue Position zurückgeben<br />
end;<br />
<br />
function StreamRWRead( context: PSDL_RWops; Ptr: Pointer; size: Integer; <br />
maxnum : Integer ): Integer; {$IFNDEF ___GPC___}cdecl;{$ENDIF}<br />
var<br />
Stream: TStream;<br />
Start: Int64;<br />
WillRead: Int64;<br />
begin<br />
Stream := TStream(context^.unknown.data1); // Den Stream aus dem RWops holen<br />
if Stream = nil then<br />
begin<br />
Result := -1;<br />
Exit;<br />
end;<br />
// Patch (14.9.2007)<br />
Start := Stream.Position;<br />
WillRead := Size*Maxnum; <br />
if WillRead + Stream.Position > Stream.Size then<br />
WillRead := Stream.Size - Stream.Position;<br />
WillRead := (WillRead div Size) * Size;<br />
// endpatch<br />
<br />
Result := Stream.Read(Ptr^, WillRead) div Size; // Daten auslesen und die Anzahl der Blocks zurückgeben<br />
end;<br />
<br />
function StreamRWWrite( context: PSDL_RWops; Ptr: Pointer; size: Integer; <br />
num: Integer ): Integer; {$IFNDEF ___GPC___}cdecl;{$ENDIF}<br />
var<br />
Stream: TStream;<br />
begin <br />
Stream := Tstream(context^.unknown.data1); // Den Stream aus dem RWops holen<br />
if Stream = nil then<br />
begin<br />
Result := -1;<br />
Exit;<br />
end;<br />
Result := Stream.Write(Ptr^, Size*num) div Size; // Die Daten schreiben und die Anzahl der geschriebenen Blocks zurückgeben<br />
end;<br />
<br />
function StreamRWClose( context: PSDL_RWops ): Integer; {$IFNDEF ___GPC___}cdecl;{$ENDIF}<br />
var<br />
Stream: TStream;<br />
begin<br />
Stream := Tstream(context^.unknown.data1); // Den Stream aus dem RWops holen<br />
if Stream = nil then<br />
begin<br />
Result := -1;<br />
Exit;<br />
end;<br />
Stream.Destroy; // Speicher des Streams freigeben<br />
context^.unknown.data1 := nil; // Im RWops den Pointer leeren<br />
Result := 0; // 0 zurückgeben<br />
end;<br />
<br />
function StreamToRWops(Stream: TStream): TSDL_RWops;<br />
begin<br />
Result.type_ := 2; // Mir ist die Bedeutung der type_-Variable nicht ganz klar,<br />
// aber ich setze sie auf 2, wie im dynamischen Teil für Unknown <br />
Result.unknown.data1 := Stream; // Pointer auf den Stream setzen<br />
Result.seek := @StreamRWSeek; // Und noch die Funktionen zuweisen<br />
Result.read := @StreamRWRead;<br />
Result.write := @StreamRWWrite;<br />
Result.close := @StreamRWClose;<br />
end; <br />
<br />
procedure RWopsToStream(RW: PSDL_RWops; Stream: Tstream);<br />
var<br />
Buf: Pointer; // Pufferspeicher zum Lesen<br />
Read: Integer; // Anzahl der gelesenen Bytes<br />
begin<br />
if Stream = nil then Exit; // Sicherheitsbedingungen<br />
if RW = nil then Exit;<br />
<br />
GetMem(Buf, 1024); // Speicher für Puffer reservieren<br />
try<br />
Read := RW^.read(RW, Buf, 1, 1024); // Ersten Datenblock auslesen<br />
while Read > 0 do // Solange Auslesen, bis keine Daten mehr gelesen wurden<br />
begin<br />
Stream.Write(Buf^, Read); // Ausgelesene Daten in Stream schreiben<br />
Read := RW^.read(RW, Buf, 1, 1024); // Neue Daten auslesen<br />
end;<br />
finally<br />
FreeMem(Buf); // Puffer wieder freigeben.<br />
end;<br />
end;</source> <br />
<br />
{{Hinweis|Das @ vor den Funktionen ist nur im FPC nötig, in Delphi kann man sie sich glaube ich sparen. Auch die cdecl-Direktive hinter den Funktionen für die RWops ist für den FPC wichtig und sollte bei ihm übernommen werden, da es sonst Typenfehler bei den Zuweisungen gibt.}}<br />
Mit den obigen Funktionen kann man eigentlich alles mit RWops machen, was man auch mit Streams machen kann. Es ist dabei völlig gleich, ob ein TMemoryStream oder ein TFileStream übergeben wird, solange es von TStream abstammt. Was man jetzt auch noch machen könnte, wäre eine Nachfolger-Klasse zu TStream, mit der man auf RWops zugreifen kann. Aber wer dann auf die Idee kommt, die in einen RWops zu packen, den... naja egal ;)<br />
<br />
= Das Ende naht =<br />
So, das war es erstmal. Fünf (korrigiere: sechs) Seiten reichen ja auch für's erste. Vielleicht fällt mir später noch mehr zu dem Thema ein, sodass es für noch ein Tutorial reicht (aber ich glaube, ich habe jetzt die RWops eigentlich ausgereizt obwohl man auch TCP-Verbindungen darüber laufen lassen könnte, aber dafür sollte man sich dann doch besser ne eigene Klasse machen).<br />
<br />
Bei Kommentaren, Kritik, Hinweisen, Morddrohungen oder Informationen einfach an mich im Forum wenden.<br />
:[[Benutzer:Lord Horazont|Lord Horazont]]<br />
<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial SDL Einstieg]]|-}}<br />
<br />
[[Kategorie:Tutorial|SDL_RWops]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Tutorial_MultiTexturing&diff=23838Tutorial MultiTexturing2009-07-06T14:24:56Z<p>Lord Horazont: /* Schritt 2 - Texturstufen festlegen */ Geht nur bis GL_TEXTURE31, nicht bis GL_TEXTURE32</p>
<hr />
<div>=Tutorial Multitexturing=<br />
==Einleitung==<br />
Mit der Markteinführung von NVidias Riva TNT (TwiNTexel Engine) hat auch das Multitexturing im PC-Grafikkartenbereich Einzug gefunden, und wird seit einigen Jahren von allen neu entwickelten Grafikchips unterstützt. Beim Multitexturing können den einzelnen Textureinheiten der Grafikkarte unterschiedliche Texturen zugeordnet werden, die dann später in einem Durchgang auf ein Polygon tapeziert bzw. verknüpft werden. Vor der Einführung dieser Technik musste man das Polygon im ersten Durchgang mit Textur A rendern und in einem zweiten Durchgang Blending aktivieren und es mit Textur B zeichnen. Dadurch verdoppelte sich nicht nur die Anzahl der Polygone, sondern auch die Verknüpfungsmöglichkeiten des Blendings waren weit weniger nützlich als die beim Multitexturing.<br />
<br />
Die Anzahl der Textureinheiten (TMU = Textur Mapping Unit) gibt an, wie viele Texturen in einem Durchgang auf eine Textur gezeichnet werden können. Bei einer GeForce4 sind dies z.&nbsp;B. 4 und bei den Kyro-Karten von PowerVR sogar bis zu 8 (obwohl man eher selten 8 Texturstufen benötigt). Besitzt eine Grafikkarte nun weniger TMUs als man gleichzeitig Texturen auf ein Polygon zeichnen möchte, dann wird die Arbeit in mehreren Durchgängen erledigt. Ein Grafikchip der nur 2 TMUs besitzt, braucht also 3 Durchgänge um ein Polygon mit 6 Texturen zu zeichnen.<br />
<br />
Multitexturing ist heute besonders in Spielen ein unverzichtbares Mittel geworden. Dabei werden zwei Anwendungsmöglichkeiten häufig genutzt.Zum einen das [[Lightmap]], bei dem Schattenwurf und Lichteinfall auf ein Polygon vorberechnet und in einer separaten Textur abgelegt werden, sowie das [[Detailmapping|Detailtexturing]], bei dem die Basistextur mit einem Muster überzogen wird, das kleine Details simulieren soll.<br />
<br />
Im Falle des Beispielprogramms werden diese beiden Texturen mit Hilfe des Multitexturings mit der Basistextur verknüpft:<br />
<br />
<div align="center"><br />
{|{{Prettytable}}<br />
!Schritt1<br />
|[[Bild:Multitex01.jpg|framed|Basistextur]]<br />
| +<br />
|[[Bild:Multitex02.jpg|framed|Lightmap]]<br />
| =<br />
|[[Bild:Multitex04.jpg|framed|Zwischenergebnis]]<br />
|-<br />
!Schritt2<br />
|[[Bild:Multitex04.jpg|framed|Zwischenergebnis]]<br />
| +<br />
|[[Bild:Multitex03.jpg|framed|Detailtextur]]<br />
| =<br />
|[[Bild:Multitex05.jpg|framed|Endergebnis]]<br />
|-<br />
|}<br />
</div><br />
<br />
==Schritt 1 - Hardware auf Multitexturing-Fähigkeit prüfen==<br />
Bevor man das Multitexturing nutzen kann, sollte man natürlich erst einmal prüfen ob dies auch von der Hardware unterstützt wird. Es wird zwar kaum noch Grafikkarten geben, bei denen dies nicht der Fall ist, aber um sicherzugehen ist dieser Schritt doch nötig.<br />
<br />
Dabei hat man zwei einfache Möglichkeiten. Entweder man prüft, ob die Erweiterung GL_ARB_MULTITEXTURE von der Grafikkarte untersützt wird, oder man liest die Anzahl der TMUs mit dem Befehl [[glGetIntegerv]] und dem Parameter GL_MAX_TEXTURE_UNITS_ARB aus [06].Ist dieser Wert kleiner als zwei, so wird das Multitexturing nicht unterstützt.<br />
<br />
<source lang="pascal">[01] procedure TForm1.GLInit;<br />
[02] var<br />
[03] TMUs : Integer;<br />
[04] begin<br />
[05] ActivateRenderingContext(FDC, FRC);<br />
[06] glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB, @TMUs);<br />
[07] if TMUs < 2 then<br />
[08] begin<br />
[09] ShowMessage('Sorry!Your card doesn''t support Multitexturing');<br />
[10] Close;<br />
[11] end;<br />
...<br />
[12] end;</source><br />
<br />
==Schritt 2 - Texturstufen festlegen==<br />
Hat man sich nun von den Multitexture-Fähigkeiten der Grafikkarte überzeugt, kann man endlich damit beginnen, die einzelnen Texturen ihren Texturstufen zuzuordnen. Auf die Texturstufen wird in OpenGL mit Hilfe der Konstanten GL_TEXTURE0...GL_TEXTURE31 zugegriffen.<br />
<br />
<source lang="pascal">[01] procedure DrawRoom;<br />
[02] begin<br />
...<br />
[03] glActiveTexture(GL_TEXTURE1);<br />
[04] if TSActive[0] then<br />
[05] begin<br />
[06] glEnable(GL_TEXTURE_2D);<br />
[07] Texture[2].Bind;<br />
[08] glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);<br />
[09] end<br />
[10] else<br />
[11] glDisable(GL_TEXTURE_2D);<br />
<br />
[12] glActiveTexture(GL_TEXTURE2);<br />
[13] if TSActive[1] then<br />
[14] begin<br />
[15] glEnable(GL_TEXTURE_2D);<br />
[16] Texture[4].Bind;<br />
[17] glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);<br />
[18] end<br />
[19] else<br />
[20] glDisable(GL_TEXTURE_2D);<br />
<br />
[21] glActiveTexture(GL_TEXTURE0);<br />
[22] glEnable(GL_TEXTURE_2D);<br />
[23] glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);</source><br />
<br />
Mit der Funktion {{INLINE_CODE|glActiveTexture(target : Cardinal)}} wird die im Parameter target angegebene Texturstufe aktiviert [03],[12]&[21].Von nun an beziehen sich alle Befehle, die etwas an einer Textur verändern (Binden von Texturen, Blending, Alphamasking, Veränderung der Texturmatrix, etc.) auf diese aktivierte Texturstufe, und zwar solange, bis auf ihr das Texturing deaktiviert wird oder die Texturstufe gewechselt wird.<br />
<br />
Um nun die Texturstufe zu aktiveren, muss wie gewohnt mit dem Befehl {{INLINE_CODE|glEnable}} und dem Parameter GL_TEXTURE_2D das 2D-Texturing aktiviert werden. Dies muss für jede Texturstufe geschehen, die genutzt wird [06],[15]&[22]. Möchte man eine Texturstufe ausschalten, so wählt man diese aus und nutzt {{INLINE_CODE|glDisable}} mit dem Parameter GL_TEXTURE_2D [11]&[20].<br />
<br />
Bevor nun die Polygone gezeichnet werden, muss für jede Texturstufe die Art der Verknüpfung mit den anderen Texturstufen festgelegt werden. Dies geschieht mit dem Befehl {{INLINE_CODE|[[glTexEnv]]i}}, der als Parameter das Ziel des Parameters (in unseren Falle GL_TEXTURE_ENV, was bedeutet, das als Grundwerte die Ausgabewerte der vorherigen Texturstufe genutzt werden), den Parameter selbst (auch GL_TEXTURE_ENV) und die Art der Verknüpfung erhält.<br />
<br />
Für die Basistextur wird dieser Parameter auf GL_REPLACE gesetzt [23], alles was vor dieser Textur kam wird also überschrieben. Der Parameter GL_MODULATE für die Lightmap [08] und die Detailtextur [17] geben an, das deren Farbwerte mit den vorherigen Werten multipliziert werden. Neben diesen Verknüpfungen gibt es noch etliche andere, die jedoch weniger oft, oder für andere Anwendungen wie z.&nbsp;B. BumpMapping verwendet werden.<br />
<br />
==Schritt 3 - Übergabe der Texturkoordinaten==<br />
Nachdem die Texturstufen nun vorbereitet wurden, müssen für diese noch die Texturkoordinaten festgelegt werden. Wurde dies vorher mit der Funktion glTexCoord2f(s,t : single) erledigt, so wird nun die an das Multitexturing angepasste Variante glMultiTexCoord2f(target : cardinal;s,t : single) genutzt. Im Parameter target wird die Texturstufe gewählt und in s bzw. t wie gewohnt die Texturkoordinaten übergeben:<br />
<br />
<source lang="pascal">[01] glActiveTexture(GL_TEXTURE1);<br />
[02] Texture[6].Bind;<br />
<br />
[03] glActiveTexture(GL_TEXTURE0);<br />
[04] Texture[0].Bind;<br />
<br />
[05] glBegin(GL_QUADS);<br />
[06] glMultiTexCoord2f(GL_TEXTURE0, 0, 0);<br />
[07] glMultiTexCoord2f(GL_TEXTURE1, 0, 0);<br />
[08] glMultiTexCoord2f(GL_TEXTURE2, 0, 0);<br />
[09] glVertex3f(px, py, pz);<br />
[10] glMultiTexCoord2f(GL_TEXTURE0, 0, 2);<br />
[11] glMultiTexCoord2f(GL_TEXTURE1, 0, 1);<br />
[12] glMultiTexCoord2f(GL_TEXTURE2, 0, 8);<br />
[13] glVertex3f(px, py, pz + pLength);<br />
[14] glMultiTexCoord2f(GL_TEXTURE0, 2, 2);<br />
[15] glMultiTexCoord2f(GL_TEXTURE1, 1, 1);<br />
[16] glMultiTexCoord2f(GL_TEXTURE2, 8, 8);<br />
[17] glVertex3f(px + pWidth, py, pz + pLength);<br />
[18] glMultiTexCoord2f(GL_TEXTURE0, 2, 0);<br />
[19] glMultiTexCoord2f(GL_TEXTURE1, 1, 0);<br />
[20] glMultiTexCoord2f(GL_TEXTURE2, 8, 0);<br />
[21] glVertex3f(px + pWidth, py, pz);<br />
[22] glEnd;<br />
<br />
[23] glActiveTexture(GL_TEXTURE2);<br />
[24] if TSActive[1] then<br />
[25] begin<br />
[26] glEnable(GL_TEXTURE_2D);<br />
[27] Texture[4].Bind;<br />
[28] glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);<br />
[29] end<br />
[30] else<br />
[31] glDisable(GL_TEXTURE_2D);<br />
<br />
[32] glActiveTexture(GL_TEXTURE0);<br />
[33] glEnable(GL_TEXTURE_2D);<br />
[34] glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);</source><br />
<br />
Wie in dieser, aus dem Beispielprogramm entnommenen Prozedur sichtbar, werden vor dem Zeichnen des Quads zuerst die Texturen an die einzelnen Texturstufen gebunden. In [01]-[02] wird die Lightmap an die Texturstufe 1 gebunden und in [03]-[04] wird die Basistextur für diesen Quad an Texturstufe 0 gebunden. Da die an Texturstufe 2 gebundene Detailtextur bereits vorher definiert wurde und sich für diesen Quad nicht ändert, muss diese hier nicht gebunden werden.<br />
<br />
Danach werden nur noch die Texturkoordinaten an jede genutzte Texturstufe für jeden Eckpunkt des Polygons übergeben um es mit den an die Stufen gebundenen Texturen zu überziehen.<br />
<br />
==Das Beispielprogramm==<br />
Das von mir zu diesem Tutorial geschriebene Beispielprogramm benutzt 3 Texturstufen. Texturstufe 0 für die Basistextur, Texturstufe 1 für die Lightmap und Texturstufe 2 für die Detailtextur. Die Texturstufen 1 und 2 können mit den entsprechenden Ziffern auf der Tastatur aktiviert bzw. deaktiviert werden. Mit der Maus wird der Blickwinkel verändert und mit Cursor Hoch/Runter kann man sich bewegen.<br />
<br />
Die Lightmaps wurden von mir in einem herkömmlichen Zeichenprogramm erstellt, es steckt also kein Radiosity-Renderer dahinter.<br />
<br />
Das Programm wurde unter Delphi 6.0 geschrieben, sollte jedoch ab Delphi 4.0 kompilierbar sein.<br />
Das Programm läuft natürlich nur auf Grafikkarten, die Multitexturing in der Hardware unterstützen. Dies ist jedoch seit der Riva TNT bei allen Karten der Fall.<br />
<br />
Zu guter Letzt noch ein Screenshot aus dem Beispielprogramm:<br />
<br />
[[Bild:Multitex06.jpg|center]]<br />
<br />
Autor: [[Benutzer:Sascha_Willems|Sascha Willems]]<br />
<br />
==Download==<br />
[http://www.delphigl.de/files/multitex.zip Das Multitexturingdemo (inklusive Quellcode) herunterladen]<br />
<br />
{{TUTORIAL_NAVIGATION|[[Tutorial Bumpmaps mit Blender]]|[[Tutorial StencilSpiegel]]}}<br />
<br />
[[Kategorie:Tutorial|Multitexturing]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=DGL_Treffen/2009/Organisation&diff=23822DGL Treffen/2009/Organisation2009-06-27T13:42:52Z<p>Lord Horazont: </p>
<hr />
<div>Für den Sommer 2009 planen einige Unverbesserliche einen 1Wochen Urlaub an der Küste.<br />
<br />
;Thema: "Nerds On Tour." ;-)<br />
<br />
<br />
==Wo?==<br />
Dieses [http://www.cofman.de/detail.php?houseid=29152 Ferienhaus] in Süddänemark an der Ostseeküste.<br />
<br />
==Wann?==<br />
*Anreise: Sa. 25.07.2009<br />
*Abreise: Sa. 01.08.2009<br />
<br />
==Was kostet das?==<br />
Je mehr Personenübernachtungen desto billiger. Dank einem generösen Angebot von Phobeus, sind die Übernachtungskosten nicht höher als '''25€/Person'''.<br />
<br />
'''7Nächte kosten entsprechend max. 175€'''<br />
<br />
Verpflegung und Anreise sind da aber nicht mit dabei.<br />
<br />
==Wer kommt mit?==<br />
Hier eine vorläufige Teilnehmerliste. Es sind insgesamt 8 Betten im Haus.<br />
{|{{Prettytable_B1}}<br />
!Teilnehmer<br />
!Fest<br />
!Tage<br />
(7=max)<br />
!Anreise<br />
!Kommentar<br />
|-<br />
|Flash<br />
|X<br />
|7<br />
|PKW <br />
|(2,5 Plätze im PKW frei.)<br />
|-<br />
|i0n0s<br />
|X<br />
|7<br />
|Zug in DE, nach Dänemark ungewiss<br />
|<br />
|-<br />
|Lord Horazont<br />
|X<br />
|7<br />
|Mit Phobeus/Flash<br />
|<br />
|-<br />
|Phobeus<br />
|<br />
|<br />
|<br />
|<br />
|-<br />
|Aya<br />
|<br />
|<br />
|<br />
|<br />
|-<br />
|Frase<br />
|<br />
|<br />
|<br />
|<br />
|-<br />
|''frei''<br />
|<br />
|<br />
|<br />
|<br />
|-<br />
|''frei''<br />
|<br />
|<br />
|<br />
|<br />
|-<br />
|}<br />
<br />
==Wie kommt man hin?==<br />
Treffpunkt wird Hamburg sein. Vorraussichtlich am Hauptbahnhof oder bei Phobeus.<br />
Von Hamburg aus fahren wir mit PKWs (Flash, Phobeus?) zum Ziel.<br />
<br />
Flash reist mit dem PKW aus Chemnitz an. Wer zwischen Chemnitz und Hamburg wohnt (z.B. Dresden, Leipzig, Magdeburg, Berlin) kann kostengünstig mitfahren.<br />
<br />
==Was gibts zu erleben?==<br />
*DGLer in echt ;)<br />
*Baden und Grillen<br />
*Eine Minni-LAN<br />
*Live Projektvorstellung<br />
*Däninnen *hust* ;)</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=DGL_Treffen/2009/Organisation&diff=22696DGL Treffen/2009/Organisation2009-02-22T16:53:26Z<p>Lord Horazont: </p>
<hr />
<div>Für den Sommer 2009 planen einige Unverbesserliche einen 1Wochen Urlaub an der Küste.<br />
<br />
;Thema: "Nerds On Tour." ;-)<br />
<br />
<br />
==Wo?==<br />
Ort ist noch nicht 100% klar.<br />
Irgendwo zwischen "Neustadt in Holstein" und "Boltenhagen". Beides ist Ostsee.<br />
<br />
==Wann?==<br />
*Anreise: Sa. 26.07.2009<br />
*Abreise: So. 02.08.2009 <br />
<br />
==Wer kommt mit?==<br />
Hier eine vorläufige Teilnehmerliste.<br />
*Flash<br />
*i0n0s<br />
*Lord Horazont</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Normalisierte_Ger%C3%A4tekoordinate&diff=22131Normalisierte Gerätekoordinate2008-09-05T19:14:23Z<p>Lord Horazont: Bereich korrigiert: Vorher [0..1] (inkl.), jetzt [-1..1] (inkl).</p>
<hr />
<div>'''Normalisierte Gerätekoordinaten''' (engl. normalized device coordinates, auch '''NDC''') bilden den Bereich eines Ausgabemediums (in Verbindung mit OpenGL eigentlich immer der Viewport auf einem Fenster) auf die Koordinatenspanne -1..1 auf jeder Achse ab. Man kann sie ähnlich wie auch die [[Normale]] eines Vektors dann beliebig skalieren.<br />
<br />
In der Praxis besteht der Hauptnutzen darin, dass man weitestgehend Auflösungsunabhängige Koordinaten verwenden kann (solange das Seitenverhältnis gleich bleibt). Das ist vorallem bei Vektorgrafiken, die beliebig skalierbar sein sollten, wichtig.<br />
<br />
Nehmen wir an, wir haben einen Viewport mit einer Fläche von 1024 Pixeln in der Breite und 768 Pixeln in der Höhe. Ein Punkt P mit den absoluten, also nicht normalisierten, Koordinaten P(350/210) würde in normalisieren Gerätekoordinaten gerundet P(-0.32/-0.59) ergeben. Diese Koordinaten könnte man jetzt auf einen Viewport mit einer Fläche von 800x600 Pixeln projizieren, indem man die normalisieren Gerätekoordinaten ähnlich wie beim Skalieren von Vektoren mit den Ausmaßen des Viewports multipliziert. In diesem Fall ergäbe das gerundet: P(273/164).</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Renderkontext&diff=21984Renderkontext2008-08-04T23:57:07Z<p>Lord Horazont: Hinweis auf das Problem mit erweiterungsabhängigen Pixelformaten hinzugefügt</p>
<hr />
<div>{{Template:Unvollständig|Hier fehlen noch einige Angaben zur Erzeugung und Verwendung. Zudem sollte noch mal jemand drüberschauen, ob die angegebenen Informationen zu den enthaltenen Info's korrekt sind.}}<br />
<br />
Der [[Renderkontext]] ist eine Beschreibung verschiedener Informationen, die eine Zeichenfläche und die zu dieser zugeordneten Ressourcen enthält. Diese Informationen enthalten dabei Angaben zum verwendeten [[Pixelformat]], den Abmessungen der Zeichenfläche (Höhe, Breite, Aspekt-Ratio), sowie Angaben zur Lage der Datenpuffer für [[Farbpuffer|Farb-]], [[Stencil-Buffer|Stencil-]], [[Tiefenpuffer|Tiefen-]] und Transparenz-Informationen. Zudem enthält der Render-Kontext allgemeine Statusinformationen wie die Art und Dicke zu zeichnender Linien, die zum Zeichnen zu verwendenden Texturen sowie andere Angaben, die für das Zeichnen notwendig sind.<br />
<br />
== Anlegen eines Renderkontext ==<br />
{{Template:Hinweis|Dieser Abschnitt beschreibt das Vorgehen unter Windows. Unter Linux können Details abweichen (Wenn jemand mit Linux mehr Erfahrung hat, wäre eine Teilung und differenziertere Beschreibung erwünscht)}}<br />
<br />
Um überhaupt mit OpenGL arbeiten zu können, muss ein Renderkontext zuerst einmal angelegt werden. Dabei muss man einen vorhandenen [[Gerätekontext]] (DC) zugrunde legen. Ein Renderkontext spannt sich dabei immer über den gesamten Gerätekontext und damit über den gesamten Clientbereich des Fensters, zu dem der Gerätekontext gehört.<br />
<br />
Beim Anlegen werden sowohl auf der Grafikkarte als auch im RAM abhängig von den Einstellungen des Gerätekontextes ([[Pixelformat]], Höhe, Breite...) diverse Speicherbereiche reserviert, in dem später dann [[Farbpuffer|Farb-]] oder [[Tiefenpuffer]], die Informationen der Statemachine, Texturen, Displaylisten oder VBOs abgelegt werden (natürlich werden auch einige Bereiche erst später dynamisch reserviert).<br />
<br />
=== Problem mit erweiterungsabhängigen Pixelformaten ===<br />
Ein bekanntes Problem bei der Tatsache, dass ein Renderkontext an einen Gerätekontext gebunden ist und dass man das Pixelformat eines Gerätekontextes nur ein einziges Mal festlegen kann, ist, dass man für einige Einstellungen im Pixelformat erst wissen muss, ob die passenden Erweiterungen zur verfügung stehen. Um das zu ermitteln muss man allerdings wiederum einen Renderkontext erstellt haben.<br />
<br />
Um dieses Problem zu umgehen, wird häufig ein Testgerätekontext auf einem versteckten Fenster erstellt, auf dem dann ein Testrenderkontext erstellt wird, wo man die Erweiterungen abfragt.<br />
<br />
Diese Problematik tritt häufig bei [[Multisampling]] ([[Antialiasing]]) auf, da dies eine entsprechende Erweiterung beziehungsweise die Fähigkeit der Hardware benötigt und im Pixelformat festgelegt sein muss.<br />
<br />
== Einflussnahme auf den Renderkontext ==<br />
Bei jedem Aufruf einer Funktion von OpenGL, die an der Art und Weise, wie etwas gezeichnet werden soll, oder die selbst etwas zeichnet, nimmt OpenGL Veränderungen an den im Renderkontext hinterlegten Angaben vor. Diese Veränderungen spiegeln dabei die auszuführende Operation (z.B. Ändern der Linienstärke beim Zeichnen mit GL_LINES) in Form einer Status-Änderung wieder (Ändern des Wertes, der die zu verwendende Linien-Breite angibt).<br />
<br />
=== Renderkontextübergreifende Daten ===<br />
OpenGL bietet (zumindest unter Windows) mittels [[wglShareLists]] die Möglichkeit, kontextspezifische Objekte wie Texturen oder Displaylisten übergreifend über zwei Renderkontexte zu verwenden. Dies ist zum Beispiel im Falle einer Editoranwendung hilfreich, die mehrere Viewports hat, die aus technischen Gründen nicht in einem Renderkontext liegen können. Für jeden Viewport alle Texturen und VBOs beziehungsweise Displaylisten zu laden, wäre natürlich viel zu großer Aufwand. Statt dessen benutzt man etwas wie wglShareLists, um die Objekte nur einmal laden zu müssen.<br />
<br />
=== Das Thread-Problem ===<br />
Ein Problem, auf das man im Zusammenhang mit dem Laden von Daten in den Renderkontext später eventuell treffen kann ist, dass man einen Renderkontext nur so lange verändern kann, wie man sich in demjenigen Thread befindet, in dem der Kontext erstellt wurde. Das heißt, dass man Texturen oder andere OpenGL-Objekte nicht aus einem nebenläufigen Lade-Thread heraus erstellen kann. Wohl aber kann man die Daten erstmal in den RAM laden und dann an den Hauptthread weiterreichen, der sie dann an die Grafikkarte schickt.<br />
<br />
== Siehe auch ==<br />
[[Tutorial_Quickstart]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Renderkontext&diff=21983Renderkontext2008-08-04T23:53:03Z<p>Lord Horazont: Ein bisschen erweitert, umgestellt und verbessert (hoffe ich ;) )</p>
<hr />
<div>{{Template:Unvollständig|Hier fehlen noch einige Angaben zur Erzeugung und Verwendung. Zudem sollte noch mal jemand drüberschauen, ob die angegebenen Informationen zu den enthaltenen Info's korrekt sind.}}<br />
<br />
Der [[Renderkontext]] ist eine Beschreibung verschiedener Informationen, die eine Zeichenfläche und die zu dieser zugeordneten Ressourcen enthält. Diese Informationen enthalten dabei Angaben zum verwendeten [[Pixelformat]], den Abmessungen der Zeichenfläche (Höhe, Breite, Aspekt-Ratio), sowie Angaben zur Lage der Datenpuffer für [[Farbpuffer|Farb-]], [[Stencil-Buffer|Stencil-]], [[Tiefenpuffer|Tiefen-]] und Transparenz-Informationen. Zudem enthält der Render-Kontext allgemeine Statusinformationen wie die Art und Dicke zu zeichnender Linien, die zum Zeichnen zu verwendenden Texturen sowie andere Angaben, die für das Zeichnen notwendig sind.<br />
<br />
== Anlegen eines Renderkontext ==<br />
{{Template:Hinweis|Dieser Abschnitt beschreibt das Vorgehen unter Windows. Unter Linux können Details abweichen (Wenn jemand mit Linux mehr Erfahrung hat, wäre eine Teilung und differenziertere Beschreibung erwünscht)}}<br />
<br />
Um überhaupt mit OpenGL arbeiten zu können, muss ein Renderkontext zuerst einmal angelegt werden. Dabei muss man einen vorhandenen [[Gerätekontext]] (DC) zugrunde legen. Ein Renderkontext spannt sich dabei immer über den gesamten Gerätekontext und damit über den gesamten Clientbereich des Fensters, zu dem der Gerätekontext gehört.<br />
<br />
Beim Anlegen werden sowohl auf der Grafikkarte als auch im RAM abhängig von den Einstellungen des Gerätekontextes ([[Pixelformat]], Höhe, Breite...) diverse Speicherbereiche reserviert, in dem später dann [[Farbpuffer|Farb-]] oder [[Tiefenpuffer]], die Informationen der Statemachine, Texturen, Displaylisten oder VBOs abgelegt werden (natürlich werden auch einige Bereiche erst später dynamisch reserviert).<br />
<br />
== Einflussnahme auf den Renderkontext ==<br />
Bei jedem Aufruf einer Funktion von OpenGL, die an der Art und Weise, wie etwas gezeichnet werden soll, oder die selbst etwas zeichnet, nimmt OpenGL Veränderungen an den im Renderkontext hinterlegten Angaben vor. Diese Veränderungen spiegeln dabei die auszuführende Operation (z.B. Ändern der Linienstärke beim Zeichnen mit GL_LINES) in Form einer Status-Änderung wieder (Ändern des Wertes, der die zu verwendende Linien-Breite angibt).<br />
<br />
=== Renderkontextübergreifende Daten ===<br />
OpenGL bietet (zumindest unter Windows) mittels [[wglShareLists]] die Möglichkeit, kontextspezifische Objekte wie Texturen oder Displaylisten übergreifend über zwei Renderkontexte zu verwenden. Dies ist zum Beispiel im Falle einer Editoranwendung hilfreich, die mehrere Viewports hat, die aus technischen Gründen nicht in einem Renderkontext liegen können. Für jeden Viewport alle Texturen und VBOs beziehungsweise Displaylisten zu laden, wäre natürlich viel zu großer Aufwand. Statt dessen benutzt man etwas wie wglShareLists, um die Objekte nur einmal laden zu müssen.<br />
<br />
=== Das Thread-Problem ===<br />
Ein Problem, auf das man im Zusammenhang mit dem Laden von Daten in den Renderkontext später eventuell treffen kann ist, dass man einen Renderkontext nur so lange verändern kann, wie man sich in demjenigen Thread befindet, in dem der Kontext erstellt wurde. Das heißt, dass man Texturen oder andere OpenGL-Objekte nicht aus einem nebenläufigen Lade-Thread heraus erstellen kann. Wohl aber kann man die Daten erstmal in den RAM laden und dann an den Hauptthread weiterreichen, der sie dann an die Grafikkarte schickt.<br />
<br />
== Siehe auch ==<br />
[[Tutorial_Quickstart]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Tutorial_Wassereffekt&diff=21966Tutorial Wassereffekt2008-07-17T09:16:45Z<p>Lord Horazont: Anpassung an die neuen Bilder</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. Wer schon etwas häufiger mit OpenGL gearbeitet hat, der kann sich vielleicht schon denken, wie man das am Besten machen 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 />
= 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>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Tutorial_Wassereffekt&diff=21965Tutorial Wassereffekt2008-07-17T09:16:15Z<p>Lord Horazont: Rechtschreibfehler korrigiert, danke an LittleDave</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. Wer schon etwas häufiger mit OpenGL gearbeitet hat, der kann sich vielleicht schon denken, wie man das am Besten machen 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 />
= 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. Man sieht natürlich bis jetzt nur eine reflektierende Oberfläche (Rotes Wireframe), die mit ein bisschen Fantasie eine Wasseroberfläche sein könnte. Die Shader werden das ganze noch ein wenig verschönern.<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>Lord Horazonthttps://wiki.delphigl.com/index.php?title=glBlendFunc&diff=21920glBlendFunc2008-07-08T08:34:05Z<p>Lord Horazont: Kurzbeschreibung geändert</p>
<hr />
<div>{{Excellent}}<br />
= glBlendFunc =<br />
<br />
<br />
<br />
== Name ==<br />
'''glBlendFunc''' - Bestimmt das Verhalten beim Schreiben von Fragmenten (Pixeln) in den Framebuffer<br />
<br />
<br />
<br />
== Delphi-Spezifikation ==<br />
procedure '''glBlendFunc'''(''sfactor'' : TGLEnum; ''dfactor'': TGLEnum);<br />
<br />
<br />
<br />
== Parameter ==<br />
{|border=1 rules=all<br />
!'''sfactor'''<br />
|Gibt an, wie der Rot-, Grün-, Blau- und Alphaanteil des Quell-Blendfaktors berechnet wird. Folgende symbolische Konstanten sind erlaubt : '''GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR_EXT, GL_ONE_MINUS_CONSTANT_COLOR_EXT, GL_CONSTANT_ALPHA_EXT, GL_ONE_MINUS_CONSTANT_ALPHA_EXT''' und '''GL_SRC_ALPHA_SATURATE'''. <br><br />
Vorgabewert ist '''GL_ONE'''.<br />
|-<br />
!'''dfactor'''<br />
|Gibt an, wie der Rot-, Grün-, Blau- und Alphaanteil des Ziel-Blendfaktors berechnet wird. Folgende symbolische Konstanten sind erlaubt : '''GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA''' und '''GL_ONE_MINUS_CONSTANT_ALPHA,'''.<br><br />
Vorgabewert ist '''GL_ZERO'''.<br />
|}<br />
<br />
== Beschreibung ==<br />
Im RGBA-Modus, können Pixel so gezeichnet werden, dass anhand einer Funktion die ankommenden RGBA-Werte (Quelle) mit dem bereits im Framepuffer befindlichen RGBA-Werten (Ziel) gemischt werden. Mischung ist als Vorgabe deaktiviert, kann jedoch mit [[glEnable]]/[[glEnable|glDisable]] und dem Token '''GL_BLEND''' gesteuert werden.<br />
<br />
'''glBlendFunc''' beschreibt die Funktion der Mischung, sofern aktiviert. ''sfactor'' gibt dabei an welche Methode genutzt wird um die Quellfarbkomponenten zu skalieren. ''dfactor'' gibt an, welche der Methode genutzt wird um die Zielfarbkomponenten zu skalieren. Die möglichen Methoden werden in der folgenden Tabelle beschrieben, wobei jede insgesamt vier Skalierungsfaktoren besitzt, für die vier Komponenten einer RGBA-Farbe.<br />
<br />
In der Tabelle (und allen folgenden Gleichungen), werden Quell- bzw. Zielfarbkomponenten als (Rs, Gs, Bs, As) und (Rd, Gd, Bd, Ad) dargestellt. Diese Komponenten besitzen Ganzzahlwerte, die zwischen 0 und (kR, kG, kB, kA) liegen, wobei <br />
<br />
kc = 2^mc - 1;<br />
<br />
und (mR, mG, mB, mA) gleich der Zahl der Rot-, Grün-, Blau- und Alphabitflächen ist. (Beispiel: Wenn man 8 Bit für eine Farbe (z.B. Rot) hat, dann ist kR = 2^8 - 1 = 255.)<br />
<br />
Konstante Farbkomponenten werden als (Rc, Gc, Bc, Ac) angegeben und werden nicht mit (kR, kG, kB, kA) skaliert, da sie bereits im Intervall [0,1] liegen.<br />
<br />
Quell- und Zielskalierungsfaktoren werden als (sR, sG, sB, sA) und (dR, dG, dB, dA) angegeben. Die in der Tabelle beschriebenen Skalierungsfaktoren, angegeben als (fR, fG, fB, fA) repräsentieren entweder Quell- oder Zielfaktoren. Alle Skalierungsfaktoren liegen im Intervall [0,1].<br />
<br />
<div align="center"><br />
{| {{Prettytable_B1}}<br />
|'''Parameter'''<br />
|'''(fR, fG, fB, fA)'''<br />
|-<br />
|GL_ZERO <br />
|(0,0,0,0)<br />
|-<br />
|GL_ONE <br />
|(1,1,1,1)<br />
|-<br />
|GL_SRC_COLOR <br />
|(Rs/kR, Gs/kG, Bs/kB, As/kA)<br />
|-<br />
|GL_ONE_MINUS_SRC_COLOR <br />
|(1,1,1,1) - (Rs/kR, Gs/kG, Bs/kB, As/kA)<br />
|-<br />
|GL_DST_COLOR <br />
|(Rd/kR, Gd/kG, Bd/kB, Ad/kA)<br />
|-<br />
|GL_ONE_MINUS_DST_COLOR <br />
|(1,1,1,1) - (Rd/kR, Gd/kG, Bd/kB, Ad/kA)<br />
|-<br />
|GL_SRC_ALPHA <br />
|(As/kA, As/kA, As/kA, As/kA)<br />
|-<br />
|GL_ONE_MINUS_SRC_ALPHA <br />
|(1,1,1,1) - (As/kA, As/kA, As/kA, As/kA)<br />
|-<br />
|GL_DST_ALPHA <br />
|(Ad/kA, Ad/kA, Ad/kA, Ad/kA)<br />
|-<br />
|GL_ONE_MINUS_DST_ALPHA <br />
|(1,1,1,1) - (Ad/kA, Ad/kA, Ad/kA, Ad/kA)<br />
|-<br />
|GL_SRC_ALPHA_SATURATE <br />
|(i,i,i,1)<br />
|-<br />
|GL_CONSTANT_COLOR_EXT <br />
|(Rc, Gc, Bc, Ac)<br />
|-<br />
|GL_ONE_MINUS_CONSTANT_COLOR_EXT <br />
|(1,1,1,1) - (Rc, Gc, Bc, Ac)<br />
|-<br />
|GL_CONSTANT_ALPHA_EXT <br />
|(Ac, Ac, Ac, Ac)<br />
|-<br />
|GL_ONE_MINUS_CONSTANT_ALPHA_EXT <br />
|(1,1,1,1) - (Ac, Ac, Ac, Ac)<br />
|}<br />
</div><br />
<br />
In der Tabelle ist<br />
<br />
i = min(As, kA - Ad) /kA<br />
<br />
Um die gemischten RGBA-Werte eines Pixels im RGBA-Modus zu ermitteln, nutzt das System folgende Gleichungen (wenn die Mischungs-Gleichung Erweiterung nicht unterstützt wird) :<br />
<br />
Rd = min(kR, RssR + RddR)<br />
Gd = min(kG, GssG + GddR)<br />
Bd = min(kB, BssB + BddB)<br />
Ad = min(kA, AssB + AddA)<br />
<br />
Trotz der offensichtlichen Präzisions obiger Gleichungen, ist die Mischungsarithmetik nicht exakt spezifiziert, aufgrund Mischungsoperationen mit unpräzisen Ganzzahlwerten. Bei einem Mischungsfaktor von 1 ist jedoch garantiert, dass dieser seinen Mulitplikanten nicht verändert, und ein Mischungsfaktor gleich 0 verrringert seinen Multiplikanten auf 0. Wenn ''sfactor'' z.B. '''GL_SCR_ALPHA''' ist, und ''dfactor'' gleich '''GL_ONE_MINUS_SRC_ALPHA''', dann ist ''As'' gleich ''kA'', und die Funktionen können auf ihre einfache Entsprechung reduziert werden :<br />
<br />
Rd = Rs<br />
Gd = Gs<br />
Bd = Bs<br />
Ad = As<br />
<br />
Wenn die Erweiterung für Mischungsgleichungen vorhanden ist, werden die gemischten RGBA-Werte anhand der gesetzten Mischungsgleichung gesetzt. Siehe dazu [[glBlendEquation]].<br />
<br />
== Hinweise ==<br />
Der Alpha-Eingangswert (Quelle) kann im Endeffekt als Materialdurchlässigkeit betrachtet werden, reichend von ''KA'' = 1, also komplett solide, bis ''KA''=0, was ein komplett transparentes (=unsichtbares) Material darstellen würde.<br />
<br />
Wenn mehr als ein Farbpuffer zum Rendern aktiv ist, wird die GL für jeden dieser Puffer separat Mischung durchführen, unter Nutzung des entsprechenden Pufferinhaltes als Zielkomponente. Siehe [[glDrawBuffer]].<br />
<br />
Mischung funktioniert übrigens nur im RGBA-Modus. Im Farbindex-Modus nicht. <br />
<br />
<br />
== Beispiele ==<br />
'''Transparenz''' implementiert man am besten mit den Mischungsfunktionen ('''GL_SRC_ALPHA''', '''GL_ONE_MINUS_SRC_ALPHA) und Sortierung der Primitiven von hinten nach vorne. <br />
<br />
Die Mischungsfunktion('''GL_SRC_ALPHA''', '''GL_ONE_MINUS_SRC_ALPHA''') ist auch zum '''Rendern kantengeglätteter''' Punkte nützlich, egal in welcher Reihenfolge diese vorkommen.<br />
<br />
'''Kantenglättung von Polygonen''' lässt sich mit der Mischungsfunktion ('''GL_SRC_ALPHA_SATURATE''', '''GL_ONE''') optimieren, wobei die Polygone von vorne nach hinten sortiert werden. <br />
<br />
<br />
Nachfolgend finden Sie eine Übersicht einiger Blendergebnisse. Der Code blieb unverändert, nur die Blendfunktion wurde geändert.<br />
<br />
<div align="center"><br />
{|{{Prettytable_B1}}<br />
!code<br />
|-<br />
|<br />
<pascal> glClearColor(1.0,1.0,1.0,0.0);<br />
[...]<br />
<br />
glEnable(GL_BLEND);<br />
glBlendFunc(GL_ONE, GL_ONE);<br />
<br />
glBegin(GL_QUADS);<br />
glColor4f(0.8,0.2,0.2,0.7);<br />
glVertex3f(-1, 1, 1);<br />
glVertex3f(-1,-1, 1);<br />
glVertex3f( 1,-1, 1);<br />
glVertex3f( 1, 1, 1);<br />
glColor4f(0.2,0.8,0.2,0.5);<br />
glVertex3f(-1, 1, 1.5);<br />
glVertex3f(-1,-1, 1.5);<br />
glVertex3f( 1,-1, 1.5);<br />
glVertex3f( 1, 1, 1.5);<br />
glColor4f(0.2,0.2,0.8,0.3);<br />
glVertex3f(-1, 1, 2);<br />
glVertex3f(-1,-1, 2);<br />
glVertex3f( 1,-1, 2);<br />
glVertex3f( 1, 1, 2);<br />
glEnd;</pascal><br />
|-<br />
|<br />
{| width="100%"<br />
|[[Bild:Blend GL DST ALPHA GL ONE MINUS DST ALPHA.jpg|center|framed|GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA]]<br />
|[[Bild:Blend GL DST COLOR, GL SRC COLOR.jpg|center|framed|GL_DST_COLOR, GL_SRC_COLOR]]<br />
|-<br />
|[[Bild:Blend GL DST COLOR GL SRC ALPHA.jpg|center|framed|GL_DST_COLOR, GL_SRC_ALPHA]]<br />
|[[Bild:Blend GL ONE GL ZERO.jpg|center|framed|GL_ONE, GL_ZERO]]<br />
|-<br />
|[[Bild:Blend GL ONE GL ONE s.jpg|center|framed|GL_ONE, GL_ONE mit glClearColor(0,0,0,0) entspricht additiven Blending]]<br />
|[[Bild:Blend GL ONE GL ONE w.jpg|center|framed|GL_ONE, GL_ONE mit glClearColor(1,1,1,0) weiß wegen "Überbelichtung"]]<br />
|-<br />
|[[Bild:Blend GL ONE MINUS DST COLOR GL ONE MINUS SRC COLOR.jpg|center|framed|GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR]]<br />
|[[Bild:Blend GL ONE MINUS SRC ALPHA GL SRC ALPHA.jpg|center|framed|GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA]]<br />
|-<br />
|[[Bild:Blend GL ONE MINUS DST ALPHA GL DST ALPHA.jpg|center|framed|GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA]]<br />
|[[Bild:Blend GL SRC ALPHA GL ONE MINUS SRC ALPHA.jpg|center|framed|GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA]]<br />
|-<br />
|[[Bild:Blend GL SRC ALPHA GL SRC COLOR.jpg|center|framed|GL_SRC_ALPHA, GL_SRC_COLOR]]<br />
|[[Bild:Blend GL ZERO GL ZERO.jpg|center|framed|GL_ZERO, GL_ZERO]]<br />
|-<br />
|[[Bild:Blend GL ZERO GL ONE.jpg|center|framed|GL_ZERO, GL_ONE]]<br />
|<br />
|-<br />
|}<br />
|}<br />
</div><br />
<br />
<br />
== Sinnvolle Kombinationen ==<br />
<br />
Im folgenden sind Rq, Gq, Bq und Aq die Farbkomponenten des Fragmentes, dass neu dazukommt. Rz, Gz, Bz und Az sind die Farbkomponenten des bereits im Framebuffer enthaltenen Pixels und Rn, Gn, Bn und An sind die Farbkomponenten des sich ergebenden Fragmentes. Alle Werte sind skaliert auf einen Bereich von 0.0 bis 1.0, inklusive.<br />
Die Ergebnisswerte werden danach auf den Bereich 0.0 bis 1.0 geclampt.<br />
<br />
=== Normales Blending ===<br />
Das normale Blending ist das, was man wohl am meisten gebraucht wird. Je stärker die Deckkraft (also das Alpha) der Quelle ist, desto weniger bleibt vom ursprünglichen Framebufferinhalt erhalten und desto mehr wird von der Quelle genommen. Daraus ergibt sich:<br />
Rn = Rz * (1.0 - Aq) + Rq * Aq;<br />
Gn = Gz * (1.0 - Aq) + Gq * Aq;<br />
Bn = Bz * (1.0 - Aq) + Bq * Aq;<br />
An = Az * (1.0 - Aq) + Aq * Aq;<br />
Die beiden Konstanten hierfür sind '''GL_SRC_ALPHA''' und '''GL_SRC_ONE_MINUS_ALPHA'''.<br />
<br />
=== Additives Blending (ohne Alpha) ===<br />
Das additive Blending wird zum Beispiel für Lichteffekte und dergleichen verwendet. Eben für Dinge, wo man die Helligkeit im Framebuffer erhöhen will.<br />
Rn = Rz + Rq;<br />
Gn = Gz + Gq;<br />
Bn = Bz + Bq;<br />
An = Az + Aq;<br />
Die beiden Konstanten hierfür sind '''GL_ONE''' und '''GL_ONE'''.<br />
<br />
=== Additives Blending (mit Alpha) ===<br />
Das funktioniert eigentlich genauso wie das andere additive Blending, nur wird hier der Alphawert des neuen Fragmentes beachtet.<br />
Rn = Rz + Rq * Aq;<br />
Gn = Gz + Gq * Aq;<br />
Bn = Bz + Bq * Aq;<br />
An = Az + Aq * Aq;<br />
Die beiden Konstanten hierfür sind '''GL_SRC_ALPHA''' und '''GL_ONE'''.<br />
<br />
=== Multiplikatives Blending ===<br />
Beim multiplikativen Blending werden die beiden Werte multipliziert. Zum Einfärben eines Bildes ist das sehr praktisch, aber auch für andere Effekte kann man es gebrauchen.<br />
Rn = Rz * Rq;<br />
Gn = Gz * Gq;<br />
Bn = Bz * Bq;<br />
An = Az * Aq;<br />
Die beiden Konstanten hierfür sind '''GL_DST_COLOR''' und '''GL_ZERO'''.<br />
<br />
== Fehlermeldungen ==<br />
'''GL_INVALID_ENUM''' wird generiert, wenn ''sfactor'' oder ''dfactor'' keine gültigen Werte darstellen.<br />
<br />
'''GL_INVALID_OPERATION''' wird generiert, wenn '''glBlendFunc''' in einem [[glBegin]]-[[glEnd]] Block aufgerufen wird.<br />
<br />
<br />
<br />
== Zugehörige Wertrückgaben ==<br />
'''glGet''' mit dem Argument '''GL_BLEND_SRC'''.<br><br />
'''glGet''' mit dem Argument '''GL_BLEND_DST'''.<br><br />
'''glGet''' mit dem Argument '''GL_BLEND_COLOR_EXT'''.<br><br />
'''glIsEnabled''' mit dem Argument '''GL_BLEND'''.<br />
<br />
<br />
<br />
== Siehe auch ==<br />
[[glAlphaFunc]], [[glClear]], [[glBlendEquation]], [[glBlendFuncSeparate]], [[glDrawBuffer]], [[glEnable]], [[glLogicOp]], [[glStencilFunc]]<br />
<br />
<br />
<br />
== Literatur ==<br />
[[RedBook]] Kapitel 7<br />
<br />
[[Kategorie:GL|BlendFunc]]<br />
[[Kategorie:GL1.0]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Tutorial_Wassereffekt&diff=21766Tutorial Wassereffekt2008-05-26T16:52:29Z<p>Lord Horazont: /* Die Wasserebene */</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. Wer schon etwas häufiger mit OpenGL gearbeitet hat, der kann sich vielleicht schon denken, wie man das am Besten machen 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 />
= Waser 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. Man sieht natürlich bis jetzt nur eine reflektierende Oberfläche (Rotes Wireframe), die mit ein bisschen Fantasie eine Wasseroberfläche sein könnte. Die Shader werden das ganze noch ein wenig verschönern.<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>Lord Horazonthttps://wiki.delphigl.com/index.php?title=glBlendFunc&diff=21731glBlendFunc2008-05-16T16:14:51Z<p>Lord Horazont: /* Sinnvolle Kombinationen */</p>
<hr />
<div>{{Excellent}}<br />
= glBlendFunc =<br />
<br />
<br />
<br />
== Name ==<br />
'''glBlendFunc''' - Setzt bestimmte Pixel-Berechnungen.<br />
<br />
<br />
<br />
== Delphi-Spezifikation ==<br />
procedure '''glBlendFunc'''(''sfactor'' : TGLEnum; ''dfactor'': TGLEnum);<br />
<br />
<br />
<br />
== Parameter ==<br />
{|border=1 rules=all<br />
!'''sfactor'''<br />
|Gibt an, wie der Rot-, Grün-, Blau- und Alphaanteil des Quell-Blendfaktors berechnet wird. Folgende symbolische Konstanten sind erlaubt : '''GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR_EXT, GL_ONE_MINUS_CONSTANT_COLOR_EXT, GL_CONSTANT_ALPHA_EXT, GL_ONE_MINUS_CONSTANT_ALPHA_EXT''' und '''GL_SRC_ALPHA_SATURATE'''. <br><br />
Vorgabewert ist '''GL_ONE'''.<br />
|-<br />
!'''dfactor'''<br />
|Gibt an, wie der Rot-, Grün-, Blau- und Alphaanteil des Ziel-Blendfaktors berechnet wird. Folgende symbolische Konstanten sind erlaubt : '''GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA''' und '''GL_ONE_MINUS_CONSTANT_ALPHA,'''.<br><br />
Vorgabewert ist '''GL_ZERO'''.<br />
|}<br />
<br />
== Beschreibung ==<br />
Im RGBA-Modus, können Pixel so gezeichnet werden, dass anhand einer Funktion die ankommenden RGBA-Werte (Quelle) mit dem bereits im Framepuffer befindlichen RGBA-Werten (Ziel) gemischt werden. Mischung ist als Vorgabe deaktiviert, kann jedoch mit [[glEnable]]/[[glEnable|glDisable]] und dem Token '''GL_BLEND''' gesteuert werden.<br />
<br />
'''glBlendFunc''' beschreibt die Funktion der Mischung, sofern aktiviert. ''sfactor'' gibt dabei an welche Methode genutzt wird um die Quellfarbkomponenten zu skalieren. ''dfactor'' gibt an, welche der Methode genutzt wird um die Zielfarbkomponenten zu skalieren. Die möglichen Methoden werden in der folgenden Tabelle beschrieben, wobei jede insgesamt vier Skalierungsfaktoren besitzt, für die vier Komponenten einer RGBA-Farbe.<br />
<br />
In der Tabelle (und allen folgenden Gleichungen), werden Quell- bzw. Zielfarbkomponenten als (Rs, Gs, Bs, As) und (Rd, Gd, Bd, Ad) dargestellt. Diese Komponenten besitzen Ganzzahlwerte, die zwischen 0 und (kR, kG, kB, kA) liegen, wobei <br />
<br />
kc = 2^mc - 1;<br />
<br />
und (mR, mG, mB, mA) gleich der Zahl der Rot-, Grün-, Blau- und Alphabitflächen ist. (Beispiel: Wenn man 8 Bit für eine Farbe (z.B. Rot) hat, dann ist kR = 2^8 - 1 = 255.)<br />
<br />
Konstante Farbkomponenten werden als (Rc, Gc, Bc, Ac) angegeben und werden nicht mit (kR, kG, kB, kA) skaliert, da sie bereits im Intervall [0,1] liegen.<br />
<br />
Quell- und Zielskalierungsfaktoren werden als (sR, sG, sB, sA) und (dR, dG, dB, dA) angegeben. Die in der Tabelle beschriebenen Skalierungsfaktoren, angegeben als (fR, fG, fB, fA) repräsentieren entweder Quell- oder Zielfaktoren. Alle Skalierungsfaktoren liegen im Intervall [0,1].<br />
<br />
<div align="center"><br />
{| {{Prettytable_B1}}<br />
|'''Parameter'''<br />
|'''(fR, fG, fB, fA)'''<br />
|-<br />
|GL_ZERO <br />
|(0,0,0,0)<br />
|-<br />
|GL_ONE <br />
|(1,1,1,1)<br />
|-<br />
|GL_SRC_COLOR <br />
|(Rs/kR, Gs/kG, Bs/kB, As/kA)<br />
|-<br />
|GL_ONE_MINUS_SRC_COLOR <br />
|(1,1,1,1) - (Rs/kR, Gs/kG, Bs/kB, As/kA)<br />
|-<br />
|GL_DST_COLOR <br />
|(Rd/kR, Gd/kG, Bd/kB, Ad/kA)<br />
|-<br />
|GL_ONE_MINUS_DST_COLOR <br />
|(1,1,1,1) - (Rd/kR, Gd/kG, Bd/kB, Ad/kA)<br />
|-<br />
|GL_SRC_ALPHA <br />
|(As/kA, As/kA, As/kA, As/kA)<br />
|-<br />
|GL_ONE_MINUS_SRC_ALPHA <br />
|(1,1,1,1) - (As/kA, As/kA, As/kA, As/kA)<br />
|-<br />
|GL_DST_ALPHA <br />
|(Ad/kA, Ad/kA, Ad/kA, Ad/kA)<br />
|-<br />
|GL_ONE_MINUS_DST_ALPHA <br />
|(1,1,1,1) - (Ad/kA, Ad/kA, Ad/kA, Ad/kA)<br />
|-<br />
|GL_SRC_ALPHA_SATURATE <br />
|(i,i,i,1)<br />
|-<br />
|GL_CONSTANT_COLOR_EXT <br />
|(Rc, Gc, Bc, Ac)<br />
|-<br />
|GL_ONE_MINUS_CONSTANT_COLOR_EXT <br />
|(1,1,1,1) - (Rc, Gc, Bc, Ac)<br />
|-<br />
|GL_CONSTANT_ALPHA_EXT <br />
|(Ac, Ac, Ac, Ac)<br />
|-<br />
|GL_ONE_MINUS_CONSTANT_ALPHA_EXT <br />
|(1,1,1,1) - (Ac, Ac, Ac, Ac)<br />
|}<br />
</div><br />
<br />
In der Tabelle ist<br />
<br />
i = min(As, kA - Ad) /kA<br />
<br />
Um die gemischten RGBA-Werte eines Pixels im RGBA-Modus zu ermitteln, nutzt das System folgende Gleichungen (wenn die Mischungs-Gleichung Erweiterung nicht unterstützt wird) :<br />
<br />
Rd = min(kR, RssR + RddR)<br />
Gd = min(kG, GssG + GddR)<br />
Bd = min(kB, BssB + BddB)<br />
Ad = min(kA, AssB + AddA)<br />
<br />
Trotz der offensichtlichen Präzisions obiger Gleichungen, ist die Mischungsarithmetik nicht exakt spezifiziert, aufgrund Mischungsoperationen mit unpräzisen Ganzzahlwerten. Bei einem Mischungsfaktor von 1 ist jedoch garantiert, dass dieser seinen Mulitplikanten nicht verändert, und ein Mischungsfaktor gleich 0 verrringert seinen Multiplikanten auf 0. Wenn ''sfactor'' z.B. '''GL_SCR_ALPHA''' ist, und ''dfactor'' gleich '''GL_ONE_MINUS_SRC_ALPHA''', dann ist ''As'' gleich ''kA'', und die Funktionen können auf ihre einfache Entsprechung reduziert werden :<br />
<br />
Rd = Rs<br />
Gd = Gs<br />
Bd = Bs<br />
Ad = As<br />
<br />
Wenn die Erweiterung für Mischungsgleichungen vorhanden ist, werden die gemischten RGBA-Werte anhand der gesetzten Mischungsgleichung gesetzt. Siehe dazu [[glBlendEquation]].<br />
<br />
== Hinweise ==<br />
Der Alpha-Eingangswert (Quelle) kann im Endeffekt als Materialdurchlässigkeit betrachtet werden, reichend von ''KA'' = 1, also komplett solide, bis ''KA''=0, was ein komplett transparentes (=unsichtbares) Material darstellen würde.<br />
<br />
Wenn mehr als ein Farbpuffer zum Rendern aktiv ist, wird die GL für jeden dieser Puffer separat Mischung durchführen, unter Nutzung des entsprechenden Pufferinhaltes als Zielkomponente. Siehe [[glDrawBuffer]].<br />
<br />
Mischung funktioniert übrigens nur im RGBA-Modus. Im Farbindex-Modus nicht. <br />
<br />
<br />
== Beispiele ==<br />
'''Transparenz''' implementiert man am besten mit den Mischungsfunktionen ('''GL_SRC_ALPHA''', '''GL_ONE_MINUS_SRC_ALPHA) und Sortierung der Primitiven von hinten nach vorne. <br />
<br />
Die Mischungsfunktion('''GL_SRC_ALPHA''', '''GL_ONE_MINUS_SRC_ALPHA''') ist auch zum '''Rendern kantengeglätteter''' Punkte nützlich, egal in welcher Reihenfolge diese vorkommen.<br />
<br />
'''Kantenglättung von Polygonen''' lässt sich mit der Mischungsfunktion ('''GL_SRC_ALPHA_SATURATE''', '''GL_ONE''') optimieren, wobei die Polygone von vorne nach hinten sortiert werden. <br />
<br />
<br />
Nachfolgend finden Sie eine Übersicht einiger Blendergebnisse. Der Code blieb unverändert, nur die Blendfunktion wurde geändert.<br />
<br />
<div align="center"><br />
{|{{Prettytable_B1}}<br />
!code<br />
|-<br />
|<br />
<pascal> glClearColor(1.0,1.0,1.0,0.0);<br />
[...]<br />
<br />
glEnable(GL_BLEND);<br />
glBlendFunc(GL_ONE, GL_ONE);<br />
<br />
glBegin(GL_QUADS);<br />
glColor4f(0.8,0.2,0.2,0.7);<br />
glVertex3f(-1, 1, 1);<br />
glVertex3f(-1,-1, 1);<br />
glVertex3f( 1,-1, 1);<br />
glVertex3f( 1, 1, 1);<br />
glColor4f(0.2,0.8,0.2,0.5);<br />
glVertex3f(-1, 1, 1.5);<br />
glVertex3f(-1,-1, 1.5);<br />
glVertex3f( 1,-1, 1.5);<br />
glVertex3f( 1, 1, 1.5);<br />
glColor4f(0.2,0.2,0.8,0.3);<br />
glVertex3f(-1, 1, 2);<br />
glVertex3f(-1,-1, 2);<br />
glVertex3f( 1,-1, 2);<br />
glVertex3f( 1, 1, 2);<br />
glEnd;</pascal><br />
|-<br />
|<br />
{| width="100%"<br />
|[[Bild:Blend GL DST ALPHA GL ONE MINUS DST ALPHA.jpg|center|framed|GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA]]<br />
|[[Bild:Blend GL DST COLOR, GL SRC COLOR.jpg|center|framed|GL_DST_COLOR, GL_SRC_COLOR]]<br />
|-<br />
|[[Bild:Blend GL DST COLOR GL SRC ALPHA.jpg|center|framed|GL_DST_COLOR, GL_SRC_ALPHA]]<br />
|[[Bild:Blend GL ONE GL ZERO.jpg|center|framed|GL_ONE, GL_ZERO]]<br />
|-<br />
|[[Bild:Blend GL ONE GL ONE s.jpg|center|framed|GL_ONE, GL_ONE mit glClearColor(0,0,0,0) entspricht additiven Blending]]<br />
|[[Bild:Blend GL ONE GL ONE w.jpg|center|framed|GL_ONE, GL_ONE mit glClearColor(1,1,1,0) weiß wegen "Überbelichtung"]]<br />
|-<br />
|[[Bild:Blend GL ONE MINUS DST COLOR GL ONE MINUS SRC COLOR.jpg|center|framed|GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR]]<br />
|[[Bild:Blend GL ONE MINUS SRC ALPHA GL SRC ALPHA.jpg|center|framed|GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA]]<br />
|-<br />
|[[Bild:Blend GL ONE MINUS DST ALPHA GL DST ALPHA.jpg|center|framed|GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA]]<br />
|[[Bild:Blend GL SRC ALPHA GL ONE MINUS SRC ALPHA.jpg|center|framed|GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA]]<br />
|-<br />
|[[Bild:Blend GL SRC ALPHA GL SRC COLOR.jpg|center|framed|GL_SRC_ALPHA, GL_SRC_COLOR]]<br />
|[[Bild:Blend GL ZERO GL ZERO.jpg|center|framed|GL_ZERO, GL_ZERO]]<br />
|-<br />
|[[Bild:Blend GL ZERO GL ONE.jpg|center|framed|GL_ZERO, GL_ONE]]<br />
|<br />
|-<br />
|}<br />
|}<br />
</div><br />
<br />
<br />
== Sinnvolle Kombinationen ==<br />
<br />
Im folgenden sind Rq, Gq, Bq und Aq die Farbkomponenten des Fragmentes, dass neu dazukommt. Rz, Gz, Bz und Az sind die Farbkomponenten des bereits im Framebuffer enthaltenen Pixels und Rn, Gn, Bn und An sind die Farbkomponenten des sich ergebenden Fragmentes. Alle Werte sind skaliert auf einen Bereich von 0.0 bis 1.0, inklusive.<br />
Die Ergebnisswerte werden danach auf den Bereich 0.0 bis 1.0 geclampt.<br />
<br />
=== Normales Blending ===<br />
Das normale Blending ist das, was man wohl am meisten gebraucht wird. Je stärker die Deckkraft (also das Alpha) der Quelle ist, desto weniger bleibt vom ursprünglichen Framebufferinhalt erhalten und desto mehr wird von der Quelle genommen. Daraus ergibt sich:<br />
Rn = Rz * (1.0 - Aq) + Rq * Aq;<br />
Gn = Gz * (1.0 - Aq) + Gq * Aq;<br />
Bn = Bz * (1.0 - Aq) + Bq * Aq;<br />
An = Az * (1.0 - Aq) + Aq * Aq;<br />
Die beiden Konstanten hierfür sind '''GL_SRC_ALPHA''' und '''GL_SRC_ONE_MINUS_ALPHA'''.<br />
<br />
=== Additives Blending (ohne Alpha) ===<br />
Das additive Blending wird zum Beispiel für Lichteffekte und dergleichen verwendet. Eben für Dinge, wo man die Helligkeit im Framebuffer erhöhen will.<br />
Rn = Rz + Rq;<br />
Gn = Gz + Gq;<br />
Bn = Bz + Bq;<br />
An = Az + Aq;<br />
Die beiden Konstanten hierfür sind '''GL_ONE''' und '''GL_ONE'''.<br />
<br />
=== Additives Blending (mit Alpha) ===<br />
Das funktioniert eigentlich genauso wie das andere additive Blending, nur wird hier der Alphawert des neuen Fragmentes beachtet.<br />
Rn = Rz + Rq * Aq;<br />
Gn = Gz + Gq * Aq;<br />
Bn = Bz + Bq * Aq;<br />
An = Az + Aq * Aq;<br />
Die beiden Konstanten hierfür sind '''GL_SRC_ALPHA''' und '''GL_ONE'''.<br />
<br />
=== Multiplikatives Blending ===<br />
Beim multiplikativen Blending werden die beiden Werte multipliziert. Zum Einfärben eines Bildes ist das sehr praktisch, aber auch für andere Effekte kann man es gebrauchen.<br />
Rn = Rz * Rq;<br />
Gn = Gz * Gq;<br />
Bn = Bz * Bq;<br />
An = Az * Aq;<br />
Die beiden Konstanten hierfür sind '''GL_DST_COLOR''' und '''GL_ZERO'''.<br />
<br />
== Fehlermeldungen ==<br />
'''GL_INVALID_ENUM''' wird generiert, wenn ''sfactor'' oder ''dfactor'' keine gültigen Werte darstellen.<br />
<br />
'''GL_INVALID_OPERATION''' wird generiert, wenn '''glBlendFunc''' in einem [[glBegin]]-[[glEnd]] Block aufgerufen wird.<br />
<br />
<br />
<br />
== Zugehörige Wertrückgaben ==<br />
'''glGet''' mit dem Argument '''GL_BLEND_SRC'''.<br><br />
'''glGet''' mit dem Argument '''GL_BLEND_DST'''.<br><br />
'''glGet''' mit dem Argument '''GL_BLEND_COLOR_EXT'''.<br><br />
'''glIsEnabled''' mit dem Argument '''GL_BLEND'''.<br />
<br />
<br />
<br />
== Siehe auch ==<br />
[[glAlphaFunc]], [[glClear]], [[glBlendEquation]], [[glBlendFuncSeparate]], [[glDrawBuffer]], [[glEnable]], [[glLogicOp]], [[glStencilFunc]]<br />
<br />
<br />
<br />
== Literatur ==<br />
[[RedBook]] Kapitel 7<br />
<br />
[[Kategorie:GL|BlendFunc]]<br />
[[Kategorie:GL1.0]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=glBlendFunc&diff=21730glBlendFunc2008-05-16T15:51:35Z<p>Lord Horazont: </p>
<hr />
<div>{{Excellent}}<br />
= glBlendFunc =<br />
<br />
<br />
<br />
== Name ==<br />
'''glBlendFunc''' - Setzt bestimmte Pixel-Berechnungen.<br />
<br />
<br />
<br />
== Delphi-Spezifikation ==<br />
procedure '''glBlendFunc'''(''sfactor'' : TGLEnum; ''dfactor'': TGLEnum);<br />
<br />
<br />
<br />
== Parameter ==<br />
{|border=1 rules=all<br />
!'''sfactor'''<br />
|Gibt an, wie der Rot-, Grün-, Blau- und Alphaanteil des Quell-Blendfaktors berechnet wird. Folgende symbolische Konstanten sind erlaubt : '''GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_CONSTANT_COLOR_EXT, GL_ONE_MINUS_CONSTANT_COLOR_EXT, GL_CONSTANT_ALPHA_EXT, GL_ONE_MINUS_CONSTANT_ALPHA_EXT''' und '''GL_SRC_ALPHA_SATURATE'''. <br><br />
Vorgabewert ist '''GL_ONE'''.<br />
|-<br />
!'''dfactor'''<br />
|Gibt an, wie der Rot-, Grün-, Blau- und Alphaanteil des Ziel-Blendfaktors berechnet wird. Folgende symbolische Konstanten sind erlaubt : '''GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA GL_CONSTANT_COLOR, GL_ONE_MINUS_CONSTANT_COLOR, GL_CONSTANT_ALPHA''' und '''GL_ONE_MINUS_CONSTANT_ALPHA,'''.<br><br />
Vorgabewert ist '''GL_ZERO'''.<br />
|}<br />
<br />
== Beschreibung ==<br />
Im RGBA-Modus, können Pixel so gezeichnet werden, dass anhand einer Funktion die ankommenden RGBA-Werte (Quelle) mit dem bereits im Framepuffer befindlichen RGBA-Werten (Ziel) gemischt werden. Mischung ist als Vorgabe deaktiviert, kann jedoch mit [[glEnable]]/[[glEnable|glDisable]] und dem Token '''GL_BLEND''' gesteuert werden.<br />
<br />
'''glBlendFunc''' beschreibt die Funktion der Mischung, sofern aktiviert. ''sfactor'' gibt dabei an welche Methode genutzt wird um die Quellfarbkomponenten zu skalieren. ''dfactor'' gibt an, welche der Methode genutzt wird um die Zielfarbkomponenten zu skalieren. Die möglichen Methoden werden in der folgenden Tabelle beschrieben, wobei jede insgesamt vier Skalierungsfaktoren besitzt, für die vier Komponenten einer RGBA-Farbe.<br />
<br />
In der Tabelle (und allen folgenden Gleichungen), werden Quell- bzw. Zielfarbkomponenten als (Rs, Gs, Bs, As) und (Rd, Gd, Bd, Ad) dargestellt. Diese Komponenten besitzen Ganzzahlwerte, die zwischen 0 und (kR, kG, kB, kA) liegen, wobei <br />
<br />
kc = 2^mc - 1;<br />
<br />
und (mR, mG, mB, mA) gleich der Zahl der Rot-, Grün-, Blau- und Alphabitflächen ist. (Beispiel: Wenn man 8 Bit für eine Farbe (z.B. Rot) hat, dann ist kR = 2^8 - 1 = 255.)<br />
<br />
Konstante Farbkomponenten werden als (Rc, Gc, Bc, Ac) angegeben und werden nicht mit (kR, kG, kB, kA) skaliert, da sie bereits im Intervall [0,1] liegen.<br />
<br />
Quell- und Zielskalierungsfaktoren werden als (sR, sG, sB, sA) und (dR, dG, dB, dA) angegeben. Die in der Tabelle beschriebenen Skalierungsfaktoren, angegeben als (fR, fG, fB, fA) repräsentieren entweder Quell- oder Zielfaktoren. Alle Skalierungsfaktoren liegen im Intervall [0,1].<br />
<br />
<div align="center"><br />
{| {{Prettytable_B1}}<br />
|'''Parameter'''<br />
|'''(fR, fG, fB, fA)'''<br />
|-<br />
|GL_ZERO <br />
|(0,0,0,0)<br />
|-<br />
|GL_ONE <br />
|(1,1,1,1)<br />
|-<br />
|GL_SRC_COLOR <br />
|(Rs/kR, Gs/kG, Bs/kB, As/kA)<br />
|-<br />
|GL_ONE_MINUS_SRC_COLOR <br />
|(1,1,1,1) - (Rs/kR, Gs/kG, Bs/kB, As/kA)<br />
|-<br />
|GL_DST_COLOR <br />
|(Rd/kR, Gd/kG, Bd/kB, Ad/kA)<br />
|-<br />
|GL_ONE_MINUS_DST_COLOR <br />
|(1,1,1,1) - (Rd/kR, Gd/kG, Bd/kB, Ad/kA)<br />
|-<br />
|GL_SRC_ALPHA <br />
|(As/kA, As/kA, As/kA, As/kA)<br />
|-<br />
|GL_ONE_MINUS_SRC_ALPHA <br />
|(1,1,1,1) - (As/kA, As/kA, As/kA, As/kA)<br />
|-<br />
|GL_DST_ALPHA <br />
|(Ad/kA, Ad/kA, Ad/kA, Ad/kA)<br />
|-<br />
|GL_ONE_MINUS_DST_ALPHA <br />
|(1,1,1,1) - (Ad/kA, Ad/kA, Ad/kA, Ad/kA)<br />
|-<br />
|GL_SRC_ALPHA_SATURATE <br />
|(i,i,i,1)<br />
|-<br />
|GL_CONSTANT_COLOR_EXT <br />
|(Rc, Gc, Bc, Ac)<br />
|-<br />
|GL_ONE_MINUS_CONSTANT_COLOR_EXT <br />
|(1,1,1,1) - (Rc, Gc, Bc, Ac)<br />
|-<br />
|GL_CONSTANT_ALPHA_EXT <br />
|(Ac, Ac, Ac, Ac)<br />
|-<br />
|GL_ONE_MINUS_CONSTANT_ALPHA_EXT <br />
|(1,1,1,1) - (Ac, Ac, Ac, Ac)<br />
|}<br />
</div><br />
<br />
In der Tabelle ist<br />
<br />
i = min(As, kA - Ad) /kA<br />
<br />
Um die gemischten RGBA-Werte eines Pixels im RGBA-Modus zu ermitteln, nutzt das System folgende Gleichungen (wenn die Mischungs-Gleichung Erweiterung nicht unterstützt wird) :<br />
<br />
Rd = min(kR, RssR + RddR)<br />
Gd = min(kG, GssG + GddR)<br />
Bd = min(kB, BssB + BddB)<br />
Ad = min(kA, AssB + AddA)<br />
<br />
Trotz der offensichtlichen Präzisions obiger Gleichungen, ist die Mischungsarithmetik nicht exakt spezifiziert, aufgrund Mischungsoperationen mit unpräzisen Ganzzahlwerten. Bei einem Mischungsfaktor von 1 ist jedoch garantiert, dass dieser seinen Mulitplikanten nicht verändert, und ein Mischungsfaktor gleich 0 verrringert seinen Multiplikanten auf 0. Wenn ''sfactor'' z.B. '''GL_SCR_ALPHA''' ist, und ''dfactor'' gleich '''GL_ONE_MINUS_SRC_ALPHA''', dann ist ''As'' gleich ''kA'', und die Funktionen können auf ihre einfache Entsprechung reduziert werden :<br />
<br />
Rd = Rs<br />
Gd = Gs<br />
Bd = Bs<br />
Ad = As<br />
<br />
Wenn die Erweiterung für Mischungsgleichungen vorhanden ist, werden die gemischten RGBA-Werte anhand der gesetzten Mischungsgleichung gesetzt. Siehe dazu [[glBlendEquation]].<br />
<br />
== Hinweise ==<br />
Der Alpha-Eingangswert (Quelle) kann im Endeffekt als Materialdurchlässigkeit betrachtet werden, reichend von ''KA'' = 1, also komplett solide, bis ''KA''=0, was ein komplett transparentes (=unsichtbares) Material darstellen würde.<br />
<br />
Wenn mehr als ein Farbpuffer zum Rendern aktiv ist, wird die GL für jeden dieser Puffer separat Mischung durchführen, unter Nutzung des entsprechenden Pufferinhaltes als Zielkomponente. Siehe [[glDrawBuffer]].<br />
<br />
Mischung funktioniert übrigens nur im RGBA-Modus. Im Farbindex-Modus nicht. <br />
<br />
<br />
== Beispiele ==<br />
'''Transparenz''' implementiert man am besten mit den Mischungsfunktionen ('''GL_SRC_ALPHA''', '''GL_ONE_MINUS_SRC_ALPHA) und Sortierung der Primitiven von hinten nach vorne. <br />
<br />
Die Mischungsfunktion('''GL_SRC_ALPHA''', '''GL_ONE_MINUS_SRC_ALPHA''') ist auch zum '''Rendern kantengeglätteter''' Punkte nützlich, egal in welcher Reihenfolge diese vorkommen.<br />
<br />
'''Kantenglättung von Polygonen''' lässt sich mit der Mischungsfunktion ('''GL_SRC_ALPHA_SATURATE''', '''GL_ONE''') optimieren, wobei die Polygone von vorne nach hinten sortiert werden. <br />
<br />
<br />
Nachfolgend finden Sie eine Übersicht einiger Blendergebnisse. Der Code blieb unverändert, nur die Blendfunktion wurde geändert.<br />
<br />
<div align="center"><br />
{|{{Prettytable_B1}}<br />
!code<br />
|-<br />
|<br />
<pascal> glClearColor(1.0,1.0,1.0,0.0);<br />
[...]<br />
<br />
glEnable(GL_BLEND);<br />
glBlendFunc(GL_ONE, GL_ONE);<br />
<br />
glBegin(GL_QUADS);<br />
glColor4f(0.8,0.2,0.2,0.7);<br />
glVertex3f(-1, 1, 1);<br />
glVertex3f(-1,-1, 1);<br />
glVertex3f( 1,-1, 1);<br />
glVertex3f( 1, 1, 1);<br />
glColor4f(0.2,0.8,0.2,0.5);<br />
glVertex3f(-1, 1, 1.5);<br />
glVertex3f(-1,-1, 1.5);<br />
glVertex3f( 1,-1, 1.5);<br />
glVertex3f( 1, 1, 1.5);<br />
glColor4f(0.2,0.2,0.8,0.3);<br />
glVertex3f(-1, 1, 2);<br />
glVertex3f(-1,-1, 2);<br />
glVertex3f( 1,-1, 2);<br />
glVertex3f( 1, 1, 2);<br />
glEnd;</pascal><br />
|-<br />
|<br />
{| width="100%"<br />
|[[Bild:Blend GL DST ALPHA GL ONE MINUS DST ALPHA.jpg|center|framed|GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA]]<br />
|[[Bild:Blend GL DST COLOR, GL SRC COLOR.jpg|center|framed|GL_DST_COLOR, GL_SRC_COLOR]]<br />
|-<br />
|[[Bild:Blend GL DST COLOR GL SRC ALPHA.jpg|center|framed|GL_DST_COLOR, GL_SRC_ALPHA]]<br />
|[[Bild:Blend GL ONE GL ZERO.jpg|center|framed|GL_ONE, GL_ZERO]]<br />
|-<br />
|[[Bild:Blend GL ONE GL ONE s.jpg|center|framed|GL_ONE, GL_ONE mit glClearColor(0,0,0,0) entspricht additiven Blending]]<br />
|[[Bild:Blend GL ONE GL ONE w.jpg|center|framed|GL_ONE, GL_ONE mit glClearColor(1,1,1,0) weiß wegen "Überbelichtung"]]<br />
|-<br />
|[[Bild:Blend GL ONE MINUS DST COLOR GL ONE MINUS SRC COLOR.jpg|center|framed|GL_ONE_MINUS_DST_COLOR, GL_ONE_MINUS_SRC_COLOR]]<br />
|[[Bild:Blend GL ONE MINUS SRC ALPHA GL SRC ALPHA.jpg|center|framed|GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA]]<br />
|-<br />
|[[Bild:Blend GL ONE MINUS DST ALPHA GL DST ALPHA.jpg|center|framed|GL_ONE_MINUS_DST_ALPHA, GL_DST_ALPHA]]<br />
|[[Bild:Blend GL SRC ALPHA GL ONE MINUS SRC ALPHA.jpg|center|framed|GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA]]<br />
|-<br />
|[[Bild:Blend GL SRC ALPHA GL SRC COLOR.jpg|center|framed|GL_SRC_ALPHA, GL_SRC_COLOR]]<br />
|[[Bild:Blend GL ZERO GL ZERO.jpg|center|framed|GL_ZERO, GL_ZERO]]<br />
|-<br />
|[[Bild:Blend GL ZERO GL ONE.jpg|center|framed|GL_ZERO, GL_ONE]]<br />
|<br />
|-<br />
|}<br />
|}<br />
</div><br />
<br />
<br />
== Sinnvolle Kombinationen ==<br />
<br />
<br />
== Fehlermeldungen ==<br />
'''GL_INVALID_ENUM''' wird generiert, wenn ''sfactor'' oder ''dfactor'' keine gültigen Werte darstellen.<br />
<br />
'''GL_INVALID_OPERATION''' wird generiert, wenn '''glBlendFunc''' in einem [[glBegin]]-[[glEnd]] Block aufgerufen wird.<br />
<br />
<br />
<br />
== Zugehörige Wertrückgaben ==<br />
'''glGet''' mit dem Argument '''GL_BLEND_SRC'''.<br><br />
'''glGet''' mit dem Argument '''GL_BLEND_DST'''.<br><br />
'''glGet''' mit dem Argument '''GL_BLEND_COLOR_EXT'''.<br><br />
'''glIsEnabled''' mit dem Argument '''GL_BLEND'''.<br />
<br />
<br />
<br />
== Siehe auch ==<br />
[[glAlphaFunc]], [[glClear]], [[glBlendEquation]], [[glBlendFuncSeparate]], [[glDrawBuffer]], [[glEnable]], [[glLogicOp]], [[glStencilFunc]]<br />
<br />
<br />
<br />
== Literatur ==<br />
[[RedBook]] Kapitel 7<br />
<br />
[[Kategorie:GL|BlendFunc]]<br />
[[Kategorie:GL1.0]]</div>Lord Horazonthttps://wiki.delphigl.com/index.php?title=Tutorial_Scriptsprachen_Teil_1&diff=21627Tutorial Scriptsprachen Teil 12008-05-01T20:38:55Z<p>Lord Horazont: nowiki-Tag für doppelte einfache Anführungszeichen</p>
<hr />
<div>==Einführung==<br />
<br />
Hoi,<br />
ich bin wieder aus meinem Urlaub zurück mit einem ganzen Satz neuer Ideen. Schon einmal als Gott versucht? Ihr werdet euch als Programmierer echt so fühlen... Diesmal geht es um Scriptsprachen. Sie müssen nicht nötigerweise in Spielen eingesetzt werden (siehe Emacs) ist aber häufig so, wie man an Unreal, Quake, Halflife, Tribes, etc. erkennen kann. Teilweise basiert fast das komplette Spiel auf diesen Sprachen, nur sehr zeitkritische Bereiche sind in den 'echten' Code ausgelagert, denn die Interpretierung des Scriptcodes läuft um einiges langsamer ab, als der Maschinenassembler. Andererseits können sie natürlich auch die Portierung der Spiele erleichtern, denn die Scriptsprache ist normalerweise nicht systemabhängig.<br />
<br />
Hat man sich entschieden, eine Scriptsprache zu verwenden, steht man vor einer Entscheidung: Nehme ich eine bereits existierende Scriptsprache, wie Python für Delphi oder Innerfuse Pascal Script oder schreibe ich eine Engine selbst. Ich bin der Ansicht, als Programmierer sollte man einmal hinter die Kulissen einer Programmiersprache geschaut haben - man kann daraus viel lernen. Entsprechend stelle ich hier die Variante vor, bei der man sich nicht erst in die API einer bestehenden Sprache einarbeiten muss, sondern bei der man die API selbst entwirft. Es lohnt sich sicherlich, dieses Tutorial einmal näher unter die Lupe zu nehmen, auch wenn man nicht plant, eine Scriptsprache zu verwenden.<br />
==Erweiterte Backus-Naur Form(EBNF)==<br />
<br />
Bevor wir uns daran machen können, einen Compiler für unsere Sprache zu entwickeln, müssen wir uns ein wenig mit Programmiersprachen selbst etwas beschäftigen. Form und Aufbau der Sprache müssen festgelegt werden. Einfach darauf loszuarbeiten ist hier auf alle Fälle der falsche Weg und führt mit Sicherheit nur zu massenhaft unbrauchbarem Codeschrott. Tatsächlich gibt es spezielle Sprachen, mit denen Programmiersprachen beschrieben werden können. Algol 60 war die erste Programmiersprache die mit BNF beschrieben wurde, aber längst nicht die einzige. Es folgten z.B. PL/0, PL/1, Pascal, Oberon, Modula, Component Pascal, Object Pascal und viele andere, mehr oder weniger bekannte. EBNF eignet sich auch sehr gut unsere "eigene" Sprache zu beschreiben, doch kennen wohl die meisten unter euch EBNF noch nicht, es ist also Zeit hier eine ''kleine'' Definition zu machen:<br />
<br />
{|{{Prettytable}} <br />
|1. Besteht A allein aus B, so schreibt man:<br />
:A = B.<br />
|-<br />
|2. Ein Zeichen zwischen <nowiki>''</nowiki> oder "" bedeutet das Zeichen selbst.<br />
:Beispiel:<br />
:Punkt = '.'.<br />
|-<br />
|3. Besteht A aus B gefolgt von C, so schreibt man:<br />
:A = B C.<br />
|-<br />
|4. Besteht A aus B oder C, so schreibt man<br />
:A = B | C.<br />
<br />
:Beispiel:<br />
:<nowiki>Digit = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'.</nowiki><br />
|-<br />
|5. Besteht A aus keinem, einem, oder mehreren B, so schreibt man:<br />
:A = {B}.<br />
<br />
:Beispiel:<br />
:Vorzeichenlose Zahl = Digit {Digit}.<br />
|-<br />
|6. Ist A entweder leer oder gleich B, so schreibt man<br />
:A = [B].<br />
<br />
:Beispiel:<br />
:<nowiki>Vorzeichen = '+'|'-'</nowiki>.<br />
:Zahl = [Vorzeichen] PositiveZahl. <br />
|-<br />
|7. Runde Klammern gruppieren Elemente:<br />
:<nowiki>A = (B|C)(D|E)</nowiki>.<br />
|}<br />
<br />
EBNF bereitet am Anfang sicherlich Probleme und es dauert ein wenig, bis man sich in der Sprache zurecht findet. Am einfachsten übt sich der Gebrauch an einem Beispiel - an der Scriptsprache die wir implementieren wollen:<br />
<br />
{|{{Prettytable}} <br />
|#Kleinkrams<br />
Digit = "0"|..|"9".<br />
HexDigit = Digit|"A"|..|"F".<br />
Letter = "A"|..|"Z"|"_".<br />
Vorzeichen = "+"|"-".<br />
|-<br />
|#Zahlen<br />
Zahl = (Digit {Digit}) | ("$" HexDigit {HexDigit}).<br />
|-<br />
|#Bezeichner, Zahlen, etc.<br />
Ident = Letter {Letter|Digit}.<br />
|-<br />
|#Ausdrücke<br />
Condition = Expression ("="|"<"|">"|">="|"<="|"#") Expression.<br />
Expression = [Vorzeichen] Term {Vorzeichen Term}.<br />
Term = Factor {"*"|"/" Factor}.<br />
Factor = Ident | Zahl | ("(" Expression ")").<br />
|-<br />
|#Deklarationen, Statements, etc.<br />
VarDecl = Ident {"," Ident}.<br />
ConstDecl = Ident "=" Zahl.<br />
<br />
VarSeq = VAR VarDecl {";" VarDecl}";".<br />
ConstSeq = CONST ConstDecl {";" ConstDecl}";".<br />
DeclSeq = {VarSeq | ConstSeq | ProcedureDecl}.<br />
ProcedureDecl = PROCEDURE Ident";"<br />
DeclSeq BEGIN StatementSeq END Ident ";".<br />
<br />
Statement = ( Ident ":=" Expression ) |<br />
Ident | ( WRITE Expression ) |<br />
( BEGIN StatementSeq END ) |<br />
( IF Condition THEN StatementSeq END<br />
{ELSEIF Condition THEN StatementSeq END }<br />
[ELSE StatementSeq END] ) |<br />
( WHILE Condition DO StatementSeq END )| ";".<br />
StatementSeq = [Statement {";" Statement}]<br />
<br />
Comment = "(*" ... "*)".<br />
Module = MODULE Ident ";"<br />
DeclSeq<br />
BEGIN<br />
StatementSeq<br />
END Ident ".".<br />
|}<br />
<br />
==Lexikalische Analyse==<br />
<br />
Eine der ersten Aufgaben die wir beim Bau unseres Compilers zu bewältigen haben ist, die Lexikalische Analyse. Die Funktion dieser ist in den Eingabedaten Wörter und Symbole zu erkennen, reservierte Wörter als solche zu markieren und Kommentare herauszufiltern.<br />
<br />
Zuvor wieder eine kleine Entscheidung: Soll unsere Sprache einen Unterschied zwischen Groß und Kleinschreibung machen? Das beeinflusst das Aussehen unserer Sprache in hohem Maße, denn in einem Fall ist es egal ob "begin", "BEGIN", "Begin" oder "bEgIn", im anderen nicht. Ich beschließe einfach mal, sie à la Pascal nicht case sensitive zu halten.<br />
<br />
Der erste Schritt beim Scannerbau ist, alle verwendeten Symbole zu definieren und die schreibweise der reservierten Wörter festzulegen:<br />
<br />
'''scanner.dpr'''<br />
<pascal><br />
program scanner;<br />
{$APPTYPE CONSOLE}<br />
uses<br />
sysutils;<br />
<br />
type<br />
TSymbol = (sUnknown, sIdent, sInteger, sPlus, sMinus, sStar, sSlash, sEqual,<br />
sSmaller, sBigger, sBiggerEqual, sSmallerEqual, sUnEqual,<br />
sOpenBracket, sCloseBracket, sComma, sDot, sSemiColon, sBecomes,<br />
sVar, sConst, sProcedure, sBegin, sEnd, sIf, sThen,<br />
sElseIf, sElse, sWhile, sDo, sModule, sWrite, sNone);<br />
const<br />
cSymbols : Array[TSymbol] of ShortString = ('', '', '', '+', '-', '*', '/', '=',<br />
'<', '>', '>=', '<=', '#',<br />
'(', ')', ',', '.', ';', ':=',<br />
'VAR', 'CONST', 'PROCEDURE', 'BEGIN', 'END', 'IF', 'THEN',<br />
'ELSEIF', 'ELSE', 'WHILE', 'DO', 'MODULE', 'WRITE', '');<br />
</pascal><br />
Zum Scanner selbst. Die Daten die wir erhalten kommen sequentiell, da wir sie aus der Quelldatei auslesen. Leerzeichen und Sonderzeichen werden übersprungen, weil für uns uninterssant. Je nach gefundenem Zeichen wird anders verfahren, z.B wird ein "Letter" (siehe EBNF) gefunden, handelt es sich um einen Bezeichner oder ein reserviertes Wort. Bei '+','-',... um Symbole.<br />
<br />
'''scanner.dpr'''<br />
<pascal><br />
resourcestring<br />
ERR_SCANNER_UNEXPECTED_CHAR = 'Error 0: Scanner: Unexpected char found in stream';<br />
<br />
<br />
var<br />
InFile : File;<br />
Line : Integer;<br />
<br />
procedure Error(ErrorText : String);<br />
begin<br />
WriteLn(Format('%d: ' + ErrorText, [Line]));<br />
ReadLn;<br />
Halt(0)<br />
end;<br />
<br />
var<br />
Ch : Char;<br />
Str : String;<br />
Symbol : TSymbol;<br />
<br />
procedure GetSym;<br />
procedure GetCh;<br />
begin<br />
if not EoF(InFile) then<br />
BlockRead(InFile, Ch, 1)<br />
else<br />
Ch := ' ';<br />
//Case in-sensitive<br />
Ch := UpCase(Ch);<br />
<br />
//Zeile erhöhen?<br />
if Ch = #13 then Inc(Line);<br />
//Spezialzeichen filtern<br />
if Ord(Ch) < Ord(' ') then Ch := ' '<br />
end (*GetCh*);<br />
var<br />
I : TSymbol;<br />
begin<br />
//Überspringen von Kommentaren<br />
while true do<br />
begin<br />
Str := '';<br />
Symbol := sNone;<br />
<br />
while (Ch = ' ') and not EoF(InFile) do<br />
GetCh;<br />
<br />
//Wir brauchen nichts machen, wenn das Dateiende erreicht ist<br />
if EoF(InFile) then<br />
Exit;<br />
<br />
case Ch of<br />
'A'..'Z','_': begin (*Ident/Reserved Word*)<br />
while Ch in ['A'..'Z','_','0'..'9'] do<br />
begin<br />
Str := Str + Ch;<br />
GetCh<br />
end;<br />
Symbol := sIdent;<br />
<br />
for i := sUnknown to sNone do<br />
if Str = cSymbols[i] then<br />
begin<br />
Symbol := i;<br />
Break<br />
end;<br />
Exit<br />
end; (*Ident/Reserved Word*)<br />
';','+','-','=','#',',','.', '*', '/':<br />
begin (*Symbole die nur aus 1 Zeichen bestehen können*)<br />
Str := Ch;<br />
Symbol := sUnknown;<br />
for i := sUnknown to sNone do<br />
if Str = cSymbols[i] then<br />
begin<br />
Symbol := i;<br />
Break<br />
end;<br />
GetCh;<br />
Exit<br />
end; (*Symbole die nur aus 1 Zeichen bestehen können*)<br />
':','<','>': (* Zeichen die ein nachfolgendes Zeichen haben können(in diesm Falle ein = )*)<br />
begin<br />
Str := Ch;<br />
GetCh;<br />
if Ch = '=' then Str := Str + Ch;<br />
GetCh;<br />
Symbol := sUnknown;<br />
for i := sUnknown to sNone do<br />
if Str = cSymbols[i] then<br />
begin<br />
Symbol := i;<br />
Break<br />
end;<br />
Exit<br />
end;<br />
'(', ')': begin (*Klammern oder Kommentar*)<br />
Str := Ch;<br />
GetCh;<br />
if (Str='(') and (Ch = '*') then<br />
begin<br />
//Kommentar entdeckt<br />
GetCh;<br />
while True do<br />
begin<br />
GetCh;<br />
if Ch = '*' then<br />
begin<br />
GetCh;<br />
if Ch = ')' then<br />
begin<br />
GetCh;<br />
Break<br />
end<br />
end<br />
else if EoF(InFile) then<br />
Break<br />
end<br />
end else<br />
if Str = '(' then<br />
begin<br />
Symbol := sOpenBracket;<br />
Exit<br />
end<br />
else<br />
begin<br />
Symbol := sCloseBracket;<br />
Exit<br />
end<br />
end;<br />
'0'..'9','$': (*Zahlen*)<br />
begin<br />
Symbol := sInteger;<br />
Str := Ch;<br />
GetCh;<br />
if (Str = '$') then<br />
begin<br />
//HexZahl<br />
while Ch in ['0'..'9','A'..'F'] do<br />
begin<br />
Str := Str + Ch;<br />
GetCh;<br />
end;<br />
Exit<br />
end<br />
else<br />
begin<br />
//NormaleZahl<br />
while Ch in ['0'..'9'] do<br />
begin<br />
Str := Str + Ch;<br />
GetCh;<br />
end;<br />
Exit<br />
end<br />
end; (*Zahlen*)<br />
<br />
else<br />
Error(ERR_SCANNER_UNEXPECTED_CHAR)<br />
end;<br />
Assert(Symbol <> SUnknown);<br />
end<br />
end (*GetSym*);<br />
<br />
begin<br />
AssignFile(InFile, ParamStr(1));<br />
Reset(InFile, 1);<br />
//Ch mit leerzeichen initialisieren<br />
Ch := ' ';<br />
Line := 1;<br />
GetSym;<br />
while Symbol <> sNone do<br />
begin<br />
WriteLn(Str);<br />
GetSym<br />
end;<br />
WriteLn('Done...');<br />
ReadLn;<br />
CloseFile(InFile)<br />
end.<br />
</pascal><br />
GetSym ist der eigentliche Scanner. Die für den Compiler später wichtigen Variablen sind Symbol und Str. Symbol besagt immer, was der Scanner genau gefunden hat, eine Zahl, einen Bezeichner, ein reserviertes Wort, ... GetCh holt immer das nächste Zeichen nach Ch und filtert gleichzeitig Sonderzeichen heraus. Error erzeut eine Fehlermeldung und beendet das Programm. Ein Beispiel in Scriptcode wäre:<br />
<br />
'''test.script'''<br />
<pascal><br />
module Test;<br />
<br />
const<br />
HexZahl = $12ABCDEF;<br />
NormaleZahl = 12345;<br />
<br />
var<br />
a;<br />
<br />
procedure Hello;<br />
begin<br />
Write a<br />
end Hello;<br />
<br />
begin<br />
a := 0;<br />
(* Testkommentar * Test *)<br />
end Test;<br />
</pascal><br />
Wenn wir das Programm mit dem Scanner darüber laufen lassen, erhalten wir folgenden Output:<br />
MODULE<br />
TEST<br />
;<br />
CONST<br />
HEXZAHL<br />
=<br />
$12ABCDEF<br />
;<br />
NORMALEZAHL<br />
=<br />
12345<br />
;<br />
VAR<br />
A<br />
;<br />
PROCEDURE<br />
HELLO<br />
;<br />
BEGIN<br />
WRITE<br />
A<br />
END<br />
HELLO<br />
;<br />
BEGIN<br />
A<br />
:=<br />
0<br />
;<br />
END<br />
TEST<br />
;<br />
Done...<br />
<br />
Dies ist unser Beispielprogramm in einzelne Symbole zerlegt. Um den Scanner-Code besser zu verstehen, sollte man einfach einmal in den Code Debuggen und schauen was der Reihe nach passiert. In die Liste der überwachten Ausdrücke gehört dabei unbedingt: Str, Ch, Symbol.<br />
<br />
==Parsen des Quellcodes==<br />
<br />
Der nächste zu bewältigende Schritt ist das Parsen ("Satzzerlegen") des Quelltextes. Es handelt sich um die Überprüfung des Quellcodes auf syntaktische Korrektheit. Wir wollen uns erst einmal nur auf den Aufbau des Skripts konzentrieren. Die Korrektheit der Bezeichner sei erstmal noch unwichtig.<br />
<br />
'''An alle, die Angst vorm Schachteln haben (besonders Phobeus ;)):''' Ich hoffe das wird hier nicht zu hart für euch... Danach werdet ihr Schachteln lieben, das verspreche ich ;-).<br />
<br />
Zurück zum Parsen. Hier müssen wir wieder die EBNF zur Hand nehmen, sie wird uns gute Dienste leisten. Suchen wir uns einen Startpunkt heraus. In unserer Sprache wäre das Module. So nennen wir dann auch die entsprechende Prozedur. Diese prüft ihre paar reservierten Wörter und Bezeichner ab und ruft dann DeclSeq auf. Sobald DeclSeq abgeschlossen ist, prüfen wir ob ein BEGIN gefunden wurde und rufen StatementSeq auf... Genauso verfahren wir bei DeclSeq und StatementSeq, dadurch bleibt die Sache übersichtlich und vieles wird einfacher.<br />
<br />
'''parser.dpr'''<br />
<pascal><br />
program parser;<br />
{$APPTYPE CONSOLE}<br />
uses<br />
sysutils,<br />
symbols in 'symbols.pas',<br />
scanner in 'scanner.pas',<br />
errors in 'errors.pas';<br />
<br />
procedure Expect(Expected : TSymbol);<br />
begin<br />
if Symbol <> Expected then<br />
ErrorExpected([Expected], Symbol)<br />
end;<br />
<br />
procedure Module;<br />
<br />
procedure StatementSequence;<br />
(* StatementSeq = [Statement {";" Statement}]*)<br />
...<br />
end; (*Statement Sequence*)<br />
<br />
procedure Declarations;<br />
(*<br />
VarSeq = VAR VarDecl {";" VarDecl}";".<br />
ConstSeq = CONST ConstDecl {";" ConstDecl}";".<br />
DeclSeq = {VarSeq | ConstSeq | ProcedureDecl}.<br />
<br />
*)<br />
...<br />
end; (*Declarations*)<br />
<br />
(*Module = MODULE Ident ";"<br />
DeclSeq<br />
BEGIN<br />
StatementSeq<br />
END Ident ".".*)<br />
begin (*Module*)<br />
Expect(sModule);<br />
GetSym;<br />
Expect(sIdent);<br />
GetSym;<br />
Expect(sSemiColon);<br />
GetSym;<br />
<br />
Declarations;<br />
<br />
Expect(sBegin);<br />
GetSym;<br />
StatementSequence;<br />
Expect(sEnd);<br />
GetSym;<br />
Expect(sIdent);<br />
GetSym;<br />
Expect(sSemiColon);<br />
GetSym;<br />
if Symbol <> sNone then<br />
WriteLn(Format('%d: Code after Module END is ignored!', [Line]))<br />
end (*Module*);<br />
<br />
begin<br />
AssignFile(InFile, ParamStr(1));<br />
Reset(InFile, 1);<br />
//Ch mit leerzeichen initialisieren<br />
Ch := ' ';<br />
Line := 1;<br />
GetSym;<br />
Module;<br />
<br />
WriteLn('Done, no syntax errors detected...');<br />
ReadLn;<br />
CloseFile(InFile)<br />
end.<br />
</pascal><br />
Soweit zur Kurzfassung. Der Scanner und die ErrorX Funktionen sollten übrigens in Units ausgelagert werden, da sie massenhaft Platz beanspruchen und stark die Übersicht in unserem Programm belasten. Ideal wäre jetzt natürlich, wenn ihr versuchen würdet, den Rest selber zu implementieren. Mein Werk findet ihr bei den Sources in '''parser.dpr'''. Noch ein kleiner Tipp am Rande: Wenn man glaubt, alles eingebaut zu haben sollte man auch alle Möglichkeiten der Sprache in einem Testskript ausprobiert haben, nur um sicherzugehen...<br />
<br />
==Ein Aufbaucheck langt nicht aus==<br />
<br />
Jetzt müssen wir unseren Parser erweitern. Wir haben vorhin beschlossen, die Überprüfung der Bezeichner erst einmal auszulassen. Das kommt nun auf uns zu. Die typische Methode Bezeichner einzubinden ist über Tabellen. Sie enthalten die für uns relevanten Inhalte: Name und Typ(Prozedur, Konstante, Variable) des Bezeichners. Für die Codeerzeugung werden wir dieses Feld erweitern müssen, doch vorerst langt uns das einmal aus. Wir müssen dabei auch bedenken, dass Prozeduren lokale Variablen haben, die für Prozeduren eines niedrigeren level nicht erreichbar sein dürfen, egal wann und wo sie definiert wurden.<br />
<br />
'''parser_idents.dpr'''<br />
<pascal><br />
type<br />
TIdentType = (itConstant, itVariable, itProzedure);<br />
TIdent = record<br />
Name : ShortString;<br />
Kind : TIdentType;<br />
end;<br />
TIdentList = Array of TIdent;<br />
<br />
var<br />
Table : TIdentList;<br />
</pascal><br />
Den Zugriff auf die Tabelle wollen wir über 2 Funktionen Steuern: Position(Name) sucht die Position eines Eintrags in der Tabelle und Enter(TIdentType) fügt der Liste einen Eintrag hinzu. Der Scanner bekommt noch eine kleine Änderung verabreicht. Erkennt er einen Bezeichner, wird dessen Name in einer Variable id abgelegt, erkennt er eine Zahl, wird deren Wert in num abgelegt:<br />
<br />
'''parser_idents.dpr'''<br />
<pascal><br />
...<br />
procedure Enter(Typ : TIdentType);<br />
begin<br />
Inc(TablePosition);<br />
if TablePosition > Length(Table) - 1 then<br />
SetLength(Table, Length(Table)+16);<br />
with Table[TablePosition] do<br />
begin<br />
Name := ID;<br />
Kind := Typ<br />
end<br />
end;<br />
...<br />
function Position(ID : ShortString; TablePosition : Integer) : Integer;<br />
var<br />
I : Integer;<br />
begin<br />
Table[0].Name := ID;<br />
I := TablePosition;<br />
while Table[I].Name <> ID do<br />
Dec(I);<br />
Result := I<br />
end;<br />
...<br />
</pascal><br />
Enter wird immer dann aufgerufen, wenn ein Bezeichner eingeführt wird (ConstDecl, VarDecl, ProcedureDecl). Position wird immer benötigt, wenn ein Bezeichner angewand wird (Statement).<br />
<br />
'''parser_idents.dpr'''<br />
<pascal><br />
program compiler;<br />
{$APPTYPE CONSOLE}<br />
uses<br />
sysutils,<br />
symbols in '..\parser\symbols.pas',<br />
scanner in '..\parser\scanner.pas',<br />
errors in '..\parser\errors.pas';<br />
<br />
type<br />
TIdentType = (itConstant, itVariable, itProcedure);<br />
TIdent = record<br />
Name : ShortString;<br />
Kind : TIdentType;<br />
end;<br />
TIdentList = Array of TIdent;<br />
<br />
var<br />
Table : TIdentList;<br />
<br />
procedure Expect(Expected : TSymbol);<br />
begin<br />
if Symbol <> Expected then<br />
ErrorExpected([Expected], Symbol)<br />
end;<br />
<br />
procedure Module;<br />
<br />
function Position(ID : ShortString; TablePosition : Integer) : Integer;<br />
var<br />
I : Integer;<br />
begin<br />
Table[0].Name := ID;<br />
I := TablePosition;<br />
while Table[I].Name <> ID do<br />
Dec(I);<br />
Result := I<br />
end;<br />
<br />
procedure StatementSequence(TablePosition : Integer);<br />
(* StatementSeq = [Statement {";" Statement}]*)<br />
procedure Statement;<br />
(*Statement = ( Ident ":=" (Ident | Expression) |<br />
Ident | (WRITE Expression) |<br />
( BEGIN StatementSeq END ) |<br />
( IF Condition THEN StatementSeq END<br />
{ELSEIF Condition THEN StatementSeq END }<br />
[ELSE StatementSeq END] ) |<br />
( WHILE Condition DO StatementSeq END ) | ";". *)<br />
procedure Expression;<br />
(*Expression = [Vorzeichen] Term {Vorzeichen Term}.*)<br />
procedure Term;<br />
(*Term = Factor {"*"|"/" Factor}.*)<br />
procedure Factor;<br />
(*Factor = Ident | Zahl | ("(" Expression ")").*)<br />
var<br />
IdentPos : Integer;<br />
begin (*Factor*)<br />
if (Symbol in [sIdent, sInteger]) then<br />
begin<br />
IdentPos := Position(ID, TablePosition);<br />
if (IdentPos = 0) then<br />
Error(ERR_PARSE_UNKNOWN_IDENT);<br />
if Table[IdentPos].Kind = itProcedure then<br />
Error(ERR_PARSE_VAR_CONSTANT_EXPECTED);<br />
GetSym<br />
end<br />
else if (Symbol = sOpenBracket) then<br />
begin<br />
GetSym;<br />
Expression;<br />
Expect(sCloseBracket);<br />
GetSym<br />
end<br />
else<br />
ErrorExpected([sIdent, sInteger, sOpenBracket], Symbol)<br />
end; (*Factor*)<br />
<br />
begin(*Term*)<br />
Factor;<br />
while Symbol in [sStar, sSlash] do<br />
begin<br />
GetSym;<br />
Factor<br />
end<br />
end;(*Term*)<br />
begin (*Expresion*)<br />
if (Symbol in[sPlus, sMinus]) then<br />
GetSym;<br />
Term;<br />
while (Symbol in[sPlus, sMinus]) do<br />
begin<br />
GetSym;<br />
Term<br />
end<br />
end; (*Expression*)<br />
<br />
procedure Condition;<br />
(*Condition = Expression ("="|"<"|">"|">="|"<="|"#") Expression.*)<br />
begin (*Condition*)<br />
Expression;<br />
if Symbol in [sEqual, sSmaller, sBigger, sBiggerEqual, sSmallerEqual, sUnequal] then<br />
begin<br />
GetSym;<br />
Expression<br />
end<br />
else<br />
ErrorExpected([sEqual, sSmaller, sBigger, sBiggerEqual, sSmallerEqual, sUnequal], Symbol)<br />
end; (*Condition*)<br />
<br />
var<br />
IdentPos : Integer;<br />
Ident : ShortString;<br />
<br />
begin (*Statement*)<br />
case Symbol of<br />
sIdent: begin<br />
Ident := Id;<br />
IdentPos := Position(ID, TablePosition);<br />
if (IdentPos = 0) then<br />
Error(ERR_PARSE_UNKNOWN_IDENT);<br />
if Table[IdentPos].Kind = itProcedure then<br />
begin<br />
//Prozeduraufruf<br />
GetSym<br />
end else if Table[IdentPos].Kind = itVariable then<br />
begin<br />
GetSym;<br />
Expect(sBecomes);<br />
GetSym;<br />
Expression<br />
end<br />
else<br />
Error(ERR_PARSE_NO_CONST_ALLOWED)<br />
end;<br />
sWrite: begin<br />
GetSym;<br />
Expression<br />
end;<br />
sIf : begin<br />
GetSym;<br />
Condition;<br />
Expect(sThen);<br />
GetSym;<br />
StatementSequence(TablePosition);<br />
Expect(sEnd);<br />
GetSym;<br />
while Symbol = sElseIf do<br />
begin<br />
GetSym;<br />
Condition;<br />
Expect(sThen);<br />
GetSym;<br />
StatementSequence(TablePosition);<br />
Expect(sEnd);<br />
GetSym<br />
end;<br />
if Symbol = sElse then<br />
begin<br />
GetSym;<br />
StatementSequence(TablePosition);<br />
Expect(sEnd);<br />
GetSym<br />
end<br />
end;<br />
sWhile :<br />
begin<br />
GetSym;<br />
Condition;<br />
Expect(sDo);<br />
GetSym;<br />
StatementSequence(TablePosition);<br />
Expect(sEnd);<br />
GetSym<br />
end;<br />
sBegin :<br />
begin<br />
GetSym;<br />
StatementSequence(TablePosition);<br />
Expect(sEnd);<br />
GetSym<br />
end;<br />
<br />
else<br />
(*Der Fehler darf getrost ignoriert werden*)<br />
//Error(ERR_PARSE_UNALLOWED_STATEMENT)<br />
end<br />
end; (*Statement*)<br />
<br />
begin (*Statement Sequence*)<br />
Statement;<br />
while Symbol = sSemiColon do<br />
begin<br />
GetSym;<br />
Statement<br />
end<br />
end; (*Statement Sequence*)<br />
<br />
function Declarations(TablePosition : Integer): Integer;<br />
(*<br />
VarSeq = VAR VarDecl {";" VarDecl}";".<br />
ConstSeq = CONST ConstDecl {";" ConstDecl}";".<br />
DeclSeq = {VarSeq | ConstSeq | ProcedureDecl}.<br />
<br />
*)<br />
procedure Enter(Typ : TIdentType);<br />
begin<br />
Inc(TablePosition);<br />
if TablePosition > Length(Table) - 1 then<br />
SetLength(Table, Length(Table)+16);<br />
with Table[TablePosition] do<br />
begin<br />
Name := ID;<br />
Kind := Typ<br />
end<br />
end;<br />
<br />
<br />
procedure ProcedureDecl;<br />
(*ProcedureDecl = PROCEDURE Ident";"<br />
DeclSeq BEGIN StatementSeq END Ident ";".*)<br />
var<br />
ProcedureName : String;<br />
ProcTablePos : Integer;<br />
begin<br />
Expect(sProcedure);<br />
GetSym;<br />
Expect(sIdent);<br />
Enter(itProcedure);<br />
ProcedureName := ID;<br />
<br />
GetSym;<br />
Expect(sSemiColon);<br />
GetSym;<br />
ProcTablePos := Declarations(TablePosition);<br />
Expect(sBegin);<br />
GetSym;<br />
StatementSequence(ProcTablePos);<br />
Expect(sEnd);<br />
GetSym;<br />
Expect(sIdent);<br />
if ProcedureName <> ID then<br />
Error(Format(ERR_PARSE_WRONG_PROCEDURE_ENDED, [ProcedureName, ID]));<br />
GetSym;<br />
Expect(sSemiColon);<br />
GetSym<br />
end;<br />
<br />
procedure ConstDecl;<br />
(*ConstDecl = Ident "=" Zahl.*)<br />
begin (*ConstDecl*)<br />
Expect(sIdent);<br />
GetSym;<br />
Expect(sEqual);<br />
GetSym;<br />
Expect(sInteger);<br />
Enter(itConstant);<br />
GetSym<br />
end; (*ConstDecl*)<br />
<br />
procedure VarDecl;<br />
(*VarDecl = Ident {"," Ident}.*)<br />
begin (*VarDecl*)<br />
Expect(sIdent);<br />
Enter(itVariable);<br />
GetSym;<br />
while Symbol = sComma do<br />
begin<br />
GetSym;<br />
Expect(sIdent);<br />
Enter(itVariable);<br />
GetSym<br />
end<br />
end; (*VarDecl*)<br />
<br />
begin (*Declarations*)<br />
while Symbol in [sVar, sConst, sProcedure] do<br />
case Symbol of<br />
sVar : begin<br />
GetSym;<br />
VarDecl;<br />
Expect(sSemiColon);<br />
GetSym;<br />
while Symbol = sIdent do<br />
begin<br />
VarDecl;<br />
Expect(sSemiColon);<br />
GetSym<br />
end<br />
end;<br />
sConst : begin<br />
GetSym;<br />
ConstDecl;<br />
Expect(sSemiColon);<br />
GetSym;<br />
while Symbol = sIdent do<br />
begin<br />
ConstDecl;<br />
Expect(sSemiColon);<br />
GetSym<br />
end<br />
end;<br />
sProcedure : ProcedureDecl;<br />
end;<br />
Result := TablePosition<br />
end; (*Declarations*)<br />
<br />
(*Module = MODULE Ident ";"<br />
DeclSeq<br />
BEGIN<br />
StatementSeq<br />
END Ident ".".*)<br />
var<br />
TablePosition : Integer;<br />
ModuleName : ShortString;<br />
<br />
begin (*Module*)<br />
Expect(sModule);<br />
GetSym;<br />
Expect(sIdent);<br />
ModuleName := Id;<br />
GetSym;<br />
Expect(sSemiColon);<br />
GetSym;<br />
TablePosition := Declarations(0);<br />
Expect(sBegin);<br />
GetSym;<br />
StatementSequence(TablePosition);<br />
Expect(sEnd);<br />
GetSym;<br />
Expect(sIdent);<br />
if ModuleName <> Id then<br />
Writeln(Format('%d: Warning: Module ID <> End ID', [Line]));<br />
GetSym;<br />
Expect(sSemiColon);<br />
GetSym;<br />
if Symbol <> sNone then<br />
WriteLn(Format('%d: Code after Module END is ignored!', [Line]))<br />
end (*Module*);<br />
<br />
begin<br />
AssignFile(InFile, ParamStr(1));<br />
Reset(InFile, 1);<br />
//Ch mit leerzeichen initialisieren<br />
Ch := ' ';<br />
Line := 1;<br />
GetSym;<br />
Module;<br />
<br />
WriteLn('Done, no syntax errors detected...');<br />
ReadLn;<br />
CloseFile(InFile)<br />
end.<br />
</pascal><br />
Ich schlage vor, hier eine kleine Pause einzulegen, bevor ihr euch an den Rest des Tutorials wagt. Ich denke nämlich, dass sich ein paar Informationen erst setzten müssen, bevor der nächste Teil in Angriff genommen werden kann. Niemand schreibt beim ersten Versuch einen Compiler an einem Tag...<br />
<br />
<br />
<br />
{{TUTORIAL_NAVIGATION| [[Tutorial Scripting mit JvInterpreterProgram]] | [[Tutorial Scriptsprachen Teil 2]] }}<br />
<br />
[[Kategorie:Tutorial|Scriptsprachen Teil 1]]</div>Lord Horazont