Skip to content

阴影

阴影为场景和模型添加了一定程度的深度和真实感,因为它们可以显示物体的比例和位置,使场景中的物体看起来更加立体。在引擎中,光源可以将物体的阴影投射到自身的其他部分或者附近的场景中,这些阴影可以是实时的,也可以是静态的。

配置阴影

阴影是通过光源来实现的,目前引擎中 平行光(DirectLight)点光源(PointLight)聚光灯(SpotLight) 都可以产生阴影。需要渲染阴影效果时,只需要将对应灯光的 castShadow 属性设置为 true 即可。

ts
let lightObj = new Object3D();
// 设置灯光位置
lightObj.x = 0;
lightObj.y = 0;
lightObj.z = 0;
// 设置灯光角度,绕X轴旋转45度
lightObj.rotationX = 45;
lightObj.rotationY = 0;
lightObj.rotationZ = 0;
// 添加平行光组件,并开启阴影
let lc = lightObj.addComponent(DirectLight);
lc.castShadow = true; // 默认为 false
lc.intensity = 5;
scene.addChild(lightObj);

想要看到阴影效果,我们还需要产生阴影的物体和承载阴影的物体。如果希望光线照射到某个物体上产生阴影效果,需要在物体上添加一个 MeshRenderer 组件,并且将该组件的 castShadow 属性设置为 true

ts
//创建一个box,用于产生阴影
let castShadowObj = new Object3D();
let mr1 = castShadowObj.addComponent(MeshRenderer);
mr1.geometry = new BoxGeometry();
mr1.material = new LitMaterial();
mr1.castShadow = true
scene.addChild(castShadowObj);

然后需要在接受阴影的物体上添加一个 MeshRenderer 组件,并且将该组件的 receiveShadow 属性设置为 true

ts
//创建一个plane,用于接受阴影
let receiveShadowObj = new Object3D();
let mr2 = receiveShadowObj.addComponent(MeshRenderer);
mr2.geometry  = new PlaneGeometry(1000,1000);
mr2.material =new LitMaterial();
mr2.receiveShadow = true;
scene.addChild(receiveShadowObj);

这样就可以在场景上看到一个 box 在平行光的照射下,产生了一个阴影并投射在了 plane 上:

平行光阴影


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

<
ts
import { Engine3D, Scene3D, Object3D, Camera3D, Vector3, AtmosphericComponent, View3D, LitMaterial, BoxGeometry, MeshRenderer, UnLitMaterial, SphereGeometry, DirectLight, PointLight, SpotLight, HoverCameraController, PlaneGeometry, Color } from '@orillusion/core';

// shadow setting
Engine3D.setting.shadow.autoUpdate = true;
Engine3D.setting.shadow.shadowBound = 50;
Engine3D.setting.shadow.shadowBias = 0.01;
Engine3D.setting.shadow.type = 'HARD';

await Engine3D.init({
    canvasConfig: { devicePixelRatio: 1 }
});
let scene3D: Scene3D = new Scene3D();
let cameraObj: Object3D = new Object3D();
let camera = cameraObj.addComponent(Camera3D);
camera.perspective(60, Engine3D.aspect, 1, 5000.0);
let controller = cameraObj.addComponent(HoverCameraController);
controller.setCamera(-45, -45, 100, new Vector3(0, 0, 0));
scene3D.addChild(cameraObj);

//DirectLight
{
    let obj = new Object3D();
    obj.rotationX = 45;
    obj.rotationY = 0;
    obj.rotationZ = 0;
    let light = obj.addComponent(DirectLight);
    scene3D.addChild(obj);
    // enable light shadow
    light.castShadow = true;
    light.intensity = 30;
    scene3D.addChild(obj);
}

// create a box as shadow source
{
    let castShadowObj = new Object3D();
    castShadowObj.y = 5;
    castShadowObj.rotationY = 45;
    let mr = castShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(10, 10, 10);
    mr.material = new LitMaterial();
    mr.material.baseColor = new Color(1, 0, 0);
    mr.castShadow = true;
    scene3D.addChild(castShadowObj);
}

// create a plane to receive shadow
{
    let receiveShadowObj = new Object3D();
    let mr = receiveShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(2000, 1, 2000);
    mr.material = new LitMaterial();
    mr.receiveShadow = true;
    scene3D.addChild(receiveShadowObj);
}
// create a view with target scene and camera
let view = new View3D();
view.scene = scene3D;
view.camera = camera;
// start render
Engine3D.startRenderView(view);

