Skip to content

Instantly share code, notes, and snippets.

@itsdouges
Created November 16, 2022 23:17
Show Gist options
  • Save itsdouges/05621f06bd38848ab4704a280f596104 to your computer and use it in GitHub Desktop.
Save itsdouges/05621f06bd38848ab4704a280f596104 to your computer and use it in GitHub Desktop.
Cascaded Shadow Maps for React Three Fiber
import { useFrame, useThree } from '@react-three/fiber';
import { useLayoutEffect, useMemo } from 'react';
import { Camera, Vector3, Vector3Tuple } from 'three';
import CSM, { Params } from 'three-csm';
interface CascadedShadowMapProps extends Omit<Params, 'lightDirection' | 'camera' | 'parent'> {
fade?: boolean;
lightDirection?: Vector3Tuple;
}
class CSMProxy {
instance: CSM | undefined;
args: Params;
constructor(args: Params) {
this.args = args;
}
set fade(fade: boolean) {
if (this.instance) {
this.instance.fade = fade;
}
}
set camera(camera: Camera) {
if (this.instance) {
this.instance.camera = camera;
}
}
set lightDirection(vector: Vector3 | Vector3Tuple) {
if (this.instance) {
this.instance.lightDirection = Array.isArray(vector)
? new Vector3().fromArray(vector).normalize()
: vector;
}
}
attach() {
this.instance = new CSM(this.args);
}
dispose() {
if (this.instance) {
this.instance.dispose();
}
}
}
export function CascadedShadowMap({
maxFar = 50,
shadowMapSize = 1024,
lightIntensity = 0.25,
cascades = 2,
fade,
lightDirection = [1, -1, 1],
shadowBias = 0.000001,
customSplitsCallback,
lightFar,
lightMargin,
lightNear,
mode,
}: CascadedShadowMapProps) {
const camera = useThree((three) => three.camera);
const parent = useThree((three) => three.scene);
const proxyInstance = useMemo(
() =>
new CSMProxy({
camera,
cascades,
customSplitsCallback,
lightDirection: new Vector3().fromArray(lightDirection).normalize(),
lightFar,
lightIntensity,
lightMargin,
lightNear,
maxFar,
mode,
parent,
shadowBias,
shadowMapSize,
}),
// These values will cause CSM to re-instantiate itself.
// This is an expensive operation and should be avoided.
// eslint-disable-next-line react-hooks/exhaustive-deps
[
// Values that can be updated during runtime are omitted from this deps check.
cascades,
customSplitsCallback,
fade,
lightFar,
lightIntensity,
lightMargin,
lightNear,
maxFar,
mode,
shadowBias,
shadowMapSize,
]
);
useFrame(() => {
if (proxyInstance && proxyInstance.instance) {
proxyInstance.instance.update(camera.matrix);
}
});
useLayoutEffect(() => {
proxyInstance.attach();
return () => {
proxyInstance.dispose();
};
}, [proxyInstance]);
return (
<primitive object={proxyInstance} camera={camera} fade={fade} lightDirection={lightDirection} />
);
}
diff --git a/node_modules/three-csm/build/CSM.d.ts b/node_modules/three-csm/build/CSM.d.ts
index 465eefa..f738080 100644
--- a/node_modules/three-csm/build/CSM.d.ts
+++ b/node_modules/three-csm/build/CSM.d.ts
@@ -1,8 +1,8 @@
-import { Vector3, DirectionalLight, Object3D, Material, PerspectiveCamera } from 'three';
+import { Vector3, DirectionalLight, Object3D, Material, PerspectiveCamera, Matrix4, Camera } from 'three';
import CSMHelper from './CSMHelper';
import CSMFrustum from './CSMFrustum';
-interface Params {
- camera: PerspectiveCamera;
+export interface Params {
+ camera: Camera;
parent: Object3D;
cascades?: number;
maxFar?: number;
@@ -17,7 +17,7 @@ interface Params {
customSplitsCallback?: (cascadeCount: any, nearDistance: any, farDistance: any) => number[];
}
declare class CSM {
- camera: PerspectiveCamera;
+ camera: Camera;
parent: Object3D;
cascades: number;
maxFar: number;
@@ -41,7 +41,7 @@ declare class CSM {
private initCascades;
private updateShadowBounds;
private updateBreaks;
- update(): void;
+ update(matrix: Matrix4): void;
private injectInclude;
setupMaterial(material: Material): void;
private updateUniforms;
diff --git a/node_modules/three-csm/build/three-csm.module.js b/node_modules/three-csm/build/three-csm.module.js
index 7bd13ed..21ba916 100644
--- a/node_modules/three-csm/build/three-csm.module.js
+++ b/node_modules/three-csm/build/three-csm.module.js
@@ -251,6 +251,7 @@ uniform float shadowFar;
class CSMHelper extends Group {
constructor(csm) {
super();
+
this.displayFrustum = true;
this.displayPlanes = true;
this.displayShadowBounds = true;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment