全局光照
一般光照系统只考虑光源直接照射到物体表面所产生的效果,不会计算光源经过物体表面反射或折射的光线,即间接光照
。全局光照系统能够对间接光照进行建模,实现更加逼真的光线效果。
以下为同一个测试场景,关闭GI(左图)和开启GI(右图)的效果对比
原理简介
引擎在场景中按照设定的行列以及纵深数量放置了一系列探针 (Probe)
,用来收集周围物体的反射光信息。它们会根据所处位置对所在区域的光照信息进行搜集和存储,形成一个动态的间接光 Irrandiance 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 配置参数。
参数 | 类型 | 描述 |
---|---|---|
enable | boolean | 开启/关闭 |
offsetX | number | 探针组的注册点在x轴的偏移量 |
offsetY | number | 探针组的注册点在y轴的偏移量 |
offsetZ | number | 探针组的注册点在z轴的偏移量 |
probeXCount | number | 探针在x轴的数量 |
probeYCount | number | 探针在y轴的数量 |
probeZCount | number | 探针在z轴的数量 |
probeSize | number | 每个探针采样到的数据尺寸 |
probeSpace | number | 探针与探针之间的距离 |
ddgiGamma | number | 颜色gamma校正系数 |
indirectIntensity | number | 间接光的光照强度 |
bounceIntensity | number | 反射光的光照强度 |
octRTMaxSize | number | 设置八面体贴图的总尺寸 |
octRTSideSize | number | 设置八面体贴图,每个八面体正方形的尺寸 |
autoRenderProbe | boolean | 设置探针是否自动更新 |
注意事项
使用全局光照会消耗GPU的部分算力,由于所有的探针Probe
对全场景的光照信息进行搜集,这个计算量是不可忽视的,为了让引擎能够正常流畅运行,我们做了分帧处理优化。最终GI的完整效果呈现是一个随时间累积的过程。如果用户对 Irrandiance Volume
区域做了修改,其结果也不是瞬间呈现出来,也会需要一个响应过程。
如果你的场景是静态的,在引擎运行了一段时间后,可以主动关闭
autoRenderProbe
,让引擎不再更新GI信息,从而解放这部分算力。
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();