diff --git a/packages/engine/src/scene/systems/ShadowSystem.tsx b/packages/engine/src/scene/systems/ShadowSystem.tsx index 80915d3d79..0570281960 100644 --- a/packages/engine/src/scene/systems/ShadowSystem.tsx +++ b/packages/engine/src/scene/systems/ShadowSystem.tsx @@ -55,15 +55,7 @@ import { createEntity, removeEntity, useEntityContext } from '@etherealengine/ec import { defineQuery, QueryReactor } from '@etherealengine/ecs/src/QueryFunctions' import { defineSystem, useExecute } from '@etherealengine/ecs/src/SystemFunctions' import { AnimationSystemGroup } from '@etherealengine/ecs/src/SystemGroups' -import { - defineState, - getMutableState, - getState, - hookstate, - NO_PROXY, - useHookstate, - useMutableState -} from '@etherealengine/hyperflux' +import { defineState, getMutableState, getState, hookstate, NO_PROXY, useHookstate } from '@etherealengine/hyperflux' import { Vector3_Back } from '@etherealengine/spatial/src/common/constants/MathConstants' import { createPriorityQueue, @@ -74,11 +66,10 @@ import { DirectionalLightComponent } from '@etherealengine/spatial/src/renderer/ import { addObjectToGroup, GroupComponent } from '@etherealengine/spatial/src/renderer/components/GroupComponent' import { MeshComponent } from '@etherealengine/spatial/src/renderer/components/MeshComponent' import { ObjectLayerComponents } from '@etherealengine/spatial/src/renderer/components/ObjectLayerComponent' -import { useScene } from '@etherealengine/spatial/src/renderer/components/SceneComponents' import { VisibleComponent } from '@etherealengine/spatial/src/renderer/components/VisibleComponent' import { ObjectLayers } from '@etherealengine/spatial/src/renderer/constants/ObjectLayers' import { CSM } from '@etherealengine/spatial/src/renderer/csm/CSM' -import { CSMHelper } from '@etherealengine/spatial/src/renderer/csm/CSMHelper' +//import { CSMHelper } from '@etherealengine/spatial/src/renderer/csm/CSMHelper' import { getShadowsEnabled, useShadowsEnabled @@ -89,7 +80,7 @@ import { compareDistanceToCamera } from '@etherealengine/spatial/src/transform/c import { EntityTreeComponent, iterateEntityNode, - useTreeQuery + useChildWithComponent } from '@etherealengine/spatial/src/transform/components/EntityTree' import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent' import { XRLightProbeState } from '@etherealengine/spatial/src/xr/XRLightProbeSystem' @@ -123,10 +114,10 @@ const raycaster = new Raycaster() raycaster.firstHitOnly = true const raycasterPosition = new Vector3() -const EntityCSMReactor = (props: { entity: Entity; rendererEntity: Entity }) => { - const { entity, rendererEntity } = props - const renderSettings = useComponent(entity, RenderSettingsComponent) +const EntityCSMReactor = (props: { entity: Entity; rendererEntity: Entity; renderSettingsEntity: Entity }) => { + const { entity, rendererEntity, renderSettingsEntity } = props const rendererComponent = useComponent(rendererEntity, RendererComponent) + const renderSettingsComponent = useComponent(renderSettingsEntity, RenderSettingsComponent) const directionalLightComponent = useComponent(entity, DirectionalLightComponent) const shadowMapResolution = useHookstate(getMutableState(RendererState).shadowMapResolution) @@ -137,12 +128,15 @@ const EntityCSMReactor = (props: { entity: Entity; rendererEntity: Entity }) => useEffect(() => { if (!directionalLightComponent.value) return + if (!directionalLightComponent.castShadow.value) return const csm = new CSM({ light: directionalLight as DirectionalLight, + shadowMapSize: shadowMapResolution.value, shadowBias: directionalLightComponent.shadowBias.value, maxFar: directionalLightComponent.cameraFar.value, lightIntensity: directionalLightComponent.intensity.value, - cascades: renderSettings.cascades.value + lightColor: directionalLightComponent.color.value, + cascades: renderSettingsComponent?.cascades.value }) rendererComponent.csm.set(csm) return () => { @@ -154,6 +148,7 @@ const EntityCSMReactor = (props: { entity: Entity; rendererEntity: Entity }) => /** Must run after scene object system to ensure source light is not lit */ useExecute( () => { + if (!directionalLightComponent.castShadow.value) return directionalLight.visible = false }, { after: SceneObjectSystem } @@ -161,34 +156,43 @@ const EntityCSMReactor = (props: { entity: Entity; rendererEntity: Entity }) => useEffect(() => { if (!csm) return + if (!directionalLightComponent.castShadow.value) return csm.shadowBias = directionalLight.shadow.bias + csm.maxFar = directionalLightComponent.cameraFar.value + csm.shadowMapSize = shadowMapResolution.value for (const light of csm.lights) { light.color.copy(directionalLightComponent.color.value) light.intensity = directionalLightComponent.intensity.value - light.shadow.bias = directionalLightComponent.shadowBias.value light.shadow.mapSize.setScalar(shadowMapResolution.value) - csm.needsUpdate = true + light.shadow.radius = directionalLightComponent.shadowRadius.value } + csm.needsUpdate = true }, [ - csm, + rendererComponent.csm, shadowMapResolution, - directionalLightComponent?.shadowBias, - directionalLightComponent?.intensity, - directionalLightComponent?.color, - directionalLightComponent?.castShadow, - directionalLightComponent?.shadowRadius, - directionalLightComponent?.cameraFar + directionalLightComponent.shadowBias, + directionalLightComponent.intensity, + directionalLightComponent.color, + directionalLightComponent.castShadow, + directionalLightComponent.shadowRadius, + directionalLightComponent.cameraFar ]) useEffect(() => { if (!csm) return - csm.cascades = renderSettings.cascades.value + csm.cascades = renderSettingsComponent.cascades.value csm.needsUpdate = true - }, [csm, renderSettings.cascades]) + }, [csm, renderSettingsComponent.cascades]) - return + return ( + + ) } const PlainCSMReactor = (props: { rendererEntity: Entity }) => { @@ -222,22 +226,18 @@ const PlainCSMReactor = (props: { rendererEntity: Entity }) => { } }, [rendererComponent.csm, shadowMapResolution]) - return -} - -const ChildCSMReactor = (props: { rendererEntity: Entity }) => { - const entities = useTreeQuery(props.rendererEntity) return ( - <> - {entities.map((entity) => ( - - ))} - + ) } -const EntityChildCSMReactor = (props: { entity: Entity; rendererEntity: Entity }) => { - const { entity, rendererEntity } = props +const EntityChildCSMReactor = (props: { rendererEntity: Entity }) => { + const entity = useEntityContext() + const { rendererEntity } = props const shadowComponent = useComponent(entity, ShadowComponent) const groupComponent = useComponent(entity, GroupComponent) @@ -246,6 +246,8 @@ const EntityChildCSMReactor = (props: { entity: Entity; rendererEntity: Entity } useEffect(() => { if (!csm || !shadowComponent.receive.value) return + if (!groupComponent) return + const objs = [...groupComponent.value] as Mesh[] for (const obj of objs) { if (obj.material) { @@ -266,17 +268,18 @@ const EntityChildCSMReactor = (props: { entity: Entity; rendererEntity: Entity } } function _CSMReactor() { - const renderSettingsEntity = useEntityContext() - const rendererEntity = useScene(renderSettingsEntity) + const rendererEntity = useEntityContext() + const renderSettingsEntity = useChildWithComponent(rendererEntity, RenderSettingsComponent) if (!rendererEntity) return null + if (!renderSettingsEntity) return null - return + return } -function CSMReactor(props: { renderSettingsEntity: Entity; rendererEntity: Entity }) { - const { renderSettingsEntity, rendererEntity } = props - const rendererComponent = useComponent(rendererEntity, RendererComponent) +function CSMReactor(props: { rendererEntity: Entity; renderSettingsEntity: Entity }) { + const { rendererEntity, renderSettingsEntity } = props + //const rendererComponent = useComponent(rendererEntity, RendererComponent) const renderSettingsComponent = useComponent(renderSettingsEntity, RenderSettingsComponent) @@ -284,19 +287,19 @@ function CSMReactor(props: { renderSettingsEntity: Entity; rendererEntity: Entit const activeLightEntity = useHookstate(UndefinedEntity) - const rendererState = useMutableState(RendererState) + //const rendererState = useMutableState(RendererState) - useEffect(() => { - if (!rendererComponent) return - if (!rendererComponent.csm.value || !rendererState.nodeHelperVisibility.value) return + // useEffect(() => { + // if (!rendererComponent) return + // if (!rendererComponent.csm.value || !rendererState.nodeHelperVisibility.value) return - const helper = new CSMHelper() - rendererComponent.csmHelper.set(helper) - return () => { - helper.remove() - rendererComponent.csmHelper.set(null) - } - }, [rendererComponent, renderSettingsComponent?.csm, rendererState.nodeHelperVisibility]) + // const helper = new CSMHelper() + // rendererComponent.csmHelper.set(helper) + // return () => { + // helper.remove() + // rendererComponent.csmHelper.set(null) + // } + // }, [rendererComponent, renderSettingsComponent.csm, rendererState.nodeHelperVisibility]) useEffect(() => { if (rendererEntity === Engine.instance.viewerEntity && xrLightProbeEntity.value) { @@ -304,20 +307,24 @@ function CSMReactor(props: { renderSettingsEntity: Entity; rendererEntity: Entit return } - if (renderSettingsComponent?.primaryLight.value) { + if (renderSettingsComponent.primaryLight.value) { activeLightEntity.set(UUIDComponent.getEntityByUUID(renderSettingsComponent.primaryLight.value)) return } activeLightEntity.set(UndefinedEntity) - }, [xrLightProbeEntity.value, renderSettingsComponent?.primaryLight?.value]) + }, [xrLightProbeEntity.value, renderSettingsComponent.primaryLight.value]) - if (!renderSettingsComponent?.csm.value) return null + if (!renderSettingsComponent.csm.value) return null if (!activeLightEntity.value) return return ( - + ) } @@ -432,7 +439,6 @@ const updateDropShadowTransforms = () => { } } -const groupQuery = defineQuery([GroupComponent, VisibleComponent, ShadowComponent]) const rendererQuery = defineQuery([RendererComponent]) const execute = () => { @@ -448,14 +454,7 @@ const execute = () => { const { csm, csmHelper } = getComponent(entity, RendererComponent) if (csm) { csm.update() - if (csmHelper) csmHelper.update(csm) - - /** hack fix to ensure CSM material is applied to all materials (which are not set reactively) */ - for (const entity of groupQuery()) { - for (const obj of getComponent(entity, GroupComponent) as any as Mesh[]) { - if (obj.material && obj.receiveShadow) csm.setupMaterial(obj) - } - } + //if (csmHelper) csmHelper.update(csm) } } } @@ -489,7 +488,7 @@ const reactor = () => { return ( <> {useShadows ? ( - + ) : ( )} diff --git a/packages/spatial/src/renderer/csm/CSM.ts b/packages/spatial/src/renderer/csm/CSM.ts index d1ea35f04b..32e7a338db 100644 --- a/packages/spatial/src/renderer/csm/CSM.ts +++ b/packages/spatial/src/renderer/csm/CSM.ts @@ -25,6 +25,7 @@ Ethereal Engine. All Rights Reserved. import { Box3, + ColorRepresentation, DirectionalLight, Material, MathUtils, @@ -41,17 +42,15 @@ import { getComponent, setComponent } from '@etherealengine/ecs/src/ComponentFun import { Engine } from '@etherealengine/ecs/src/Engine' import { Entity } from '@etherealengine/ecs/src/Entity' import { createEntity, removeEntity } from '@etherealengine/ecs/src/EntityFunctions' -import { getState } from '@etherealengine/hyperflux' import { CameraComponent } from '../../camera/components/CameraComponent' +import { NameComponent } from '../../common/NameComponent' import { Vector3_Zero } from '../../common/constants/MathConstants' import { addOBCPlugin, removeOBCPlugin } from '../../common/functions/OnBeforeCompilePlugin' -import { NameComponent } from '../../common/NameComponent' import { addObjectToGroup } from '../../renderer/components/GroupComponent' import { VisibleComponent } from '../../renderer/components/VisibleComponent' import { EntityTreeComponent } from '../../transform/components/EntityTree' import { TransformComponent } from '../../transform/components/TransformComponent' -import { RendererState } from '../RendererState' import Frustum from './Frustum' import Shader from './Shader' @@ -85,6 +84,7 @@ type CSMParams = { lightDirection?: Vector3 lightDirectionUp?: Vector3 lightIntensity?: number + lightColor?: ColorRepresentation lightNear?: number lightFar?: number lightMargin?: number @@ -98,8 +98,10 @@ export class CSM { mode: (typeof CSMModes)[keyof typeof CSMModes] shadowBias: number shadowNormalBias: number + shadowMapSize: number lightDirection: Vector3 lightDirectionUp: Vector3 + lightColor: ColorRepresentation lightIntensity: number lightMargin: number customSplitsCallback?: (amount: number, near: number, far: number, target: number[]) => void @@ -119,10 +121,12 @@ export class CSM { this.cascades = data.cascades ?? 5 this.maxFar = data.maxFar ?? 100 this.mode = data.mode ?? CSMModes.PRACTICAL + this.shadowMapSize = data.shadowMapSize ?? 1024 this.shadowBias = data.shadowBias ?? 0 this.shadowNormalBias = 0 this.lightDirection = data.lightDirection ?? new Vector3(1, -1, 1).normalize() this.lightDirectionUp = data.lightDirectionUp ?? Object3D.DEFAULT_UP + this.lightColor = data.lightColor ?? 0xffffff this.lightIntensity = data.lightIntensity ?? 1 this.lightMargin = data.lightMargin ?? 200 this.customSplitsCallback = data.customSplitsCallback @@ -164,6 +168,30 @@ export class CSM { }) } + createLight(light: DirectionalLight, i: number): void { + light.castShadow = true + light.frustumCulled = false + + light.shadow.mapSize.width = this.shadowMapSize + light.shadow.mapSize.height = this.shadowMapSize + + light.shadow.camera.near = 0 + light.shadow.camera.far = 1 + + light.intensity = this.lightIntensity + + const entity = createEntity() + addObjectToGroup(entity, light) + setComponent(entity, NameComponent, 'CSM light ' + i) + setComponent(entity, VisibleComponent) + setComponent(entity, EntityTreeComponent, { parentEntity: Engine.instance.originEntity }) + + this.lightEntities.push(entity) + this.lights.push(light) + light.name = 'CSM_' + light.name + light.target.name = 'CSM_' + light.target.name + } + createLights(sourceLight?: DirectionalLight): void { if (sourceLight) { this.sourceLight = sourceLight @@ -172,47 +200,15 @@ export class CSM { for (let i = 0; i < this.cascades; i++) { const light = sourceLight.clone() - light.castShadow = true - light.frustumCulled = false - - light.intensity = this.lightIntensity - - const entity = createEntity() - addObjectToGroup(entity, light) - setComponent(entity, NameComponent, 'CSM light ' + i) - setComponent(entity, VisibleComponent) - setComponent(entity, EntityTreeComponent, { parentEntity: Engine.instance.originEntity }) - - this.lightEntities.push(entity) - this.lights.push(light) - light.name = 'CSM_' + sourceLight.name - light.target.name = 'CSM_' + sourceLight.target.name + this.createLight(light, i) } return } // if no lights are provided, create default ones - for (let i = 0; i < this.cascades; i++) { - const light = new DirectionalLight(0xffffff, this.lightIntensity) - - light.castShadow = true - - light.shadow.camera.near = 0 - light.shadow.camera.far = 1 - - light.frustumCulled = false - - const entity = createEntity() - addObjectToGroup(entity, light) - setComponent(entity, NameComponent, 'CSM light ' + i) - setComponent(entity, VisibleComponent) - setComponent(entity, EntityTreeComponent, { parentEntity: Engine.instance.originEntity }) - - this.lightEntities.push(entity) - this.lights.push(light) - light.name = 'CSM_' + light.name - light.target.name = 'CSM_' + light.target.name + const light = new DirectionalLight(this.lightColor, this.lightIntensity) + this.createLight(light, i) } } @@ -322,6 +318,7 @@ export class CSM { update(): void { if (this.sourceLight) this.lightDirection.subVectors(this.sourceLight.target.position, this.sourceLight.position) if (this.needsUpdate) { + this.injectInclude() this.updateFrustums() for (const light of this.lights) { light.shadow.map?.dispose() @@ -338,10 +335,8 @@ export class CSM { const frustum = frustums[i] const shadowCam = light.shadow.camera - const shadowMapSize = getState(RendererState).shadowMapResolution - - const texelWidth = (shadowCam.right - shadowCam.left) / shadowMapSize - const texelHeight = (shadowCam.top - shadowCam.bottom) / shadowMapSize + const texelWidth = (shadowCam.right - shadowCam.left) / this.shadowMapSize + const texelHeight = (shadowCam.top - shadowCam.bottom) / this.shadowMapSize // This matrix only represents sun orientation, origin is zero _lightOrientationMatrix.lookAt(Vector3_Zero, this.lightDirection, this.lightDirectionUp) @@ -490,6 +485,10 @@ export class CSM { this.lightEntities.forEach((entity) => { removeEntity(entity) }) + this.lights.forEach((light) => { + light.dispose() + }) + this.lightEntities = [] this.lights = [] } diff --git a/packages/spatial/src/transform/components/EntityTree.tsx b/packages/spatial/src/transform/components/EntityTree.tsx index bd1fd8e720..e6d6222826 100644 --- a/packages/spatial/src/transform/components/EntityTree.tsx +++ b/packages/spatial/src/transform/components/EntityTree.tsx @@ -432,7 +432,11 @@ export function useAncestorWithComponent(entity: Entity, component: ComponentTyp return result.value } -export function useChildWithComponent(entity: Entity, component: ComponentType) { +/** + * @todo - return an array of entities that have the component + * + */ +export function useChildWithComponent(rootEntity: Entity, component: ComponentType) { const result = useHookstate(UndefinedEntity) useLayoutEffect(() => { @@ -463,13 +467,23 @@ export function useChildWithComponent(entity: Entity, component: ComponentType + const isScene = useOptionalComponent(rootEntity, SceneComponent) + if (isScene) { + return ( + <> + {isScene.children.value.map((entity) => ( + + ))} + + ) + } + return }) return () => { unmounted = true root.stop() } - }, [entity, component]) + }, [rootEntity, component]) return result.value }