Usually the whole scene is rendered directly to the back buffer of the on-screen framebuffer. When all drawing operations are considered complete, the back buffer and the front buffer are swapped and the calculated image is displayed on the monitor (double buffering).
Some applications may want to render images without actually displaying them to the user. This "off-screen rendering" can be accomplished with Graph::FrameBuffer
nodes. They will render parts of the scene graph to an "off-screen framebuffer". The off-screen framebuffer is not directly visible on the display but can be used e.g. as a dynamically created texture.
Framebuffer & FrameBufferTexture
A Graph::FrameBuffer
node represents the base object for off-screen rendering (render target).
To be able to access the generated contents, a Graph::FrameBuffer
object must refer to at least one proper Graph::ITexture
object. The rendered data will then be stored in the referred Graph::FrameBufferTexture
object (or in multiple texture objects).
The assignment is done by specifying the corresponding id
:
colorTextureId
(RGBA pixel color values)depthTextureId
(depth buffer values)stencilTextureId
(stencil buffer values)
Often, only color values are needed but the rendering process requires an active depth buffer for visible surface detection. In such a case, it is not necessary to create and attach a depth texture (with depthTextureId
); instead, it is sufficient to explicitly set a depth buffer format (attribute depthBufferFormat
) to create a depth buffer that is used internally only.
To use a FrameBuffer
object for rendering, one or more Graph::View
nodes must refer to this framebuffer via the frameBufferId
attribute. All objects visible to the camera of this view are rendered to the FrameBuffer
.
- Note
- Attention! When multiple textures are attached to a FrameBuffer object, all of these textures must have the same dimensions, or initialization will fail.
Overlay Example
As a simple example we create a FrameBufferTexture
and display it as overlay above a background image. We use a PlaneGeometry
object and a suitable texture to draw the background image.
<TextureState slot="0" textureId="/textures/image"/> <PlaneGeometry textureSlots="0" materialSlot="3" scaleFactorX="800" scaleFactorY="600"/>
We create a FrameBufferTexture
object and a FrameBuffer
object for the off-screen rendering. The attribute colorTextureId
is used to assign the color texture.
<FrameBufferTexture id="fb_tex_overlay" type="FLAT" sizeX="700" sizeY="500"/> <FrameBuffer id="fb_overlay" colorTextureId="fb_tex_overlay"/>
Now we need to create a view
and a camera
object. By specifying the frameBufferId
we define that the view
should render into our framebuffer
instead of the back buffer. All visible objects of the camera
fb_camera
will therefore be drawn to our FrameBufferTexture
fb_tex_overlay
.
<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">
We use a orthographic camera
with unitSize="1"
. By doing so, we will get a perfect 1:1 pixel match. The parameter colorClearValue="c0000000h"
defines a semitransparent black as background color (ARGB).
To creata a visible content, we add some TextGeometry
objects and one Button
object as child nodes to the Camera
node; a Aligner
node is used to automatically arrange the objects.
<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>
- Remarks
- The dummy text has been created with the text generator Vidya Gaemz Ipsum.
To display the rendered texture, we create a PlaneGeometry
object which uses the FrameBufferTexture
and which is drawn with the main camera.
<TextureState textureId="fb_tex_overlay" slot="1"/> <PlaneGeometry id="planeDFB" materialSlot="4" textureSlots="1" scaleFactorX="700" scaleFactorY="500" depthOrder="2"/>
As a result we get two PlaneGeometry
objects, where one object uses an image as texture and one object uses a FrameBufferTexture
. Unfortunately the texture of the FrameBufferTexture
is drawn upside down.
To mirror the Y coordinates we change the attributes texCoordY1
and texCoordY2
of the PlaneGeometry
from default 0/1 to 1/0.
<PlaneGeometry id="planeDFB" materialSlot="4" textureSlots="1" scaleFactorX="700" scaleFactorY="500" depthOrder="2" texCoordY1="1" texCoordY2="0"/>
Scrolling
By adding some lines of code we make the displayed text scrollable. To be able to do this, we define a second Button
with id="buttonDFB"
in accordance to the PlaneGeometry
object.
<Button id="buttonDFB" sizeX="700" sizeY="500"/>
We evaluate the button event values and directly change the position values of the Aligner
node.
// 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); }
To get a looped scrolling, we need to additionally correct the position value, when the Aligner
has been completely scrolled out of the visible area:
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
In addition to the TextGeometry
objects we also defined a Button
as child node of the framebuffer camera. If we query the Button
from within the program logic as usual, we see that the Button
doesn't react to mouse and touch events. When we look closer, the reason soon becomes clear: The main camera does not know anything about the button; it only sees a texture which is drawn on a PlaneGeometry
object.
To still be able to query the button, we use the Button
buttonDFB
and assign our FrameBuffer
object via the attribute frameBufferId
.
<Button id="buttonDFB" sizeX="700" sizeY="500" frameBufferId="fb_overlay" outCoordY1="-1" outCoordY2="1"/>
The button buttonDFB
forwards all events to the frambuffer scene and we are able to query the button as usual. By specifying outCoordY1="-1"
and outCoordY2="1"
we are again correcting the mirrored Y coordinates.
Now we are able to react to button events in the program logic as usual. We indicate a button press by showing a debug message and by opening a website in the default browser.
// Button Press if (mButton->WasReleasedInside()) { state->SetUserDebugMessage("Button pressed"); if (deviceHandler->IsWebControlAvailable()) { deviceHandler->OpenUrlInSystemBrowser("https://murlengine.com/?id=FrameBufferTutorial"); } }