Global Illumination
Conventional lighting systems only consider the direct illumination from light sources onto the surfaces of objects and do not calculate the light that is reflected or refracted by the surfaces, known as indirect illumination. Global illumination systems can model the indirect illumination, resulting in more realistic lighting effects.
The following images compare the effects of disabling GI (left) and enabling GI (right) in the same test scene:

Principle Introduction
The engine places a series of probes in the scene, arranged in rows, columns, and depth as specified, to collect the reflected light information from the surrounding objects. Based on their positions, these probes gather and store the lighting information for their respective regions, forming a dynamic indirect light Irradiance Volume region:

During the real-time shading phase, in addition to calculating the color and intensity of direct light sources, the engine locates the corresponding probe group based on the world coordinates of the shading unit, and uses trilinear interpolation to obtain the indirect light source information from the surrounding area.
Usage
Global illumination can be enabled simply by adding the GlobalIlluminationComponent. Note: when adding this component, you must explicitly pass the scene it belongs to as the second argument of addComponent.
//Initialize the engine
let engine = await Engine3D.init({
setting: {
gi: {
//Configure Global Irradiance parameters
probeYCount: 3,
probeXCount: 6,
probeZCount: 6,
probeSpace: 60,
offsetX: 0,
offsetY: 10,
offsetZ: 0,
// Automatically update GI information; in static scenes you can manually disable it after rendering completes to save performance
autoRenderProbe: true,
}
}
});
let scene = new Scene3D()
let camera = new Object3D()
let mainCamera = camera.addComponent(Camera3D)
scene.addChild(camera)
// Initialize the global illumination component (you must explicitly pass the scene it belongs to)
let probeObj = new Object3D();
probeObj.addComponent(GlobalIlluminationComponent, scene);
scene.addChild(probeObj);
// Render the scene
let view = new View3D()
view.scene = scene
view.camera = mainCamera
engine.startRenderView(view)Depending on the scene size, users can dynamically adjust the probe region:
- Adjust the number of probes by setting
probeXCount,probeYCount,probeZCount(must be set before rendering); - Adjust the center position of the region by setting
offsetX,offsetY,offsetZ; - Adjust the spacing between probes by modifying
probeSpace;
Configuration Parameters
Configuration parameters for engine.setting.gi.
| Parameter | Type | Description |
|---|---|---|
| enable | boolean | Enable/disable |
| offsetX | number | Offset of the probe group's registration point on the x-axis |
| offsetY | number | Offset of the probe group's registration point on the y-axis |
| offsetZ | number | Offset of the probe group's registration point on the z-axis |
| probeXCount | number | Number of probes on the x-axis |
| probeYCount | number | Number of probes on the y-axis |
| probeZCount | number | Number of probes on the z-axis |
| probeSize | number | Size of the data sampled by each probe |
| probeSpace | number | Distance between probes |
| ddgiGamma | number | Color gamma correction factor |
| indirectIntensity | number | Intensity of indirect lighting |
| bounceIntensity | number | Intensity of reflected light |
| octRTMaxSize | number | Total size of the octahedral texture |
| octRTSideSize | number | Size of each octahedral square in the octahedral texture |
| autoRenderProbe | boolean | Whether the probes update automatically |
Considerations
Using global illumination consumes some GPU processing power. Since all probes collect lighting information for the entire scene, this computational workload cannot be ignored. To ensure the engine runs smoothly, we have optimized the processing by dividing it across frames. The complete GI effect is therefore presented as a process that accumulates over time. If you modify the Irradiance Volume region, the results will not appear instantly either and will require a response time.
If your scene is static, you can manually disable
autoRenderProbeafter the engine has been running for a while, so that the engine no longer updates GI information and frees up this computational power.
import { Object3D, Scene3D, Engine3D, GlobalIlluminationComponent, Vector3, GTAOPost, PostProcessingComponent, BloomPost, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, DirectLight, KelvinUtil } from '@orillusion/core';
import * as dat from 'dat.gui';
class Sample_GICornellBox {
scene: Scene3D;
engine: Engine3D;
async run() {
this.engine = await Engine3D.init({
canvasConfig: {
devicePixelRatio: 1
},
setting: {
gi: {
enable: true,
probeYCount: 6,
probeXCount: 6,
probeZCount: 6,
offsetX: 0,
offsetY: 10,
offsetZ: 0,
indirectIntensity: 1,
lerpHysteresis: 0.004, //default value is 0.01
maxDistance: 16,
probeSpace: 5.8,
normalBias: 0,
probeSize: 32,
octRTSideSize: 16,
octRTMaxSize: 2048,
ddgiGamma: 2.2,
depthSharpness: 1,
autoRenderProbe: true
},
shadow: {
shadowBound: 50,
shadowSize: 2048,
shadowBias: 0.002,
autoUpdate: true,
updateFrameRate: 1
}
}
});
this.scene = new Scene3D();
this.scene.addComponent(AtmosphericComponent);
let mainCamera = CameraUtil.createCamera3DObject(this.scene);
mainCamera.perspective(60, this.engine.aspect, 1, 5000.0);
let hoverCameraController = mainCamera.object3D.addComponent(HoverCameraController);
hoverCameraController.setCamera(0, 0, 40, new Vector3(0, 10, 0));
await this.initScene();
let view = new View3D();
view.scene = this.scene;
view.camera = mainCamera;
this.engine.startRenderView(view);
let postProcessing = this.scene.addComponent(PostProcessingComponent);
postProcessing.addPost(BloomPost);
// add GI
this.addGIProbes();
}
private addGIProbes() {
let probeObj = new Object3D();
let GI = probeObj.addComponent(GlobalIlluminationComponent, this.scene);
this.scene.addChild(probeObj);
// add a delay to render GUIHelp menu
setTimeout(() => {
this.renderGUI(GI);
}, 1000);
}
private renderGUI(component: GlobalIlluminationComponent): void {
let volume = component['_volume'];
let giSetting = volume.setting;
function onProbesChange(): void {
component['changeProbesPosition']();
}
let gui = new dat.GUI();
let f = gui.addFolder('GI');
f.add(giSetting, `lerpHysteresis`, 0.001, 0.1, 0.0001).onChange(onProbesChange);
f.add(giSetting, `depthSharpness`, 1.0, 100.0, 0.001).onChange(onProbesChange);
f.add(giSetting, `normalBias`, -100.0, 100.0, 0.001).onChange(onProbesChange);
f.add(giSetting, `irradianceChebyshevBias`, -100.0, 100.0, 0.001).onChange(onProbesChange);
f.add(giSetting, `rayNumber`, 0, 512, 1).onChange(onProbesChange);
f.add(giSetting, `irradianceDistanceBias`, 0.0, 200.0, 0.001).onChange(onProbesChange);
f.add(giSetting, `indirectIntensity`, 0.0, 3.0, 0.001).onChange(onProbesChange);
f.add(giSetting, `bounceIntensity`, 0.0, 1.0, 0.001).onChange(onProbesChange);
f.add(giSetting, `probeRoughness`, 0.0, 1.0, 0.001).onChange(onProbesChange);
f.add(giSetting, `ddgiGamma`, 0.0, 4.0, 0.001).onChange(onProbesChange);
f.add(giSetting, 'autoRenderProbe');
f.close();
let f2 = gui.addFolder('probe volume');
f2.add(volume.setting, 'probeSpace', 0.1, volume.setting.probeSpace * 5, 0.001).onChange(() => {
onProbesChange();
});
f2.add(volume.setting, 'offsetX', -100, 100, 0.001).onChange(onProbesChange);
f2.add(volume.setting, 'offsetY', -100, 100, 0.001).onChange(onProbesChange);
f2.add(volume.setting, 'offsetZ', -100, 100, 0.001).onChange(onProbesChange);
f2.add(
{
show: () => {
component.object3D.transform.enable = true;
}
},
'show'
);
f2.add(
{
hide: () => {
component.object3D.transform.enable = false;
}
},
'hide'
);
f2.open();
}
async initScene() {
let box = await this.engine.res.loadGltf('https://cdn.orillusion.com/gltfs/cornellBox/cornellBox.gltf');
box.localScale = new Vector3(10, 10, 10);
this.scene.addChild(box);
let lightObj = new Object3D();
lightObj.x = 0;
lightObj.y = 30;
lightObj.z = -40;
lightObj.rotationX = 30;
lightObj.rotationY = 160;
lightObj.rotationZ = 0;
this.scene.addChild(lightObj);
let dirLight = lightObj.addComponent(DirectLight);
dirLight.lightColor = KelvinUtil.color_temperature_to_rgb(5355);
dirLight.castShadow = true;
dirLight.intensity = 2;
}
}
new Sample_GICornellBox().run();
