Tutorial StereoSehen

Aus DGL Wiki
Wechseln zu: Navigation, Suche

Stereo einmal sehen statt hören

Tutorial Stereo anaglyph.jpg

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ätzen, 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 sind 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. Für diese Technik ist jedoch ein schnell schaltender Monitor von Nöten - Entweder ein Röhrenmonitor oder ein neuer TFT-Bildschirm mit 120Hz Bildwiederholrate.

Bei der im Jahr 2009 neu aufgelebten 3D-Technik in den Kinos kommen entweder die oben beschriebenen Shutterbrillen oder Brillen mit Polarisationsfiltern zum Einsatz.

Eine äußerst alte aber gut funktionierende Technik ist die der Anaglyphe: 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 genießen können üblicherweise nur stark Schielende, Einäugige und Blinde. Eine wesentliche Einschränkung bei, Anaglyphenverfahren 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.

Tutorial Stereo brille.png

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:

Tutorial Stereo perspektivisch.png

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:

Tutorial Stereo stereo.png

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 entsprechen - 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


Kleine Anmerkung am Rande

Nach Fertigstellung des Tutorials wurde ich darauf aufmerksam gemacht, dass 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.

Dateien


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.