bRenderer

The Basic Renderer (bRenderer) is a cross-platform educational framework written in C++ and suited to teach basic knowledge in computer graphics. It was written for and tested on iOS, Windows, Mac OS X, and Linux.

Important: This framework is now outdated and not compatible with current versions of iOS.

Paper

The paper "bRenderer: A Flexible Basis for a Modern Computer Graphics Curriculum" was presented at Eurographics 2017 as part of the Education Program. It is available in the download section of this page.

Example Project: The Cave

Features

  • User inputs
  • Object management
  • Queuing render calls
    • Sorting for efficiency
    • Transparency sorting
  • Bounding volumes and culling
  • OBJ loading (materials and models)
  • Sprites and text sprites
  • Textures
  • Cube maps and Depth maps
  • Framebuffers
    • Drawing to textures
  • Shaders
    • Shader generation
  • Camera objects
  • Light objects
  • Matrix stacks

Platforms

  • Windows
  • Mac OS X
  • Linux
  • iOS

Architecture

Click on each of the boxes in the architecture overview below for more details about the classes.

Your browser does not support SVG

Download

Framework including example project

Download directly from GitHub: click here

Paper:

Download: click here

Bachelor's thesis

Download: click here

Tutorial

Introduction

In this tutorial we are going to write a simple "Hello World"-application to get familiar with the basics of the framework. The final result will be a window on desktop systems or a view on iOS displaying a text of our choosing.

More advanced techniques to create beautiful scenes and effects are available in the guides section of this website. Additionally an example project can be downloaded in the download section, which demonstrates a multitude of possibilities the Basic Renderer offers.

Setup

To get started we first download the current version of the framework from the download section. After extracting the zip file, we are left with the following folders and files:

  • bRenderer: This folder contains the actual framework
  • bRenderer_ios.xcodeproj: A ready-to-use Xcode project to implement an iOS application based on the Basic Renderer
  • bRenderer_osx.xcodeproj: An Xcode project to create an application for Mac OS X
  • doc: A folder containing a Doxyfile and some additional data to generate a documentation based on Doxygen
  • externalLibraries: A collection of third party libraries, which are essential for the Basic Renderer to work
  • project: Contains example code to setup a basic application based on the framework
  • bRenderer.sln: A Visual Studio 2013 solution containing a ready-made project for the Windows platform

The easiest way to start is by opening one of the provided Visual Studio or Xcode projects and adjusting or replacing the provided code. In this case the setup phase is over and we can proceed with the next section.

If we want to start from scratch or use a different IDE (such as Eclipse), we need to follow a few steps first, which are mostly the same on all desktop systems yet differ on iOS by quite a bit. In this case please refer to the setup guide before continuing the tutorial.

The Render Project

The Basic Renderer can be downloaded with an example project or an empty project already included. If we are inexperienced with app development on iOS, this may be very helpful as we only need to write our code in the "RenderProject" class. The "main.cpp" file and the contents of the "project/ios/resources" folder take care of setting up the project on Windows, Mac or iOS.

However we'll probably need to adjust the standard file path defined in the "main.cpp" for Mac ("project/osx") and Windows ("project/windows"). If we keep the relative path, we need to know where the application will be located after compilation. This may differ depending on the operating system and the IDE. If we work on Windows, Visual Studio will create a "Debug" or "Release" folder where the executable will be placed, however if started in Visual Studio the relative path will refer to the location of the project in the folder "project/windows". On Mac we should set the working directory to "$PROJECT_DIR", which is the location of our Xcode project. To do that, we go to Product > Scheme > Edit Scheme > Run (on the right) > Options and select “Use custom working directory”. On iOS we just drag and drop the data into Xcode and the Basic Renderer will automatically request its URL.

If we decide to just use the provided project, we can now adjust it to our needs and continue with the next section of this tutorial. If we already have an application or want to start from scratch, we need to setup our own class inheriting the "IRenderProject" interface. Please refer to the setup guide and the renderer guide for details. In a nutshell we need to create a class and include the Basic Renderer as shown below:

#include "bRenderer.h"
class RenderProject : public IRenderProject
{
...
}

This requires us to inherit three callback functions: the "initFunction", "loopFunction" and "terminateFunction". Additionally in either the constructor or a setup function of the custom class we need to initialize and run the renderer:

/* Initialize the Project */
void init()
{
bRenderer::setStandardFilePath("../data"); // set the standard path for all files
bRenderer::loadConfigFile("config.json"); // load custom configurations replacing the default values in Configuration.cpp
if(Input::isTouchDevice())
bRenderer().initRenderer(true); // full screen on iOS
else
bRenderer().initRenderer(1920, 1080, false, "Demo"); // windowed mode on desktop
//bRenderer().initRenderer(View::getScreenWidth(), View::getScreenHeight(), true); // full screen using full width and height of the screen
// start main loop
bRenderer().runRenderer();
}

Create Objects

Now that we have our "RenderProject", we can start creating objects in the "initFunction", which gets called right after the Basic Renderer has been initialized. Since the goal of this tutorial is to display a text (e.g. "Hello World"), we first need a font. On Windows we can just copy a font file from "C:\Windows\Fonts" or we could download a file in the internet and place it in our specified data folder. As the fonts are loaded by FreeType, please consult the official homepage of the library for more information about supported file formats.

Next we can load our font file using the renderer's object manager as shown below:

FontPtr font = bRenderer().getObjects()->loadFont("arial.ttf", 50);

To be able to draw text using the font, we need to create a text sprite. There are two different options in the object manager to do that: The first one creates a material and a shader automatically, whereas the second one would require us to do this ourselves. In this tutorial we'll choose the easier option that even lets us specify a color for the text to be drawn. For more advanced techniques refer to the guides. In the following example we set the red, green and blue values of the color to 1.0, which results in a white color, and choose the text to be "Hello World":

bRenderer().getObjects()->createTextSprite("text", vmml::Vector3f(1.f, 1.f, 1.f), "Hello World", font);

That's all it takes. In the next section we will draw our text sprite.

Draw Objects

Objects can be drawn in the "loopFunction" of our class as it gets called by the renderer for every frame. Yet before we can draw our text, we first need a model matrix, a view matrix and a projection matrix as shown below:

GLfloat scale = 0.1f;
vmml::Matrix4f scalingMatrix = vmml::create_scaling(vmml::Vector3f(scale / bRenderer().getView()->getAspectRatio(), scale, scale));
vmml::Matrix4f modelMatrix = vmml::create_translation(vmml::Vector3f(-0.25f, 0.f, 0.f)) * scalingMatrix;
vmml::Matrix4f viewMatrix = Camera::lookAt(vmml::Vector3f(0.0f, 0.0f, 0.25f), vmml::Vector3f::ZERO, vmml::Vector3f::UP);
vmml::Matrix4f projectionMatrix = vmml::Matrix4f::IDENTITY;

By dividing the x-value of our scale by the aspect ratio of our view, the text will not be stretched in one direction if we change the resolution of our window. The view and projection matrix would usually be created by a camera object, yet for a text overlay this is not necessary. If we want to fly through a scene composed of actual models, we can consult the camera guide.

Now that we have our matrices we can draw the text sprite as a model using the model renderer further explained in the drawing guide:

ModelPtr text = bRenderer().getObjects()->getTextSprite("text");
// draw without lighting (empty vector of strings)
bRenderer().getModelRenderer()->drawModel(text, modelMatrix, viewMatrix, projectionMatrix, std::vector<std::string>({}));

If we run the program, we should get the following result:

hello_world

As a last step we will animate our text. We can do this by manipulating our matrices in every frame. As an example we use the sinus of the elapsed time as our scale:

GLfloat scale = 0.1f*abs(sin(elapsedTime));

Have fun experimenting and observing the results.

Guides

Setup

For Windows, Mac and iOS ready-to-use Visual Studio and Xcode projects are provided in the download section. If another IDE should be used or the Basic Render is integrated into an existing project, this guide offers instructions on how to setup the framework on desktop systems as well as iOS.

Desktop Systems

If we are on Windows or Mac, we can use the external libraries provided as part of the project from the download section. On Linux we first need to compile some of the libraries (GLFW, GLEW and FreeImage are only available as dynamic libraries for Windows and OS X) to be able to include them in our project or we can directly use their source code. Please refer to the documentation of the respective libraries to integrate them into your project:

