Tutorial Skyboxen: Unterschied zwischen den Versionen
(→Skyboxen: + Hinweise) |
DGLBot (Diskussion | Beiträge) K (Der Ausdruck ''<pascal>(.*?)</pascal>'' wurde ersetzt mit ''<source lang="pascal">$1</source>''.) |
||
Zeile 70: | Zeile 70: | ||
− | <pascal>procedure TGLForm.LoadSkyBox; | + | <source lang="pascal">procedure TGLForm.LoadSkyBox; |
const | const | ||
SkyBoxName : array[0..5] of String = ('BK', 'FR', 'DN', 'UP', 'LF', 'RT'); | SkyBoxName : array[0..5] of String = ('BK', 'FR', 'DN', 'UP', 'LF', 'RT'); | ||
Zeile 84: | Zeile 84: | ||
end; | end; | ||
GenerateSkyBox(256, 256, 256); | GenerateSkyBox(256, 256, 256); | ||
− | end;</ | + | end;</source> |
Zeile 99: | Zeile 99: | ||
− | <pascal>procedure TGLForm.GenerateSkyBox(pWidth, pHeight, pLength : TGLFloat); | + | <source lang="pascal">procedure TGLForm.GenerateSkyBox(pWidth, pHeight, pLength : TGLFloat); |
var | var | ||
px,py,pz : TGLFloat; | px,py,pz : TGLFloat; | ||
Zeile 151: | Zeile 151: | ||
glEnd; | glEnd; | ||
glEndList; | glEndList; | ||
− | end;</ | + | end;</source> |
Zeile 160: | Zeile 160: | ||
Bevor wir die Skybox nun zeichnen '''deaktivieren wir das Schreiben in den Tiefenpuffer''' via [[glDepthMask]]. So vermeiden wir dass später gerenderte Objekte durch den von der Skybox geschriebenen Tiefenwert betroffen werden : | Bevor wir die Skybox nun zeichnen '''deaktivieren wir das Schreiben in den Tiefenpuffer''' via [[glDepthMask]]. So vermeiden wir dass später gerenderte Objekte durch den von der Skybox geschriebenen Tiefenwert betroffen werden : | ||
− | <pascal>glDepthMask(False);</ | + | <source lang="pascal">glDepthMask(False);</source> |
Aufgerufen wird die Skybox nun über ihre Displayliste : | Aufgerufen wird die Skybox nun über ihre Displayliste : | ||
− | <pascal> | + | <source lang="pascal"> |
glMatrixMode(GL_MODELVIEW); | glMatrixMode(GL_MODELVIEW); | ||
glLoadIdentity; | glLoadIdentity; | ||
Zeile 172: | Zeile 172: | ||
glRotatef(yrot, 0, 1, 0); | glRotatef(yrot, 0, 1, 0); | ||
glCallList(List); | glCallList(List); | ||
− | SwapBuffers(FDC);</ | + | SwapBuffers(FDC);</source> |
Einleitend wechseln wir in die Modelansichtsmatrix und laden dort die Grundmatrix. Dann muss nur der Tiefenpuffer gelöscht (und eventuell andere verwendete Puffer) werden, den Farbpuffer müssen Sie nicht mehr löschen, da die Skybox den ganzen Schirm füllt. Jetzt muss unsere Matrix nur noch entsprechend dem Sichtwinkel des Betrachters rotiert werden, bevor unsere Displayliste aufgerufen wird. An die Spielerposition wird hier natürlich nicht transformiert, da die Skybox ja unendlich weit vom Spieler entfernt sein soll. | Einleitend wechseln wir in die Modelansichtsmatrix und laden dort die Grundmatrix. Dann muss nur der Tiefenpuffer gelöscht (und eventuell andere verwendete Puffer) werden, den Farbpuffer müssen Sie nicht mehr löschen, da die Skybox den ganzen Schirm füllt. Jetzt muss unsere Matrix nur noch entsprechend dem Sichtwinkel des Betrachters rotiert werden, bevor unsere Displayliste aufgerufen wird. An die Spielerposition wird hier natürlich nicht transformiert, da die Skybox ja unendlich weit vom Spieler entfernt sein soll. | ||
Zeile 179: | Zeile 179: | ||
Nach dem Rendern der Skybox müssen wir das Schreiben in den Tiefenpuffer natürlich wieder aktivieren : | Nach dem Rendern der Skybox müssen wir das Schreiben in den Tiefenpuffer natürlich wieder aktivieren : | ||
− | <pascal>glDepthMask(True);</ | + | <source lang="pascal">glDepthMask(True);</source> |
Version vom 10. März 2009, 19:14 Uhr
Inhaltsverzeichnis
Skyboxen
Teil 1: Die Texturen mit Terragen erzeugen
Dieser Teil des Tutorials beschäftigt sich mit dem Erstellen der für eine Skybox benötigten Texturen mit Hilfe des Programms Terragen. Sie sollten jedoch grundlegende Kenntnisse über dieses Programm besitzen, da ich nicht zu sehr ins Detail gehen werde. Ich werde nur die Schritte erklären, die nötig sind, um ein grundlegendes Terrain zu erstellen und die Skybox zu rendern. Sie sollten also zuerst besser das Online-Tutorial zu Terragen lesen.
Wenn Terragen gestartet wird, erscheint die Oberfläche wie abgebildet:
Wir bechäftigen uns zuerst mit dem rechten Fenster, um unsere Landschaft zu generieren. Wählen Sie es aus und klicken sie auf den "Generate Terrain"-Button, um den folgenden Dialog zu öffnen:
In der Auswahlbox "Method" kann die zur Landschaftsgenerierung genutzte Methode gewählt werden, die mit den Optionen auf der rechten Seite noch angepasst werden kann. Ich habe mich dazu entschieden, für dieses Tutorial einen Canyon zu generieren. Klicken Sie nach ihrer Wahl auf "Generate Terrain" und Sie werden Ihre neu generierte Landschaft im "Landscape"-Dialog sehen:
Wenn Ihnen das generierte Terrain nicht gefällt, können Sie es mit den oberen drei Buttons nachträglich anpassen.
Der Punkt, von dem drei Linien ausgehen, ist die Position des Betrachters. Mit einem Linksklick kann man dessen Position und mit einem Rechtsklick dessen Blickpunkt ändern. Jetzt ist es Zeit die Oberflächentextur zu wählen. Sie könnten zwar selbst eine generieren, da dies jedoch sehr zeitintensiv ist ,habe ich mich dazu entschieden, eine der mit Terragen gelieferten Vorlagen zu öffnen. Klicken Sie dazu auf den "Open..."-Button in der "Surface Map"-Sektion des Dialogs und wählen Sie eine Vorlage aus. Natürlich kann diese im Nachhinein noch abgeändert werden.
Und jetzt zum "Rendering"-Dialog:
Oben links können Sie eine Vorschau Ihrer Landschaft sehen, welche mit dem "Render Preview"-Button aktualisiert werden kann. Aber benutzen Sie nicht die höchste Detailstufe, nur um die Vorschau zu rendern, da dies sonst relativ lange dauern kann. Diese Detailstufe sollte nur zum endgültigen Rendern genutzt werden, während für das Preview die mittlere Stufe ausreicht.
Unter "Image Size" können Sie die Größe Ihrer Skyboxtexturen wählen. Für eine Skybox sollten Höhe und Breite gleich sein. Ich habe 512x512 Pixel gewählt, so das die Skybox später detailliert genug aussieht, um einen realisitschen Eindruck zu vermitteln. Aber vergessen Sie nicht, das größere Texturen mehr Speicher benötigen und Performance kosten.
Bevor Sie die Skybox rendern können, müssen noch einige Einstellungen geändert werden, um das benötigte 90° Sichtfeld zu erhalten. Klicken Sie auf "Camera Settings" um den folgenden Dialog zu öffnen:
Für das korrekte Sichtfeld müssen Sie den "Zoom"-Wert auf 1 setzen. Danach können Sie diesen Dialog auch schon wieder schliessen und zum "Rendering Control"-Dialog wechseln. Setzen Sie dort alle Werte für die Kameraausrichtung ("Camera orientation") auf 0.
Nachdem Sie den Detailschieber auf Maxmimum gesetzt haben- sind Sie Bereit die Texturen zu rendern. Der erste Teil wird bei einer Kopfausrichtung ("head orientation") von 0° generiert und stellt ihr hinteres Bild dar. Das rechte wird bei 90°, das vordere bei 180° und das linke bei 270° gerendert.
Setzen Sie die Kopfausrichtung jetzt wieder auf 0° und den "Pitch" auf 90°, um den Himmel zu rendern, also ihr oberes Bild. Die untere Textur für die Skybox wird bei einem "Pitch" von -90° gerendert.
Die hier von mir gewählten Namen sind natürlich unverbindlich und an das Beispielprogramm angepasst.
Hier die 6 Texturen meiner Skybox:
Teil 2 - Nutzung der Skybox in OpenGL
Nachdem Sie nun ihre Skyboxtexturen generiert haben, ist es Zeit, sie in Ihrer OpenGL-Anwendung zu nutzen. Ich werde hier nicht die Grundschritte zur Erstellung eines Renderkontextes oder zur Darstellung von Dreiecken zeigen. Wenn Sie darüber mehr wissen wollen, suchen sie eine der diversen OpenGL Seiten mit Tutorials zu diesen Themen auf.
Schritt 1 - Die Texturen laden
Texturen mit der TglBMP Klasse zu laden ist sehr leicht. Der Einfachheit halber bevorzuge ich es, alle Texturen der Skybox in einem Array zu organisieren. Ausserdem nutze ich lokal in der Ladeprozedur ein Array in dem alle Dateinamen abgelegt sind, so dass die Texturen leicht in einer Schleife geladen werden können:
procedure TGLForm.LoadSkyBox;
const
SkyBoxName : array[0..5] of String = ('BK', 'FR', 'DN', 'UP', 'LF', 'RT');
var
i : Integer;
begin
for i := 0 to High(SkyBoxTexture) do
begin
SkyBoxTexture[i] := TGLBmp.Create;
SkyBoxTexture[i].LoadImage(SkyBoxName[i]+'.jpg');
SkyBoxTexture[i].SetTextureWrap(GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
SkyBoxTexture[i].GenTexture(False, False);
end;
GenerateSkyBox(256, 256, 256);
end;
Das global deklarierte SkyBoxTexture-Array beinhaltet unsere sechs Texturen und im lokal deklarierten SkyBoxName-Array befinden sich alle zugehörigen Dateinamen für jede Textur, so dass Sie die Ladeprozedur ganz einfach an Ihre Namenskonventionen anpassen können.
Zuerst laden wir die Texturen in einer Schleife. Dabei muss jedesmal die TglBMP Klasse erstellt werden, dann wird die Bilddatei geladen und der Texturewrap auf GL_CLAMP_TO_EDGE für die S und die T Koordinate gesetzt. Dies ist nötig damit unsere Kanten passend aneinandergefügt werden.
Beachten Sie auch, dass ich die GenTexture-Prozedur der TglBMP-Klasse abgeändert habe. Ihr werden jetzt zwei Parameter vom Typ BOOLEAN übergeben, wobei der erste angibt, ob die Textur im komprimierten S3-Format geladen gibt und der zweite, ob das anisotropische Filtering genutzt werden soll. Wenn Sie diese Eigenschaften nutzen wollen, müssen Sie sich jedoch zuerst davon versichern, dass die Grafikkarte diese unterstützt.
Sie sollten jedoch davon absehen, Ihre Skybox Texturen komprimiert zu laden weil dadurch die Qualität stark herabgesetzt werden kann.
Schritt 2 - Die Displayliste erstellen
Nachdem die Texturen geladen wurden, generieren wir die Displayliste für unsere Skybox. Wie Sie bereits wissen sollten, sind Displaylisten um ein Vielfaches schneller, als die Quads in jedem Frame neu zu zeichnen.
procedure TGLForm.GenerateSkyBox(pWidth, pHeight, pLength : TGLFloat);
var
px,py,pz : TGLFloat;
begin
List := glGenLists(1);
glNewList(List, GL_COMPILE);
px := - pWidth / 2;
py := - pHeight / 2;
pz := - pLength / 2;
SkyBoxTexture[0].Bind;
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f(px, py, pz);
glTexCoord2f(0, 1); glVertex3f(px, py + pHeight, pz);
glTexCoord2f(1, 1); glVertex3f(px + pWidth, py + pHeight, pz);
glTexCoord2f(1, 0); glVertex3f(px + pWidth, py, pz);
glEnd;
SkyBoxTexture[1].Bind;
glBegin(GL_QUADS);
glTexCoord2f(1, 0); glVertex3f(px, py, pz + pLength);
glTexCoord2f(1, 1); glVertex3f(px, py + pHeight, pz + pLength);
glTexCoord2f(0, 1); glVertex3f(px + pWidth, py + pHeight, pz + pLength);
glTexCoord2f(0, 0); glVertex3f(px + pWidth, py, pz + pLength);
glEnd;
SkyBoxTexture[2].Bind;
glBegin(GL_QUADS);
glTexCoord2f(1, 1); glVertex3f(px + pWidth, py, pz);
glTexCoord2f(1, 0); glVertex3f(px + pWidth, py, pz + pLength);
glTexCoord2f(0, 0); glVertex3f(px, py, pz + pLength);
glTexCoord2f(0, 1); glVertex3f(px, py, pz);
glEnd;
SkyBoxTexture[3].Bind;
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f(px, py + pHeight, pz);
glTexCoord2f(0, 1); glVertex3f(px, py + pHeight, pz + pLength);
glTexCoord2f(1, 1); glVertex3f(px + pWidth, py + pHeight, pz + pLength);
glTexCoord2f(1, 0); glVertex3f(px + pWidth, py + pHeight, pz);
glEnd;
SkyBoxTexture[4].Bind;
glBegin(GL_QUADS);
glTexCoord2f(1, 0); glVertex3f(px, py, pz);
glTexCoord2f(0, 0); glVertex3f(px, py, pz + pLength);
glTexCoord2f(0, 1); glVertex3f(px, py + pHeight, pz + pLength);
glTexCoord2f(1, 1); glVertex3f(px, py + pHeight, pz);
glEnd;
SkyBoxTexture[5].Bind;
glBegin(GL_QUADS);
glTexCoord2f(0, 0); glVertex3f(px + pWidth, py, pz);
glTexCoord2f(1, 0); glVertex3f(px + pWidth, py, pz + pLength);
glTexCoord2f(1, 1); glVertex3f(px + pWidth, py + pHeight, pz + pLength);
glTexCoord2f(0, 1); glVertex3f(px + pWidth, py + pHeight, pz);
glEnd;
glEndList;
end;
Dieser Teil des Quelltextes ist sehr einfach zu verstehen. Die Variablen px, py und pz werden genutzt, um die Skybox zu zentrieren. Dann wird ein Quader mit den Kantenlängen pWidth, pHeight und pLength und den zugehörigen Texturen in unsere Displayliste kompiliert. Der erste Quad ist die hintere Seite, der zweite die vordere Seite, der dritte ist der Boden, der vierte der Deckel, der fünfte die linke Seite und der letzte ist die rechte Seite.
Schritt 3 - Die Skybox zeichnen
Bevor wir die Skybox nun zeichnen deaktivieren wir das Schreiben in den Tiefenpuffer via glDepthMask. So vermeiden wir dass später gerenderte Objekte durch den von der Skybox geschriebenen Tiefenwert betroffen werden :
glDepthMask(False);
Aufgerufen wird die Skybox nun über ihre Displayliste :
glMatrixMode(GL_MODELVIEW);
glLoadIdentity;
glClear(GL_DEPTH_BUFFER_BIT);
glRotatef(xrot, 1, 0, 0);
glRotatef(yrot, 0, 1, 0);
glCallList(List);
SwapBuffers(FDC);
Einleitend wechseln wir in die Modelansichtsmatrix und laden dort die Grundmatrix. Dann muss nur der Tiefenpuffer gelöscht (und eventuell andere verwendete Puffer) werden, den Farbpuffer müssen Sie nicht mehr löschen, da die Skybox den ganzen Schirm füllt. Jetzt muss unsere Matrix nur noch entsprechend dem Sichtwinkel des Betrachters rotiert werden, bevor unsere Displayliste aufgerufen wird. An die Spielerposition wird hier natürlich nicht transformiert, da die Skybox ja unendlich weit vom Spieler entfernt sein soll.
Nach dem Rendern der Skybox müssen wir das Schreiben in den Tiefenpuffer natürlich wieder aktivieren :
glDepthMask(True);
Kleiner Hinweis : Wer möchte kann die beiden glDepthMask-Aufrufe natürlich auch direkt in der oben erstellten Displayliste ablegen.
Jetzt könnte zusätzlich noch eine animierte Wolkenschicht wie in Quake3 oder Unreal zu sehen hinzugefügt werden. Eine Alternative zu einer Skybox sind übrigens Skydomes, die mittels passender Formeln (und evtl. auch Shadern) dynamischer gestaltet werden können.
Hinweise
Größe der Texturen
Je nach Anwendung bzw. Blickrichtung des Betrachters nimmt die Skybox einen recht großen Platz auf dem Schirm bei, wodurch Qualitätsunterschiede hier direkt sichtbar sind. Daher sollte eine entsprechend hohe Auflösung gewählt werden, also 1024x1024 und höher. Man benötigt zwar 6 solch großer Texturen (die untere Textur kann man sich aber evtl. je nach Szenario sparen) die allerdings auch trotz solch hoher Auflösung in den Speicher moderner Grafikkarten passen sollten.
Texturenkompression
Bereits seit mehreren Jahren können 3D-Beschleuniger Texturen in einem Format mit fester Kompressionsrate ablegen und nutzen, wodurch sowohl Grafikkartenspeicher als auch Bandbreite gespart wird. Allerdings wirkt sich diese besonders bei Texturen mit langen Farbverläufen bzw. Übergängen (wie bei Skyboxen oft der Fall) negativ aus und ist i.d.R. auch direkt erkennbar, wie auf folgendem Bild sichtbar ist :
Die Verschlechterung des Bildes auf der rechten Seite sollte leicht erkennbar sein. Wenn man die Texturen dann auch noch als JPG abgelegt hat und dort bereits komprimiert hat (auch bei 100% Qualitätseinstellung komprimiert JPG immernoch) verschlechtert sich das Gesamtbild nochmals.
Das Beispielprogramm
Und zu guter Letzt noch ein Bild unserer Skybox aus dem Demoprogramm:
Hinweis: Für diesen Screenshot habe ich 512x512 Pixel große Texturen genutzt, während die Texturen im Download nur 256x256 Pixel groß sind!
Download
Die Skyboxdemo (inklusive Quellcode) herunterladen
Autor: Sascha Willems
|
||
Vorhergehendes Tutorial: Tutorial_Terrain3 |
Nächstes Tutorial: - |
|
Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com. Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen. |