Tutorial Framebufferobject: Unterschied zwischen den Versionen

Aus DGL Wiki
Wechseln zu: Navigation, Suche
K
K (Mipmaps)
 
(20 dazwischenliegende Versionen von 6 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
{{Offline}}
+
=GL_EXT_FRAMEBUFFER_OBJECT=
 
+
==Einleitung==
==GL_EXT_FRAMEBUFFER_OBJECT==
 
===Einleitung===
 
 
Willkommen zu meinem ersten Tutorial. Das Thema des Tutorials sind Framebufferobjekte. Diese stellen derzeit die wohl einfachste Möglichkeit für Offscreen Rendering dar. Im Gegensatz zu Pixelbuffern sind sie plattformunabhängig und bieten automatisches Mipmapping. Jedoch benötigen sie mindestens OpenGL 1.1. Außerdem unterstützen Framebufferobjekte kein AntiAliasing. In der Vergangenheit hatten auch ATI Treiber einige Probleme mit ihnen, dies dürfte aber der Vergangenheit angehören.  
 
Willkommen zu meinem ersten Tutorial. Das Thema des Tutorials sind Framebufferobjekte. Diese stellen derzeit die wohl einfachste Möglichkeit für Offscreen Rendering dar. Im Gegensatz zu Pixelbuffern sind sie plattformunabhängig und bieten automatisches Mipmapping. Jedoch benötigen sie mindestens OpenGL 1.1. Außerdem unterstützen Framebufferobjekte kein AntiAliasing. In der Vergangenheit hatten auch ATI Treiber einige Probleme mit ihnen, dies dürfte aber der Vergangenheit angehören.  
  
===Das FBO erstellen===
+
==Das FBO erstellen==
 
Zuerst einmal muss das Framebufferobjekt natürlich generiert werden. Dies läuft ähnlich wie das Erstellen einer Textur ab:
 
Zuerst einmal muss das Framebufferobjekt natürlich generiert werden. Dies läuft ähnlich wie das Erstellen einer Textur ab:
<pascal>
+
<source lang="pascal">
 
var
 
var
fbo: GluInt;
+
  fbo: GluInt;
  
glGenFramebuffersEXT(1, @fbo);
+
  glGenFramebuffersEXT(1, @fbo);
</pascal>
+
</source>
 
Wie bei allem anderen auch müssen wir das Framebufferobjekt jetzt binden, damit alles, was folgt sich auf dieses Objekt bezieht:
 
Wie bei allem anderen auch müssen wir das Framebufferobjekt jetzt binden, damit alles, was folgt sich auf dieses Objekt bezieht:
<pascal>
+
<source lang="pascal">
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
+
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
</pascal>
+
</source>
 
Der erste Parameter von [[glBindFramebufferEXT]] ist das Ziel an das gebunden wird. Derzeit existiert aber nur GL_FRAMEBUFFER_EXT.
 
Der erste Parameter von [[glBindFramebufferEXT]] ist das Ziel an das gebunden wird. Derzeit existiert aber nur GL_FRAMEBUFFER_EXT.
  
===Einen Depthbuffer hinzufügen===
+
==Einen Depthbuffer hinzufügen==
 
Ein einfaches Framebufferobjekt ist noch ziemlich nutzlos. Deswegen fügen wir zuerst einmal einen der neu eingeführten Renderbuffer als Depthbuffer hinzu.  
 
Ein einfaches Framebufferobjekt ist noch ziemlich nutzlos. Deswegen fügen wir zuerst einmal einen der neu eingeführten Renderbuffer als Depthbuffer hinzu.  
 
Renderbuffer sind im Prinzip Objekte, die benutzt werden wenn ein Buffer kein zugehöriges Texturformat besitzt. Dazu zählen u.a. der Stencilbuffer, der Accumulationbuffer und der Depthbuffer. Das Binden an den Accumulationbuffer wurde allerdings auf eine spätere Extension verschoben.  
 
Renderbuffer sind im Prinzip Objekte, die benutzt werden wenn ein Buffer kein zugehöriges Texturformat besitzt. Dazu zählen u.a. der Stencilbuffer, der Accumulationbuffer und der Depthbuffer. Das Binden an den Accumulationbuffer wurde allerdings auf eine spätere Extension verschoben.  
 
Wie auch beim Framebufferobjekt müssen wir einen Renderbuffer zuerst generieren und dann binden:
 
Wie auch beim Framebufferobjekt müssen wir einen Renderbuffer zuerst generieren und dann binden:
<pascal>
+
<source lang="pascal">
 
var
 
var
depthbuffer: GluInt;
+
  depthbuffer: GluInt;
  
glGenRenderbuffersEXT(1, @depthbuffer);
+
  glGenRenderbuffersEXT(1, @depthbuffer);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
+
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
</pascal>
+
</source>
 
Derzeit hat der Renderbuffer allerdings noch keinen Speicher zugeteilt bekommen. Deswegen sagen wir OpenGL, wie wir den Renderbuffer benutzen wollen und welche Größe er haben soll:
 
Derzeit hat der Renderbuffer allerdings noch keinen Speicher zugeteilt bekommen. Deswegen sagen wir OpenGL, wie wir den Renderbuffer benutzen wollen und welche Größe er haben soll:
<pascal>
+
<source lang="pascal">
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, 512, 512);
+
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, 512, 512);
</pascal>
+
</source>
 
Mit [[glRenderbufferStorageEXT]] hat OpenGL den Speicher für den Renderbuffer, den wir als Depthbuffer nutzen, reserviert. Jetzt können wir unseren Depthbuffer zum FBO hinzufügen:
 
Mit [[glRenderbufferStorageEXT]] hat OpenGL den Speicher für den Renderbuffer, den wir als Depthbuffer nutzen, reserviert. Jetzt können wir unseren Depthbuffer zum FBO hinzufügen:
<pascal>
+
<source lang="pascal">
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
+
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
GL_RENDERBUFFER_EXT, depthbuffer);
+
  GL_RENDERBUFFER_EXT, depthbuffer);
</pascal>
+
</source>
 
Die letzte Anweisung sollte ziemlich einfach zu verstehen sein. Der zweite Parameter sagt einfach, als was der Renderbuffer genutzt werden soll.
 
