The EaZD Deferred Renderer API

Home About EaZD Deferred renderer API SceneGraph3D API SceneGraph3D scene description language

Introduction

The EaZD deferred renderer is designed to operate either as a standalone rendering library using OpenGL, or as am integral part of the SceneGraph3D scene graph management toolkit. This document provides the essential information about linking and using the deferred renderer in your projects.

Including EaZD Deferred Renderer in your Project

EaZD comes with a static library file, DeferredRenderer.lib, and two header files, DeferredRenderer.h and DeferredRendererShaders.h. When building your application you must link with this static library, include DeferredRenderer.h and provide a path to this header file. The second header file is not necessary for linking and compiling your application and is only required when recompiling the library. In order to make the library easier to use and port, the shaders are stored as strings in DeferredRendererShaders.h. Additionally, most of them have been divided into modular parts to facilitate the reconfiguration of the renderer at run-time. So if you want to experiment with the rendering itself, this is probably the first place where you should look.

Standalone Initialization and Use

The first thing to take care of - if not already done - is the support for OpenGL extensions in your program, when compiling under Microsoft Windows. It is advised to use the OpenGL Extensions Wrangler library (GLEW) for this purpose, although manual initialization of the DLL entries is also possible:

#ifdef WIN32
glewInit();
#endif

Then, you may allocate the deferred renderer and after acquiring a GL context, initialize it:

DeferredRenderer *dr = new DeferredRenderer();
dr->init();

When in standalone operation, the EaZD deferred renderer requires the registration of two callback functions, the draw callback and the camera transformation callback. The first function contains all code necessary to render the geometry in OpenGL relative to the World Coordinate System (WCS). The second function provides all geometric transformations and the projection to transform everything to screen coordinates, including any (modelview) camera rotations and positioning, the perspective projection and the viewport transformation setup. The signatures of the two functions must be as follows:

void (*draw_callback)();			
void (*camera_callback)();

You can pass the pointers to the draw and camera functions to the deferred renderer using the member functions:


setDrawCallback(void (*drw)() ) {}

and


setCameraCallback(void (*cm)() ) {}

For example:

void drawFunc();
void cameraFunc();
dr->setDrawCallback(drawFunc);
dr->setCameraCallback(cameraFunc);
...
void drawFunc()
{
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glTranslatef(-10,0,0);
    glutSolidTeapot(5);
    glPopMatrix();
    glPushMatrix();
    glTranslatef(10,0,0);
    glutSolidTeapot(5);
    glPopMatrix();
}
void cameraFunc()
{
    extern int width, height;
    extern float look_at[3], pos[3];
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(pos[0], pos[1], pos[2], look_at[0], look_at[1], look_at[2], 0, 1, 0);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0f, width/(float)height, 0.1f, 1000.0f);
    glViewport(0, 0, width, height);
}

To re-render the view when required, call the draw() member function of the deferred renderer:

dr->draw();

If shadows are enabled for at least one of the light sources, you should also call the updateShadows() method before the draw call in order to prepare the shadow maps. Note that as will be explained later in the performance tuning section, the deferred renderer can be ordered to skip frames in the calculation of the shadow maps for specific lights, so you do not have to take care of this in the drawing loop yourself. Therefore, in every window update, you can safely call the following code, regardless of the existance of shadows or the rate of their update:

dr->updateShadows();
dr->draw();

Initialization and Use with SceneGraph3D

Using the EaZD deferred renderer with the integrated scene graph is even easier. The OpenGL extensions initialization step remains the same of course (see above) and same goes for the allocation of the deferred renderer and its initialization after acquiring a GL context:

DeferredRenderer *dr = new DeferredRenderer();
dr->init();

To use the SceneGraph3D along with the deferred renderer, you must include the SceneGraph.h file in your project and also link with the SceneGraph.lib static library. In the scene graph mode of operation, EaZD does not require the registration of any callback function, as all transformations and geometry rendering is handled internally by SceneGraph3D. You only have to initialize the scene graph and tell it which renderer object to use for output:

World3D *world = new World3D();
world->setRenderer(dr);

Yes, this means that you may provide alternative editions of a renderer, switch between different renderers, or attach a renderer to multiple scene graphs. As long as you can provide an object subclassed from the DeferredRenderer class as a renderer, you are fine. If you pay close attention to the declaration of the setRenderer() method of the scene graph, you will discover that it actually takes as input a GenericRenderer object, i.e. a superclass instance of DeferredRenderer. This is done to facilitate the deployment of the scene graph even in platforms where shaders cannot be used or the specifications do not meet the requirements of EaZD. Of course you will have to provide your own implementation for most operations. In this case, you must also disable the deferred rendering support of the scene graph by undefining (commenting out) the _USING_DEFERRED_RENDERING_ in SceneGraph.h.

After EaZD initialization, you can proceed to loading the scene file and prepare the scene graph for rendering:

world->load(argv[1]);
world->init();

Do not forget to deallocate the scene graph object before doing the same with the deferred renderer object, when closing down your application.

 

Geometry Bins

To be able to treat parts of the content in a different manner or in a specified order, EaZD uses four bins, where geometry must be sorted either at creation or dynamically. The renderer traverses the scene graph in separate passes, each time enabling one bin state. It is up to the programmer to perform conditional checks to allow geometry to be displayed in each pass according to the bin it is assigned to. All implemented scene ghraph nodes adhere to this principle. For instance, geometry nodes split primitives according to material transparency, light sources get drawn as glowing particles when a particle bin is enabled and so on. There are 4 bins at the moment:

In general, one specifies that certain parts of a node's geometry are targeted to a specific pass, by checking:

// within the draw method of a scene graph node:

if (render_mode==SCENE_GRAPH_RENDER_MODE_[HIDDEN|NORMAL|TRANSPARENCY|PARTICLES])
{

// do some drawing

}

 

Buffer Preview and Statistics

EaZD provides full preview of all buffers for debugging and educational purposes. You can show a buffer by calling in each frame:

dr->show(buf);

Where (int) buf is one of:

  • DR_TARGET_COLOR
  • DR_TARGET_NORMAL
  • DR_TARGET_SPECULAR
  • DR_TARGET_LIGHTING
  • DR_TARGET_DEPTH
  • DR_TARGET_FRAMEBUFFER
  • DR_TARGET_SHADOWMAP
  • DR_TARGET_GLOW
  • DR_TARGET_AO

Simple statistics (average frame rate and average frames per second) are obtained by calling the showStatistics() member function. You can also obtain the current average framerate by calling the getFrameRate() mathod. So, the rendering of the main window may include the following calls:

void RenderMainWindow()
{
    extern bool enable_preciew; 
    extern int cur_preview_buf;
    
    dr->updateShadows();              //shadow buffer rendering
    dr->draw();                       //main drawing call
    if (enable_preview)                
    {
        glViewport(0,0,width,height); //set the preview window area 
        dr->show(cur_preview_buf);    //render the buffer preview 
    }
    dr->showStatistics();             //draw textual statistics
    glutSwapBuffers();                //finish frame and swap buffers 
}

Lights and Shadows

The EaZD deferred renderer implements its own light sources, so the OpenGL lights are completeley disregarded. The light managment of the renderer (also present in the GenericRenderer class to be able to simulate its behavior in plain OpenGL) defines a maximum of 128 internal light sources that can be created, set, switched on/off, modified and moved around at any time. The light sources can be point lights, area lights (point lights with a non-zero radius) and spot lights, with or without attenuation or shadows. Lights also have a range of effect (and a near range for defining the closest-to-light distance for the shadow map) as well as an RGB color value which can be set to more than (1,1,1). Internally, illumination can withstand a 200% overexposure without being saturated, although the final result depends on the output buffer color accuracy.

To add a light to the application (not via the scene graph, which handles them as normal scene graph nodes and performs the rest of the operations internally), you simply call the int createLight() method of the renderer. This method initializes a new light source (disabled at the beginning) and returns an identifier to it. Further calls to modify the light source are done through this identifier:


int light1 - dr->createLight();
dr->setLightColor(light1, 1.0f, 0.95f, 0.89f);
dr->setLightPosition(light1, 10.0f, 300.0f, -50.0f);
dr->enableLight(light1);
...

Light position is defined relative to the WCS. The createLight() method returns -1 if a light source cannot be allocated. The order of light commands is not restrictive, as far as the light has been succesfully created first and any call may be made at any time. Bear in mind though that switching some of the attributes such as the shadow sampling method, requires the re-parsing of the shaders, resulting in a small delay.

The various attributes of the light sources are set with the following methods:

Shadows are enabled individually for each light source. Four different modes are supported for sampling the shadow maps, nearest neighbor, 2X2 multisampling with percentage closer filtering (PCF), 16-tap disk sampling PCF and 16-tap variance shadow maps (VSM - not yet implemented). The sampling mode is the same for all shadow maps. Soft shadows are produced with all modes except the nearest neighbor sampling, when the light radious is greater than zero. The size of the light source determines the fuzziness of the penumbra. EaZD produces realistic penumbrae by taking into account the distance to the occluder in order to calculate the shadow spread factor:

The methods associated with the shadow generation are listed below:

Sky Model

EaZD provides a custom empirical sky illumination model. The user declares which light source is the sun and computes the sky dome illumination according to the light's position (see also the scene graph node description). The model is not physically-based but it is very fast to compute. To further increase realism, the sun can be rendererd as volumetric light, casting correct light shafts by ray marching (with shadows). The number of samples along each ray is adjustable.

To enable the sky model use the method void enableSkylight(bool e). You can obtain a pointer to the data structure SkyModel to set its parameters by calling SkyModel * getSkyModel(). The SkyModel class isdefined as follows:

class SkyModel
{
public:
Vector3D fog_color;
float density;
float fog_distance;
float fog_alpha;
bool use_fog;
bool volumetric;
int samples;
SkyModel()
};

 

Particles

EaZD includes a particle renderer for creating view-aligned "spherical" particles whose transparency is modulated according to the portion of the particle's sphere not intersected with background geometry. This is done in order to convey a true three-dimensional impression for effects involving particles (e.g. glowing particles, smoke etc) and help blend the billboards with the opaque geometry more naturally. This procedure is implemented as a separate pass, where a shader is enabled to process all user data and construct the necessary geometry for the particlres. All the user has to do is declare a part of the draw function as using "particle mode" (e.g. a conditional statement within a draw method of a scene graph node) and write a piece of code that declares position and radius of particles as glVertex4f points, particle colors as glColor4f and bind (or not) a texture to use over the particle's billboard. The reference frame of the current modelview matrix is maintained, thus allowing to very easily generate a simple scene graph node that draws a number of particles on screen. No specific particle class has been implemented at this moment but the particle rendering is used to produce glowing light sources (see respective draw method of light source node).

To enable the rendering of particles in a scene graph node, just check if the current pass involves particle rendering. If this is the case, then the appropriate shader has been enabled and all subsequent commands are interpreted in a special way:

if (render_mode==SCENE_GRAPH_RENDER_MODE_PARTICLES)
{

// draw particles here. All vertex transformations apply.

}

To draw the particles themselves, call glVertex4f, where the XYZ components are the local coordinates of the particle's center and W is the particle radius. The color and alpha are controlled by the glColor4f function. Make sure to render the particles as GL_POINTS:

renderer->getParticleRenderer()->useTexture(false);
renderer->getParticleRenderer()->setIntensity(2.0f*max(color.x,max(color.y,color.z)));
glEnable(GL_COLOR_MATERIAL);
glBegin(GL_POINTS);
glColor4f(color.x,color.y,color.z,0.2f);
glVertex4f(position.x,position.y,position.z,glow_radius);
glEnd();
glDisable(GL_COLOR_MATERIAL);

The useTexture and setIntensity methods of the ParticleRenderer (obtained from the deferred renderer by its getPArticleRenderer method) notify the renderer to use or ignore texturing and boost the brightness of the particles (color burn - glow). Texturing must be enabled or disabled from the particle renderer state, otherwise black particles will be shown (color is modulated by the texture) if no texture is provided or enabled by OpenGL. Furthermore, if no texturing is needed, this can save a signifficant number of texture fetches in case of many particles.

 

Other Effects

EaZD supports a camera depth-of-field defocusing effect to simulate realistic non-pinhole camera lenses. This is controlled by the following methods:

The frame buffer supports high dynamic range operations such as tone mapping and bloom. Tone mapping is applied at the post-processing stage and is controlled by the following methods:

 

Helper Methods

In order to adjust the size of the internal buffers of the deferred renderer one must call the void resize(int w, int h) method from within the corrsponding window callback or event handler function, otherwise distortion and loss of buffer resolution will occur. The two arguments are the actual width and height of the window. To set the internal resolution to a different scaling do not pass modified values to this method; there are specialized methods for setting the internal scaling of of the deferred renderer buffers (see performance tuning section).

To obtain the texture associated with an internal buffer for use outside the deferred renderer, you can call the GLuint getBuffer(int id) method and pass the buffer id (same as the ids for the buffer preview, except the shadow buffer, which is hidden).

 

Performance Tuning

If performance is slow due to a high resolution output or any other reason, there are a few things that you can do to increase the framerate. First, you can reduce the internal buffer size by either setting a a scale ration between the actual window size and the frame buffer size using the method void setBufferScale(float scale) or by providing a fixed size for the internal buffer, regardless of the actua lwindow size using the method void setFixedBufferSize(int width, int height). You can unlock the internal buffer size by calling void setFreeBufferSize(). The (uniform) scale factor parameter of the setBufferScale(float scale) is defiend as:
(Internal Buffer Size)/(Actual Window Size).

Turning off the ambient occlusion can cause a dramatic improvement in the framerate as it is a major GPU hog, or alternatively you may want to reduce the internal buffer resolution. Halving the internal buffer resolution effectively doubles the performance.

As mentioned in the lights and shadows section, if you have too many lights enabled that cast shadows, you can decrease the rate of shadow map calculations by calling the void setLightSkipFrames(int light, int sf) method (and of course reduce their size).

Finally, EaZD facilitates the dynamic resizing of the display buffer as a function of the frame rate. using a combination of the getFrameRate() and setBufferScale() methods, you can dynamically adjust the internal buffers to maintain a steady performance. In future versions this will be also included as an optional internal operation.



Last update: 23 Feb. 2010