点光源阴影


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

<
ts
import { Engine3D, Scene3D, Object3D, Camera3D, Vector3, AtmosphericComponent, View3D, LitMaterial, BoxGeometry, MeshRenderer, UnLitMaterial, SphereGeometry, DirectLight, PointLight, SpotLight, HoverCameraController, PlaneGeometry, Color } from '@orillusion/core';

// shadow setting
Engine3D.setting.shadow.autoUpdate = true;
Engine3D.setting.shadow.shadowBound = 50;
Engine3D.setting.shadow.pointShadowBias = 0.0001;
Engine3D.setting.shadow.type = 'HARD';

await Engine3D.init({
    canvasConfig: { devicePixelRatio: 1 }
});
let scene3D: Scene3D = new Scene3D();
let cameraObj: Object3D = new Object3D();
let camera = cameraObj.addComponent(Camera3D);
camera.perspective(60, Engine3D.aspect, 1, 5000.0);
let controller = cameraObj.addComponent(HoverCameraController);
controller.setCamera(0, -45, 150, new Vector3(0, 0, 0));
scene3D.addChild(cameraObj);

//PointLight
{
    let obj = new Object3D();
    let light = obj.addComponent(PointLight);
    scene3D.addChild(obj);
    obj.x = -15;
    obj.y = 30;
    obj.z = -20;
    obj.rotationX = 0;
    obj.rotationY = 0;
    obj.rotationZ = 0;
    light.intensity = 15;
    light.range = 100;
    // enable light shadow
    light.castShadow = true;
    light.debug();
    light.debugDraw(true);
}

// create a box as shadow source
{
    let castShadowObj = new Object3D();
    castShadowObj.y = 5;
    let mr = castShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(10, 10, 10);
    mr.material = new LitMaterial();
    mr.material.baseColor = new Color(1, 0, 0);
    mr.castShadow = true;
    scene3D.addChild(castShadowObj);
}

// create a plane to receive shadow
{
    let receiveShadowObj = new Object3D();
    let mr = receiveShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(2000, 1, 2000);
    mr.material = new LitMaterial();
    mr.receiveShadow = true;
    scene3D.addChild(receiveShadowObj);
}
// create a view with target scene and camera
let view = new View3D();
view.scene = scene3D;
view.camera = camera;
// start render
Engine3D.startRenderView(view);

聚光灯阴影


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

<
ts
import { Engine3D, Scene3D, Object3D, Camera3D, Vector3, AtmosphericComponent, View3D, LitMaterial, BoxGeometry, MeshRenderer, UnLitMaterial, SphereGeometry, DirectLight, PointLight, SpotLight, HoverCameraController, PlaneGeometry, Color } from '@orillusion/core';

// shadow setting
Engine3D.setting.shadow.autoUpdate = true;
Engine3D.setting.shadow.shadowBound = 50;
Engine3D.setting.shadow.pointShadowBias = 0.0001;
Engine3D.setting.shadow.type = 'HARD';

await Engine3D.init({
    canvasConfig: { devicePixelRatio: 1 }
});
let scene3D: Scene3D = new Scene3D();
let cameraObj: Object3D = new Object3D();
let camera = cameraObj.addComponent(Camera3D);
camera.perspective(60, Engine3D.aspect, 1, 5000.0);
let controller = cameraObj.addComponent(HoverCameraController);
controller.setCamera(0, -45, 150, new Vector3(0, 0, 0));
scene3D.addChild(cameraObj);

//SpotLight
{
    let obj = new Object3D();
    let light = obj.addComponent(SpotLight);
    scene3D.addChild(obj);
    obj.x = -30;
    obj.y = 15;
    obj.z = 40;
    obj.rotationX = 0;
    obj.rotationY = 145;
    obj.rotationZ = 0;
    light.intensity = 50;
    light.range = 150;
    light.outerAngle = 110;
    light.innerAngle = 30;
    // enable light shadow
    light.castShadow = true;
}

// create a box as shadow source
{
    let castShadowObj = new Object3D();
    castShadowObj.y = 5;
    let mr = castShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(10, 10, 10);
    mr.material = new LitMaterial();
    mr.material.baseColor = new Color(1, 0, 0);
    mr.castShadow = true;
    scene3D.addChild(castShadowObj);
}

