全局光照
一般光照系统只考虑光源直接照射到物体表面所产生的效果,不会计算光源经过物体表面反射或折射的光线,即间接光照
。全局光照系统能够对间接光照进行建模,实现更加逼真的光线效果。
以下为同一个测试场景,关闭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)
//配置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, HDRBloomPost, 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
Engine3D.setting.render.postProcessing.bloom = {
enable: true,
debug: false,
blurX: 4,
blurY: 4,
luminosityThreshold: 0.9,
radius: 4,
strength: 1.2
}
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(HDRBloomPost)
// 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()
import { Object3D, Scene3D, Engine3D, GlobalIlluminationComponent, Vector3, GTAOPost, PostProcessingComponent, HDRBloomPost, 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
Engine3D.setting.render.postProcessing.bloom = {
enable: true,
debug: false,
blurX: 4,
blurY: 4,
luminosityThreshold: 0.9,
radius: 4,
strength: 1.2
}
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(HDRBloomPost)
// 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()