Viewport

The 2D/3D Firemonkey components (TButton, TLabel, …) are still compatible with Gorilla3D. But instead of using a TViewport3D or TForm3D, we need to drag at first the TGorillaViewport onto our form.

Creating a Gorilla3D Viewport at design-time

You can find the viewport at designtime in the toolbar under:

Gorilla3D > TGorillaViewport.

Simply drag it onto your form or create it at runtime by the following method.

CAUTION: Gorilla3D is not compatible with TForm3D and TViewport3D!

Creating a viewport at runtime

If you need to create the Gorilla3D viewport at runtime, you can do it the following way:

Form1.pas
uses
  System.UITypes, // on elder IDE versions FMX.UITypes
  Gorilla.Viewport;
 
// in our form (TForm1) we added a field named "FGorilla"
procedure TForm1.FormCreate(Sender: TObject);
begin
  FGorilla := TGorillaViewport.Create(Self);
  FGorilla.Parent := Form1;
  FGorilla.Color  := TAlphaColorRec.Black;
end;

Rendering-Pipeline

Since v0.8.1.x we've changed the default rendering pipeline of the TGorillaViewport. The pipeline is responsible for rendering all render passes and the main scene itself. It computes transparency by using Weighted-Blended-Order-Independent-Transparency (WBOIT) rendering mechanism.

Read more: Transparency

Rendering-Order

Render-Cycle

A render-cycle is the process of rendering everything needed for a frame.

  1. all pre-processing render passes (represented by instances of TGorillaRenderPassController)
  2. main render pass
  3. all post-processing render passes (represented by instances of TGorillaRenderPassController)

Render-Pass

A render pass is a closed unit for rendering specific elements or some kind of effect, inside of a step of the render-cycle.

  1. all opaque objects (not transparent)
  2. all transparent objects (rendered into separated buffers)
  3. compositing opaque and transparent render result

Read more: Renderpasses

Frame-Rate

Requesting FrameRate (FPS)

  • If UseFixedFrameRate is set to TRUE, the FPS property returns the current frames per second.
  • If UseFixedFrameRate is set to FALSE, the TGorillaViewport provides an estimated framerate value.

Simply setup a timer and request this value every 100 milliseconds.

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  Self.Caption := Format('Gorilla 3D - %n FPS', [FGorilla.FPS]);
end;

Notice

If UseFixedFrameRate is set to FALSE the FPS property provides an estimated framerate.

The estimated framerate measures the time a complete render cycle needs, including all objects, render passes and the main pass itself. Based on this value we compute the possible frames per second for the last interval. So it's only an orientation for you of the possible performance of your application.

Animation-Framerate (TAnimation): Because skin- and skeleton animation extends basic Firemonkey TAnimation. Therefore animations are limited to 30 FPS for rendering.

UseFixedFrameRate

The viewport is able to render a scene on event feedback (when visual components change) or by a fixed framerate interval. If UseFixedFrameRate is set to true, the rendering pipeline tries to render the scene with the configured FixedFrameRate value. By default this value is set to 60.

Notice: At design-time fixed framerate rendering is disabled due to limitations in IDE.

FrustumCulling

The viewing frustum is a geometric representation of the volume visible to the virtual camera. Naturally, objects outside this volume will not be visible in the final image, so they are discarded.

Source:https://en.wikipedia.org/wiki/Hidden-surface_determination#View_frustum_culling

This is a great boost to the performance of your application, when only objects will be rendered that would be visible. The FrustumCulling property enables or disables the functionality in general.

But you can activate/deactivate it for each derived type of TGorillaControl, by setting:

GorillaCube1.FrustumCullingCheck := false;

If FrustumCullingCheck is set to FALSE, it will always be rendered, wether it's inside or outside of the view frustum.

Take care, that the frustum culling may affect render-passes, where a different view matrix is used.

HighRes

The High-Resolution property is currently only useful on Android platform. When activating higher resolution, than context size for rendering will be increased by device display scaling factor.

Otherwise the size of the viewport will be used for rendering. Deactivated HighRes property could lead to a more rastarized result, but is faster in rendering.

Fog

The viewport provides a classic render effect as basis implementation. Fog computation is a very popular element larger scenes when hiding elements in far distance, and making a scene disappear.

PropertyValueDescription
Fog True/False Enable or disable fog computation
FogMode TGorillaFogMode Get or set fog computation mode. Allowed modes are: linear, exp and exp2.
FogDensity Single Get or set density of computed fog.
FogStart Single Get or set lower limit of fog by distance.
FogEnd Single Get or set upper limit of fog by distance.
FogColor TAlphaColorF Get or set fog color.

Fog computation is a global scene effect over each visible element of the scene. Due to necessary shader operations this is only allowed for derived types of TGorillaDefaultMaterialSource.

This means the fog effect will not work for default FMX materials.

Optimization

In Firemonkey every time you change a visual property of a component, for example the position of a TCube, FMX will throw an event to update the viewport.

Cube1.Position.X := Cube1.Position.X + 0.1;
Cube1.Position.Y := Cube1.Position.Y + 0.2;
Cube1.Position.Z := Cube1.Position.Z + 0.3;

As you can imagine this is very slow, especially when you did not expected an update on changing the x, y and z value of a position.

Therefor the TGorillaViewport provides an optimization mechanism. Encapsulate those operations into BeginUpdate and EndUpdate to perform only one update command on the viewport renderer.

  FGorilla.BeginUpdate();
  try
    Cube1.Position.X := Cube1.Position.X + 0.1;
    Cube1.Position.Y := Cube1.Position.Y + 0.2;
    Cube1.Position.Z := Cube1.Position.Z + 0.3;  
  finally
    FGorilla.EndUpdate();
  end;

In case you need explicit render instructions, you can suppress updates completely, by calling EndUpdateWithoutInvalidate instead of EndUpdate.

  FGorilla.BeginUpdate();
  try
    Cube1.Position.X := Cube1.Position.X + 0.1;
    Cube1.Position.Y := Cube1.Position.Y + 0.2;
    Cube1.Position.Z := Cube1.Position.Z + 0.3;  
  finally
    FGorilla.EndUpdateWithoutInvalidate();
  end;

But you have to update viewport yourself by calling:

  FGorilla.Invalidate();

Manual Rendering

Because FMX renders through property changes on 3D objects, it still may produce a lot of draw calls. And also draw call limitation (by FixedFrameRate property) is not a solution for best performance. Here manual rendering comes in place. TGorillaViewport offers a “ManualRendering” property to disable automatic drawing on changed objects.

By activating, calling “Invalidate” or changing a 3D object property will no longer lead to re-rendering the scene!

From that point you have to call rendering by RenderManual() method yourself constantly. You can do this by a TTimer or by TGorillaTimer (a synchronized thread) component.

uses
  Gorilla.Utils.Timer;
 
TForm1 = class(TForm)
    GorillaViewport1 : TGorillaViewport;
    procedure FormCreate(Sender : TObject);
    procedure FormDestroy(Sender : TObject);
  private
  protected
    FCadender : TGorillaTimer    
    procedure DoOnCadencer(ASender : TObject);
  public
end;
 
[...]
 
procedure TForm1.FormCreate(Sender : TObject);
begin
  // disable auto-rendering
  GorillaViewport1.ManualRendering := true;
 
  // create a interval timer
  FCadencer := TGorillaTimer.Create();
  FCadencer.Interval := 16; // 16ms for 60 FPS | 33ms for 30 FPS
  FCadencer.OnTimer := DoOnCadencer;
  // at last, we need to start the timer thread
  FCadencer.Start();
end;
 
procedure TForm1.FormDestroy(Sender : TObject);
begin
  if FCadencer.Started then
  begin
    FCadencer.Terminate();
    FCadencer.WaitFor();
  end;
  FreeAndNil(FCadencer);
end;
 
procedure TForm1.DoOnCadencer(ASender : TObject);
begin
  GorillaViewport1.RenderManual();
end;

TGorillaTimer

TGorillaTimer is the better option to a regular TTimer, because as a separated thread it runs more constantly than the event based component. Nevertheless also TGorillaTimer has to synchronize with main-thread, where rendering happens. So the thread calls TThread.Queue() method to enqueue and synchronize the OnTimer event. This leads to a delay in execution. After OnTimer was enqueued, the thread sleeps for the set interval.

All in all, TGorillaTimer is more accurate than TTimer, but it cannot be ensured that the set interval is 100% accurate.

Native OpenGL Rendering

Since Gorilla3D uses OpenGL / OpenGLES graphics library for rendering on all available platforms, you're able to implement native OpenGL function calls in rendering cycle. To allow those injection you need to add a TDummy container to your viewport and assign the OnRender event of it. Inside of the event method you can then write native OpenGL code.

