Normalerweise wird die gesamte Szene direkt in den Backbuffer des Onscreen-Framebuffers gerendert. Nachdem ein Bild fertig gerendert wurde, wird Back- und Frontbuffer getauscht und die GPU zeigt das berechnete Bild auf dem Bildschirm an (Doppelpufferung).
In manchen Anwendungen ist es wünschenswert ein Bild zu rendern, ohne es gleich direkt dem Anwender anzuzeigen. Dieses "Offscreen-Rendering" kann mit dem Graph::FrameBuffer
Knoten bewerkstelligt werden. Dabei werden Teile des Szenengraphen in einen "Offscreen-Framebuffer" gerendern. Der Offscreen-Framebuffer ist nicht direkt am Bildschirm sichtbar, kann aber z.B. als dynamische Textur verwendet werden.
Framebuffer & FrameBufferTexture
Ein Graph::FrameBuffer
Knoten stellt das Basisobjekt für Offscreen-Rendering zur Verfügung ("Render Target"). Um auf die berechneten Daten zugreifen zu können, muss das Graph::FrameBuffer
-Objekt mit zumindest einem passenden Graph::ITexture
-Objekt verknüpft werden. Das Render Ergebnis wird dann in dem verknüpften Graph::FrameBufferTexture
-Objekt (oder in mehreren Textur-Objekten) gespeichert.
Die Zuordnung erfolgt über die entsprechende id
:
colorTextureId
(RGBA Pixel-Farbwerte)depthTextureId
(z-Buffer Werte)stencilTextureId
(Stencil Buffer Werte)
Oftmals werden lediglich die Farbwerte benötigt, der Renderingprozess benötigt aber für die korrekte Sichtbarkeitsbestimmung einen aktiven Z-Buffer ("Depth Buffer"). In solchen Fällen ist es nicht notwendig eine eigene Z-Buffer-Textur (über depthTextureId
) anzugeben. Das Setzen eines expliziten Z-Buffer Formats (Attribut depthBufferFormat
) ist ausreichend, damit ein interner Z-Buffer angelegt und verwendet wird.
Um in den FrameBuffer
rendern zu können, muss dieser mit einem oder mehreren Graph::View
Knoten verknüpft werden (Attribut frameBufferId
). Alle Objekte die für die Kamera deses Views sichtbar sind, werden in den FrameBuffer
gerendert.
- Zu beachten
- Achtung! Werden mehrere Texturen mit einem Framebuffer verbunden, müssen alle Texturen dieselbe Größe haben.
Overlay Example
Als einfaches Beispiel erzeugen wir eine FrameBufferTexture
und zeigen diese als Overlay über einem Hintergrundbild an. Das Hintergrundbild zeichnen wir als PlaneGeometry
-Objekt mit passender Textur.
<TextureState slot="0" textureId="/textures/image"/> <PlaneGeometry textureSlots="0" materialSlot="3" scaleFactorX="800" scaleFactorY="600"/>
Für das Offscreen-Rendering benötigen wir ein FrameBufferTexture
-Objekt und ein FrameBuffer
-Objekt. Mit dem Attribut colorTextureId
wird dem FrameBuffer
die id
der Farbtextur zugewiesen.
<FrameBufferTexture id="fb_tex_overlay" type="FLAT" sizeX="700" sizeY="500"/> <FrameBuffer id="fb_overlay" colorTextureId="fb_tex_overlay"/>
Jetzt benötigen wir noch einen View
und ein Kamera
-Objekt. Mit dem Attribut frameBufferId
legen wir fest, dass dieser View
nicht direkt in den Backbuffer sondern in unser FrameBuffer
-Objekt zeichnen soll. Alles was für die Kamera
fb_camera
sichtbar ist, wird daher direkt in die FrameBufferTexture
fb_tex_overlay
gezeichnet.
<View id="view_overlay" frameBufferId="fb_overlay"/> <Camera id="fb_camera" projectionType="ORTHOGRAPHIC" viewId="view_overlay" unitSizeX="1" unitSizeY="1" nearPlane="0.1" farPlane="10" colorClearValue="c0000000h">
Wir verwenden eine orthografische Kamera
mit einer unitSize
von 1. Dadurch erhalten wir einen 1:1 Pixelmatch. Mit colorClearValue="c0000000h"
definieren wir ein halbtransparentes Schwarz als Hintergrundfarbe (ARGB).
Als Anzeigeobjekte definieren wir einfach einige TextGeometry
-Objekte und ein Button
-Objekt als Kindknoten der Kamera
und ordnen diese nacheinander mit einem Aligner
-Objekt an.
<Transform posZ="-1"> <Aligner id="aligner" axis="Y" order="DESCENDING" spacing="20" posX="0" posY="200" containerAlignmentY="TOP"> <Instance graphResourceId="package_main:graph_textnode" fontSize="30" textAlignmentX="CENTER" text="Lightsword inspired 100% 2D nightmare."/> <Instance graphResourceId="package_main:graph_textnode" fontSize="12" textAlignmentX="CENTER" text="Charming by hate for endless runner Kill Screen! Superbot / John Doe / et al."/> <Instance graphResourceId="package_main:graph_textnode" text="Tactical because kids impressive experimentation but Software Development Kit in Windows VVVVVV trailer. Rip-off podcast Crayon Physics Deluxe Kill Screen Gnome’s Lair map editor Ludum Dare Indiegogo passion. The Banner Saga permadeath Molleindustria Microsoft god mode rapidshare explosions, Agency while self-publishing Minecraft Pac-Man Messhof retina otherwise indiegames.com otherwise internet amazing Twitter invite."/> <Instance graphResourceId="package_main:graph_textnode" text="Nanomachines and developer or sequel 16GB RAM before artsy although inventory inspiration famous. Infamous Anita Sarkeesian Gabe Newell Daedalic Geforce sale Third-Person-Shooter. McPixel if extra life Batman otherwise railgun although financial until Ron Gilbert and Sony new point and click. VVVVVV damsel in distress beat ‘em up PES Derek Yu 100% World of Warcraft internet gameplay hard to master."/> <Instance graphResourceId="package_main:graph_textnode" text="Speed was niche market when Bastion itch.io deep art tollerance video coding. Love when small rpgmaker in FTW for FTL and experimentation by Messhof while energy 2D. Analogue: A Hate Story 1-Bit since color palette IndieCade since Messhof split-screen. Feature Desktop Dungeons so history writer text adventure. Early access Daniel Benmergui and announcement because style parallax scrolling otherwise clones not-game The Banner Saga in peaceful self-publishing small team, IOS inclusive happy glitch if Analogue: A Hate Story provocative local multiplayer."/> <Instance graphResourceId="package_main:graph_textnode" text="Explore Christine Love or Phil Fish Assassin's Creed when Tecmo i7 while Flash Win8 future. Quirky Polytron Corporation gamestar.de PlayStation 3 in-game advertising action wrestling theme small-size bugged."/> <Instance graphResourceId="package_main:graph_textnode" text="Spiritual stealth 2016 portable lovely Street Fighter cactus Mountain Dew flight simulator shard. Map editor procedural content generation Vlambeer it's like experimentation studio i7. Doritos life-changing thoughtful gamescom IndieCade. Text trash non-commercial even if WASD Luftrausers cartridge life-changing. Limited 10/10 Tecmo television PC non-commercial bugged re-release FPS member Minecraft. "/> <Node> <Button id="button" sizeX="300" sizeY="50"/> <PlaneGeometry parametersSlot="0" materialSlot="2" scaleFactorX="300" scaleFactorY="50"/> <Instance graphResourceId="package_main:graph_textnode" fontSize="20" text="murlengine.com" textAlignmentX="CENTER"/> </Node> </Aligner> </Transform>
- Bemerkungen
- Der Blindtext wurde mit dem Text-Generator Vidya Gaemz Ipsum erzeugt.
Um die gerenderte Textur auch anzuzeigen, verwenden wir ein PlaneGeometry
-Objekt, welches die FrameBufferTexture
verwendet und mit der "normalen" Kamera gezeichnet wird.
<TextureState textureId="fb_tex_overlay" slot="1"/> <PlaneGeometry id="planeDFB" materialSlot="4" textureSlots="1" scaleFactorX="700" scaleFactorY="500" depthOrder="2"/>
Als Ergebnis erhalten wir zwei PlaneGeometry
-Objekte, wobei ein Objekt ein vorgegebenes Bild als Textur verwendet und das zweite Objekt die FrameBufferTexture
verwendet. Allerdings ist das Bild der FrameBufferTexture
auf den Kopf gestellt.
Um die Y-Koordinaten zu spiegeln ändern wir die texCoordY1
und texCoordY2
Attribute der PlaneGeometry
von default 0/1 auf 1/0.
<PlaneGeometry id="planeDFB" materialSlot="4" textureSlots="1" scaleFactorX="700" scaleFactorY="500" depthOrder="2" texCoordY1="1" texCoordY2="0"/>
Scrolling
Mit ein paar Zeilen Code können wir den angezeigten Text auch noch verschiebbar machen. Dafür definieren wir einen zweiten Button
mit id="buttonDFB"
passend zum PlaneGeometry
-Objekt.
<Button id="buttonDFB" sizeX="700" sizeY="500"/>
Abhängig von der Event-Position am Button ändern wir direkt die Position des Aligner
Knotens.
// Scrolling if (deviceHandler->WasMouseButtonPressed(IEnums::MOUSE_BUTTON_LEFT) || deviceHandler->WasTouchPressed(0)) { UInt32 id = mButtonDFB->GetActiveEventId(0); const Graph::Vector& vectorB = mButtonDFB->GetLocalEventPosition(id); mOldPosY = vectorB.y; } else if (deviceHandler->IsMouseButtonPressed(IEnums::MOUSE_BUTTON_LEFT) || deviceHandler->IsTouchPressed(0)) { UInt32 id = mButtonDFB->GetActiveEventId(0); const Graph::Vector& vectorB = mButtonDFB->GetLocalEventPosition(id); Real transformY = mAligner->GetTransformInterface()->GetPositionY(); Real newPos = transformY + (vectorB.y - mOldPosY); mOldPosY = vectorB.y; mAligner->GetTransformInterface()->SetPositionY(newPos); }
Soll der Text in einer Schleife laufen, müssen wir die Position noch passend korrigieren, wenn der Aligner
vollständig aus dem sichtbaren Bereich geschoben wurde:
Real fbSizeYHalf = mButtonDFB->GetScaleFactorY()/2; const Graph::IBoundingVolume* boundingVolume = mAligner->GetBoundingVolume(); const Graph::Box& box = boundingVolume->GetInnerLocalBox(); const Graph::Vector& min = box.GetMinimum(); const Graph::Vector& max = box.GetMaximum(); Real alignerSizeY = max.y - min.y; if (newPos < -fbSizeYHalf) newPos = alignerSizeY + fbSizeYHalf; else if (newPos > alignerSizeY + fbSizeYHalf) newPos = -fbSizeYHalf;
Button Events
Zusätzlich zu den TextGeometry
-Objekten haben wir einen Button
als Kindknoten der Framebuffer Kamera erstellt. Wenn wir diesen Button
wie gewohnt aus der Programmlogik abfragen wollen, müssen wir feststellen, dass dieser Button
nicht auf Maus- und Touch-Events reagiert. Bei genauerer Überlegung wird auch klar warum das so ist: Für die Bildschirmkamera existiert der Button
ja nicht, sondern lediglich die Textur die auf ein PlaneGeometry
Objekt gezeichnet wird.
Um trotzdem eine Abfrage durchführen zu können, verwenden wir den Button
buttonDFB
und verknüpfen diesen über das Attribut frameBufferId
mit unserem FrameBuffer
-Objekt:
<Button id="buttonDFB" sizeX="700" sizeY="500" frameBufferId="fb_overlay" outCoordY1="-1" outCoordY2="1"/>
Der Button buttonDFB
reicht nun die Events an die Framebuffer-Szene weiter und eine Abfrage kann wie gewohnt erfolgen. Durch die Angabe von outCoordY1="-1"
und outCoordY2="1"
wird wiederum die Spiegelung an der Y-Achse berücksichtigt.
In der Programmlogik können wir nun wie gewohnt auf den Button-Event reagieren. Wir zeigen den Event als Debug Meldung an und öffnen eine Webseite im System-Browser.
// Button Press if (mButton->WasReleasedInside()) { state->SetUserDebugMessage("Button pressed"); if (deviceHandler->IsWebControlAvailable()) { deviceHandler->OpenUrlInSystemBrowser("https://murlengine.com/?id=FrameBufferTutorial"); } }