Die letzte Anweisung sollte ziemlich einfach zu verstehen sein. Der zweite Parameter sagt einfach, als was der Renderbuffer genutzt werden soll.
  
===Eine Textur hinzufügen===
+
==Eine Textur hinzufügen==
 
Um Farbinformationen in unser Framebufferobjekt schreiben zu können, fügen wir eine Textur hinzu. Bevor wir sie hinzufügen können müssen wir sie natürlich noch erstellen:
 
Um Farbinformationen in unser Framebufferobjekt schreiben zu können, fügen wir eine Textur hinzu. Bevor wir sie hinzufügen können müssen wir sie natürlich noch erstellen:
<pascal>
+
<source lang="pascal">
 
var
 
var
tex: GluInt;
+
  tex: GluInt;
  
glGenTextures(1, @tex);
+
  glGenTextures(1, @tex);
glBindTextures(GL_TEXTURE_2D, tex);
+
  glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,  512, 512, 0, GL_RGBA8,
+
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  512, 512, 0, GL_RGBA8, GL_UNSIGNED_BYTE, nil);
GL_UNSIGNED_BYTE, nil);
+
</source>
</pascal>
 
 
Es dürfte eigentlich nichts völlig Unverständliches da stehen. Wir erstellen die Textur, binden sie, stellen die Filtermodi ein und erzeugen im Speicher den entsprechenden Platz. Anzumerken ist aber, dass die Textur die '''gleiche Größe wie der Renderbuffer haben muss'''. Alle Größen müssen einheitlich sein. Man kann also nicht einen Renderbuffer von 512*512 und eine Textur von 256*256 erstellen und sie zusammen in einem FBO nutzen. Hinzugefügt wird sie nach dem gleichen Schema wie der Depthbuffer:
 
Es dürfte eigentlich nichts völlig Unverständliches da stehen. Wir erstellen die Textur, binden sie, stellen die Filtermodi ein und erzeugen im Speicher den entsprechenden Platz. Anzumerken ist aber, dass die Textur die '''gleiche Größe wie der Renderbuffer haben muss'''. Alle Größen müssen einheitlich sein. Man kann also nicht einen Renderbuffer von 512*512 und eine Textur von 256*256 erstellen und sie zusammen in einem FBO nutzen. Hinzugefügt wird sie nach dem gleichen Schema wie der Depthbuffer:
<pascal>
+
<source lang="pascal">
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex, 0);
GL_TEXTURE_2D, tex, 0);
+
</source>
</pascal>
 
 
Wie gesagt, nicht viel neues hier. Der zweite Parameter, GL_COLOR_ATTACHMENT0_EXT sagt OpenGL wo die Textur hinzugefügt werden soll (man kann mehrere hinzufügen, doch dazu später mehr). Der letzte Parameter beschreibt das Mipmaping-Level.
 
Wie gesagt, nicht viel neues hier. Der zweite Parameter, GL_COLOR_ATTACHMENT0_EXT sagt OpenGL wo die Textur hinzugefügt werden soll (man kann mehrere hinzufügen, doch dazu später mehr). Der letzte Parameter beschreibt das Mipmaping-Level.
  
===Die Fehlerabfrage===
+
==Die Fehlerabfrage==
 
Derzeit wissen wir nicht, ob alles so geklappt hat, wie wir es wollten oder ob wir einen Fehler gemacht haben. Deswegen ist nun die Fehlerabfrage an der Reihe. Dazu gibt es die Funktion [[glCheckFramebufferStatusEXT]]. Die Fehlerabfrage lässt sich schön in so einer Prozedur verpacken:
 
Derzeit wissen wir nicht, ob alles so geklappt hat, wie wir es wollten oder ob wir einen Fehler gemacht haben. Deswegen ist nun die Fehlerabfrage an der Reihe. Dazu gibt es die Funktion [[glCheckFramebufferStatusEXT]]. Die Fehlerabfrage lässt sich schön in so einer Prozedur verpacken:
<pascal>
+
<source lang="pascal">
 
procedure TForm1.CheckFBO;
 
procedure TForm1.CheckFBO;
 
var
 
var
s: GlEnum;
+
  error: GlEnum;
 
begin
 
begin
s := glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
+
  error := glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
case s of:
+
  case error of
GL_FRAMEBUFFER_COMPLETE_EXT:
+
    GL_FRAMEBUFFER_COMPLETE_EXT:
Exit;
+
      Exit;
GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
+
    GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
raise Exception.Create('Incomplete attachment');
+
      raise Exception.Create('Incomplete attachment');
GL_FRAMEBUFFER_INCOMOPLETE_MISSING_ATTACHMENT_EXT:
+
    GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
raise Exception.Create('Missing attachment');
+
      raise Exception.Create('Missing attachment');
GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT:
+
    GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
raise Exception.Create('Duplicate attachment');
+
      raise Exception.Create('Incomplete dimensions');
GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
+
    GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
raise Exception.Create('Incomplete dimensions');
+
      raise Exception.Create('Incomplete formats');
GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
+
    GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
raise Exception.Create('Incomplete formats');
+
      raise Exception.Create('Incomplete draw buffer');
GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
+
    GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
raise Exception.Create('Incomplete draw buffer');
+
      raise Exception.Create('Incomplete read buffer');
GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
+
    GL_FRAMEBUFFER_UNSUPPORTED_EXT:
raise Exception.Create('Incomplete read buffer');
+
      raise Exception.Create('Framebufferobjects unsupported');
GL_FRAMEBUFFER_UNSUPPORTED_EXT:
+
  end;
raise Exception.Create('Framebuffer unsupported');
 
end;
 
 
end;
 
end;
</pascal>
+
</source>
  
===Die Codeübersicht===
+
==Die Codeübersicht==
 
Zur Übersicht noch einmal der komplette Code zum erstellen eines Framebufferobjekts:
 
Zur Übersicht noch einmal der komplette Code zum erstellen eines Framebufferobjekts:
<pascal>
+
<source lang="pascal">
 
var
 
var
fbo: GluInt;
+
  fbo:         GluInt;
depthbuffer: GluInt;
+
  depthbuffer: GluInt;
tex: GluInt;
+
  tex:         GluInt;
 
 
  
 
