Skip to content

全局光照

一般光照系统只考虑光源直接照射到物体表面所产生的效果,不会计算光源经过物体表面反射或折射的光线,即间接光照。全局光照系统能够对间接光照进行建模,实现更加逼真的光线效果。

以下为同一个测试场景,关闭GI(左图)和开启GI(右图)的效果对比

probe

原理简介

引擎在场景中按照设定的行列以及纵深数量放置了一系列探针 (Probe),用来收集周围物体的反射光信息。它们会根据所处位置对所在区域的光照信息进行搜集和存储,形成一个动态的间接光 Irrandiance Volume 区域:

volume

在实时着色的阶段,除了计算直接光源的颜色和强度,还会根据着色单元所处的世界坐标,找到对应的探针组,使用三线性插值获得该区域周围的间接光源信息。

使用方法

和其它组件用法一样,只要在场景中添加 GlobalIlluminationComponent 即可打开全局光照

ts
//配置Global Irrandiance 参数
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
// 自动更新GI信息,静态场景中可以在渲染完成后手动关闭节省性能
Engine3D.setting.gi.autoRenderProbe = true

//初始化引擎
await Engine3D.init();
let scene = new Scene3D()
let camera = new Object3D()
let mainCamera = camera.addComponent(Camera3D)
scene.addChild(camera)

// 初始化全局光照组件
let probeObj = new Object3D();
probeObj.addComponent(GlobalIlluminationComponent);
this.scene.addChild(probeObj);

// 渲染场景
let view = new View3D()
view.scene = this.scene
view.camera = mainCamera
Engine3D.startRenderView(view)

根据场景大小,用户可以动态调整探针区域范围:

  • 通过设置 probeXCount, probeYCount, probeZCount 调整探针数量(需渲染前设置);
  • 通过设置 offsetX, offsetY, offsetZ 调整区域中心位置;
  • 通过调整 probeSpace 调整探针的间距;

配置参数

Engine3D.setting.gi 配置参数。

参数类型描述
enableboolean开启/关闭
offsetXnumber探针组的注册点在x轴的偏移量
offsetYnumber探针组的注册点在y轴的偏移量
offsetZnumber探针组的注册点在z轴的偏移量
probeXCountnumber探针在x轴的数量
probeYCountnumber探针在y轴的数量
probeZCountnumber探针在z轴的数量
probeSizenumber每个探针采样到的数据尺寸
probeSpacenumber探针与探针之间的距离
ddgiGammanumber颜色gamma校正系数
indirectIntensitynumber间接光的光照强度
bounceIntensitynumber反射光的光照强度
octRTMaxSizenumber设置八面体贴图的总尺寸
octRTSideSizenumber设置八面体贴图,每个八面体正方形的尺寸
autoRenderProbeboolean设置探针是否自动更新

注意事项

使用全局光照会消耗GPU的部分算力,由于所有的探针Probe对全场景的光照信息进行搜集,这个计算量是不可忽视的,为了让引擎能够正常流畅运行,我们做了分帧处理优化。最终GI的完整效果呈现是一个随时间累积的过程。如果用户对 Irrandiance Volume 区域做了修改,其结果也不是瞬间呈现出来,也会需要一个响应过程。

如果你的场景是静态的,在引擎运行了一段时间后,可以主动关闭 autoRenderProbe,让引擎不再更新GI信息,从而解放这部分算力。

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.shadowSize = 2048;
        Engine3D.setting.shadow.shadowBias = 0.002;
        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 = 2;
    }
}

new Sample_GICornellBox().run();