GI and Light Baking

This tutorial will detail how HOOPS Luminate can be used to perform baking. By baking, we mean pre-calculating a given information, storing it and restituting it, faster, as a later rendering phase. Two types of data will be baked in our example:

  • The global illumination information of our scene

  • The direct contribution of physical lights in the scene

The baking process for this model takes between a few seconds up to a few minutes, depending on the quality of the baked signal. Then, the replay of the baked cache is either performed in real-time or in software.

The image below shows the scene after baking, as the second step of the tutorial will display it:

../../../_images/wf_GIAndLightBaking1.png

Note

This scene is purely diffuse to focus only on baking lighting informations.

Selecting Filtering Positions

A global illumination cache that is processed by RED::IWindow::FrameTracingGICache in world mode (RED::GICM_WORLD) should specify a list of filtering positions. A sample in the cache must see at least one filtering position in the list to be kept alive. Otherwise, it gets discarded. Filtering positions are a convenient way to reduce the scope of a GI cache to the meaningful part of the model to be captured. You can think of filtering positions as possible cameras positions that could be visited when the cache will be used. Therefore, filtering positions are only needed for world cache calculations.

The simple example below shows the effect of using filtering positions:

../../../_images/wf_GIAndLightBaking_FilteringPositions.png

Effect of a single filtering position

The single filtering position which is declared prevent the generation of cache samples in regions that it don’t see directly. Consequently, we don’t see any baked lighting signal in the shadow of the box, seen from the filtering position.

A few dozens of filtering positions are generally needed to visualize the most important regions of a model. This number can be increased at will to cover more and more hidden details in a scene. However, adding more filtering positions will slightly increase the calculation time of the cache. In the office scene, example chosen in this tutorial, we use around 30 filtering positions.

As we use a model exported from REDmax, filtering positions are “Dummy” helpers. The code to find them is linked below:

int count;
const RED::Matrix* matx;
RED::Vector< RED::Vector3 > filterpos;
RED::Vector< RED::Object* > noobjects;

RED::Map< RED::Object*, unsigned int > transforms;
RC_TEST( iroot->GetShapes( transforms, CID_REDTransformShape ) );

for( transforms.begin(); !transforms.end(); transforms.next() )
{
    RED::Object* node = transforms.current_key();
    RED::IShape* inode = node->As< RED::IShape >();

    RC_TEST( inode->GetChildrenCount( count ) );
    if( count == 0 )
    {
        RED::ITransformShape* itnode = node->As< RED::ITransformShape >();
        RC_TEST( itnode->GetMatrix( matx ) );
        if( matx )
        {
            RC_TEST( filterpos.push_back( matx->GetTranslation() ) );
        }
    }
}


if( filterpos.empty() == true )
{
    RED::Vector3 veye;
    RC_TEST( icamera->GetEye( veye ) );
    RC_TEST( filterpos.push_back( veye ) );
}

Selecting Lights for Baking

Any light can be declared as baked. A baked light is neither rendered in hardware nor in software. It’s ignored by all rendering phases but the GI cache generation phase. Consequently, a baked light generates a static lighting that is stored in the global illumination cache. In our example, we do select all physical lights and the skylight for baking:

RED::Map< RED::Object*, unsigned int > lshapes;
RC_TEST( iroot->GetShapes( lshapes, CID_REDLightShape ) );

for( lshapes.begin(); !lshapes.end(); lshapes.next() )
{
    RED::Object* light = lshapes.current_key();
    if( !light )
        RC_TEST( RED_FAIL );

    RED::ILightShape* ilight = light->As< RED::ILightShape >();

    bool is_physical, is_skylight, is_hemi;

    RC_TEST( ilight->IsPhysicalLight( is_physical ) );
    RC_TEST( ilight->IsSkyLight( is_skylight, is_hemi ) );

    if( is_physical == true || is_skylight == true )
    {
        RC_TEST( ilight->SetRenderMode( RED::RM_BAKED, 1, iresmgr->GetState() ) )
    }
}