But take care of the framework version restrictions here: Requirements

uses
  WinApi.OpenGL, WinApi.OpenGLext;
 
procedure TForm1.FormCreate(ASender : TObject);
begin
  // by default dummies are opaque = false
  // true - native drawing without transparency
  // false - native drawing with transparency
  Dummy1.Opaque := true;
  Dummy1.OnRender := Dummy1Render;
end;
 
procedure TForm1.Dummy1Render(Sender: TObject; Context: TContext3D);
var LNativeColor : TAlphaColorF;
begin
  LNativeColor := TAlphaColorF.Create(TAlphaColorRec.Blue);
 
  glBegin(GL_TRIANGLES);
    glColor4f(LNativeColor.R, LNativeColor.G, LNativeColor.B, LNativeColor.A);
    glVertex3f(-0.5, 0, 0);
 
    glColor4f(LNativeColor.R, LNativeColor.G, LNativeColor.B, LNativeColor.A);
    glVertex3f(0.5, 0, 0);
 
    glColor4f(LNativeColor.R, LNativeColor.G, LNativeColor.B, LNativeColor.A);
    glVertex3f(0, -0.5, 0);
  glEnd;
end;

Transformation & Settings

When writing native OpenGL code inside of the TDummy OnRender routine, you have to be aware that you are responsible for managing transformation, projections and viewmatrix yourself.

To setup global settings before and after rendering a cycle, use the “OnPainting” and “OnPaint” callback events of the viewport. Those are encapsulated callbacks around the draw routine of the scene.

EventDescription
OnPaintingCalled before rendering any object or render-pass
OnPaintCalled after rendering all objects and render-passes

But remember, that during rendering FMX and Gorilla3D are changing settings and may overwrite yours. Especially blend settings, depth-test, color-writing and so on are important for the rendering.

Best practice is to preserve and restore settings in your OnRender event.

Update rendering

When you are asking yourself why nothing is drawn, it's maybe because FMX re-renders a scene only by changing an object value. It is necessary to call Invalidate() routine yourself to show changes of your native code. This can be done in a simple TTimer or by a TGorillaTimer thread.

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  [...]
 
  GorillaViewport1.Invalidate();
end;

Alternatively use the manual rendering mechanism of TGorillaViewport.

Buffers and textures

Even in OnRender event you are in Gorilla3D rendering context. This means the framework has bound a framebuffer and the necessary render target textures.

Remark: It is recommended to only use opaque rendering, by setting elements to “Opaque := true;”. If the opaque property is false, you will render to translucent albedo channel.

CAUTION: This translucent render target is NOT a 1:1 represented color value. Instead it is weighted color value used later for compositing.

To write a valid translucent albedo channel color value, you can have a look at the following GLSL implementation:

varying vec4 v_WorldViewProjVertPos;
 
[...]
 
// get the final REAL color value
vec4 l_WBOITColor = gl_FragColor;
 
// compute color-vertex-weight
float l_Weight = max(min(1.0, max(max(l_WBOITColor.r, l_WBOITColor.g), l_WBOITColor.b) * l_WBOITColor.a), l_WBOITColor.a) *
  clamp(0.001 / (1e-5 + pow(v_WorldViewProjVertPos.z / 1000.0, 2.0)), 1e-5, 1e4);
 
// compute a final weighted color value written to translucent albedo render target
gl_FragColor = vec4(l_WBOITColor.rgb * l_Weight, l_Weight);

Remark: This is only necessary if Forward-Renderer is activated!

To understand rendering pipeline better, have a closer look here.

Legacy Rendering

For advanced developers, we provide a legacy rendering pipeline, which allows you to do rendering yourself. This will replace the easy-to-use forward-renderer by a basic renderer, which is sometimes better for native rendering. The legacy implementation will only setup a single framebuffer with a single render target texture. It will then render opaque elements at first and then all other elements.

Read more about legacy rendering here.

Shaders

Since v0.8.0 rendering pipeline was optimized due to unnecessary render instructions. An update limitation was implemented, that not every property change leads to a new rendering. Besides that you may use a shader / material which updates itself by time. The TGorillaWaterMaterialSource is an example. Waves are generated by a timevalue, but it will only update on re-rendering.

Here we need an explicit render instruction. Like you may already know from other Delphi components the TGorillaViewport supports the “Invalidate” method to tell Gorilla3D to re-render. Call this method constantly in a TTimer or by synchronized thread to update water material.

Next step: Loading models