StrandedKitty / three-csm

☀️ Cascaded shadow maps (CSMs) implementation for Three.js
MIT License
296 stars 21 forks source link

TypeScript and React Three Fiber support #22

Open itsdouges opened 1 year ago

itsdouges commented 1 year ago

Hello! Found your library through the threejs examples, it looks great! Currently I'm trying to integrate it into my point & click game and found some friction points:

Would you consider adding support for both? Types would either be the creation of a typedef file or converting to TS, and R3F would be following https://docs.pmnd.rs/react-three-fiber/tutorials/typescript#extending-threeelements. A few libraries come with support such as https://github.com/FarazzShaikh/THREE-CustomShaderMaterial

StrandedKitty commented 1 year ago

I will migrate it to TS soon. Basically, I'm nearly finished, but there are some issues with rollup I can't figure out ;-; Regarding React stuff: sorry, I'm not really interested in this. Feel free to contribute though, I will merge a PR if someone implements this stuff you are talking about.

itsdouges commented 1 year ago

Sweet! TS stuff sounds great, and no worries with the React stuff. I'll put something up in the future when I get things working 😄

StrandedKitty commented 1 year ago

TS migration done in v3.0.0

itsdouges commented 1 year ago

Thanks mate! For the type defs you'll need to do some work https://github.com/StrandedKitty/three-csm/blob/master/package.json#L7

They're currently pointing to a file that doesn't exist. Pointing to CSM.d.ts gets it working.

Looking at the generated types some are off:

// Should take Camera (as it could be an orth camera too no?)
camera: PerspectiveCamera;

// Would be great to type the args
customSplitsCallback: (cascadeCount: any, nearDistance: any, farDistance: any) => number[];

I can contribute anything here if you'd like.

itsdouges commented 1 year ago

Raised https://github.com/StrandedKitty/three-csm/pull/29 and https://github.com/StrandedKitty/three-csm/pull/28

drcmda commented 1 year ago

@itsdouges regarding react, it wouldn't need changes from a library, it will work ootb. trying it here but getting shaders errors from csm, did you figure this out? https://codesandbox.io/s/exemple-basique-avec-tentative-csm-ver2-forked-5cywrd?file=/src/App.js

ERROR: 0:1703: '[]' : array index out of range
ERROR: 0:1703: '[]' : array index out of range
ERROR: 0:1705: '[]' : array index out of range
ERROR: 0:1705: '[]' : array index out of range

  1698:             getDirectionalLightInfo( directionalLight, geometry, directLight );
  1699: 
  1700:             #if defined( USE_SHADOWMAP ) && ( 4 < 5 )
  1701: 
  1702:             directionalLightShadow = directionalLightShadows[ 4 ];
> 1703:             if(linearDepth >= CSM_cascades[4].x && linearDepth < CSM_cascades[4].y) directLight.color *= all( bvec2(
drcmda commented 1 year ago

figured it out from another github issue about that

itsdouges commented 1 year ago

I can link code if you want - I had to defer instantiation of the class similar to some three Controls. Which issue did you use?

drcmda commented 1 year ago

someone had the same issue above, you can't have lights that castShadow in your scene, that's now disallowed with csm.

itsdouges commented 1 year ago

This is the code I used for CSM, your shader errors are probably from CSM instantiating a few times because it adds scene objects immediately... when instantiated.

import { useFrame, useThree } from '@react-three/fiber';
import { useLayoutEffect, useMemo } from 'react';
import { OrthographicCamera, PerspectiveCamera, Vector3, Vector3Tuple } from 'three';
import CSM, { CSMParams } from 'three-csm';

interface CascadedShadowMapProps extends Omit<CSMParams, 'lightDirection' | 'camera' | 'parent'> {
  fade?: boolean;
  lightDirection?: Vector3Tuple;
}

class CSMProxy {
  instance: CSM | undefined;
  args: CSMParams;

  constructor(args: CSMParams) {
    this.args = args;
  }

  set fade(fade: boolean) {
    if (this.instance) {
      this.instance.fade = fade;
    }
  }

  set camera(camera: PerspectiveCamera | OrthographicCamera) {
    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();
    }
  });

  useLayoutEffect(() => {
    proxyInstance.attach();

    return () => {
      proxyInstance.dispose();
    };
  }, [proxyInstance]);

  return (
    <primitive object={proxyInstance} camera={camera} fade={fade} lightDirection={lightDirection} />
  );
}