The GI cache is made of samples that are positioned in space. The global illumination and the baked lighting at each point in the scene image are then calculated by interpolating from these cache samples. Consequently, it’s very difficult to accurately capture hard shadows cast by very small lights. The GI cache tends to smooth all the effects it captures.

That’s why a default choice to bake all physical lights and the skylight is generally acceptable. The choice of baking one or the other light occurs by changing the RED::ILightShape::SetRenderMode``( ``RED::RM_BAKED, 0 or 1, … ), enabling or disabling baking for that light. That’s also why the sunlight is not easy to bake as the shadows it produces are quite strong.

Setting Up Baking Options

The fact of recording a world space global illumination cache that needs to be seen in motion from all viewing positions may require higher GI options settings than usual ones for the same scene calculated with a view space cache. Similarly, baked lighting informations that are recorded should be calculated with a fairly high lighting quality. Consequently, we use these settings:

RC_TEST( icamopt->SetOptionValue( RED::OPTIONS_RAY_WORLD_CACHE_SAMPLES_COUNT, 400000, iresmgr->GetState() ) );
RC_TEST( icamopt->SetOptionValue( RED::OPTIONS_RAY_LIGHTS_SAMPLING_RATE, 40, iresmgr->GetState() ) );
RC_TEST( icamopt->SetOptionValue( RED::OPTIONS_RAY_GI_CACHE_HEMI_SAMPLING_RATE, 40, iresmgr->GetState() ) );
RC_TEST( icamopt->SetOptionValue( RED::OPTIONS_RAY_GI_ESTIMATOR_SAMPLING_RATE, 1000, iresmgr->GetState() ) );

Of course, the higher the values, the higher the GI cache calculation time. For the sample model, we’re around 5 minutes of computation time on a normal computer with 8 cores, using these options. Time for a coffee ;-) To compute a cache, we request a number of samples in it: This is the RED::OPTIONS_RAY_WORLD_CACHE_SAMPLES_COUNT. The higher the value, the more the density of the resulting cache.

Visualizing the GI Cache on the GPU

By default, the cache “as is” can be used by a software rendering call. It don’t require any change. The GPU display of a cache requires one more call to be performed, so that the data in the cache can be setup for GPU display:

RED::IGICache* igicache = g_gicache->As< RED::IGICache >();
RC_TEST( igicache->GPUBuild( g_show_gi, g_show_baking, g_border_shrink, g_smoothness, false, iresmgr->GetState() ) );

RED::Vector< RED::Object* > giclist;
RC_TEST( giclist.push_back( g_gicache ) );
RC_TEST( ivrl->SetViewpointGICaches( giclist, camera, iresmgr->GetState() ) );

The RED::IGICache::GPUBuild method does the GPU setup of the cache. Some display options are available, to visualize the GI part of the signal or the baked lighting part of it. A GPU GI cache is made of splats that are rendered on screen. The splat coverage “paints” the entire scene with the GI cache informations. The tutorial toolbar controls let you adjust the splat radius, using the RED::OPTIONS_RAY_WORLD_CACHE_SAMPLES_SPREAD.

../../../_images/wf_GIAndLightBaking2.png

Visualizing cache samples with a low spread radius. The cache here contains less than 100.000 final samples.

A Word on GPU Performances

The GI cache replay on the GPU consumes a lot of GPU fillrate. The amount of fillrate consumed directly depends on the samples spread size. The number of samples in the cache does not have a significant impact on display performances, unless this number excesses the GPU capabilities. More samples mean smaller samples, reducing the fillrate at the expense of more splats being displayed. Therefore, calculating caches up to 5 million samples is generally not an issue on desktop GPUs, and the rendering remains real-time. Of course, more samples will consume more GPU video memory.

The memory used by GI caches on the GPU is approximately 1 megabyte of video memory for 10000 - real - GI cache samples.

The real number of samples in the cache may vary compared to the requested number of samples. This depends a lot on the placement of the filtering positions, that’ll remove part of the samples distribution that are not viewing any filtering position. As an example, the cache in this scene was computed asking for 500.000 samples and it finally contains less than 100.000 samples.