Tiefenunschärfe

Aus DGL Wiki
Wechseln zu: Navigation, Suche
Hinweis: Dieser Artikel wird gerade Offline bearbeitet!

Bitte haben Sie etwas Geduld und nehmen Sie keine Änderungen vor, bis der Artikel hochgeladen wurde.

(weitere Artikel)
WIP Offline.jpg

Realität und Virtualität

Bevor wir zum Programmieren übergehen erstmal ein paar Kleinigkeiten vorweg. Wen diese nicht interessieren, kann den gesamten Punkt 1 eigentlich überspringen und direkt bei 2.1 anfangen. Ansonsten geht es los mit folgender Frage:

Was ist Tiefenunschärfe?

Beispiel aus der Realität anhand von Schachfiguren

Die sogenannte Tiefenunschärfe ist ein Effekt, den zu mindest aus der Realität jeder kennt, (und ich meine damit ausnahmslos jeder, der nicht gerade blind ist,) dem aber kaum jemand große Beachtung schenkt. Das liegt hauptsächlich daran, dass es sich um einen Effekt handelt, der für das menschliche Auge (bzw. für die beiden menschlichen Augen) vollkommen alltäglich ist.

Eine theoretische Praxisübung: Daumenakrobatik.

Zur Demonstration nehme man seinen rechten Daumen (oder irgendeinen anderen Finger) und halte ihn vor einen größeren, entfernten Gegenstand wie zum Beispiel eine Wand. Dann fokussiere man mal den Daumen im Blickfeld. Was uns interessiert ist jetzt nicht, wie schön unser Daumen denn aussieht, sondern viel mehr was mit dem Hintergrund passiert. Er verschwimmt und solange wir unseren Daumen klar erkennen können, können wir den Hintergrund nur unscharf sehen. Anders herum können wir unseren Daumen nicht klar erkennen, während wir den Hintergrund fokussieren. Dieser Effekt verstärkt sich übrigens, je größer der Abstand von Daumen und Hintergrund ist. Das funktioniert übrigens auch seitlich. Man halte seine zwei Daumen auf eine Entfernung von etwa fünf Zentimetern nebeneinander, und versuche mal Beide gleichzeitig zu fokussieren.

Erkenntnis

Nach spätestens einer Minute anstrengender Verrenkungen sollte man gemerkt haben, was der hauptsächliche Aspekt ist, um den es hier geht; Der Mensch ist nicht in der Lage, mehrere Bildpunkte gleichzeitig scharf darzustellen. (Kameras haben übrigens das selbe Problem)

Virtuelle Tiefenunschärfe

Jetzt erst einmal weg vom "RL". Tiefenunschärfe ist wie wir nun herausgefunden haben sollten kein Effekt, der unglaublich viel Spannung erzeugt wie zum Beispiel eine Explosion. Trotzdem bin ich der Meinung, dass sie in vielen Spielen fehlt und optisch gesehen einiges hermachen kann. Die fehlende Tiefenunschärfe ist oftmals der Grund, warum Computerspiele oder -Animationen sehr künstlich wirken. Warum? Ganz einfach: Ein jeder Mensch mit gesundem Geist, hält das was er gewohnt ist für realistisch, beziehungsweise reell. Er ist darauf spezialisiert, Muster zu erkennen und wieder zu erkennen. Auf unser Beispiel bezogen heißt das: wir sind es gewohnt, immer nur einen bestimmten Punkt in unserem Blickfeld scharf erkennen zu können. Da in Computerspielen häufig einfach alles gleichermaßen scharf gestellt wird, bemerkt unser Unterbewusstsein den Unterschied und stempelt das gesehene als künstlich ab. In der folgenden Anleitung versuche ich eine Technik zu erläutern, wie man Tiefenunschärfe ganz simpel in OpenGL umsetzen kann.

Programmierung