// create a plane to receive shadow
{
    let receiveShadowObj = new Object3D();
    let mr = receiveShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(2000, 1, 2000);
    mr.material = new LitMaterial();
    mr.receiveShadow = true;
    scene3D.addChild(receiveShadowObj);
}
// create a view with target scene and camera
let view = new View3D();
view.scene = scene3D;
view.camera = camera;
// start render
Engine3D.startRenderView(view);

阴影偏移

阴影偏移 shadowBias 是影响阴影渲染的重要参数,因为一般阴影贴图的尺寸和最终渲染贴图的尺寸不完全相同的,会造成阴影采样失真等情况,通常可以通过手动设置一个微小的偏移量 shadowBias 来解决采样失真的情况。

ts
Engine3D.setting.shadow.shadowBias = 0.0002 // 平行光阴影偏移
Engine3D.setting.shadow.pointShadowBias = 0.2 // 点光源/聚光灯阴影偏移

一般 shadowBias 设置过小,会出现大面积摩尔纹或者完全覆盖阴影的情况;反之,如果设置过大,则会出现阴影和物体分离(漏光)的情况:

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

<
ts
import { Engine3D, Scene3D, Object3D, Camera3D, Vector3, AtmosphericComponent, View3D, LitMaterial, BoxGeometry, MeshRenderer, UnLitMaterial, SphereGeometry, DirectLight, PointLight, SpotLight, HoverCameraController, PlaneGeometry, Color } from '@orillusion/core';
import * as dat from 'dat.gui';

// shadow setting
Engine3D.setting.shadow.autoUpdate = true;
Engine3D.setting.shadow.debug = false;
Engine3D.setting.shadow.shadowBound = 100;
Engine3D.setting.shadow.shadowBias = 0.01;

await Engine3D.init({
    canvasConfig: { devicePixelRatio: 1 }
});
let scene3D: Scene3D = new Scene3D();
let cameraObj: Object3D = new Object3D();
let camera = cameraObj.addComponent(Camera3D);
camera.perspective(60, Engine3D.aspect, 1, 5000.0);
let controller = cameraObj.addComponent(HoverCameraController);
controller.setCamera(0, -45, 100, new Vector3(0, 0, 0));
scene3D.addChild(cameraObj);

{
    let obj = new Object3D();
    let light = obj.addComponent(DirectLight);
    scene3D.addChild(obj);
    obj.rotationX = 200;
    obj.rotationY = 135;
    obj.rotationZ = 170;
    light.intensity = 10;
    // enable light shadow
    light.castShadow = true;
}
// create a box as shadow source
{
    let castShadowObj = new Object3D();
    castShadowObj.y = 5;
    let mr = castShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(10, 10, 10);
    mr.material = new LitMaterial();
    mr.material.baseColor = new Color(1, 0, 0);
    mr.castShadow = true;
    scene3D.addChild(castShadowObj);
}
// create a plane to receive shadow
{
    let receiveShadowObj = new Object3D();
    let mr = receiveShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(2000, 1, 2000);
    mr.material = new LitMaterial();
    scene3D.addChild(receiveShadowObj);
}
// create a view with target scene and camera
let view = new View3D();
view.scene = scene3D;
view.camera = camera;
// start render
Engine3D.startRenderView(view);

const GUIHelp = new dat.GUI();
GUIHelp.add(Engine3D.setting.shadow, 'shadowBias', 0, 0.1, 0.0001);

阴影类型

通过设置阴影类型,可以控制阴影的表现效果。目前支持的阴影类型有:

  • HARD:硬阴影,是一种比较锐利的阴影,通常用于阴影距离物体比较近的情况。
  • SOFT:软阴影,是一种比较柔和、模糊的阴影,阴影边缘做了模糊处理,通常用于阴影离物体比较远的情况。
  • PCF:PCF(Percentage-Closer Filtering) 是一种常见的软阴影处理算法,通过对当前像素和周围的阴影深度进行采样并按距离进行加权平均,从而得到一种人工伪造的柔和阴影效果。目前引擎默认使用这个类型的阴影。

配置阴影类型:

ts
Engine3D.setting.shadow.type = 'SOFT'; // 默认 HARD

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

<
ts
import { Color } from '@orillusion/core';
import { Engine3D, Scene3D, Object3D, Camera3D, Vector3, AtmosphericComponent, View3D, LitMaterial, BoxGeometry, MeshRenderer, UnLitMaterial, SphereGeometry, DirectLight, PointLight, SpotLight, HoverCameraController, PlaneGeometry } from '@orillusion/core';
import * as dat from 'dat.gui';