procedure TForm1.InitFBO;
 
procedure TForm1.InitFBO;
 
begin
 
begin
glGenFramebuffersEXT(1, @fmo);
+
  glGenFramebuffersEXT(1, @fbo);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
+
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glGenRenderbuffersEXT(1, @depthbuffer);
+
  glGenRenderbuffersEXT(1, @depthbuffer);
glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
+
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, 512, 512);
+
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, 512, 512);
glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
+
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer);  
GL_RENDERBUFFER_EXT, depthbuffer);  
+
 
glGenTextures(1, @tex);
+
  glGenTextures(1, @tex);
glBindTexture(GL_TEXTURE_2D, tex);
+
  glBindTexture(GL_TEXTURE_2D, tex);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
+
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex, 0);
GL_TEXTURE_2D, tex, 0);
+
  CheckFBO;
CheckFBO;
 
 
end;
 
end;
</pascal>
+
</source>
  
===In die Textur rendern===
+
==In die Textur rendern==
 
Der eigentliche Rendervorgang ist ziemlich simpel und unterscheidet sich nicht sonderlich vom normalen Rendern. Das Framebufferobjekt teilt sich alle Einstellungen mit dem eigentlichen  Renderingkontext. Deswegen muss eigentlich nur das FBO gebunden und der Viewport auf die entsprechende Breite und Höhe eingestellt werden:
 
Der eigentliche Rendervorgang ist ziemlich simpel und unterscheidet sich nicht sonderlich vom normalen Rendern. Das Framebufferobjekt teilt sich alle Einstellungen mit dem eigentlichen  Renderingkontext. Deswegen muss eigentlich nur das FBO gebunden und der Viewport auf die entsprechende Breite und Höhe eingestellt werden:
<pascal>
+
<source lang="pascal">
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
+
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
glPushAttrib(GL_VIEWPORT_BIT);
+
  glPushAttrib(GL_VIEWPORT_BIT);
glViewPort(0, 0, 512, 512);
+
  glViewPort(0, 0, 512, 512);
+
 
//ganz normal rendern – das Ergebnis landet in der Textur
+
  //ganz normal rendern – das Ergebnis landet in der Textur
 
 
glPopAttrib;
+
  glPopAttrib;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
</pascal>
+
</source>
 
Wie gesagt, hier gibt’s nichts bemerkenswertes: Viewport speichern, umstellen, rendern, Viewport laden und mit der letzten Zeile das FBO abschalten.
 
Wie gesagt, hier gibt’s nichts bemerkenswertes: Viewport speichern, umstellen, rendern, Viewport laden und mit der letzten Zeile das FBO abschalten.
 
Die Textur die an das Framebufferobjekt gebunden wurde, enthält das Gezeichnete und wird ganz normal mit [[glBindTexture]](GL_TEXTURE_2D, tex) benutzt.
 
Die Textur die an das Framebufferobjekt gebunden wurde, enthält das Gezeichnete und wird ganz normal mit [[glBindTexture]](GL_TEXTURE_2D, tex) benutzt.
  
===Mipmaps===
+
==Mipmaps==
 
FBOs bieten die Möglichkeit zum automatischem Erstellen der Mipmapdaten. Dazu gibt es die Funktion [[glGenerateMipmapEXT]]. Man bindet die Textur zunächst und ruft dann die Funktion auf:
 
FBOs bieten die Möglichkeit zum automatischem Erstellen der Mipmapdaten. Dazu gibt es die Funktion [[glGenerateMipmapEXT]]. Man bindet die Textur zunächst und ruft dann die Funktion auf:
<pascal>
+
<source lang="pascal">
 
procedure TForm1.Render;
 
procedure TForm1.Render;
 
begin
 
begin
//...
+
  //...
glBindTexture(GL_TEXTURE_2D, tex);
+
  glBindTexture(GL_TEXTURE_2D, tex);
glGenerateMipmapEXT(GL_TEXTURE_2D);
+
  glGenerateMipmapEXT(GL_TEXTURE_2D);
  
//Textur danach ganz normal benutzen
+
  //Textur danach ganz normal benutzen
  
 
end;
 
end;
</pascal>
+
</source>
 
Wenn man hingegen einen der Mipmap Filter verwendet muss der Befehl [[glGenerateMipmapEXT]] bevor der Status des FBOs überprüft oder versucht wird in die Textur zu rendern aufgerufen werden. Ein Beispiel wäre folgender Code in der Initialisierung des Framebufferobjektes:
 
Wenn man hingegen einen der Mipmap Filter verwendet muss der Befehl [[glGenerateMipmapEXT]] bevor der Status des FBOs überprüft oder versucht wird in die Textur zu rendern aufgerufen werden. Ein Beispiel wäre folgender Code in der Initialisierung des Framebufferobjektes:
<pascal>
+
<source lang="pascal">
 
var
 
var
fbo: GluInt;
+
  fbo:         GluInt;
depthbuffer: GluInt;
+
  depthbuffer: GluInt;
tex: GluInt;
+
  tex:         GluInt;
  
glGenTextures(1, @tex);
+
  glGenTextures(1, @tex);
glBindTexture(GL_TEXTURE_2D, tex);
+
  glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
+
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_MIPMAP_LINEAR);
+
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_MIPMAP_LINEAR);
glGenerateMipmapEXT(GL_TEXTURE_2D);
+
  glGenerateMipmapEXT(GL_TEXTURE_2D);
</pascal>
+
</source>
  
===Und wieder aufräumen===
+
==Und wieder aufräumen==
 
Beim Beenden unseres Programms soll das alles natürlich auch wieder gelöscht werden:
 
Beim Beenden unseres Programms soll das alles natürlich auch wieder gelöscht werden:
<pascal>
+
<source lang="pascal">
glDeleteFramebuffersEXT(1, @fbo);
+
  glDeleteFramebuffersEXT(1, @fbo);
glDeleteRenderbuffersEXT(1, @depthbuffer);
+
  glDeleteRenderbuffersEXT(1, @depthbuffer);
glDeleteTextures(1, @tex);
+
  glDeleteTextures(1, @tex);
</pascal>
+
</source>
 
Ich denke das Ganze ist selbsterklärend.
 
Ich denke das Ganze ist selbsterklärend.
  