As soon as we have compiled versions of the external libraries or alternatively their source code, we can get started by creating a project in our favorite IDE and set the paths to the headers of all the libraries we need. The following list is based on Visual Studio and the folder "externalLibraries" in our solution directory. On OS X replace "$(SolutionDir)" with "$(SRCROOT)" and use slashes instead of backslashes.

  • $(SolutionDir)\externalLibraries\vmmlib-library
  • $(SolutionDir)\externalLibraries\boost-library
  • $(SolutionDir)\externalLibraries\glew-1.13.0\include
  • $(SolutionDir)\externalLibraries\FreeImage3170
  • $(SolutionDir)\externalLibraries\glfw-3.1.1\include
  • $(SolutionDir)\externalLibraries\freetype-2.6\include
  • $(SolutionDir)\bRenderer

Next we need to include the compiled dynamic (and static) libraries. In Visual Studio we need to define their directories first (the list is again based on the folder "externalLibraries"):

  • $(SolutionDir)\externalLibraries\glew-1.13.0\lib_win32
  • $(SolutionDir)\externalLibraries\FreeImage3170
  • $(SolutionDir)\externalLibraries\glfw-3.1.1\lib_win32

Now we can define the additional dependencies in Visual Studio:

  • opengl32.lib
  • glew32.lib
  • glfw3dll.lib
  • FreeImage.lib

Or we can do the same in Xcode in the "Link Binary With Libraries" section:

  • OpenGL.framework
  • libGLEW.1.13.0.dylib
  • libglfw.3.1.dylib
  • libfreeimage.a

Finally we need to define a few preprocessor macros, which depend on whether we build the FreeType library or include the source code directly. If we include its source code, we define "FT2_BUILD_LIBRARY" on all systems, otherwise on Mac and iOS no macros are needed.

In Visual Studio some libraries could cause warnings (e.g. the vmmlib), we therefore define the following macros:

  • FT2_BUILD_LIBRARY
  • _CRT_NONSTDC_NO_DEPRECATE
  • _CRT_SECURE_NO_WARNINGS
  • _SCL_SECURE_NO_WARNINGS

In Xcode we only need two macros (both for FreeType) yet it is important to remove the "DEBUG" macro as it may cause problems with FreeType:

  • FT2_BUILD_LIBRARY
  • DARWIN_NO_CARBON

For FreeType not all source files need to be integrated into the project. Click here for a list of files to include.

Now we are all set and can include the "bRenderer" folder into our project, which contains all headers and sources we need to start using the framework.

To access all the renderer's functionality, we include bRenderer.h:

#include "bRenderer.h"

iOS

On iOS we can use built-in libraries provided by Apple. We therefore don't need FreeImage, GLEW and GLFW. This reduces the list of header search paths to:

  • $(SRCROOT)/externalLibraries/vmmlib-library
  • $(SRCROOT)/externalLibraries/boost-library
  • $(SRCROOT)/externalLibraries/freetype-2.6/include
  • $(SRCROOT)/bRenderer

Next we include the following libraries in the "Link Binary With Libraries" section:

  • CoreMotion.framework
  • QuartzCore.framework
  • CoreGraphics.framework
  • UIKit.framework
  • Foundation.framework
  • OpenGLES.framework

For FreeType we need two preprocessor macros and make sure to remove the "DEBUG" macro.

  • FT2_BUILD_LIBRARY
  • DARWIN_NO_CARBON

For FreeType not all source files need to be integrated into the project. Click here for a list of files to include.

As a final step we now drag the "bRenderer" folder into our Xcode project, which contains all headers and sources we need to start using the framework.

To access all the renderer's functionality, we include bRenderer.h:

#include "bRenderer.h"

Renderer

The "Renderer" class is meant as the main gateway to the framework through which most functionality can be setup and accessed. It maintains one instance of each of the four building blocks of the framework:

  • View: The renderer maintains a view on iOS or a window on desktop systems that displays the rendered images.
  • Input: Is closely coupled with the view as only inputs concerning the renderer's own view are made accessible.
  • Object Manager: The information needed to draw objects to the screen is stored and maintained in the object manager. Objects may be loaded, created, accessed and removed at any time without the risk of memory leaks. It is therefore strongly recommended to either create objects directly in the manager or if the framework is to be extended by custom subclasses, the created objects should be added using the provided "add"-functions.
  • Model Renderer: Objects stored in the object manager may be drawn to the view either directly or can be queued and sorted first using the renderer's model renderer.

Use the Renderer in a Project