// shadow setting
Engine3D.setting.shadow.autoUpdate = true;
Engine3D.setting.shadow.shadowBound = 20;
Engine3D.setting.shadow.pointShadowBias = 0.0001;
Engine3D.setting.shadow.type = sessionStorage._shadow_type || 'HARD';

await Engine3D.init({
    canvasConfig: { devicePixelRatio: 1 }
});
let scene3D: Scene3D = new Scene3D();
let cameraObj: Object3D = new Object3D();
let camera = cameraObj.addComponent(Camera3D);
camera.perspective(60, Engine3D.aspect, 1, 5000.0);
let controller = cameraObj.addComponent(HoverCameraController);
controller.setCamera(0, -45, 50, new Vector3(0, 0, 0));
scene3D.addChild(cameraObj);

//PointLight
{
    let obj = new Object3D();
    let light = obj.addComponent(PointLight);
    scene3D.addChild(obj);
    obj.x = -30;
    obj.y = 30;
    obj.z = -30;
    obj.rotationX = 0;
    obj.rotationY = 0;
    obj.rotationZ = 0;
    light.intensity = 15;
    light.range = 100;
    light.castShadow = true;
}

// create a box as shadow source
{
    let castShadowObj = new Object3D();
    castShadowObj.y = 5;
    let mr = castShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(10, 10, 10);
    mr.material = new LitMaterial();
    mr.material.baseColor = new Color(1, 0, 0);
    mr.castShadow = true;
    scene3D.addChild(castShadowObj);
}

// create a plane to receive shadow
{
    let receiveShadowObj = new Object3D();
    let mr = receiveShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(2000, 1, 2000);
    mr.material = new LitMaterial();
    mr.receiveShadow = true;
    scene3D.addChild(receiveShadowObj);
}
// create a view with target scene and camera
let view = new View3D();
view.scene = scene3D;
view.camera = camera;
// start render
Engine3D.startRenderView(view);

let gui = new dat.GUI();
gui.add(Engine3D.setting.shadow, 'type', ['PCF', 'SOFT', 'HARD']).onChange((v) => {
    sessionStorage._shadow_type = v;
    location.reload();
});

阴影大小

引擎的 setting 提供了对阴影大小的控制参数,可以通过参数控制不同场景下阴影所使用的纹理大小和区域大小。

相关属性如下:

属性类型说明
shadowBoundNumber阴影区域范围
shadowSizeNumber平行光阴影贴图尺寸大小,默认1024
pointShadowSizeNumber点光源阴影贴图大小尺寸,默认1024

阴影贴图尺寸大小(shadowSizepointShadowSize)直接影响最终阴影质量,且数值越小性能开销越低,并且阴影锯齿感越明显。

shadowBound 参数控制场景中受照阴影区域大小,区域越大,阴影贴图尺寸也应适当增大,当大区域投射在小阴影贴图上时,也会导致很明显的阴影锯齿。

ts
Engine3D.setting.shadow.shadowBound = 100

设置平行光阴影贴图尺寸大小:

ts
Engine3D.setting.shadow.shadowSize = 2048

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

<
ts
import { Engine3D, Scene3D, Object3D, Camera3D, Vector3, AtmosphericComponent, View3D, LitMaterial, BoxGeometry, MeshRenderer, UnLitMaterial, SphereGeometry, DirectLight, PointLight, SpotLight, HoverCameraController, PlaneGeometry, Color } from '@orillusion/core';
import * as dat from 'dat.gui';

// shadow setting
Engine3D.setting.shadow.shadowBias = 0.01;
Engine3D.setting.shadow.autoUpdate = true;
Engine3D.setting.shadow.shadowBound = sessionStorage._shadowBound || 100;
Engine3D.setting.shadow.type = 'HARD';
Engine3D.setting.shadow.shadowSize = sessionStorage._shadowSize || 512;

await Engine3D.init({
    canvasConfig: { devicePixelRatio: 1 }
});
let scene3D: Scene3D = new Scene3D();
let cameraObj: Object3D = new Object3D();
let camera = cameraObj.addComponent(Camera3D);
camera.perspective(60, Engine3D.aspect, 1, 5000.0);
let controller = cameraObj.addComponent(HoverCameraController);
controller.setCamera(-45, -45, 50, new Vector3(0, 0, 0));
scene3D.addChild(cameraObj);