===Mehrere Texturen in einem FBO===
+
==Mehrere Texturen in einem FBO==
 
Wie vorhin erwähnt gibt es auch die Möglichkeit mehrere Texturen an ein Framebufferobjekt zu binden. Dazu schauen wir uns nochmal den Befehl zum hinzufügen einer Textur an:
 
Wie vorhin erwähnt gibt es auch die Möglichkeit mehrere Texturen an ein Framebufferobjekt zu binden. Dazu schauen wir uns nochmal den Befehl zum hinzufügen einer Textur an:
<pascal>
+
<source lang="pascal">
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,  
+
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex, 0);
tex, 0);
+
</source>
</pascal>
+
Der zweite Parameter sagt OpenGL an welches COLOR_ATTACHMENT die Textur zugefügt wird. Die derzeitigen Spezifikationen erlauben bis zu 16 COLOR_ATTACHMENTS (GL_COLOR_ATTACHMENT0_EXT bis GL_COLOR_ATTACHMENT15_EXT). Allerdings ist die wirkliche Anzahl von möglichen COLOR_ATTACHMENTS durch die Grafikkarte und deren Treiber begrenzt. Die maximale Anzahl kann durch folgenden Code ermittelt werden:
Der zweite Parameter sagt OpenGL an welches COLOR_ATTATCHMENT die Textur zugefügt wird. Die derzeitigen Spezifikationen erlauben bis zu 16 COLOR_ATTACHMENTS (GL_COLOR_ATTACHMENT0_EXT bis GL_COLOR_ATTACHMENT15_EXT). Allerdings ist die wirkliche Anzahl von möglichen COLOR_ATTACHMENTS durch die Grafikkarte und deren Treiber begrenzt. Die maximale Anzahl kann durch folgenden Code ermittelt werden:
+
<source lang="pascal">
<pascal>
 
 
var
 
var
maxbuffers: GluInt;
+
  maxbuffers: GluInt;
  
glGetIntegeri(GL_MAX_COLOR_ATTACHMENTS, @maxbuffers);
+
  glGetIntegeri(GL_MAX_COLOR_ATTACHMENTS_EXT, @maxbuffers);
</pascal>
+
</source>
 
Eine zweite, wie oben beschrieben erstellte Textur, kann also mit folgendem Code zum Framebufferobjekt hinzugefügt werden:
 
Eine zweite, wie oben beschrieben erstellte Textur, kann also mit folgendem Code zum Framebufferobjekt hinzugefügt werden:
<pascal>
+
<source lang="pascal">
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT,  
+
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, tex2, 0);
GL_TEXTURE_2D, tex2, 0);
+
</source>
</pascal>
 
  
===In die zweite Textur rendern===
+
==In die zweite Textur rendern==
 
Mit dem Befehl [[glDrawBuffer]](GL_COLOR_ATTACHMENT'''n'''_EXT) lässt sich einstellen, in welchen Buffer man rendern möchte (wobei '''n''' den Buffer repräsentiert, also 0, 1, usw.). Standartmäßig ist GL_COLOR_ATTACHMENT0_EXT aktiviert (logisch, sonst hätten wir vorhin nicht viel gesehen, oder ;-) ). Soll also nur in diesen gerendert werden, ist keine weitere Anweisung nötig. Ansonsten sieht der Code folgendermaßen aus:
 
Mit dem Befehl [[glDrawBuffer]](GL_COLOR_ATTACHMENT'''n'''_EXT) lässt sich einstellen, in welchen Buffer man rendern möchte (wobei '''n''' den Buffer repräsentiert, also 0, 1, usw.). Standartmäßig ist GL_COLOR_ATTACHMENT0_EXT aktiviert (logisch, sonst hätten wir vorhin nicht viel gesehen, oder ;-) ). Soll also nur in diesen gerendert werden, ist keine weitere Anweisung nötig. Ansonsten sieht der Code folgendermaßen aus:
<pascal>
+
<source lang="pascal">
glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
+
  glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
+
  glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
glViewPort(0, 0, 512, 512);
+
  glViewPort(0, 0, 512, 512);
+
 
//in die Textur von GL_COLOR_ATTACHMENT0_EXT rendern
+
  //in die Textur von GL_COLOR_ATTACHMENT0_EXT rendern
+
 
glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);
+
  glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);
+
 
//in die Textur von GL_COLOR_ATTACHMENT1_EXT rendern  
+
  //in die Textur von GL_COLOR_ATTACHMENT1_EXT rendern  
  
glPopAttrib;
+
  glPopAttrib;
glBindFrameBuffer(GL_FRAMEBUFFER_EXT, 0);
+
  glBindFrameBuffer(GL_FRAMEBUFFER_EXT, 0);
</pascal>
+
</source>
 
Diesmal werden Viewport und Colorbuffer gespeichert, da Veränderungen immer das Framebufferobjekt und den eigentlichen Renderingkontext betreffen. Veränderungen am RC sind aber nicht gewollt, deswegen wird beides gespeichert und später wieder geladen.
 
Diesmal werden Viewport und Colorbuffer gespeichert, da Veränderungen immer das Framebufferobjekt und den eigentlichen Renderingkontext betreffen. Veränderungen am RC sind aber nicht gewollt, deswegen wird beides gespeichert und später wieder geladen.
 
Wie beim normalen Renderpass ist der Colorbuffer bereits mit den Daten die in die erste Textur geschrieben werden gefüllt. Deswegen leert man diesen normalerweise vor dem Rendern in die zweite Textur mithilfe von [[glClear]]:
 
Wie beim normalen Renderpass ist der Colorbuffer bereits mit den Daten die in die erste Textur geschrieben werden gefüllt. Deswegen leert man diesen normalerweise vor dem Rendern in die zweite Textur mithilfe von [[glClear]]:
<pascal>
+
<source lang="pascal">
glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
+
  glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
+
  glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
glViewPort(0, 0, 512, 512);
+
  glViewPort(0, 0, 512, 512);
+
 
//in die Textur von GL_COLOR_ATTACHMENT0_EXT rendern
+
  //in die Textur von GL_COLOR_ATTACHMENT0_EXT rendern
+
 
glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);
+
  glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
+
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
+
 
