Tutorial StereoSehen
Inhaltsverzeichnis
Stereo einmal sehen statt hören
Einleitung
Viele Tiere haben mit dem Menschen eins gemein: Zwei Augen. Dies kann unterschiedliche Vorteile haben, etwa in zwei Richtungen gleichzeitig sehen zu können, oder, was viel häufiger ist, um räumlich sehen zu können. Die Welt aus zwei leicht unterschiedlichen Blickwinkeln zu sehen ermöglicht es dem Gehirn Abstände und Geschwindigkeiten besser einzuschärtzen, als mit einem Auge. Das es uns auch mit einem Auge gelingt, erkennt man dran, dass das in begrenztem Umfang auch bei Fotos gelingt und wenn wir mit einem geschlossenem Auge durch ein Zimmer laufen trotzdem nicht überall anstossen. Das funktioniert sogar so gut, dass Menschen, die nur stark eingeschränkt oder gar nicht räumlich sehen können, dies gar nicht so selten erst bei Routineuntersuchungen von Augenärzten oder im Verlauf ihrer Musterung erfahren.
An einer Stelle ist es jedoch egal, ob wir räumlich sehen können oder nicht - beim Spielen und Arbeiten am Computer. Aber 3D Spiele wollen doch gerade eines: uns die Welt möglichst realistisch vor Augen führen. Leider hat der Computer meist nur einen Monitor, wir aber zwei Augen... Und damit Good Bye räumliches Sehen? Nein, nein, so einfach dürfen wir uns nicht geschlagen geben. Tatsächlich fallen mir gleich eine Reihe von Möglichkeiten ein: Stereogramme, wie sie eine Zeit lang modern waren, und in vielen Büchern vorkommen: langes Stieren auf stylisch eingefärbte Buchseiten. Bei 3D-Spielen waren eine Zeit lang 3D-Shutterbrillen beliebt, bei denen jeweils ein Auge abgedunkelt wird, auf dem Monitor für das Auge ein Bild angezeigt und dann gewechselt wird. Zeigt man für jedes Auge ein leicht versetztes Bild an und wechselt die Bilder häufig genug, entsteht für den Betrachter ein 3-Dimensionales Bild der Szenerie. Leider benötigt man für diese Technik schnell schaltende Röhrenmonitore - mit aktuellen TFT Monitoren ist dies, wegen ihrer langsamen Schaltgeschwindigkeiten nicht möglich und statt eines 3D-Bildes entsteht irgend ein wenig aussagekräftiges Geschmiere.
Das ist noch immer kein Grund, entmutigt zu sein: In Kinofilmen wird noch heute oft die Technik der Anaglyphe eingesetzt. Dabei muss der Betrachter eine 3D-Brille aufsetzen, die vor jedes Auge einen anderen Farbfilter setzt, etwa rot und grün. Bei der Filmaufnahme werden dann zwei überkreuzt filmende Kameras verwendet.
Der 3D Effekt stellt sich bei den meisten Menschen schnell ein, bei Anderen dauert es ein wenig oder erfordert etwas Übung. Den Effekt gar nicht geniessen können üblicherweise nur stark Schielende, Einäugige und Blinde. Eine wesentliche Einschränkung bei diesem Verfahren ist jedoch, dass das entstehende Bild meist einfarbig, ähnlich einem s/w Film, ist. Der entstehende 3D Effekt gleicht dieses Manko meiner Ansicht nach auf alle Fälle aus - und da wir möglichst Niemanden ausschliessen möchten, unsere Programme zu bestaunen, werden wir unsere Programme sowohl für "normale" Grafikausgabe als auch für 3D-Brillen vorbereiten.
Die 3D-Brille
Jeder der weitermachen möchte, sollte sich spätestens hier eine 3D-Brille besorgen. Diese gibts z.B. beim Optiker und sollte möglichst einen roten und einen grünen Filter besitzen. Zur Not tuts auch rot-blau, ist jedoch wegen den stark auseinanderliegenden Wellenlängen des roten und blauen Lichtes weniger gut geeignet. Einigen Büchern zu optischen Täuschungen liegen ebenfalls Brillen bei. Solltet ihr keine passende, vorgefertigte Brille finden, könnt ihr sie auch mit etwas Pappe oder Karton und Farbfolien aus dem Bastelladen selbst bauen - oder ganz nobel: mit Filtern aus dem Fotoladen statt ordinären Farbfolien.
Projektionen
Bei der üblichen, perspektivischen Projektion ist die Sache einfach: Man hat ein Auge, einen Öffnungswinkel für die Kamera und die Entfernung für die nahe Clipping-Ebene:
Beim Stereo-Sehen wird die Sache ein Wenig komplizierter. Da man mit Beiden Augen auf die Projektionsebene Bildschirm schaut, ist diese für beide Augen identisch, jedoch sind die Blickkegel nicht mehr gerade, sondern schief:
Zu allem überfluss können wir unsere Augen auch noch auf eine bestimmte Entfernung ausrichten, d.h. die Ebene, auf die die Augen eingestellt sind, muss nicht der Entfernung der nahen Clippling-Plane entsprechend - das Problem können wir jedoch ganz einfach mithilfe der Strahlensätze lösen.
... var zNear, zFar, Oeffnungswinkel, Augenabstand, Zielweite : Single; ... const PBufSize = 1024; //Seitenläne des PBuffers var SVerh, ROeffnung : Single; Breitenhaelfte, NeardZielweite : Single; left, right, top, bottom : Single; procedure CalcValues; begin ROeffnung := DegToRad(Oeffnungswinkel / 2); //Halber Öffnungswinkel in RAD Breitenhaelfte := zNear * Tan(ROeffnung); //Halbe Breite der Proj. Ebene NeardZielweite := zNear / Zielweite; end; begin if Opt.Stereo then //Stereo Modus begin glViewport(0, 0, ClientWidth, ClientHeight); //Projektionsmatrix resetten glMatrixMode(GL_PROJECTION); glLoadIdentity(); SVerh := ClientWidth/ClientHeight; //Breite zu Höhe CalcValues; //Ränder der Projektionsebene für glFrustum left := - SVerh * Breitenhaelfte - 0.5 *Augenabstand*NeardZielweite; right := SVerh * Breitenhaelfte - 0.5 *Augenabstand*NeardZielweite; top := Breitenhaelfte; bottom := -Breitenhaelfte; glFrustum(left, right, bottom, top, zNear, zFar); glMatrixMode(GL_MODELVIEW); glLoadIdentity; //PBuffer erzeugen und aktivieren, wenn noch nicht geschehen if not Assigned(PBuffer) then begin PBuffer := TPixelBuffer.Create(PBufSize, PBufSize, DC, RC, Self); wglShareLists(RC, PBuffer.RC); PBuffer.Enable; InitGl; end else PBuffer.Enable; glViewport(0, 0, PBufSize, PBufSize); //Projektionsmatrix resetten glMatrixMode(GL_PROJECTION); glLoadIdentity(); //Diesmal das Frustum in die andere Richtung schiefstellen(+ statt -) left := - SVerh * Breitenhaelfte + 0.5 *Augenabstand*NeardZielweite; right := SVerh * Breitenhaelfte + 0.5 *Augenabstand*NeardZielweite; top := Breitenhaelfte; bottom := -Breitenhaelfte; glFrustum(left, right, bottom, top, zNear, zFar); glMatrixMode(GL_MODELVIEW); glLoadIdentity; PBuffer.Disable; end else begin //Normale Ansicht glViewport(0, 0, ClientWidth, ClientHeight); //Projektionsmatrix resetten glMatrixMode(GL_PROJECTION); glLoadIdentity(); //Perspektivische Darstellung SVerh := ClientWidth/ClientHeight; CalcValues; //Hier wird nichts verschoben left := - SVerh * Breitenhaelfte; right := SVerh * Breitenhaelfte; top := Breitenhaelfte; bottom := -Breitenhaelfte; glFrustum(left, right, bottom, top, zNear, zFar); glMatrixMode(GL_MODELVIEW); glLoadIdentity; end; end; ...
Und Rendern?
Nun müssen wir nur noch zwei Bilder anzeigen. Die einfachste Möglichkeit dürfte sein, ein Bild für das linke Auge in einen PBuffer oder sonstige Textur zu rendern, dann das Bild für das rechte Auge in den Hintergrundpuffer. Der Inhalt des PBuffers wird dann mittels Blending auch in den Hintergrundpuffer gezeichnet und voila. Wir haben zwei verschiedene Bilder auf dem Schirm. Zu beachten ist noch, dass die Farben mittels glColorMask beim Rendern maskiert werden müssen - wir wollen ja für die beiden Augen unterschiedliche Farben anzeigen.
procedure TStereoForm.Render; procedure RenderScene; begin //kein glLoadIdentity am Anfang! Das muss vorher gemacht worden sein - //für die Augen muss ja bereits verschoben worden sein. Also Achtung. //Brav mit Push und Pop Matrix arbeiten. ... end; var Error : glUInt; begin if (Opt.Stereo) and (Assigned(PBuffer)) then begin //Stereo Modus PBuffer.Enable; glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT); glLoadIdentity; glTranslatef(Augenabstand/2, 0, 0); //linkes Auge bekommt grün und blau - das sollte über Optionen wählbar sein glColorMask(false, true, true, true); RenderScene; PBuffer.Disable; glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT); glLoadIdentity; glTranslatef(-Augenabstand/2, 0, 0); //rechtes Auge bekommt nuir rot glColorMask(true, false, false, true); RenderScene; //Und zusammenblenden glColorMask(true, true, true, true); glDepthFunc(GL_ALWAYS); glMatrixMode(GL_PROJECTION); glPushMatrix; glLoadIdentity; glOrtho(0, 1, 1, 0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity; glDisable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); PBuffer.Bind; glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE); glBegin(GL_QUADS); glTexCoord2f(1, 0); glVertex2f(1,1); glTexCoord2f(1, 1); glVertex2f(1,0); glTexCoord2f(0, 1); glVertex2f(0,0); glTexCoord2f(0, 0); glVertex2f(0,1); glEnd(); glDisable(GL_BLEND); glEnable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); PBuffer.Release; glMatrixMode(GL_PROJECTION); glPopMatrix; glMatrixMode(GL_MODELVIEW); glDepthFunc(GL_LESS); end else begin //"Mono" Modus glClearColor(0.0, 0.0, 0.0, 0.0); glClear(GL_DEPTH_BUFFER_BIT or GL_COLOR_BUFFER_BIT); glLoadIdentity; RenderScene; end; ... end;
Und 3D-Shutterbrillen?
Wer eine 3D-Sutterbrille und entsprechendes Equipment zuhause hat, kann seine Anwendungen auch für diese anpassen. Einige Grafikkarten haben bereits passende Treiber, die die Ausgabe der Programme automatisch anpassen. Wer das nicht will, sollte sich einmal mit Stereo-OpenGl auseinandersetzen. Bei der dglOpenGl genügt es, den Rendering Context mit der Option opStereo zu erstellen. Mit glDrawBuffer(GL_BACK_LEFT/GL_BACK_RIGHT) kann dann für jedes Auge in einen anderen Puffer gerendert werden.
Abschluss
Ich hoffe euch hat dieser kleine Exkurs gefallen. Vielleicht läuft einem ja mal das eine oder andere Programm über den Weg, wo man die Wahl zwischen Tiefensehen und normaler Ansicht hat - mich würde es jedenfalls freuen, zumal es ja nicht wirklich schwer ist, seine Programme entsprechend anzupassen und es doch ein wesentlich anderes Gefühl der Szene erzeugt, als üblicherweise vor dem Computer. Ich bin jedenfalls gespannt, erwarte mir aber nicht allzuviel - bislang war das Feedback und eure Demos ja sehr bescheiden. Wo ich da nur immer die Motivation hernehme, euch mit ein paar neuen Ideen zu versorgen? ;-D
Euer Delphic (Nico Michaelis)
Kleine Anmerkung am Rande
Nach Fertigstellung des Tutorials wurde ich darauf aufmerksam gemacht, daß man um den Einsatz des P-Buffers herumkommt - was Geschwindigkeit und Kompatibilität erhöht. Man muss nur zwischen den glColorMask Befehlen einmal den Tiefenpuffer leeren. In etwa läuft das ganze dann wie folgt:
private void RenderMono() { RenderSettings rs = new RenderSettings(); rs.Mono(RenderPanel.ClientRectangle.Width, RenderPanel.ClientRectangle.Height);
GL.Clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT); //Szene Rendern }
private void RenderStereo() { GL.Clear(GL.COLOR_BUFFER_BIT | GL.DEPTH_BUFFER_BIT);
RenderSettings rs = new RenderSettings(); rs.Stereo(true, RenderPanel.ClientRectangle.Width, RenderPanel.ClientRectangle.Height); GL.ColorMask(1, 0, 0,1); //Szene Rendern
rs.Stereo(false, RenderPanel.ClientRectangle.Width, RenderPanel.ClientRectangle.Height); GL.Clear(GL.DEPTH_BUFFER_BIT); GL.ColorMask(0, 1, 1,1); //Szene Rendern
if (GL.GetError() != GL.NO_ERROR) { } }
private void Render() { if (rc != null) { GL.PushAttrib(GL.ALL_ATTRIB_BITS); GL.ClearColor(0.0f, 0f, 0f, 1f); if (stereo) RenderStereo(); else RenderMono(); rc.SwapBuffers(); GL.PopAttrib(); } }
Die rs.stereo(true/false, ...) Befehle laden die benötigten Matrizen für das linke bzw. rechte Auge.
|
||
Vorhergehendes Tutorial: Tutorial_StencilSpiegel |
Nächstes Tutorial: Tutorial_Alphamasking |
|
Schreibt was ihr zu diesem Tutorial denkt ins Feedbackforum von DelphiGL.com. Lob, Verbesserungsvorschläge, Hinweise und Tutorialwünsche sind stets willkommen. |