Project Rembrandt

From FlightGear wiki
Revision as of 04:42, 5 January 2012 by Fredb (Talk | contribs)

Jump to: navigation, search
WIP.png Work in progress
This article or section will be worked on in the upcoming hours or days.
See history for the latest developments.

Why this name ?

Rembrandt was a dutch painter living in the 17th century, famous as one of the master of chiaroscuro.

This project is about changing the way FlightGear renders lights, shadows and shades, and aims at making Rembrandt painting style possible in FG.

What is it ?

The idea driving the project is to implement deferred rendering inside FlightGear. From the beginning FlightGear had a forward renderer that tries to render all properties of an object in one pass (shading, lighting, fog, ...), making difficult to render more sophisticated shading (see the 'Uber-shader') because one have to take into account all aspects of the rendering equation.

Main view with the content of buffers displayed at corners

On the contrary, deferred rendering is about separating operations in simplified stages and collect the intermediary results in hidden buffers that can be used by the next stage.

First stage is the Geometry Stage 
we render all the scene into 4 textures, using multi render targets, to do it in one pass: one for the depth buffer, one for the normals (lower left of the image), one for the diffuse colors (lower right) and one for the specular colors (upper right).
Next stage is the Shadow Stage 
we render the scene again into a depth texture from the point of view of the lights. There will be one texture for every light casting shadows.
Then comes the Lighting Stage, with several substages 
  • Sky and cloud pass: the sky and the clouds are first drawn using classical method.
  • Ambient pass: the diffuse buffer is modulated with the ambient color of the scene and is drawn as a screen-aligned textured quad
  • Sunlight pass: a second screen aligned quad is drawn and a shader computes the view position of every pixel to compute its diffuse and specular color, using the normal stored in the first stage. The resulting color is blended with the previous pass. Shadows are computed here by comparing the position of the pixel with the position of the light occluder stored in the shadow map.
  • Additional light pass (to be implemented): the scene graph will be traversed another time to display light volumes (cone or frusta for spot lights, sphere for omni-directional lights) and their shader will add the light contributed by the source only on pixels receiving light.
  • Fog pass: a new screen aligned quad is draw and the position of the pixel is computed to evaluate the amount of fog the pixel has. The fog color is blended with the result of the previous stage.
All lighting computations are accumulated in a single buffer that will be used for the last stage, in addition of the one computed by the Geometry stage.
In the end, the Display Stage, with optional Post-Processing effect 
The results of the previous buffers are pushed to the main framebuffer to be displayed, optionally modified to show Glow, Motion blur, HDR, redout or blackout, screen-space ambient occlusion, anti-aliasing, etc...

In FG, we end the rendering pipeline by displaying the GUI and the HUD.

All these stages are more precisely described if this tutorial that is the basis of the current code, with some addition and modifications.


Deferred rendering is not capable to display transparent. For the moment, clouds are renderer separately and should be lit and shaded by their own. Transparent surfaces are alpha-tested and not blended. They would have to be drawn in their own bin over the composited image.

It also don't fit with depth partitioning because the depth buffer should be kept to retain the view space position, so for the moment, z-fighting is quite visible. Depth partitioning with non overlapping depth range might be the solution and should be experimented at one point.



Source code and data are available in gitorious repositories :

The code is in project/rembrandt branch

Important: This is experimental code and was only compiled with MSVC 2008, but should build and run on other systems as the modification involved is quite basic.

The code is not yet optimized and may put the graphic card under pressure.

Rendering of transparent surfaces

Transparent surfaces drawn after opaque objects

Transparent surfaces are detected by OSG loader plugins and their state set receive the TRANSPARENT_BIN rendering hint. In the culling pass, the cull visitor orders transparent surfaces in transparent bin. In a cull callback attached to the Geometry camera, after the scenegraph traversal, the transparent bins are removed from the render stage and saved in a temporary collection. In a cull callback attached to the Lighting camera, after the scenegraph traversal, the transparent bins saved at the previous stage, are added to the render stage of the Lighting camera with a high order num. That way, the transparent surface are drawn on top of the scene lighted from the Gbuffer.

Guidelines for shader writers

Predefined uniforms