To integrate the renderer into our own application, we can either directly create an instance of it and pass static callback functions or we can work in an object-oriented manner and inherit the abstract class "IRenderProject" (recommended). We are then required to implement three callback functions in order to ensure compatibility with the renderer:

  • The initialize function is invoked during the setup of the renderer and is where project specific initializations may happen. This involves the loading of models, creation of cameras and lights and definition of variables.
  • The loop function is called every frame, which allows for updating and drawing the scene or even loading and creating new objects if required.
  • In the terminate function memory may be deallocated.

architecture_irenderproject

An "IRenderProject" automatically holds an instance of the renderer, which may be accessed using the function "bRenderer()". Yet before we can use it, we need to initialize it and optionally define a standard file path (has no effect on iOS) and a configuration file as shown below:

bRenderer::setStandardFilePath("../data"); // set the standard path for all files
bRenderer::loadConfigFile("config.json"); // load custom configurations replacing the default values in Configuration.cpp
if(Input::isTouchDevice())
bRenderer().initRenderer(true); // full screen on iOS
else
bRenderer().initRenderer(1920, 1080, false, "Demo"); // windowed mode on desktop
//bRenderer().initRenderer(View::getScreenWidth(), View::getScreenHeight(), true);
// full screen using full width and height of the screen

During the initialization phase our initialization function inherited from "IRenderProject" will be called and we may use the object manager to load and create the objects to be drawn later.

Next we need to run the renderer, which starts the render loop:

// start main loop
bRenderer().runRenderer();

On iOS we need to take another step to display the renderer's view. In our application's View Controller we can attach the renderer's view with either of the following functions:

RenderProject p;
// initialize the project before attaching the view since it doesn't exist yet
p.init();
// There are two ways to display the view of the renderer in your project (choose only one!)
[self.view addSubview:p.getProjectRenderer().getView()->getUIView()]; // method 1
p.getProjectRenderer().getView()->attachToUIView(self.view); // method 2

Note that we created a function "getProjectRenderer()" as "bRenderer()" is not public.

Drawing

The Basic Renderer features drawable objects inheriting the abstract class IDrawable. Those objects implement the "draw" and the "drawInstance" Method. If the "draw" function should be used, it is essential to first pass uniforms to the shader of the drawable or use a "Properties" object holding the uniforms.

The "drawInstance" method requires to add an instance to the object first and then pass the shader uniforms to the instance properties. The specific implementation of instancing may differ depending on the object to be drawn. As an example the "Model" class holds a map containing instance properties for every shader used by the model's underlying geometry objects.

Model Renderer

The model renderer is one of the four modules offered by the Renderer main class. It is meant to simplify the drawing process and automatically passes uniforms to the shaders of the specified model or model instance. Additional uniforms not covered by the model renderer may be passed by setting the "Properties" of the model.

As illustrated below, a model can either be drawn directly or an instance of it may be stored in the render queue of the model renderer. For both functions there are various settings to be passed such as the lights to be used or whether or not view frustum culling should be performed.

flow_model_drawing

Usually the drawing process is triggered in the "loopFunction" of the render project. The examples below illustrate different ways to draw models and objects inheriting the model class (sprites, text sprites):

...
/*** Cave ***/
// translate and scale
vmml::Matrix4f modelMatrix = vmml::create_translation(vmml::Vector3f(30.f, -24.0, 0.0)) * vmml::create_scaling(vmml::Vector3f(0.3f));
// submit to render queue
bRenderer().getModelRenderer()->queueModelInstance("cave", "cave_instance", camera, modelMatrix, std::vector<std::string>({ "torchLight", "firstLight", "secondLight", "thirdLight" }), true, true);
...
/*** Torch (is always shown in front of the camera) ***/
modelMatrix = bRenderer().getObjects()->getCamera(camera)->getInverseViewMatrix(); // position and orient to match the camera
modelMatrix *= vmml::create_translation(...) * vmml::create_rotation(...); // now position it relative to the camera
// submit to render queue
bRenderer().getModelRenderer()->queueModelInstance("torch", "torch_instance", camera, modelMatrix, std::vector<std::string>({ "torchLight" }));
...
// Draw the queue and clear it afterwards
bRenderer().getModelRenderer()->drawQueue();
bRenderer().getModelRenderer()->clearQueue();
...
/*** Instructions (a text sprite as part of the GUI overlaying the scene) ***/
modelMatrix = vmml::create_translation(...) * vmml::create_scaling(...);
TextSpritePtr instructions = bRenderer().getObjects()->getTextSprite("instructions");
// draw directly
bRenderer().getModelRenderer()->drawModel(instructions, modelMatrix, _viewMatrixHUD, vmml::Matrix4f::IDENTITY, std::vector<std::string>({}), false);
...

As seen in the example, the model name or a pointer to the model may be passed to the "drawModel" and "queueModelInstance" functions. If a name is passed, the model renderer will consult its object manager to obtain the model. Since text sprites are stored separately in the object manager their names can't be used in above functions. To still be able to draw text, we can either use the "drawText" and "queueTextInstance" functions or pass a pointer to the text sprite as shown in the last section of the example code.

Render Queue

The render queue allows for storing and caching draw calls. Such draw calls consist of a pointer to a drawable, a string defining the instance to be rendered and for transparent objects it is even possible to define how the blending factors are computed. When the render queue is drawn, for every draw call it first sets the operation of blending and then calls the "drawInstance" method of the drawable.

The draw calls containing opaque objects are sorted for efficiency by avoiding costly state changes as much as possible. Transparent objects are drawn after opaque ones in a back-to-front order to allow for them to be displayed correctly.

Models

Using the object manager we can either load a model from an OBJ file or create sprites and text sprites. The according functions allow us to pass a custom material or a custom shader. Otherwise the material files to load are read from the OBJ file and created automatically.

If we want to send data to the shader which is not covered by the model's material, we may pass additional "Properties" to the model at any time. Such data may include transformation matrices or light positions.

Instancing

To create multiple instances of a model, we can use the "addInstance" function. This automatically creates a map of properties for every shader used by the underlying geometry of the model. This map can be obtained using the instance name as the argument of the "getInstanceProperties" function. It is however recommended to always use "addInstance" to request the map as this function automatically checks whether the instance already exists or if it needs to be created.

Bounding Volumes

For every geometry object an axis aligned minimum bounding box is created and may be used for visibility culling or even rudimentary physics simulations. In the model the bounding volumes of all its geometry are merged and can be accessed using "getBoundingBoxObjectSpace".

The "AABB" object of the vmmlib is able to calculate its center and the distance from the center to the maximum vertex at any time. Thus it is possible to work with minimum bounding spheres as well.

Since those two choices might not be precise enough for physics simulations, the geometry objects feature the "getVertexData" function to get a pointer to their vertices and the "getNumVertices" function to allow for looping through them.

Text

To display text, we need to first load a font from a file and then create a text sprite as shown in the example below:

FontPtr font = bRenderer().getObjects()->loadFont("KozGoPro-ExtraLight.otf", 50);
if (Input::isTouchDevice())
bRenderer().getObjects()->createTextSprite("instructions", vmml::Vector3f(1.f, 1.f, 1.f), "Double tap to start", font);
else
bRenderer().getObjects()->createTextSprite("instructions", vmml::Vector3f(1.f, 1.f, 1.f), "Press space to start", font);

Since the fonts are loaded by FreeType, please consult the official homepage of the library for more information about supported file formats.

Draw the text using the "drawText" and "queueTextInstance" functions of the model renderer, which is introduced in the drawing section.

Shaders

The Basic Renderer is based on the programmable pipeline and thus relies on shaders to draw its objects. The data necessary to compile the shader programs may be either generated by the framework or loaded from custom shader files (recommended).

Shader Files

Shader files can be loaded using the "loadShaderFile" function of the object manager. Alternatively the "loadObjMaterial" function allows for specifying the shader to be loaded for a material. In addition the "loadObjModel" function may receive a boolean which determines whether to load the shader from file or to generate it. In this case the shader name needs to match the material name. If a model features multiple materials, a shader will be loaded for each of them.

To ensure compatibility to all supported platforms, it is recommended to use shader version 1.0 for OpenGL ES 2.0, which roughly corresponds to version 1.20 on desktop systems. Since the version needs to be specified in the file, the Basic Renderer offers a macro that is replaced with the correct shader version after loading. The head of every shader file should therefore consist of the following lines:

$B_SHADER_VERSION
#ifdef GL_ES
precision mediump float;
#endif