//in die Textur von GL_COLOR_ATTACHMENT1_EXT rendern  
+
  //in die Textur von GL_COLOR_ATTACHMENT1_EXT rendern  
  
glPopAttrib;
+
  glPopAttrib;
glBindFrameBuffer(GL_FRAMEBUFFER_EXT, 0);
+
  glBindFrameBuffer(GL_FRAMEBUFFER_EXT, 0);
</pascal>
+
</source>
 
Es ist im übrigen schneller ein FBO für mehrere Texturen zu nutzen als mehrere FBOs zu erstellen und zwischen ihnen zu wechseln.
 
Es ist im übrigen schneller ein FBO für mehrere Texturen zu nutzen als mehrere FBOs zu erstellen und zwischen ihnen zu wechseln.
  
===Multiple Render Targets===
+
==Multiple Render Targets==
 
Mithilfe des Befehls [[glDrawBuffers]] kann in einem FBO gleichzeitig in mehrere Texturen gerendert werden. Die Namen der GL_COLOR_ATTACHMENTS werden in einem Array abgelegt und mit dem Befehl übergeben:
 
Mithilfe des Befehls [[glDrawBuffers]] kann in einem FBO gleichzeitig in mehrere Texturen gerendert werden. Die Namen der GL_COLOR_ATTACHMENTS werden in einem Array abgelegt und mit dem Befehl übergeben:
<pascal>
+
<source lang="pascal">
 
var
 
var
buffers: Array[0..1] of GlEnum;
+
  buffers: Array[0..1] of GlEnum;
  
buffers[0] := GL_COLOR_ATTACHMENT0_EXT;
+
  buffers[0] := GL_COLOR_ATTACHMENT0_EXT;
buffers[1] := GL_COLOR_ATTACHMENT1_EXT;
+
  buffers[1] := GL_COLOR_ATTACHMENT1_EXT;
 
//...
 
//...
glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
+
  glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
+
  glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
glViewPort(0, 0, 512, 512);
+
  glViewPort(0, 0, 512, 512);
  
glDrawBuffers(2, @buffers);
+
  glDrawBuffers(2, @buffers);
//normal rendern
+
  //normal rendern
  
glPopAttrib;
+
  glPopAttrib;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
</pascal>
+
</source>
 
Die Hardware und der Treiber legen die Grenze für die Anzahl von Texturen in die gleichzeitig gerendert werden kann. Der Wert kann mit folgendem Code erhalten werden:
 
Die Hardware und der Treiber legen die Grenze für die Anzahl von Texturen in die gleichzeitig gerendert werden kann. Der Wert kann mit folgendem Code erhalten werden:
<pascal>
+
<source lang="pascal">
 
var
 
var
maxbuffers: GluInt;
+
  maxbuffers: GluInt;
  
glGetIntegeri(GL_MAX_DRAW_BUFFERS, @maxbuffers);
+
  glGetIntegeri(GL_MAX_DRAW_BUFFERS, @maxbuffers);
</pascal>
+
</source>
  
===MRT und GLSL===
+
==MRT und GLSL==
 
Solange man mindestens für die OpenGL Version 2.0 (bzw. GLSL Version 1.10) schreibt, kann man mithilfe von GLSL  gleichzeitig unterschiedliche Dinge in zwei Texturen rendern. Dazu benutzt man in GLSL anstelle von gl_FragColor den Array gl_FragData[]. Als Beispiel rendern wir ein rotes und ein grünes Viereck:
 
Solange man mindestens für die OpenGL Version 2.0 (bzw. GLSL Version 1.10) schreibt, kann man mithilfe von GLSL  gleichzeitig unterschiedliche Dinge in zwei Texturen rendern. Dazu benutzt man in GLSL anstelle von gl_FragColor den Array gl_FragData[]. Als Beispiel rendern wir ein rotes und ein grünes Viereck:
<pascal>
+
<source lang="pascal">
 
var
 
var
buffers: Array[0..1] of GlEnum;
+
  buffers: Array[0..1] of GlEnum;
 
//...
 
//...
buffers[0] := GL_COLOR_ATTACHMENT0_EXT;
+
  buffers[0] := GL_COLOR_ATTACHMENT0_EXT;
buffers[1] := GL_COLOR_ATTACHMENT1_EXT;
+
  buffers[1] := GL_COLOR_ATTACHMENT1_EXT;
 
//...
 
//...
glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
+
  glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
+
  glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
glViewPort(0, 0, 512, 512);
+
  glViewPort(0, 0, 512, 512);
  
glDrawBuffers(2, @buffers);
+
  glDrawBuffers(2, @buffers);
//Viereck zeichnen
+
  //Viereck zeichnen
  
glPopAttrib;
+
  glPopAttrib;
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
+
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
</pascal>
+
</source>
Der Vertexshader enthält nur das Nötigste:
+
Der aufmerksame Leser merkts - der Delphi Code hat sich eigentlich nicht verändert :-). Also auf zu den Shadern. Der Vertexshader enthält nur das Nötigste:
<glsl>
+
<source lang="glsl">
 
void main(void)
 
void main(void)
 
{
 
{
gl_Position = ftransform();
+
  gl_Position = ftransform();
 
}
 
}
</glsl>
+
</source>
 
Und der Fragmentshader sieht folgendermaßen aus:
 
Und der Fragmentshader sieht folgendermaßen aus:
<glsl>
+
<source lang="glsl">
 
void main(void)
 
void main(void)
 
{
 
{
gl_FragData[0] = vec4( 1.0, 0.0, 0.0, 1.0);
+
  gl_FragData[0] = vec4( 1.0, 0.0, 0.0, 1.0);
gl_FragData[1] = vec4( 0.0, 1.0, 0.0, 1.0);
+
  gl_FragData[1] = vec4( 0.0, 1.0, 0.0, 1.0);
 
}
 
}
</glsl>
+
</source>
 
Und siehe da – die Textur die zu GL_COLOR_ATTACHMENT0_EXT gehört bekommt die Daten für ein rotes Quad und die zweite Textur die Daten für ein grünes Quad.
 
Und siehe da – die Textur die zu GL_COLOR_ATTACHMENT0_EXT gehört bekommt die Daten für ein rotes Quad und die zweite Textur die Daten für ein grünes Quad.
 
Eine Sache bleibt aber noch zu erwähnen: Es ist die Reihenfolge in der die Werte der COLOR_ATTACHMENTS übergeben werden, die bestimmt was gl_FragData[0] und was gl_FragData[1] ist. Wenn wir also geschrieben hätten
 
Eine Sache bleibt aber noch zu erwähnen: Es ist die Reihenfolge in der die Werte der COLOR_ATTACHMENTS übergeben werden, die bestimmt was gl_FragData[0] und was gl_FragData[1] ist. Wenn wir also geschrieben hätten
<pascal>
+
<source lang="pascal">
 
var
 
var
buffers: Array[0..1] of GlEnum;
+
  buffers: Array[0..1] of GlEnum;
  
buffers[0] := GL_COLOR_ATTACHMENT1_EXT;
+
  buffers[0] := GL_COLOR_ATTACHMENT1_EXT;
buffers[1] := GL_COLOR_ATTACHMENT0_EXT;
+
  buffers[1] := GL_COLOR_ATTACHMENT0_EXT;
</pascal>
+
</source>
 
dann würde gl_FragData[0] GL_COLOR_ATTACHMENT1_EXT beeinflussen und gl_FragData[1] GL_COLOR_ATTACHMENT0_EXT.
 
dann würde gl_FragData[0] GL_COLOR_ATTACHMENT1_EXT beeinflussen und gl_FragData[1] GL_COLOR_ATTACHMENT0_EXT.
  
===Und fertig!===
+
==Und fertig!==
 
So! Geschafft. Schlussendlich bleibt zu sagen das Framebufferobjekte sehr nützliche Helfer sind, die sich relativ leicht handhaben lassen.  
 
So! Geschafft. Schlussendlich bleibt zu sagen das Framebufferobjekte sehr nützliche Helfer sind, die sich relativ leicht handhaben lassen.  
 
Zum Abschluss möchte ich euch nochmal daran erinnern, dass dies mein erstes Tutorial war, haltet euch also ordentlich ran mit Feedback ;-)
 
Zum Abschluss möchte ich euch nochmal daran erinnern, dass dies mein erstes Tutorial war, haltet euch also ordentlich ran mit Feedback ;-)
Zeile 308: Zeile 298:
 
Deathball
 
Deathball
  
===Quellen===
+
==Quellen==
 
* http://www.gamedev.net/reference/articles/article2331.asp
 
* http://www.gamedev.net/reference/articles/article2331.asp
 
* http://www.gamedev.net/reference/articles/article2333.asp
 
* http://www.gamedev.net/reference/articles/article2333.asp
  
===Weiterführende Links===
+
==Weiterführende Links==
 
* http://www.opengl.org/registry/specs/EXT/framebuffer_object.txt
 
* http://www.opengl.org/registry/specs/EXT/framebuffer_object.txt
 
* http://http.download.nvidia.com/developer/presentations/2005/GDC/OpenGL_Day/OpenGL_FrameBuffer_Object.pdf
 
* http://http.download.nvidia.com/developer/presentations/2005/GDC/OpenGL_Day/OpenGL_FrameBuffer_Object.pdf
 +
 +
{{TUTORIAL_NAVIGATION|[[Tutorial_Pixelbuffer]]|-}}
 +
 +
[[Kategorie:Tutorial|Framebufferobject]]

Aktuelle Version vom 15. Juli 2018, 22:12 Uhr

GL_EXT_FRAMEBUFFER_OBJECT

Einleitung

Willkommen zu meinem ersten Tutorial. Das Thema des Tutorials sind Framebufferobjekte. Diese stellen derzeit die wohl einfachste Möglichkeit für Offscreen Rendering dar. Im Gegensatz zu Pixelbuffern sind sie plattformunabhängig und bieten automatisches Mipmapping. Jedoch benötigen sie mindestens OpenGL 1.1. Außerdem unterstützen Framebufferobjekte kein AntiAliasing. In der Vergangenheit hatten auch ATI Treiber einige Probleme mit ihnen, dies dürfte aber der Vergangenheit angehören.

Das FBO erstellen

Zuerst einmal muss das Framebufferobjekt natürlich generiert werden. Dies läuft ähnlich wie das Erstellen einer Textur ab:

var
  fbo: GluInt;

  glGenFramebuffersEXT(1, @fbo);

Wie bei allem anderen auch müssen wir das Framebufferobjekt jetzt binden, damit alles, was folgt sich auf dieses Objekt bezieht:

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);

Der erste Parameter von glBindFramebufferEXT ist das Ziel an das gebunden wird. Derzeit existiert aber nur GL_FRAMEBUFFER_EXT.

Einen Depthbuffer hinzufügen

Ein einfaches Framebufferobjekt ist noch ziemlich nutzlos. Deswegen fügen wir zuerst einmal einen der neu eingeführten Renderbuffer als Depthbuffer hinzu. Renderbuffer sind im Prinzip Objekte, die benutzt werden wenn ein Buffer kein zugehöriges Texturformat besitzt. Dazu zählen u.a. der Stencilbuffer, der Accumulationbuffer und der Depthbuffer. Das Binden an den Accumulationbuffer wurde allerdings auf eine spätere Extension verschoben. Wie auch beim Framebufferobjekt müssen wir einen Renderbuffer zuerst generieren und dann binden:

var
  depthbuffer: GluInt;

  glGenRenderbuffersEXT(1, @depthbuffer);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);

Derzeit hat der Renderbuffer allerdings noch keinen Speicher zugeteilt bekommen. Deswegen sagen wir OpenGL, wie wir den Renderbuffer benutzen wollen und welche Größe er haben soll:

  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, 512, 512);

Mit glRenderbufferStorageEXT hat OpenGL den Speicher für den Renderbuffer, den wir als Depthbuffer nutzen, reserviert. Jetzt können wir unseren Depthbuffer zum FBO hinzufügen:

  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
  GL_RENDERBUFFER_EXT, depthbuffer);

Die letzte Anweisung sollte ziemlich einfach zu verstehen sein. Der zweite Parameter sagt einfach, als was der Renderbuffer genutzt werden soll.

Eine Textur hinzufügen