Name Type Purpose
fg_Planes vec3 Used to convert the value of the depth buffer to a depth that can be used to compute the eye space position of the fragment
fg_BufferSize vec2 Dimensions of the buffer, used to convert gl_FragCoord into the range [0..1][0..1]

Geometry Stage

The Geometry Stage is there to fill the G-buffer. Shading doesn't occur at this stage, so light or fog computation should not be part of the shader. The required operation in the Fragment Shader is to fill every individual buffer with sensible value :

  1. Depth buffer, modified with gl_FragDepth, will record the distance between the fragment and the camera. Default behavior is to avoid to touch it, living the GPU rasterizer doing sensible things by interpolating vertex gl_Position from the Vertex or the Geometry Shader. If altering the computed depth is required, like in the Urban shader, the value of gl_FragDepth should be set.
  2. Normal buffer, modified with gl_FragData[0].xyz, will record the normal of the fragment in eye coordinates. gl_FragData[0].w is reserved for future use. The interpolated normal is usually simply stored but bump mapping or relief mapping affecting the normal can be computed here.
  3. Diffuse color buffer, modified with gl_FragData[1].rgb, will hold the unshaded color of the fragment, usually modulating the material diffuse+ambient color with the texture map. Diffuse color from environment mapping should also go here.
  4. Specular color, modified with gl_FragData[2].rgb, and specular shininess in gl_FragData[2].a, will retain the specular color of the fragment.

In anyway, don't use gl_FragColor as it is incompatible with MRT (Multi Render Target) and would affect the three last buffers with the same value.

Additional light pass

There would be a single shader for each light type used. The plan is to create lights like animations in the model XML file. The light shader will retrieve scene geometry by combining screen space position converted in view space ray by the inverse of the projection matrix (an helper function should be provided), and the fragment depth at that screen position read from the depth buffer. With the help of the fragment normal, the diffuse and specular color and the properties of the light the shader implements, it will be possible to add to the lighting buffer the contribution of the light rendered.

Fog Pass

Using the fragment depth, it will be possible to compute any fog distribution. For the moment, the simple fog equation is implemented.

Guidelines for modelers

Every model is, by default, rendered using the model-default effect. This effect initialize the G-buffer, ignoring transparent surfaces, by doing alpha testing and rendering all the geometry in the default bin. It is not possible to redirect rendering to transparent bins when the associated texture has alpha channel because most models use a single texture atlas and even opaque parts are rendered with texture with alpha channel.

If a model needs to have transparent or translucent surfaces, these surface objects need to be assigned a different effect that sets explicitly the render bin to "DepthSortedBin", or sets the rendering hint to "transparent". This tells the renderer to render this object using forward rendering, so lighting and fog need to be enabled, and if a shader program is used, they should be computed in the classical way.

If opaque surface need to have special effect, for example to apply bump mapping, this effect should use the "RenderBin" bin, or the rendering hint set to "opaque", and the G-buffer needs to be initialized correctly in the Geometry stage.



  • Implement Cascaded Shadow Map (need to be optimized - frustum calculation and night)
  • Draw transparent objects with forward rendering (may need to capture the transparent bin from the geometry stage and move it in the display stage) (OK)
  • Add spotlights as animations (nearly finished)
  • find a solution for ambient and emissive color of material (may need an additional buffer)
  • Use effect system instead of hard-coded shaders
  • Convert existing shaders to deferred rendering
    • Modify shadows to allow multiple casters (limited list)
    • Implement a priority list of light sources, based on priority and distance from the viewer
    • Add new animation to link a light source to a model
  • Tidy up the architecture
  • Restore depth partitioning using depth ranges
  • Restore stereo and other options currently available in CameraGroup
  • Implement quality vs performance user control


Model modification log

Add an effect to the propeller disk (object Propeller.Fast) to put it in a transparent bin
Add a spot light animation
Change KSFO_light.xml to apt-light.xml

Effect/Shader modification log

Default shaders
render to the G-buffer
Urban effect
render to the G-buffer
Spot light effect
new effect to render spot lights from the animation file
new effect to classify transparent surfaces (those that are not bound to the glass shader or other shader that use explicitly the transparent bin )