The precision can of course be chosen freely between "highp", "mediump" and "lowp" and the macro can be changed in the configuration file.

You may optionally define the shader versions replacing the macro in the object manager:

bRenderer().getObjects()->setShaderVersionDesktop("#version 120");
bRenderer().getObjects()->setShaderVersionES("#version 100");

It is recommended to use the uniform and attribute names in the shader defined in the configurations. This ensures compatibility with the model renderer and the shaders constructed by the shader generator.

Shader Generator

Simple shaders based on Phong interpolation can be generated to quickly experiment with different parameters or to speed up development of large applications. However the generator is not capable of advanced effects such as shadow mapping, reflections (using e.g. a cube map) or even post-processing effects like blur, bloom or color correction.

To generate a shader we have two options based on either custom settings or material data. For the first option we can use the "generateShader" function of the object manager:

ShaderPtr customShader = bRenderer().getObjects()->generateShader("customShader", { 2, true, true, true, true, true, true, true, true, true, false, false, false }); // automatically generates a shader with a maximum of 2 lights

The second option is used if in "loadObjModel" or in the function "createMaterialShaderCombination" the boolean "shaderFromFile" is set to false. In this case the shader generator will analyze the material data and construct a shader that fits the material's characteristics.

Materials

A material in the Basic Renderer holds shader uniforms and a pointer to a shader to define the look of a drawable object. It can be either created as an empty container to set custom values or can be loaded from an OBJ material file.

It is recommended to use the uniform names defined in the configurations. This ensures compatibility with the model renderer and the shaders constructed by the shader generator.

Material Files

Some 3D applications, such as Blender, generate materials when exporting OBJ models, others including Cinema 4D do not. Therefore it might become necessary to write custom materials. A good source on how to write and adjust material files can be found on http://paulbourke.net.

However the Basic Renderer does not recognize all of the features an OBJ material would usually support. The following list shows the supported keys:

  • newmtl
  • Ka
  • Kd
  • Ks
  • Tf
  • Ns
  • Ni
  • illum
  • map_Kd
  • map_Ks
  • map_Bump
  • d
  • refl -type cube_top
  • refl -type cube_bottom
  • refl -type cube_front
  • refl -type cube_back
  • refl -type cube_left
  • refl -type cube_right
  • refl -type sphere

Textures

Textures can be loaded or created using the object manager. When creating a material from material data (which by default happens when loading an OBJ material), its textures are loaded automatically and can be accessed in the object manager.

If an empty texture holding no image data is desired, e.g. to attach it to a framebuffer, it can be created as shown below:

bRenderer().getObjects()->createTexture("fbo_texture", 0.f, 0.f); // create texture to bind to the fbo

Cube Maps

A cube map consists of six textures arranged in the shape of a cube. It allows for projecting the environment onto the sides of the cube and may therefore be used for reflections.

In the Basic Renderer a cube map can be loaded from files or created either based on a vector of six texture data objects or custom image data (allows for specifying no data for empty cube maps). For real-time reflections the cube map may be attached to a framebuffer as shown below:

CubeMapPtr cubeMap = bRenderer().getObjects()->createCubeMap("reflMap", 0.f); // create cube map to bind to the fbo
...
bRenderer().getView()->setViewportSize(512.f, 512.f); // adjust viewport size
for (GLuint i = 0; i < 6; i++)
{
...
// Bind buffer with cube map attached
fbo->bindCubeMap(cubeMap, i, false);
// Update scene
...
}
bRenderer().getView()->setViewportSize(bRenderer().getView()->getWidth(), bRenderer().getView()->getHeight()); // reset vieport size
fbo->unbind(defaultFBO);

Please note that cube maps must use square sizes. If used together with a framebuffer, it is therefore important to set the viewport's width and height to the same value.

Depth Maps

Depth maps are used for a multitude of effects such as shadow mapping and simulating shallow depth of field. Just as cube maps they can be created in the object manager and attached to framebuffers for real-time effects. An example is shown below:

DepthMapPtr depthMap = bRenderer().getObjects()->createDepthMap("depthMap", 0.f, 0.f); // create depth map to bind to the fbo
...
// Bind buffer with depth map attached
fbo->bindDepthMap(bRenderer().getObjects()->getDepthMap("depthMap"), true);
// Update scene
...
fbo->unbind();

Framebuffers

