From 4f182f02735744565b81b7dcad0aa8d1eae0171f Mon Sep 17 00:00:00 2001 From: arorad1 Date: Fri, 19 Jul 2024 20:04:42 -0700 Subject: [PATCH] Adding basic Cascading Shadows implementation --- fission/src/systems/scene/CascadingShadows.ts | 94 +++++++++++++++++++ fission/src/systems/scene/SceneRenderer.ts | 36 ++++--- 2 files changed, 115 insertions(+), 15 deletions(-) create mode 100644 fission/src/systems/scene/CascadingShadows.ts diff --git a/fission/src/systems/scene/CascadingShadows.ts b/fission/src/systems/scene/CascadingShadows.ts new file mode 100644 index 0000000000..f80a61f237 --- /dev/null +++ b/fission/src/systems/scene/CascadingShadows.ts @@ -0,0 +1,94 @@ +import * as THREE from 'three'; + +class CascadingShadows { + private _camera: THREE.PerspectiveCamera; + private _shadowCameras: THREE.OrthographicCamera[] = []; + private _scene: THREE.Scene; + + private _renderer: THREE.WebGLRenderer; + private _light: THREE.DirectionalLight; + private _shadowMaps: THREE.WebGLRenderTarget[] = []; // One shadow map per cascade + + private _numCascades: number; + private _cascadeSplits: number[]; + + constructor(camera: THREE.PerspectiveCamera, scene: THREE.Scene, renderer: THREE.WebGLRenderer, numCascades: number = 5) { + this._camera = camera; + this._scene = scene; + this._renderer = renderer; + this._numCascades = numCascades; + + // Creating the directional light + this._light = new THREE.DirectionalLight(0xffffff, 3); + this._light.position.set(-1, 3, 2); + this._light.castShadow = true; + scene.add(this._light); + + // Split the camera frustum into numCascades cascades + this._cascadeSplits = new Array(numCascades).fill(0).map((_, i) => (i + 1) / numCascades); + + // Create the shadow camera and shadow maps + for (let i = 0; i < numCascades; i++) { + const shadowCamera = new THREE.OrthographicCamera(-10, 10, 10, -10, 0.5, 500); + shadowCamera.position.copy(this._light.position); + shadowCamera.lookAt(0, 0, 0); + this._shadowCameras.push(shadowCamera); + + const shadowMap = new THREE.WebGLRenderTarget(1024, 1024, { + minFilter: THREE.NearestFilter, + magFilter: THREE.NearestFilter, + format: THREE.RGBAFormat, + }); + this._shadowMaps.push(shadowMap); + } + } + + /** Updates the shadow maps */ + public Update() { + const frustum = new THREE.Frustum(); + const projScreenMatrix = new THREE.Matrix4(); + projScreenMatrix.multiplyMatrices(this._camera.projectionMatrix, this._camera.matrixWorldInverse); + frustum.setFromProjectionMatrix(projScreenMatrix); + + for (let i = 0; i < this._numCascades; i++) { + const near = this._camera.near + this._cascadeSplits[i] * (this._camera.far - this._camera.near); + const far = this._camera.near + this._cascadeSplits[i + 1] * (this._camera.far - this._camera.near); + + const cascadeFrustum = new THREE.Frustum(); + cascadeFrustum.setFromProjectionMatrix(this.GetCascadeMatrix(near, far)); + + const shadowCamera = this._shadowCameras[i]; + shadowCamera.position.copy(this._light.position); + shadowCamera.lookAt(0, 0, 0); + shadowCamera.updateMatrixWorld(); + shadowCamera.updateProjectionMatrix(); + + this._renderer.setRenderTarget(this._shadowMaps[i]); + this._renderer.render(this._scene, shadowCamera); + } + + this._renderer.setRenderTarget(null); + } + + /** Returns the projection matrix for the cascade */ + private GetCascadeMatrix(near: number, far: number): THREE.Matrix4 { + const matrix = new THREE.Matrix4(); + const projMatrix = new THREE.Matrix4().makePerspective( + this._camera.fov, + this._camera.aspect, + near, + far, + this._camera.near, + this._camera.far + ); + const invMatrix = this._camera.matrixWorld.invert(); + matrix.multiplyMatrices(projMatrix, invMatrix); + return matrix; + } + + public getShadowMap(index: number): THREE.WebGLRenderTarget { + return this._shadowMaps[index]; + } +} + +export default CascadingShadows; diff --git a/fission/src/systems/scene/SceneRenderer.ts b/fission/src/systems/scene/SceneRenderer.ts index 1590a15c9a..6458b6508a 100644 --- a/fission/src/systems/scene/SceneRenderer.ts +++ b/fission/src/systems/scene/SceneRenderer.ts @@ -14,6 +14,7 @@ import InputSystem from "../input/InputSystem" import { PixelSpaceCoord, SceneOverlayEvent, SceneOverlayEventKey } from "@/ui/components/SceneOverlayEvents" import {} from "@/ui/components/SceneOverlayEvents" import PreferencesSystem from "../preferences/PreferencesSystem" +import CascadingShadows from "./CascadingShadows" const CLEAR_COLOR = 0x121212 const GROUND_COLOR = 0x4066c7 @@ -34,6 +35,8 @@ class SceneRenderer extends WorldSystem { private _orbitControls: OrbitControls private _transformControls: Map // maps all rendered transform controls to their size + private _light: CascadingShadows + public get sceneObjects() { return this._sceneObjects } @@ -74,23 +77,25 @@ class SceneRenderer extends WorldSystem { this._renderer.shadowMap.type = THREE.PCFSoftShadowMap this._renderer.setSize(window.innerWidth, window.innerHeight) - const directionalLight = new THREE.DirectionalLight(0xffffff, 3.0) - directionalLight.position.set(-1.0, 3.0, 2.0) - directionalLight.castShadow = true - this._scene.add(directionalLight) + // const directionalLight = new THREE.DirectionalLight(0xffffff, 3.0) + // directionalLight.position.set(-1.0, 3.0, 2.0) + // directionalLight.castShadow = true + // this._scene.add(directionalLight) + + // const shadowMapSize = Math.min(4096, this._renderer.capabilities.maxTextureSize) + // const shadowCamSize = 15 + // console.debug(`Shadow Map Size: ${shadowMapSize}`) - const shadowMapSize = Math.min(4096, this._renderer.capabilities.maxTextureSize) - const shadowCamSize = 15 - console.debug(`Shadow Map Size: ${shadowMapSize}`) + // directionalLight.shadow.camera.top = shadowCamSize + // directionalLight.shadow.camera.bottom = -shadowCamSize + // directionalLight.shadow.camera.left = -shadowCamSize + // directionalLight.shadow.camera.right = shadowCamSize + // directionalLight.shadow.mapSize = new THREE.Vector2(shadowMapSize, shadowMapSize) + // directionalLight.shadow.blurSamples = 16 + // directionalLight.shadow.normalBias = 0.01 + // directionalLight.shadow.bias = 0.0 - directionalLight.shadow.camera.top = shadowCamSize - directionalLight.shadow.camera.bottom = -shadowCamSize - directionalLight.shadow.camera.left = -shadowCamSize - directionalLight.shadow.camera.right = shadowCamSize - directionalLight.shadow.mapSize = new THREE.Vector2(shadowMapSize, shadowMapSize) - directionalLight.shadow.blurSamples = 16 - directionalLight.shadow.normalBias = 0.01 - directionalLight.shadow.bias = 0.0 + this._light = new CascadingShadows(this._mainCamera, this._scene, this._renderer) const ambientLight = new THREE.AmbientLight(0xffffff, 0.1) this._scene.add(ambientLight) @@ -143,6 +148,7 @@ class SceneRenderer extends WorldSystem { this._sceneObjects.forEach(obj => { obj.Update() }) + this._light.Update() this._skybox.position.copy(this._mainCamera.position)