Um Farbinformationen in unser Framebufferobjekt schreiben zu können, fügen wir eine Textur hinzu. Bevor wir sie hinzufügen können müssen wir sie natürlich noch erstellen:

var
  tex: GluInt;

  glGenTextures(1, @tex);
  glBindTexture(GL_TEXTURE_2D, tex);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  512, 512, 0, GL_RGBA8, GL_UNSIGNED_BYTE, nil);

Es dürfte eigentlich nichts völlig Unverständliches da stehen. Wir erstellen die Textur, binden sie, stellen die Filtermodi ein und erzeugen im Speicher den entsprechenden Platz. Anzumerken ist aber, dass die Textur die gleiche Größe wie der Renderbuffer haben muss. Alle Größen müssen einheitlich sein. Man kann also nicht einen Renderbuffer von 512*512 und eine Textur von 256*256 erstellen und sie zusammen in einem FBO nutzen. Hinzugefügt wird sie nach dem gleichen Schema wie der Depthbuffer:

  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex, 0);

Wie gesagt, nicht viel neues hier. Der zweite Parameter, GL_COLOR_ATTACHMENT0_EXT sagt OpenGL wo die Textur hinzugefügt werden soll (man kann mehrere hinzufügen, doch dazu später mehr). Der letzte Parameter beschreibt das Mipmaping-Level.

Die Fehlerabfrage

Derzeit wissen wir nicht, ob alles so geklappt hat, wie wir es wollten oder ob wir einen Fehler gemacht haben. Deswegen ist nun die Fehlerabfrage an der Reihe. Dazu gibt es die Funktion glCheckFramebufferStatusEXT. Die Fehlerabfrage lässt sich schön in so einer Prozedur verpacken:

procedure TForm1.CheckFBO;
var
  error: GlEnum;
begin
  error := glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  case error of
    GL_FRAMEBUFFER_COMPLETE_EXT:
      Exit;
    GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
      raise Exception.Create('Incomplete attachment');
    GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
      raise Exception.Create('Missing attachment');
    GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
      raise Exception.Create('Incomplete dimensions');
    GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
      raise Exception.Create('Incomplete formats');
    GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
      raise Exception.Create('Incomplete draw buffer');
    GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
      raise Exception.Create('Incomplete read buffer');
    GL_FRAMEBUFFER_UNSUPPORTED_EXT:
      raise Exception.Create('Framebufferobjects unsupported');
  end;
end;

Die Codeübersicht

Zur Übersicht noch einmal der komplette Code zum erstellen eines Framebufferobjekts:

var
  fbo:         GluInt;
  depthbuffer: GluInt;
  tex:         GluInt;

procedure TForm1.InitFBO;
begin
  glGenFramebuffersEXT(1, @fbo);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
  glGenRenderbuffersEXT(1, @depthbuffer);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthbuffer);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, 512, 512);
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthbuffer); 

  glGenTextures(1, @tex);
  glBindTexture(GL_TEXTURE_2D, tex);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil);
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex, 0);
  CheckFBO;
end;

In die Textur rendern

Der eigentliche Rendervorgang ist ziemlich simpel und unterscheidet sich nicht sonderlich vom normalen Rendern. Das Framebufferobjekt teilt sich alle Einstellungen mit dem eigentlichen Renderingkontext. Deswegen muss eigentlich nur das FBO gebunden und der Viewport auf die entsprechende Breite und Höhe eingestellt werden:

  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
  glPushAttrib(GL_VIEWPORT_BIT);
  glViewPort(0, 0, 512, 512);

  //ganz normal rendern – das Ergebnis landet in der Textur
	
  glPopAttrib;
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

Wie gesagt, hier gibt’s nichts bemerkenswertes: Viewport speichern, umstellen, rendern, Viewport laden und mit der letzten Zeile das FBO abschalten. Die Textur die an das Framebufferobjekt gebunden wurde, enthält das Gezeichnete und wird ganz normal mit glBindTexture(GL_TEXTURE_2D, tex) benutzt.

Mipmaps

FBOs bieten die Möglichkeit zum automatischem Erstellen der Mipmapdaten. Dazu gibt es die Funktion glGenerateMipmapEXT. Man bindet die Textur zunächst und ruft dann die Funktion auf:

procedure TForm1.Render;
begin
  //...
  glBindTexture(GL_TEXTURE_2D, tex);
  glGenerateMipmapEXT(GL_TEXTURE_2D);

  //Textur danach ganz normal benutzen

end;

Wenn man hingegen einen der Mipmap Filter verwendet muss der Befehl glGenerateMipmapEXT bevor der Status des FBOs überprüft oder versucht wird in die Textur zu rendern aufgerufen werden. Ein Beispiel wäre folgender Code in der Initialisierung des Framebufferobjektes:

var
  fbo:         GluInt;
  depthbuffer: GluInt;
  tex:         GluInt;

  glGenTextures(1, @tex);
  glBindTexture(GL_TEXTURE_2D, tex);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 512, 512, 0, GL_RGBA, 	GL_UNSIGNED_BYTE, nil);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_MIPMAP_LINEAR);
  glGenerateMipmapEXT(GL_TEXTURE_2D);

Und wieder aufräumen

Beim Beenden unseres Programms soll das alles natürlich auch wieder gelöscht werden:

  glDeleteFramebuffersEXT(1, @fbo);
  glDeleteRenderbuffersEXT(1, @depthbuffer);
  glDeleteTextures(1, @tex);

Ich denke das Ganze ist selbsterklärend.

Mehrere Texturen in einem FBO

Wie vorhin erwähnt gibt es auch die Möglichkeit mehrere Texturen an ein Framebufferobjekt zu binden. Dazu schauen wir uns nochmal den Befehl zum hinzufügen einer Textur an:

  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex, 0);

Der zweite Parameter sagt OpenGL an welches COLOR_ATTACHMENT die Textur zugefügt wird. Die derzeitigen Spezifikationen erlauben bis zu 16 COLOR_ATTACHMENTS (GL_COLOR_ATTACHMENT0_EXT bis GL_COLOR_ATTACHMENT15_EXT). Allerdings ist die wirkliche Anzahl von möglichen COLOR_ATTACHMENTS durch die Grafikkarte und deren Treiber begrenzt. Die maximale Anzahl kann durch folgenden Code ermittelt werden:

var
  maxbuffers: GluInt;

  glGetIntegeri(GL_MAX_COLOR_ATTACHMENTS_EXT, @maxbuffers);

Eine zweite, wie oben beschrieben erstellte Textur, kann also mit folgendem Code zum Framebufferobjekt hinzugefügt werden:

  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_TEXTURE_2D, tex2, 0);

In die zweite Textur rendern

Mit dem Befehl glDrawBuffer(GL_COLOR_ATTACHMENTn_EXT) lässt sich einstellen, in welchen Buffer man rendern möchte (wobei n den Buffer repräsentiert, also 0, 1, usw.). Standartmäßig ist GL_COLOR_ATTACHMENT0_EXT aktiviert (logisch, sonst hätten wir vorhin nicht viel gesehen, oder ;-) ). Soll also nur in diesen gerendert werden, ist keine weitere Anweisung nötig. Ansonsten sieht der Code folgendermaßen aus:

  glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
  glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
  glViewPort(0, 0, 512, 512);

  //in die Textur von GL_COLOR_ATTACHMENT0_EXT rendern

  glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);

  //in die Textur von GL_COLOR_ATTACHMENT1_EXT rendern 

  glPopAttrib;
  glBindFrameBuffer(GL_FRAMEBUFFER_EXT, 0);

Diesmal werden Viewport und Colorbuffer gespeichert, da Veränderungen immer das Framebufferobjekt und den eigentlichen Renderingkontext betreffen. Veränderungen am RC sind aber nicht gewollt, deswegen wird beides gespeichert und später wieder geladen. Wie beim normalen Renderpass ist der Colorbuffer bereits mit den Daten die in die erste Textur geschrieben werden gefüllt. Deswegen leert man diesen normalerweise vor dem Rendern in die zweite Textur mithilfe von glClear:

  glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
  glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
  glViewPort(0, 0, 512, 512);

  //in die Textur von GL_COLOR_ATTACHMENT0_EXT rendern

  glDrawBuffer(GL_COLOR_ATTACHMENT1_EXT);
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);

  //in die Textur von GL_COLOR_ATTACHMENT1_EXT rendern 

  glPopAttrib;
  glBindFrameBuffer(GL_FRAMEBUFFER_EXT, 0);

Es ist im übrigen schneller ein FBO für mehrere Texturen zu nutzen als mehrere FBOs zu erstellen und zwischen ihnen zu wechseln.

Multiple Render Targets

Mithilfe des Befehls glDrawBuffers kann in einem FBO gleichzeitig in mehrere Texturen gerendert werden. Die Namen der GL_COLOR_ATTACHMENTS werden in einem Array abgelegt und mit dem Befehl übergeben:

var
  buffers: Array[0..1] of GlEnum;

  buffers[0] := GL_COLOR_ATTACHMENT0_EXT;
  buffers[1] := GL_COLOR_ATTACHMENT1_EXT;
//...
  glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
  glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
  glViewPort(0, 0, 512, 512);

  glDrawBuffers(2, @buffers);
  //normal rendern

  glPopAttrib;
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

Die Hardware und der Treiber legen die Grenze für die Anzahl von Texturen in die gleichzeitig gerendert werden kann. Der Wert kann mit folgendem Code erhalten werden:

var
  maxbuffers: GluInt;

  glGetIntegeri(GL_MAX_DRAW_BUFFERS, @maxbuffers);

MRT und GLSL

Solange man mindestens für die OpenGL Version 2.0 (bzw. GLSL Version 1.10) schreibt, kann man mithilfe von GLSL gleichzeitig unterschiedliche Dinge in zwei Texturen rendern. Dazu benutzt man in GLSL anstelle von gl_FragColor den Array gl_FragData[]. Als Beispiel rendern wir ein rotes und ein grünes Viereck:

var
  buffers: Array[0..1] of GlEnum;
//...
  buffers[0] := GL_COLOR_ATTACHMENT0_EXT;
  buffers[1] := GL_COLOR_ATTACHMENT1_EXT;
//...
  glBindFramebuffer(GL_FRAMEBUFFER_EXT,  fbo);
  glPushAttrib(GL_VIEWPORT_BIT or GL_COLOR_BUFFER_BIT);
  glViewPort(0, 0, 512, 512);

  glDrawBuffers(2, @buffers);
  //Viereck zeichnen

  glPopAttrib;
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

Der aufmerksame Leser merkts - der Delphi Code hat sich eigentlich nicht verändert :-). Also auf zu den Shadern. Der Vertexshader enthält nur das Nötigste:

void main(void)
{
  gl_Position = ftransform();
}

Und der Fragmentshader sieht folgendermaßen aus:

void main(void)
{
  gl_FragData[0] = vec4( 1.0, 0.0, 0.0, 1.0);
  gl_FragData[1] = vec4( 0.0, 1.0, 0.0, 1.0);
}

Und siehe da – die Textur die zu GL_COLOR_ATTACHMENT0_EXT gehört bekommt die Daten für ein rotes Quad und die zweite Textur die Daten für ein grünes Quad. Eine Sache bleibt aber noch zu erwähnen: Es ist die Reihenfolge in der die Werte der COLOR_ATTACHMENTS übergeben werden, die bestimmt was gl_FragData[0] und was gl_FragData[1] ist. Wenn wir also geschrieben hätten

var
  buffers: Array[0..1] of GlEnum;

  buffers[0] := GL_COLOR_ATTACHMENT1_EXT;
  buffers[1] := GL_COLOR_ATTACHMENT0_EXT;

dann würde gl_FragData[0] GL_COLOR_ATTACHMENT1_EXT beeinflussen und gl_FragData[1] GL_COLOR_ATTACHMENT0_EXT.

Und fertig!

So! Geschafft. Schlussendlich bleibt zu sagen das Framebufferobjekte sehr nützliche Helfer sind, die sich relativ leicht handhaben lassen. Zum Abschluss möchte ich euch nochmal daran erinnern, dass dies mein erstes Tutorial war, haltet euch also ordentlich ran mit Feedback ;-)

Cya, Deathball

Quellen

Weiterführende Links


Vorhergehendes Tutorial:
Tutorial_Pixelbuffer
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.