Tutorial Framebufferobject

Aus DGL Wiki
Version vom 24. September 2007, 01:03 Uhr von Deathball (Diskussion | Beiträge) (Die Seite wurde neu angelegt: <div align="center"> {|{{Prettytable}} |widht="80%"|<div style="color:#F09000;font-size:125%"><center>'''Hinweis:''' Dieser Artikel wird gerade Offline bearbeitet!<br> ...)

(Unterschied) ← Nächstältere Version | Aktuelle Version (Unterschied) | Nächstjüngere Version → (Unterschied)
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


GL_FRAMEBUFFER_EXT

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);
	glBindTextures(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
	s: GlEnum;
begin
	s := glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	case s of:
		GL_FRAMEBUFFER_COMPLETE_EXT:
			Exit;
		GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
			raise Exception.Create('Incomplete attachment');
		GL_FRAMEBUFFER_INCOMOPLETE_MISSING_ATTACHMENT_EXT:
			raise Exception.Create('Missing attachment');
		GL_FRAMEBUFFER_INCOMPLETE_DUPLICATE_ATTACHMENT_EXT:
			raise Exception.Create('Duplicate 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('Framebuffer 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, @fmo);
	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.