Die Technik, die ich hier erläutern möchte, ist wirklich sehr simpel und erfordert nicht all zu viel an Vorkenntnissen, ist aber auch nicht unbedingt für Anfänger geeignet, da man doch das eine oder andere falsch machen und somit die Performance in den Keller reißen kann. Eine Voraussetzung wäre eventuell glAccum, es ist aber nicht all zu schlimm, wenn man damit noch nicht gearbeitet hat.

Praktische Theorie

So. Jetzt geht's an's Programmieren. Womit fangen wir an? Richtig.

Die Grundidee

Wir wollen also jetzt einen Blureffekt, der abhängig vom Abstand zu einem bestimmten Punkt verschieden stark ist. Je weiter entfernt, umso stärker. Mit anderen Worten: Blurstärke ~ Radius. Nun wäre es reichlich sinnlos, für jeden Bildpunkt, der gezeichnet wird einen Radius zu berechnen und jeden Punkt einzeln zu blurren. Das wäre programmiertechnisch viel zu umständlich und würde der Performance wohl so stark in die Suppe spucken, dass diese eine zweite Schüssel bräuchte. Wir lösen das Problem, indem wir umdenken:

Beispiel zur Proportionalen Abhängigkeit zum Radius

Rotation!

Die Idee ist so einfach wie gut. Nehmen wir zum Beispiel zwei Punkte auf einer Geraden. Dann rotieren wir die Gerade um den Ersten Punkt (rot) und den zweiten Punkt (grün) natürlich mit. der Abstand zwischen dem alten und dem neuen zweiten Punkt ist abhängig von Rotation und Abstand zum Ersten Punkt. Das klingt komplizierter als es eigentlich ist.

Umsetzung

Nun stellt sich die Frage: was bringt uns dieses Prinzip und wie setzen wir es in unserer 3D-Szenerie um? Es ist wirklich nicht all zu kompliziert. Wir wissen, dass es genau einen Punkt gibt, den wir scharf stellen wollen, der Rest soll abhängig vom Abstand verwischt dargestellt werden. Wie macht mal Blur? Genau. Man mischt nah aneinander liegende Farbwerte miteinander. Da wir das aber wie gesagt nicht per Pixel machen wollen, kommt jetzt die Idee mit der Rotation und der Accum Buffer ins Spiel. Die Idee ist es, die Szenerie mehrfach in den Accum Buffer zu Rendern und dabei jedes mal leicht um den Fokuspunkt zu rotieren.

Der erste Schritt wäre, den Accum Buffer vorzubereiten:

  glClear(GL_ACCUM_BUFFER_BIT); //Accum Buffer leeren

Dann wäre es sinnvoll eine Variable für den Fokuspunkt anzulegen:

  TVector3f = record
    x,y,z: single;
  end;
  focus: tvector3f; //fokuspunkt

Als nächstes legen wir eine for-Schleife an, in der die Szene gerendert werden wird, und setzen an den Anfang das gewohnte freiräumen des Farb- und Tiefenbuffers.

  for i := 0 to 3 do
  begin
    glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
    [...]
  end;

Nun kommt in unsere Schleife der wichtigste Teil; Als erstes speichern wir die momentane Matrix ab. Dann führen wir die Rotation um unseren Fokusspunkt aus (wie die Rotation umgesetzt wird ist Geschmackssache), Rendern sie Szene, setzen die Matrix zurück und speichern das gerade Gerenderte Szenario im Accum Buffer ab.

    glPushMatrix();
 
    //leicht um den Fokuspunkt rotieren
    if i <> 0 then
    begin
      glTranslatef(focus.x,focus.y,focus.z);
      glRotatef(-Cam.rot.y,0,1,0);
      if i = 1 then glRotatef(FoD_str,-1,0,0);
      if i = 2 then glRotatef(FoD_str,0.5,-0.5,0);
      if i = 3 then glRotatef(FoD_str,0.5,+0.5,0);
      glRotatef(Cam.rot.y,0,1,0);
      glTranslatef(-focus.x,-focus.y,-focus.z);
    end;
 
    Scene.Render; //Szenerie darstellen
 
    glPopMatrix();
 
    //zum Accum Buffer hinzufügen
    glAccum (GL_ACCUM, 1/4);

