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

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, 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.

ts
//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.

ParameterTypeDescription
enablebooleanEnable/disable
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 the data sampled by each probe
probeSpacenumberDistance between probes
ddgiGammanumberColor gamma correction factor
indirectIntensitynumberIntensity of indirect lighting
bounceIntensitynumberIntensity of reflected light
octRTMaxSizenumberTotal size of the octahedral texture
octRTSideSizenumberSize of each octahedral square in the octahedral texture
autoRenderProbebooleanWhether 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 autoRenderProbe after the engine has been running for a while, so that the engine no longer updates GI information and frees up this computational power.

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, 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();

Released under the MIT License