Framebuffer-Object (FBO)

Framebuffer objects are a collection of attachments, like textures or render-buffers. You can attach multiple textures and setup those as render targets, which means you can write data to them while rendering.

Caution: Multiple render targets are only supported since OpenGLES v3+.

Framebuffer objects are very important and are used by the TRenderer component in render-passes. The following code shows how to create your own framebuffer object at runtime.

uses Gorilla.Context.Buffers;
[...]
// create the buffer and link to a context
FFBO := TFrameBufferObject.Create(AContext);
 
// in case you need a depth and stencil buffer
FFBO.CreateDepthBuffer(Width, Height);

When working with framebuffer objects, you have to bind them before and to unbind afterwards.

  FFBO.Bind(AContext);
  try
    FFBO.Clear(TRenderTarget.Color_0, TAlphaColorF.Create(0, 0, 0, 0), 1, 0);
  finally
    FFBO.Unbind(AContext);
  end;

Multiple Rendering-Targets

As said above, we can use multiple render targets since OpenGLES v3.

Multiple render targets are useful to store values in the shader to different output textures. So you possibly could store the depth, diffuse, specular, normals or alpha values in separated textures. Afterwards you could combine those together in some kind of way. For example this is how deferred rendering works.

In Gorilla3D we create those targets as TGorillaTextureBitmap instances. Afterwards we attach those to the framebuffer object (FBO).

Notice: Gorilla3D provides an extended TClearTarget enumeration type called TRenderTarget, where the first 3 values are compatible with the Delphi type (and OpenGLES v2).

TRenderTarget = (Color_0, Depth, Stencil
  {$IFDEF GL_ES_VERSION_3_0},
    Color_1, Color_2, Color_3, Color_4, Color_5, Color_6, Color_7,
    Color_8, Color_9, Color_10, Color_11, Color_12, Color_13, Color_14,
    Color_15
  {$ENDIF});

Creating multiple targets

To create a render target, we have to setup a TGorillaTextureBitmap instance and attach it to the framebuffer object afterwards:

FMyRenderTargetTexture := TGorillaTextureBitmap.Create(LSize.X, LSize.Y);
FMyRenderTargetTexture.BeginSetup();
try
  with FMyRenderTargetTexture do
  begin
    DataType := TGorillaTextureDataType.dtFloat;
    Components := TPixelFormatEx.RGBA32F;
    Format := TPixelFormatEx.RGBA;
    MinFilter := TTextureFilter.Linear;
    MagFilter := TTextureFilter.Linear;
    WrapS := TGorillaTextureWrap.ClampToBorder;
    WrapT := TGorillaTextureWrap.ClampToBorder;
  end;
finally
  FMyRenderTargetTexture.EndSetup();
end;

Attaching multiple targets

To attach a texture to the framebuffer object we call the AttachTexture() method of the FBO and supply the intended rendertarget (one of the values above).

Caution: Not all GPU's support all render targets. Some GPU's only support up to 8 targets or less.

The following snippet shows how to attach the previously created texture to our framebuffer object.

procedure TMyRenderPass.DoSetupTexturesByViewport(const AContext : TContext3D;
  const AWidth, AHeight : Integer);
begin
  [...]
  FFBO.AttachTexture(AContext, FMyRenderTargetTexture.GorillaTexture, TRenderTarget.Color_0);
end;

Using multiple targets

To use the framebuffer object with its attached render-targets, we need to bind, clear and prepare it on each rendering step. If you forget to execute Prepare() only the default target Color_0 will be available for computation. Only color attachments need to be prepared, not stencil or depth buffers.

Notice: when working with render-passes and the embedded TRenderer, this is already done for you. But in case you established your own FBO, you enable it the following way.

procedure TMyRenderPass.DoOnBeginRenderPassSetup(
  const ACount : Integer; const APass : TRenderPass);
begin
  inherited;
 
  // bind your framebuffer to use in shaders
  FFBO.Bind(Renderer.SharedContext);
 
  // clear the specific render target buffer
  FFBO.Clear([TRenderTarget.Color_0, TRenderTarget.Depth], 
    TAlphaColorF.Create(0, 0, 0, 1), 1, 0);
 
  // activate the specific render target buffer for shader
  FFBO.Prepare([TRenderTarget.Color_0]);
end;
 
procedure TMyRenderPass.DoOnEndRenderPassSetup(
  const ACount : Integer; const APass : TRenderPass);
begin
  FFBO.Unbind(Renderer.SharedContext);
 
  inherited;
end;

In Shader

The usage in your material shader depends on your target setup and the activated targets. For example: Preparing 3 render targets will be used like this:

FFBO.Prepare([TRenderTarget.Color0, TRenderTarget.Color2, TRenderTarget.Color3]);

In Fragment-Shader you'll see, that layout location index do not correspond with the render target index, but with the array index of supplied render targets.

layout(location = 0) out vec4 outColor_0;
layout(location = 1) out vec4 outColor_2;
layout(location = 2) out vec4 outColor_3;
 
[...]
 
// output one with red color
outColor_0 = vec4(1.0, 0.0, 0.0, 1.0);
 
// output two with green color
outColor_2 = vec4(0.0, 1.0, 0.0, 1.0);
 
// output three with blue color
outColor_3 = vec4(0.0, 0.0, 1.0, 1.0);