Schlussendlich wollen wir unsere (im Beispiel) vier (man kann auch mehr oder weniger nehmen) gerenderten, übereinandergelegten Szenerien auch sehen können, also lassen wir das ganze ausgeben.

  //In den Accum Buffer gerendertes zurückgeben
  glAccum (GL_RETURN, 1.0);
  glFlush();

Diese ganzen Bestandteile fügen wir in einen großen, prozeduralen Topf und rühren kräftig.

Endergebnis

Letztendlich sollten wir etwa so etwas heraus bekommen: (FoD_str = Blurstärke gesamt)

procedure Kochtopf; // ;)
var 
  i    : integer;
  focus: tvector3f; //fokuspunkt
begin
glPushMatrix();

  //hier kann die Szenerie noch an die richtige Stelle verschoben werden,
  //falls gewünscht.

  glClear(GL_ACCUM_BUFFER_BIT); //Accum Buffer leeren
  for i := 0 to 3 do
  begin
    if (i > 0) and (FoD_str = 0) then break; //falls die verwacklungsstärke = 0 ist, wird die szene nur einmal gerendert
    glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    //leicht um den Fokuspunkt rotieren
    if i <> 0 then
    begin
      glTranslatef(focus.x,focus.y,focus.z);
      glRotatef(-Cam.rot.y,0,1,0);
      if i = 1 then glRotatef(FoD_str,-1,0,0);
      if i = 2 then glRotatef(FoD_str,0.5,-0.5,0);
      if i = 3 then glRotatef(FoD_str,0.5,+0.5,0);
      glRotatef(Cam.rot.y,0,1,0);
      glTranslatef(-focus.x,-focus.y,-focus.z);
    end;

    Scene.Render; //Szenerie darstellen

    glPopMatrix();

    //zum Accum Buffer hinzufügen
    if FoD_str = 0 then
      glAccum (GL_ACCUM, 1) 
    else 
      glAccum (GL_ACCUM, 0.25);
  end;
  //In den Accum Buffer gerendertes zurückgeben
  glAccum (GL_RETURN, 1.0);
  glFlush();

  glPopMatrix();

Achtung!

Bevor wir uns jetzt auf unseren Quellcode stürzen und diese einfache und schöne Änderung einbringen, sollten wir uns nochmal unseren Quellcode angucken oder ihn zumindest durchdenken; Die Scene.Render Prozedur wird im Beispiel vier mal aufgerufen, was zwei zu beachtende Aspekte bringt. Erstens:

Warnung.png Die Grafikkarte hat das vierfache an Arbeit zu tun.

Zweitens:

Warnung.png Berechnungen, wie zum beispiel das Bewegen von Spielfiguren dürfen auf gar keinen Fall innerhalb der Scene.Render Prozedur stattfinden! Das würde dazu führen, dass die Berechnungen vier mal statt einmal ausgeführt werden würden!

(Ich habe diesen Fehler anfangs im Zusammenhang mit Octrees, Nodes und Frustum gemacht. Kein schöner Anblick für den Grafikfanatiker.)

Beispiel zur Proportionalen Abhängigkeit zum Radius

Blurry End

Das war es eigentlich auch schon wieder, ihr könnt jetzt aufhören eure Daumen anzustarren. Rechts nochmal ein abschließendes Bild zur Erklärung. Ich hoffe, es ist klar geworden, wie einfach diese Technik anzuwenden ist. Außerdem hoffe ich auf Verbesserungsvorschläge, oder aber auch andere Methoden zur darstellung von Tiefenunschärfe, denn vollkommen ist sie nicht. In diesem Sinne: Unscharfen Abend noch. MFG Custom.