Skip to content

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 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:

probe

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:

volume

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. It uses trilinear interpolation to obtain the indirect light source information from the surrounding area.

Usage

Similar to other components, global illumination can be enabled by adding the GlobalIlluminationComponent to the scene.

ts
//Configure Global Irradiance parameters
Engine3D.setting.gi.probeYCount = 3
Engine3D.setting.gi.probeXCount = 6
Engine3D.setting.gi.probeZCount = 6
Engine3D.setting.gi.probeSpace = 60
Engine3D.setting.gi.offsetX = 0
Engine3D.setting.gi.offsetY = 10
Engine3D.setting.gi.offsetZ = 0
// Automatically update GI information, you can manually disable it after rendering in static scenes to improve performance
Engine3D.setting.gi.autoRenderProbe = true

//Initialize the engine
await Engine3D.init();
let scene = new Scene3D()
let camera = new Object3D()
let mainCamera = camera.addComponent(Camera3D)
scene.addChild(camera)

// Initialize the global illumination component
let probeObj = new Object3D();
probeObj.addComponent(GlobalIlluminationComponent);
this.scene.addChild(probeObj);

// Render the scene
let view = new View3D()
view.scene = this.scene
view.camera = mainCamera
Engine3D.startRenderView(view)

Depending on the scene size, users can dynamically adjust the probe region:

  • Adjust the number of probes on the x, y, and z axes 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

The configuration parameters for Engine3D.setting.gi are as follows:

ParameterTypeDescription
enablebooleanEnable/disable global illumination.
offsetXnumberOffset of the probe group's registration point on the x-axis.
offsetYnumberOffset of the probe group's registration point on the y-axis.
offsetZnumberOffset of the probe group's registration point on the z-axis.
probeXCountnumberNumber of probes on the x-axis.
probeYCountnumberNumber of probes on the y-axis.
probeZCountnumberNumber of probes on the z-axis.
probeSizenumberSize of data sampled by each probe.
probeSpacenumberDistance between probes.
ddgiGammanumberColor gamma correction factor.
indirectIntensitynumberIntensity of indirect lighting.
bounceIntensitynumberIntensity of reflected light.
octRTMaxSizenumberTotal size of octahedral textures.
octRTSideSizenumberSize of each square in the octahedral texture.
autoRenderProbebooleanAutomatically update probes.

Considerations

Using global illumination consumes some GPU processing power. Since all probes collect lighting information for the entire scene, the computational workload is significant. To ensure smooth engine performance, we have optimized the processing by dividing it into frames. The complete GI effect accumulates over time. If modifications are made to the Irradiance Volume region, the results will not be instantly visible and will require a response time.

If your scene is static, you can manually disable autoRenderProbe after the engine has been running for a while to free up computational resources.

WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

<
ts
import { Object3D, Scene3D, Engine3D, GlobalIlluminationComponent, Vector3, GTAOPost, PostProcessingComponent, BloomPost, AtmosphericComponent, CameraUtil, HoverCameraController, View3D, webGPUContext, DirectLight, KelvinUtil } from '@orillusion/core';
import * as dat from 'dat.gui';

class Sample_GICornellBox {
    scene: Scene3D;
    async run() {
        Engine3D.setting.gi.enable = true;
        Engine3D.setting.gi.probeYCount = 6;
        Engine3D.setting.gi.probeXCount = 6;
        Engine3D.setting.gi.probeZCount = 6;
        Engine3D.setting.gi.offsetX = 0;
        Engine3D.setting.gi.offsetY = 10;
        Engine3D.setting.gi.offsetZ = 0;
        Engine3D.setting.gi.indirectIntensity = 1;
        Engine3D.setting.gi.lerpHysteresis = 0.004; //default value is 0.01
        Engine3D.setting.gi.maxDistance = 16;
        Engine3D.setting.gi.probeSpace = 5.8;
        Engine3D.setting.gi.normalBias = 0;
        Engine3D.setting.gi.probeSize = 32;
        Engine3D.setting.gi.octRTSideSize = 16;
        Engine3D.setting.gi.octRTMaxSize = 2048;
        Engine3D.setting.gi.ddgiGamma = 2.2;
        Engine3D.setting.gi.depthSharpness = 1;
        Engine3D.setting.gi.autoRenderProbe = true;

        Engine3D.setting.shadow.shadowBound = 50;
        Engine3D.setting.shadow.autoUpdate = true;
        Engine3D.setting.shadow.updateFrameRate = 1;

        await Engine3D.init({
            canvasConfig: {
                devicePixelRatio: 1
            }
        });
        this.scene = new Scene3D();
        this.scene.addComponent(AtmosphericComponent);

        let mainCamera = CameraUtil.createCamera3DObject(this.scene);
        mainCamera.perspective(60, webGPUContext.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;
        Engine3D.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.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 Engine3D.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 = 30;
    }
}

new Sample_GICornellBox().run();

Released under the MIT License