//DirectLight
{
    let obj = new Object3D();
    obj.rotationX = 45;
    obj.rotationY = 0;
    obj.rotationZ = 0;
    let light = obj.addComponent(DirectLight);
    scene3D.addChild(obj);
    // enable light shadow
    light.castShadow = true;
    light.intensity = 30;
    scene3D.addChild(obj);
}

// create a box as shadow source
{
    let castShadowObj = new Object3D();
    castShadowObj.y = 5;
    castShadowObj.rotationY = 45;
    let mr = castShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(10, 10, 10);
    mr.material = new LitMaterial();
    mr.material.baseColor = new Color(1, 0, 0);
    mr.castShadow = true;
    scene3D.addChild(castShadowObj);
}

// create a box as shadow source
{
    let castShadowObj = new Object3D();
    castShadowObj.y = 5;
    castShadowObj.rotationY = 45;
    castShadowObj.x = 30;
    let mr = castShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(10, 10, 10);
    mr.material = new LitMaterial();
    mr.material.baseColor = new Color(0, 1, 0);
    mr.castShadow = true;
    scene3D.addChild(castShadowObj);
}

// create a plane to receive shadow
{
    let receiveShadowObj = new Object3D();
    let mr = receiveShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(2000, 1, 2000);
    mr.material = new LitMaterial();
    mr.receiveShadow = true;
    scene3D.addChild(receiveShadowObj);
}
// create a view with target scene and camera
let view = new View3D();
view.scene = scene3D;
view.camera = camera;
// start render
Engine3D.startRenderView(view);

let gui = new dat.GUI();
gui.add(Engine3D.setting.shadow, 'shadowBound', [30, 50, 100, 500]).onChange((v) => {
    sessionStorage._shadowBound = v;
    location.reload();
});
gui.add(Engine3D.setting.shadow, 'shadowSize', [128, 256, 512, 1024, 2048]).onChange((v) => {
    sessionStorage._shadowSize = v;
    location.reload();
});

设置点光源阴影贴图大小尺寸:

ts
Engine3D.setting.shadow.pointShadowSize = 2048

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

<
ts
import { Engine3D, Scene3D, Object3D, Camera3D, Vector3, AtmosphericComponent, View3D, LitMaterial, BoxGeometry, MeshRenderer, UnLitMaterial, SphereGeometry, DirectLight, PointLight, SpotLight, HoverCameraController, PlaneGeometry, Color } from '@orillusion/core';
import * as dat from 'dat.gui';

// shadow setting
Engine3D.setting.shadow.autoUpdate = true;
Engine3D.setting.shadow.shadowBound = sessionStorage._shadowBound || 100;
Engine3D.setting.shadow.type = 'HARD';
Engine3D.setting.shadow.pointShadowSize = sessionStorage._pointShadowSize || 512;
Engine3D.setting.shadow.pointShadowBias = 0.0001;

await Engine3D.init({
    canvasConfig: { devicePixelRatio: 1 }
});
let scene3D: Scene3D = new Scene3D();
let cameraObj: Object3D = new Object3D();
let camera = cameraObj.addComponent(Camera3D);
camera.perspective(60, Engine3D.aspect, 1, 5000.0);
let controller = cameraObj.addComponent(HoverCameraController);
controller.setCamera(-45, -45, 50, new Vector3(0, 0, 0));
scene3D.addChild(cameraObj);

//PointLight
{
    let obj = new Object3D();
    obj.y = 50;
    obj.z = -30;
    obj.rotationX = 45;
    obj.rotationY = 0;
    obj.rotationZ = 0;
    let light = obj.addComponent(PointLight);
    scene3D.addChild(obj);
    // enable light shadow
    light.castShadow = true;
    light.intensity = 30;
    scene3D.addChild(obj);
}

// create a box as shadow source
{
    let castShadowObj = new Object3D();
    castShadowObj.y = 5;
    castShadowObj.rotationY = 45;
    let mr = castShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(10, 10, 10);
    mr.material = new LitMaterial();
    mr.material.baseColor = new Color(1, 0, 0);
    mr.castShadow = true;
    scene3D.addChild(castShadowObj);
}

// create a box as shadow source
{
    let castShadowObj = new Object3D();
    castShadowObj.y = 5;
    castShadowObj.rotationY = 45;
    castShadowObj.x = 30;
    let mr = castShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(10, 10, 10);
    mr.material = new LitMaterial();
    mr.material.baseColor = new Color(0, 1, 0);
    mr.castShadow = true;
    scene3D.addChild(castShadowObj);
}

// create a plane to receive shadow
{
    let receiveShadowObj = new Object3D();
    let mr = receiveShadowObj.addComponent(MeshRenderer);
    mr.geometry = new BoxGeometry(2000, 1, 2000);
    mr.material = new LitMaterial();
    mr.receiveShadow = true;
    scene3D.addChild(receiveShadowObj);
}
// create a view with target scene and camera
let view = new View3D();
view.scene = scene3D;
view.camera = camera;
// start render
Engine3D.startRenderView(view);

let gui = new dat.GUI();
gui.add(Engine3D.setting.shadow, 'shadowBound', [20, 50, 100, 500]).onChange((v) => {
    sessionStorage._shadowBound = v;
    location.reload();
});
gui.add(Engine3D.setting.shadow, 'pointShadowSize', [128, 256, 512, 1024, 2048]).onChange((v) => {
    sessionStorage._pointShadowSize = v;
    location.reload();
});

联级阴影贴图(Cascaded Shadow Maps)

常用于支持大场景中更好的阴影渲染那效果,将视锥内阴影按四个等级进行划分,在着色处理阴影的阶段根据当前所处阴影等级选择合适的阴影贴图,能够获得更加精确、覆盖全视锥体范围的阴影效果。使用了CSM阴影后,能够解决阴影Bound范围过大,阴影的像素密度不够导致马赛克化过于严重;Bound范围小、阴影区域过小,远处的物体丢失阴影的问题。

启用CSM阴影

ts
let mainCamera:Camera3D;
mainCamera.enableCSM = true;

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

<
ts
import { Scene3D, HoverCameraController, Engine3D, AtmosphericComponent, Object3D, Camera3D, Vector3, View3D, DirectLight, KelvinUtil, LitMaterial, MeshRenderer, BoxGeometry, CameraUtil, SphereGeometry, Color, Object3DUtil, BlendMode } from '@orillusion/core';
import * as dat from 'dat.gui';
import { Graphic3D } from '@orillusion/graphic';

//sample of csm
class Sample_CSM {
    scene: Scene3D;
    view: View3D;
    light: DirectLight;
    boxRenderer: MeshRenderer;
    viewCamera: Camera3D;
    GUIHelp: dat.GUI;
    graphic3D: Graphic3D;
    async run() {
        Engine3D.setting.shadow.autoUpdate = true;
        Engine3D.setting.shadow.shadowSize = 2048;
        Engine3D.setting.shadow.shadowBound = 512;
        Engine3D.setting.shadow.shadowBias = 0.01;

        await Engine3D.init({
            renderLoop: () => {
                this.loop();
            }
        });
        this.GUIHelp = new dat.GUI();

        this.scene = new Scene3D();
        let sky = this.scene.addComponent(AtmosphericComponent);

        // init camera3D
        let mainCamera = CameraUtil.createCamera3D(undefined, this.scene);
        mainCamera.perspective(60, Engine3D.aspect, 1, 5000.0);
        //set camera data
        mainCamera.object3D.z = -15;
        mainCamera.object3D.addComponent(HoverCameraController).setCamera(-15, -35, 200);

        sky.relativeTransform = this.initLight('mainLight', 3, 45);
        this.initLight('subLight', 2, 10);
        this.initScene();

        let view = new View3D();
        view.scene = this.scene;
        view.camera = mainCamera;
        this.view = view;
        this.viewCamera = mainCamera;

        this.graphic3D = new Graphic3D();
        this.scene.addChild(this.graphic3D);

        mainCamera.enableCSM = true;

        let f = this.GUIHelp.addFolder('CSM');
        f.add(mainCamera, 'enableCSM');
        f.add(Engine3D.setting.shadow, 'csmScatteringExp', 0.5, 1.0, 0.01);
        f.add(Engine3D.setting.shadow, 'csmMargin', 0.01, 0.5, 0.01);
        f.add(Engine3D.setting.shadow, 'csmAreaScale', 0.1, 1, 0.01);
        f.open();
        Engine3D.startRenderView(view);
    }

