Shadow
Shadow is a way to add depth and realism to a scene or model. Shadows can show the scale and position of objects, making them look more three-dimensional. In the engine, light sources can cast shadows of objects onto other parts of themselves or nearby scenes. These shadows can be real-time or static.
Shadow Configuration
Shadow is implemented by light sources. Currently, DirectLight, PointLight, and SpotLight in the engine can produce shadows. When rendering shadow effects, just set the castShadow
property of the corresponding light source to true
.
let lightObj = new Object3D();
// Set the position of the light
lightObj.x = 0;
lightObj.y = 0;
lightObj.z = 0;
// Set the angle of the light and rotate 45 degrees around the X axis
lightObj.rotationX = 45;
lightObj.rotationY = 0;
lightObj.rotationZ = 0;
// Add a direct light component and enable shadows
let lc = lightObj.addComponent(DirectLight);
lc.castShadow = true; // 默认为 false
lc.intensity = 5;
scene.addChild(lightObj);
If you want the light to cast shadows on a certain object, you need to add a MeshRenderer component to the object, and set the castShadow
property of the component to true
.
// Create a box to generate shadows
let castShadowObj = new Object3D();
let mr1 = castShadowObj.addComponent(MeshRenderer);
mr1.geometry = new BoxGeometry();
mr1.material = new LitMaterial();
mr1.castShadow = true
scene.addChild(castShadowObj);
Then you need to add a MeshRenderer component to the object that receives the shadow, and set the receiveShadow
property of the component to true
.
// Create a plane to receive shadows
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);
In this way, you can see a box
in the scene, which casts a shadow under the parallel light and casts it on the plane
:
Direct light shadow
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);
Point light shadow
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);
Spot light shadow
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);
Shadow Bias
Shadow bias shadowBias
is an important parameter that affects shadow rendering. Because the size of the shadow texture and the size of the final rendering texture are not completely the same, it will cause sampling distortion and other situations. Usually, you can manually set a small offset shadowBias
to solve the sampling distortion.
Engine3D.setting.shadow.shadowBias = 0.0002 // Shadow bias for direct light
Engine3D.setting.shadow.pointShadowBias = 0.2 // Shadow bias for point light or spot light
Normally, if
shadowBias
is set too small, it will cause large areas of moire or completely cover the shadow; on the contrary, if it is set too large, it will cause the shadow and object to be separated (leakage).
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);
Shadow Type
You can determine the shadow performance by setting the shadow type.These are the types of shadows currently supported:
- HARD: Hard shadow is a sharper shadow, usually used for shadows close to the object.
- SOFT: Soft shadow is a softer, blurred shadow, and the edge of the shadow is blurred. It is usually used for shadows far from the object.
- PCF: PCF (Percentage-Closer Filtering) is a common soft shadow processing algorithm. By sampling the current pixel and the surrounding shadow depth and averaging them by distance, a soft shadow effect is artificially forged. The engine currently uses this type of shadow by default.
Configuration shadow type:
Engine3D.setting.shadow.type = 'HARD'; // PCF by default
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();
});
Shadow Bound
The engine's "setting" provides control parameters for the shadow size, which can control the texture size and area size used by the shadow in different scenes.
The relevant attributes are as follows:
Attribute | Type | Description |
---|---|---|
shadowBound | Number | Shadow area range |
shadowSize | Number | Parallel light shadow map size, default 1024 |
pointShadowSize | Number | Point light Shadow Map Size Size, default 1024 |
Shadow map size (shadowSize
, pointShadowSize
) directly affects the final shadow quality, and the smaller the value, the lower the performance overhead, and the more obvious the shadow jagged feeling.
The shadowBound
parameter controls the size of the illuminated shadow area in the scene. The larger the area, the size of the shadow map should also be increased appropriately. When a large area is cast on a small shadow map, it will also cause a significant shadow jagging.
Engine3D.setting.shadow.shadowBound = 100
Set the parallel light shadow map size:
Engine3D.setting.shadow.shadowSize = 2048
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();
});
Set point light shadow map size Size:
Engine3D.setting.shadow.pointShadowSize = 2048
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)
Often used to support better shadow rendering effect in large scenes, the shadow in the cone is divided into four levels, and the appropriate shadow map is selected according to the current shadow level in the shading process stage, which can obtain a more accurate shadow effect covering the full scope of the cone. After the use of CSM shadow, it can solve the problem that the shadow Bound range is too large, and the pixel density of the shadow is not enough, resulting in too serious Mosaic; The scope is small, the shadow area is too small, and distant objects lose their shadows.
enable CSM shadow
let mainCamera:Camera3D;
mainCamera.enableCSM = true;
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();
Shadow Attribute
Attribute | Type | Description |
---|---|---|
enable | Boolean | Whether to enable shadow, default false |
type | String | Shadow type, default PCF |
shadowQuality | Number | Shadow rendering quality |
shadowBound | Number | Shadow area range |
pointShadowBias | Number | Offset value of point light and spotlight shadow |
shadowSize | Number | Size of parallel light shadow map, default 2048, the smaller the number, the lower the performance cost, but the more obvious the shadow aliasing |
pointShadowSize | Number | Size of point light shadow map, default 1024 |
autoUpdate | Boolean | Whether to automatically update the shadow, default false |
csmMargin | Number | Set the transition range for different levels of shadows and adjust it in the 0-1 range |
csmScatteringExp | Number | Fine-tune the range of shades at each level to suit different scene needs |
csmAreaScale | Number | Fine-tune the maximum range that the shadow can cover, adjusted in the range of 0.0-1 |