A custom framebuffer object allows to draw to non-default framebuffer locations and offers the possibility to attach textures. It is therefore possible to render a scene into a texture, one of the six faces of a cube map or to store the scene's depth information in a depth map. Such functionality is essential for post-processing effects. The aforementioned textures may be passed to shaders to be processed and drawn to either the default or another user defined framebuffer to accumulate multiple effects.

The framebuffer of the Basic Renderer is able to automatically adjust itself and an attached texture to the size of the viewport. If this functionality is not desired, it can be turned off by passing a fixed width and height when creating it in the object manager or later using the "resize" function of the framebuffer class.

When binding the framebuffer, the current framebuffer can be preserved by setting "preserveCurrentFramebuffer" to true. This buffer will then automatically be bound when unbinding the custom framebuffer. Otherwise we can use the static function "getCurrentFramebuffer" to get the framebuffer currently bound and then pass it to the "unbind" function of our custom framebuffer.

Sample code on how to bind a framebuffer and draw to a specified texture can be found in the "Textures" guide.

Cameras

The camera object of the Basic Renderer simplifies the creation of the view and projection matrices for a model to be drawn. It can be rotated around all three of its axes by an arbitrary amount, which sets it apart from an ordinary "look at" method. The latter is still provided as a static function if needed. The class additionally allows for moving the camera upwards, sidewards, forwards and backwards at a freely selectable speed and at any time the field of view and aspect ratio can be set. The aspect ratio should be updated every frame as the resolution of the view may have changed. It can be obtained from the view using the "getAspectRatio" function.

To position objects relative to the camera, the inverse view matrix and the forward, right and up vector can be obtained. If an object should always face towards the camera yet not follow its position, its model matrix can be multiplied with the inverse rotation matrix of the camera. Since in some LOD techniques objects are replaced by sprites if they are far away, the camera offers querying the inverse rotation matrix for only one axis. Otherwise an object could rotate in an unnatural manner, as for example a tree would suddenly be uprooted when tilting the camera.

The example below shows how to create a camera using the object manager:

bRenderer().getObjects()->createCamera("camera", vmml::Vector3f(-33.0, 0.f, -13.0), vmml::Vector3f(0.f, -M_PI_F / 2, 0.f));

Lights

The Basic Renderer features dynamic lighting based on a simple light class. It allows for defining a point light by setting its position, color, intensity, attenuation and radius. It is possible to specify the values for diffuse and specular colors separately to for example intensify or soften reflections produced by the light.

Loading Files

Files, such as textures, fonts or OBJ models, can be loaded using the object manager. However for the files to be found we need to include them into our Xcode project on iOS (we just drag and drop them in) or set the file path on desktop systems.

By default, the file path is "data/" relative to the applications location. We can change that invoking the following function before loading the first file:

bRenderer::setStandardFilePath("some/location"); // set the standard path for all files

Alternatively we can set the location in the configuration file, which allows for changes even after the application was compiled:

{
"DEFAULT_FILE_PATH" : "some/location"
}

For this to work the JSON file needs to always be placed in the "data/" folder.

Configurations

The Basic Renderer features a multitude of fully configurable default values collected in the "Configuration" header and cpp-file for quick access. Those values include the string tags of the four log modes of the Logger, default settings for view creation (width, height and window title), default shader versions and the according macro to set in the shader files, as well as a pool of predefined shader uniforms. Moreover all keys to be read from OBJ .mtl-files are accessible and adjustable.

An example of how to specify custom configurations is listed below. The key to be set corresponds to the function name used to access the value in the framework. Even though they are not part of the official specifications of JSON, c-style comments are enabled as they may be helpful to explain the choice of a specific setting when working in a collaborative environment.

bRenderer::loadConfigFile("config.json"); // load custom configurations replacing the default values in Configuration.cpp
/* Demo configuration file */
{
"LOG_MODE_INFO" : "This may interest you",
"DEFAULT_WINDOW_TITLE" : "Demo",
"DEFAULT_VIEW_WIDTH" : 1024,
"DEFAULT_VIEW_HEIGHT" : 768,
// Shader uniforms are customizable
"DEFAULT_SHADER_UNIFORM_NORMAL_MAP" : "NormalMap",
// Vectors can be specified using arrays
"DEFAULT_AMBIENT_COLOR" : [ 0.0, 1.0, 0.2 ]
}