    // create direction light
    private initLight(name: string, intensity: number, rotY: number) {
        let lightObj3D = new Object3D();
        lightObj3D.name = name;
        lightObj3D.rotationX = 46;
        lightObj3D.rotationY = 62 + rotY;
        lightObj3D.rotationZ = 0;
        let sunLight = lightObj3D.addComponent(DirectLight);
        sunLight.intensity = intensity;
        sunLight.lightColor = KelvinUtil.color_temperature_to_rgb(6553);
        sunLight.castShadow = true;

        this.renderDirLight(sunLight, true, name);
        this.scene.addChild(lightObj3D);
        this.light = sunLight;
        return sunLight.transform;
    }

    public renderDirLight(light: DirectLight, open: boolean = true, name?: string) {
        name ||= 'DirectLight';
        let f = this.GUIHelp.addFolder(name);
        f.add(light, 'enable');
        f.open();
    }

    initScene() {
        {
            let obj = new Object3D();
            let mr = obj.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(20, 100, 20);
            mr.material = new LitMaterial();
            this.scene.addChild(obj);
        }

        this.createBox();
        {
            let mat = new LitMaterial();
            mat.baseMap = Engine3D.res.grayTexture;
            let floor = new Object3D();
            let mr = floor.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(10000, 1, 10000);
            mr.material = mat;
            this.scene.addChild(floor);
        }

        for (let i = 0; i < 1000; i++) {
            let item = Object3DUtil.GetSingleSphere(4, 0.6, 0.4, 0.2);
            let angle = (Math.PI * 4 * i) / 50;
            item.x = Math.sin(angle) * (50 + i ** 1.4);
            item.z = Math.cos(angle) * (50 + i ** 1.4);
            item.y = 4;
            let scale = (i ** 1.4 * 5 + 1000) / 1000;
            item.scaleX = item.scaleZ = scale;
            item.scaleY = scale * 5;
            this.scene.addChild(item);
        }
    }

    createBox() {
        let box = new Object3D();
        let geom = new BoxGeometry(1, 1, 1);
        let material = new LitMaterial();
        material.blendMode = BlendMode.NORMAL;
        material.cullMode = 'front';
        material.baseColor = new Color(0.2, 0.2, 0, 0.1);
        let renderer = box.addComponent(MeshRenderer);
        renderer.material = material;
        renderer.geometry = geom;
        this.boxRenderer = renderer;
    }

    private _shadowPos: Vector3 = new Vector3();
    private _shadowCameraTarget: Vector3 = new Vector3();
    loop() {
        let viewCamera = this.viewCamera;
        let light = this.light;
        let view = this.view;
        if (!this.boxRenderer || !this.viewCamera.csm) return;

        let csmBound = this.viewCamera.csm.children[0].bound;
        //update box
        let size = this.viewCamera.getCSMShadowWorldExtents(0) * 2;
        this.boxRenderer.object3D.scaleX = size;
        this.boxRenderer.object3D.scaleY = size;
        this.boxRenderer.object3D.scaleZ = this.viewCamera.csm.children[0].shadowCamera.far;

        this.boxRenderer.object3D.localRotation = light.transform.localRotation;
        this.boxRenderer.object3D.localPosition = csmBound.center;

        // light direction
        this._shadowPos.copy(light.direction).normalize(viewCamera.far);
        csmBound.center.add(this._shadowPos, this._shadowCameraTarget);
        csmBound.center.subtract(this._shadowPos, this._shadowPos);
        this.graphic3D.drawLines('shadowLine', [this._shadowPos, this._shadowCameraTarget], new Color(1, 1, 0, 1));
    }
}

new Sample_CSM().run();

阴影属性

属性类型说明
enableBoolean是否启用阴影,默认 false
typeString阴影类型,默认 PCF
shadowQualityNumber阴影渲染品质
shadowBoundNumber阴影区域范围
shadowSizeNumber平行光阴影贴图尺寸大小,默认1024,数值越小性能开销越低,但阴影锯齿感越明显
pointShadowSizeNumber点光源阴影贴图大小尺寸,默认1024
pointShadowBiasNumber点光源和聚光灯的阴影偏移值
autoUpdateBoolean是否自动更新阴影,默认 false
csmMarginNumber设置不同级别阴影的过渡范围,在0-1区间调节
csmScatteringExpNumber微调各个级别阴影的范围,以满足不同的场景需求
csmAreaScaleNumber微调阴影能够覆盖的最大范围,在0.0-1区间调节