diff --git a/packages/ecs/src/ComponentFunctions.ts b/packages/ecs/src/ComponentFunctions.ts index e5dd5ad9fd..7589e73441 100755 --- a/packages/ecs/src/ComponentFunctions.ts +++ b/packages/ecs/src/ComponentFunctions.ts @@ -132,7 +132,7 @@ export interface ComponentPartial< * `@todo` Explain what reactive is in this context * `@todo` Explain this function */ - reactor?: React.FC + reactor?: any // previously breaks types /** * @todo Explain ComponentPartial.errors[] */ diff --git a/packages/editor/package.json b/packages/editor/package.json index 65efc2db67..2931b30281 100755 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -43,6 +43,7 @@ "i18next": "21.6.16", "lodash": "4.17.21", "potpack": "^2.0.0", + "postprocessing": "6.34.1", "rc-dock": "3.2.18", "rc-slider": "10.5.0", "react": "18.2.0", diff --git a/packages/editor/src/EditorModule.ts b/packages/editor/src/EditorModule.ts index 133e04e108..0e167ba31b 100644 --- a/packages/editor/src/EditorModule.ts +++ b/packages/editor/src/EditorModule.ts @@ -28,6 +28,7 @@ import { RenderInfoSystem } from '@etherealengine/spatial/src/renderer/RenderInf import { EditorInstanceNetworkingSystem } from './components/realtime/EditorInstanceNetworkingSystem' import { EditorControlSystem } from './systems/EditorControlSystem' import { GizmoSystem } from './systems/GizmoSystem' +import { HighlightSystem } from './systems/HighlightSystem' import { ModelHandlingSystem } from './systems/ModelHandlingSystem' import { ObjectGridSnapSystem } from './systems/ObjectGridSnapSystem' import { UploadRequestSystem } from './systems/UploadRequestSystem' @@ -36,6 +37,7 @@ export { EditorInstanceNetworkingSystem, EditorControlSystem, GizmoSystem, + HighlightSystem, ModelHandlingSystem, ObjectGridSnapSystem, UploadRequestSystem, diff --git a/packages/editor/src/components/properties/PostProcessingSettingsEditor.tsx b/packages/editor/src/components/properties/PostProcessingSettingsEditor.tsx index 318f8b8a70..ca1cf45b5a 100755 --- a/packages/editor/src/components/properties/PostProcessingSettingsEditor.tsx +++ b/packages/editor/src/components/properties/PostProcessingSettingsEditor.tsx @@ -23,26 +23,30 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ +import { BlendFunction, SMAAPreset, VignetteTechnique } from 'postprocessing' +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { Color, DisplayP3ColorSpace, LinearDisplayP3ColorSpace, LinearSRGBColorSpace, SRGBColorSpace } from 'three' + +import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions' import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh' import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp' import Checkbox from '@mui/material/Checkbox' import Collapse from '@mui/material/Collapse' import IconButton from '@mui/material/IconButton' -import { BlendFunction, VignetteTechnique } from 'postprocessing' -import React, { useState } from 'react' -import { useTranslation } from 'react-i18next' -import { Color } from 'three' -import { useComponent } from '@etherealengine/ecs/src/ComponentFunctions' +import { getState } from '@etherealengine/hyperflux' import { PostProcessingComponent } from '@etherealengine/spatial/src/renderer/components/PostProcessingComponent' -import { Effects } from '@etherealengine/spatial/src/renderer/effects/PostProcessing' - +import { PostProcessingEffectState } from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' import BooleanInput from '../inputs/BooleanInput' import ColorInput from '../inputs/ColorInput' import CompoundNumericInput from '../inputs/CompoundNumericInput' import InputGroup from '../inputs/InputGroup' import SelectInput from '../inputs/SelectInput' +import TexturePreviewInput from '../inputs/TexturePreviewInput' +import Vector2Input from '../inputs/Vector2Input' +import Vector3Input from '../inputs/Vector3Input' import styles from '../styles.module.scss' import PropertyGroup from './PropertyGroup' import { commitProperties, commitProperty, EditorComponentType, updateProperty } from './Util' @@ -52,159 +56,20 @@ enum PropertyTypes { Number, Boolean, Color, + ColorSpace, KernelSize, SMAAPreset, EdgeDetectionMode, PredicationMode, + Texture, + Vector2, + Vector3, VignetteTechnique } -type EffectPropertyDetail = { propertyType: PropertyTypes; name: string; min?: number; max?: number; step?: number } -type EffectPropertiesType = { [key: string]: EffectPropertyDetail } -type EffectOptionsType = { [key in keyof typeof Effects]: EffectPropertiesType } - -const EffectsOptions: Partial = { - SSAOEffect: { - blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, - distanceScaling: { propertyType: PropertyTypes.Boolean, name: 'Distance Scaling' }, - depthAwareUpsampling: { propertyType: PropertyTypes.Boolean, name: 'Depth Aware Upsampling' }, - samples: { propertyType: PropertyTypes.Number, name: 'Samples', min: 1, max: 32, step: 1 }, - rings: { propertyType: PropertyTypes.Number, name: 'Rings', min: -1, max: 1, step: 0.01 }, - - rangeThreshold: { propertyType: PropertyTypes.Number, name: 'Range Threshold', min: -1, max: 1, step: 0.0001 }, // Occlusion proximity of ~0.3 world units - rangeFalloff: { propertyType: PropertyTypes.Number, name: 'Range Falloff', min: -1, max: 1, step: 0.0001 }, // with ~0.1 units of falloff. - // Render up to a distance of ~20 world units. - distanceThreshold: { propertyType: PropertyTypes.Number, name: 'Distance Threshold', min: -1, max: 1, step: 0.01 }, - luminanceInfluence: { - propertyType: PropertyTypes.Number, - name: 'Luminance Influence', - min: -1, - max: 1, - step: 0.01 - }, - // with an additional ~2.5 units of falloff. - distanceFalloff: { propertyType: PropertyTypes.Number, name: 'Distance Falloff', min: -1, max: 1, step: 0.001 }, - minRadiusScale: { propertyType: PropertyTypes.Number, name: 'Min Radius Scale', min: -1, max: 1, step: 0.01 }, - bias: { propertyType: PropertyTypes.Number, name: 'Bias', min: -1, max: 1, step: 0.01 }, - radius: { propertyType: PropertyTypes.Number, name: 'Radius', min: -1, max: 1, step: 0.01 }, - intensity: { propertyType: PropertyTypes.Number, name: 'Intensity', min: 0, max: 10, step: 0.01 }, - fade: { propertyType: PropertyTypes.Number, name: 'Fade', min: -1, max: 1, step: 0.01 }, - resolutionScale: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: -10, max: 10, step: 0.01 }, - kernelSize: { propertyType: PropertyTypes.Number, name: 'Kerne Size', min: 1, max: 5, step: 1 }, - blur: { propertyType: PropertyTypes.Boolean, name: 'Blur' } - }, - SSREffect: { - distance: { propertyType: PropertyTypes.Number, name: 'Distance', min: 0.001, max: 10, step: 0.01 }, - thickness: { propertyType: PropertyTypes.Number, name: 'Thickness', min: 0, max: 5, step: 0.01 }, - denoiseIterations: { propertyType: PropertyTypes.Number, name: 'Denoise Iterations', min: 0, max: 5, step: 1 }, - denoiseKernel: { propertyType: PropertyTypes.Number, name: 'Denoise Kernel', min: 1, max: 5, step: 1 }, - denoiseDiffuse: { propertyType: PropertyTypes.Number, name: 'Denoise Diffuse', min: 0, max: 50, step: 0.01 }, - denoiseSpecular: { propertyType: PropertyTypes.Number, name: 'Denoise Specular', min: 0, max: 50, step: 0.01 }, - radius: { propertyType: PropertyTypes.Number, name: 'Radius', min: 0, max: 50, step: 0.01 }, - phi: { propertyType: PropertyTypes.Number, name: 'Phi', min: 0, max: 50, step: 0.01 }, - lumaPhi: { propertyType: PropertyTypes.Number, name: 'Denoise Specular', min: 0, max: 50, step: 0.01 }, - depthPhi: { propertyType: PropertyTypes.Number, name: 'luminosity Phi', min: 0, max: 15, step: 0.001 }, - normalPhi: { propertyType: PropertyTypes.Number, name: 'Normal Phi', min: 0, max: 50, step: 0.001 }, - roughnessPhi: { propertyType: PropertyTypes.Number, name: 'Roughness Phi', min: 0, max: 100, step: 0.001 }, - specularPhi: { propertyType: PropertyTypes.Number, name: 'Specular Phi', min: 0, max: 50, step: 0.01 }, - envBlur: { propertyType: PropertyTypes.Number, name: 'Environment Blur', min: 0, max: 1, step: 0.01 }, - importanceSampling: { propertyType: PropertyTypes.Boolean, name: 'Importance Sampling' }, - steps: { propertyType: PropertyTypes.Number, name: 'Steps', min: 0, max: 256, step: 1 }, - refineSteps: { propertyType: PropertyTypes.Number, name: 'Refine Steps', min: 0, max: 16, step: 1 }, - resolutionScale: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: 0.25, max: 1, step: 0.25 }, - missedRays: { propertyType: PropertyTypes.Boolean, name: 'Missed Rays' } - }, - DepthOfFieldEffect: { - blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, - bokehScale: { propertyType: PropertyTypes.Number, name: 'Bokeh Scale', min: -10, max: 10, step: 0.01 }, - focalLength: { propertyType: PropertyTypes.Number, name: 'Focal Length', min: 0, max: 1, step: 0.01 }, - focalRange: { propertyType: PropertyTypes.Number, name: 'Focal Range', min: 0, max: 1, step: 0.01 }, - focusDistance: { propertyType: PropertyTypes.Number, name: 'Focus Distance', min: 0, max: 1, step: 0.01 }, - resolutionScale: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: -10, max: 10, step: 0.01 } - }, - BloomEffect: { - blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, - kernelSize: { propertyType: PropertyTypes.KernelSize, name: 'Kernel Size' }, - intensity: { propertyType: PropertyTypes.Number, name: 'Intensity', min: 0, max: 10, step: 0.01 }, - luminanceSmoothing: { - propertyType: PropertyTypes.Number, - name: 'Luminance Smoothing', - min: 0, - max: 1, - step: 0.01 - }, - luminanceThreshold: { propertyType: PropertyTypes.Number, name: 'Luminance Threshold', min: 0, max: 1, step: 0.01 }, - mipmapBlur: { propertyType: PropertyTypes.Boolean, name: 'Mipmap Blur' }, - radius: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: 0, max: 10, step: 0.01 }, - levels: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: 1, max: 10, step: 1 } - }, - ToneMappingEffect: { - blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, - adaptive: { propertyType: PropertyTypes.Boolean, name: 'Adaptive' }, - adaptationRate: { propertyType: PropertyTypes.Number, name: 'Adaptation Rate', min: -1, max: 1, step: 0.01 }, - averageLuminance: { propertyType: PropertyTypes.Number, name: 'Average Luminance', min: -1, max: 1, step: 0.01 }, - maxLuminance: { propertyType: PropertyTypes.Number, name: 'Max Luminance', min: -1, max: 1, step: 0.01 }, - middleGrey: { propertyType: PropertyTypes.Number, name: 'Middle Grey', min: -1, max: 1, step: 0.01 }, - resolution: { propertyType: PropertyTypes.Number, name: 'Resolution' }, - whitePoint: { propertyType: PropertyTypes.Number, name: 'Resolution' }, - minLuminance: { propertyType: PropertyTypes.Number, name: 'Resolution' } - }, - BrightnessContrastEffect: { - brightness: { propertyType: PropertyTypes.Number, name: 'Brightness', min: -1, max: 1, step: 0.01 }, - contrast: { propertyType: PropertyTypes.Number, name: 'Contrast', min: -1, max: 1, step: 0.01 } - }, - HueSaturationEffect: { - hue: { propertyType: PropertyTypes.Number, name: 'Hue', min: -1, max: 1, step: 0.01 }, - saturation: { propertyType: PropertyTypes.Number, name: 'Saturation', min: -1, max: 1, step: 0.01 } - }, - ColorDepthEffect: { - bits: { propertyType: PropertyTypes.Number, name: 'Bits', min: -1, max: 1, step: 0.01 } - }, - LinearTosRGBEffect: {}, - SSGIEffect: { - distance: { propertyType: PropertyTypes.Number, name: 'Distance', min: 0.001, max: 10, step: 0.01 }, - thickness: { propertyType: PropertyTypes.Number, name: 'Thickness', min: 0, max: 5, step: 0.01 }, - denoiseIterations: { propertyType: PropertyTypes.Number, name: 'Denoise Iterations', min: 0, max: 5, step: 1 }, - denoiseKernel: { propertyType: PropertyTypes.Number, name: 'Denoise Kernel', min: 1, max: 5, step: 1 }, - denoiseDiffuse: { propertyType: PropertyTypes.Number, name: 'Denoise Diffuse', min: 0, max: 50, step: 0.01 }, - denoiseSpecular: { propertyType: PropertyTypes.Number, name: 'Denoise Specular', min: 0, max: 50, step: 0.01 }, - radius: { propertyType: PropertyTypes.Number, name: 'Radius', min: 0, max: 50, step: 0.01 }, - phi: { propertyType: PropertyTypes.Number, name: 'Phi', min: 0, max: 50, step: 0.01 }, - lumaPhi: { propertyType: PropertyTypes.Number, name: 'Denoise Specular', min: 0, max: 50, step: 0.01 }, - depthPhi: { propertyType: PropertyTypes.Number, name: 'luminosity Phi', min: 0, max: 15, step: 0.001 }, - normalPhi: { propertyType: PropertyTypes.Number, name: 'Normal Phi', min: 0, max: 50, step: 0.001 }, - roughnessPhi: { propertyType: PropertyTypes.Number, name: 'Roughness Phi', min: 0, max: 100, step: 0.001 }, - specularPhi: { propertyType: PropertyTypes.Number, name: 'Specular Phi', min: 0, max: 50, step: 0.01 }, - envBlur: { propertyType: PropertyTypes.Number, name: 'Environment Blur', min: 0, max: 1, step: 0.01 }, - importanceSampling: { propertyType: PropertyTypes.Boolean, name: 'Importance Sampling' }, - steps: { propertyType: PropertyTypes.Number, name: 'Steps', min: 0, max: 256, step: 1 }, - refineSteps: { propertyType: PropertyTypes.Number, name: 'Refine Steps', min: 0, max: 16, step: 1 }, - resolutionScale: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: 0.25, max: 1, step: 0.25 }, - missedRays: { propertyType: PropertyTypes.Boolean, name: 'Missed Rays' } - }, - TRAAEffect: { - blend: { propertyType: PropertyTypes.Number, name: 'Blend', min: 0, max: 1, step: 0.001 }, - constantBlend: { propertyType: PropertyTypes.Boolean, name: 'Constant Blend' }, - dilation: { propertyType: PropertyTypes.Boolean, name: 'Dilation' }, - blockySampling: { propertyType: PropertyTypes.Boolean, name: 'Blocky Sampling' }, - logTransform: { propertyType: PropertyTypes.Boolean, name: 'Log Transform' }, - depthDistance: { propertyType: PropertyTypes.Number, name: 'Depth Distance', min: 0.01, max: 100, step: 0.01 }, - worldDistance: { propertyType: PropertyTypes.Number, name: 'World Distance', min: 0.01, max: 100, step: 0.01 }, - neighborhoodClamping: { propertyType: PropertyTypes.Boolean, name: 'Neighborhood Clamping' } - }, - MotionBlurEffect: { - intensity: { propertyType: PropertyTypes.Number, name: 'Intensity', min: 0, max: 10, step: 0.01 }, - jitter: { propertyType: PropertyTypes.Number, name: 'Jitter', min: 0, max: 10, step: 0.01 }, - samples: { propertyType: PropertyTypes.Number, name: 'Samples', min: 1, max: 64, step: 1 } - }, - VignetteEffect: { - blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, - technique: { propertyType: PropertyTypes.VignetteTechnique, name: 'Technique' }, - eskil: { propertyType: PropertyTypes.Boolean, name: 'Eskil' }, - offset: { propertyType: PropertyTypes.Number, name: 'Offset', min: 0, max: 10, step: 0.1 }, - darkness: { propertyType: PropertyTypes.Number, name: 'Darkness', min: 0, max: 10, step: 0.1 } - } -} +const SMAAPresetSelect = Object.entries(SMAAPreset).map(([label, value]) => { + return { label, value } +}) const BlendFunctionSelect = Object.entries(BlendFunction).map(([label, value]) => { return { label, value } @@ -214,6 +79,14 @@ const VignetteTechniqueSelect = Object.entries(VignetteTechnique).map(([label, v return { label, value } }) +const ColorSpaceSelect = [ + { label: 'NONE', value: '' }, + { label: 'SRGB', value: SRGBColorSpace }, + { label: 'SRGB LINEAR', value: LinearSRGBColorSpace }, + { label: 'DISPLAY P3', value: DisplayP3ColorSpace }, + { label: 'DISPLAY P3 LINEAR', value: LinearDisplayP3ColorSpace } +] + const KernelSizeSelect = [ { label: 'VERY_SMALL', value: 0 }, { label: 'SMALL', value: 1 }, @@ -223,13 +96,6 @@ const KernelSizeSelect = [ { label: 'HUGE', value: 5 } ] -const SMAAPreset = [ - { label: 'LOW', value: 0 }, - { label: 'MEDIUM', value: 1 }, - { label: 'HIGH', value: 2 }, - { label: 'ULTRA', value: 3 } -] - const EdgeDetectionMode = [ { label: 'DEPTH', value: 0 }, { label: 'LUMA', value: 1 }, @@ -246,26 +112,23 @@ export const PostProcessingSettingsEditor: EditorComponentType = (props) => { const { t } = useTranslation() const [openSettings, setOpenSettings] = useState(false) + const effects = getState(PostProcessingEffectState) const postprocessing = useComponent(props.entity, PostProcessingComponent) - const renderProperty = ( - propertyDetail: EffectPropertyDetail, - effectName: keyof typeof Effects, - property: string, - index: number - ) => { - const effectSettingState = postprocessing.effects[effectName][property] + const renderProperty = (effectName: string, property: string, index: number) => { + const effectSettingState = effects[effectName].schema[property] + const effectSettingValue = postprocessing.effects[effectName][property].value let renderVal = <> - switch (propertyDetail.propertyType) { + switch (effectSettingState.propertyType) { case PropertyTypes.Number: renderVal = ( @@ -276,7 +139,17 @@ export const PostProcessingSettingsEditor: EditorComponentType = (props) => { renderVal = ( + ) + break + + case PropertyTypes.SMAAPreset: + renderVal = ( + ) break @@ -286,7 +159,7 @@ export const PostProcessingSettingsEditor: EditorComponentType = (props) => { ) break @@ -296,11 +169,37 @@ export const PostProcessingSettingsEditor: EditorComponentType = (props) => { + ) + break + + case PropertyTypes.Vector2: + renderVal = ( + + ) + break + + case PropertyTypes.Vector3: + renderVal = ( + ) break + case PropertyTypes.Texture: + renderVal = ( + + ) + break case PropertyTypes.Color: renderVal = ( { ) break - case PropertyTypes.SMAAPreset: + case PropertyTypes.ColorSpace: renderVal = ( ) break @@ -340,7 +239,7 @@ export const PostProcessingSettingsEditor: EditorComponentType = (props) => { ) break @@ -350,7 +249,7 @@ export const PostProcessingSettingsEditor: EditorComponentType = (props) => { ) break @@ -367,7 +266,7 @@ export const PostProcessingSettingsEditor: EditorComponentType = (props) => { alignItems: 'center' }} > - + {renderVal} @@ -375,12 +274,12 @@ export const PostProcessingSettingsEditor: EditorComponentType = (props) => { } const renderEffectsTypes = (effectName) => { - const effect = EffectsOptions[effectName] - return Object.keys(effect).map((prop, index) => renderProperty(effect[prop], effectName, prop, index)) + const effect = getState(PostProcessingEffectState)[effectName].schema + return Object.keys(effect).map((prop, index) => renderProperty(effectName, prop, index)) } const renderEffects = () => { - const items = Object.keys(EffectsOptions).map((effect: keyof typeof Effects) => { + const items = Object.keys(getState(PostProcessingEffectState)).map((effect) => { return (
{ visualScriptQuery().forEach((entity) => dispatchAction(VisualScriptActions.execute({ entity }))) transformGizmoControlledQuery().forEach((entity) => removeComponent(entity, TransformGizmoControlledComponent)) //just remove all gizmo in the scene + + //just clear selection to remove all higlights in the scene + SelectionState.updateSelection([]) } } diff --git a/packages/editor/src/functions/EditorControlFunctions.ts b/packages/editor/src/functions/EditorControlFunctions.ts index 0068993a27..c3b5cd13e5 100644 --- a/packages/editor/src/functions/EditorControlFunctions.ts +++ b/packages/editor/src/functions/EditorControlFunctions.ts @@ -49,7 +49,6 @@ import { ComponentJsonType } from '@etherealengine/engine/src/scene/types/SceneT import { dispatchAction, getMutableState, getState } from '@etherealengine/hyperflux' import { DirectionalLightComponent, HemisphereLightComponent } from '@etherealengine/spatial' import { MAT4_IDENTITY } from '@etherealengine/spatial/src/common/constants/MathConstants' -import { PostProcessingComponent } from '@etherealengine/spatial/src/renderer/components/PostProcessingComponent' import { VisibleComponent } from '@etherealengine/spatial/src/renderer/components/VisibleComponent' import { getMaterial } from '@etherealengine/spatial/src/renderer/materials/materialFunctions' import { @@ -61,6 +60,7 @@ import { import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent' import { computeTransformMatrix } from '@etherealengine/spatial/src/transform/systems/TransformSystem' +import { PostProcessingComponent } from '@etherealengine/spatial/src/renderer/components/PostProcessingComponent' import { EditorHelperState } from '../services/EditorHelperState' import { EditorState } from '../services/EditorServices' import { SelectionState } from '../services/SelectionServices' diff --git a/packages/editor/src/systems/HighlightSystem.ts b/packages/editor/src/systems/HighlightSystem.ts new file mode 100644 index 0000000000..ed49fc0643 --- /dev/null +++ b/packages/editor/src/systems/HighlightSystem.ts @@ -0,0 +1,53 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { removeComponent, setComponent } from '@etherealengine/ecs' +import { defineSystem } from '@etherealengine/ecs/src/SystemFunctions' +import { AnimationSystemGroup } from '@etherealengine/ecs/src/SystemGroups' +import { HighlightComponent } from '@etherealengine/spatial/src/renderer/components/HighlightComponent' +import { useEffect } from 'react' +import { SelectionState } from '../services/SelectionServices' + +const reactor = () => { + const selectedEntities = SelectionState.useSelectedEntities() + + useEffect(() => { + if (!selectedEntities) return + const lastSelection = selectedEntities[selectedEntities.length - 1] + if (!lastSelection) return + setComponent(lastSelection, HighlightComponent) + return () => { + removeComponent(lastSelection, HighlightComponent) + } + }, [selectedEntities]) + + return null +} + +export const HighlightSystem = defineSystem({ + uuid: 'ee.editor.HighlightSystem', + insert: { with: AnimationSystemGroup }, + reactor +}) diff --git a/packages/engine/src/EngineModule.ts b/packages/engine/src/EngineModule.ts index 71ed525d71..079e26e80a 100644 --- a/packages/engine/src/EngineModule.ts +++ b/packages/engine/src/EngineModule.ts @@ -31,6 +31,7 @@ export * from './avatar/AvatarModule' export * from './interaction/InteractionModule' export * from './interaction/MediaModule' export * from './mocap/MocapModule' +export * from './postprocessing/PopulateEffectRegistry' export * from './recording/RecordingModule' export * from './scene/SceneModule' export * from './visualscript/VisualScriptModule' diff --git a/packages/engine/src/postprocessing/BloomEffect.tsx b/packages/engine/src/postprocessing/BloomEffect.tsx new file mode 100644 index 0000000000..b5b5e3bfa0 --- /dev/null +++ b/packages/engine/src/postprocessing/BloomEffect.tsx @@ -0,0 +1,114 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, BloomEffect, KernelSize } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + BloomEffect: BloomEffect + } +} + +const effectKey = 'BloomEffect' + +export const BloomEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new BloomEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const bloomAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: BloomEffectProcessReactor, + defaultValues: { + isActive: true, + blendFunction: BlendFunction.SCREEN, + kernelSize: KernelSize.LARGE, + luminanceThreshold: 0.9, + luminanceSmoothing: 0.025, + mipmapBlur: false, + intensity: 1.0, + radius: 0.85, + levels: 8 + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + kernelSize: { propertyType: PropertyTypes.KernelSize, name: 'Kernel Size' }, + intensity: { propertyType: PropertyTypes.Number, name: 'Intensity', min: 0, max: 10, step: 0.01 }, + luminanceSmoothing: { + propertyType: PropertyTypes.Number, + name: 'Luminance Smoothing', + min: 0, + max: 1, + step: 0.01 + }, + luminanceThreshold: { + propertyType: PropertyTypes.Number, + name: 'Luminance Threshold', + min: 0, + max: 1, + step: 0.01 + }, + mipmapBlur: { propertyType: PropertyTypes.Boolean, name: 'Mipmap Blur' }, + radius: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: 0, max: 10, step: 0.01 }, + levels: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: 1, max: 10, step: 1 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/BrightnessContrastEffect.tsx b/packages/engine/src/postprocessing/BrightnessContrastEffect.tsx new file mode 100644 index 0000000000..2016e33f54 --- /dev/null +++ b/packages/engine/src/postprocessing/BrightnessContrastEffect.tsx @@ -0,0 +1,91 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, BrightnessContrastEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + BrightnessContrastEffect: BrightnessContrastEffect + } +} + +const effectKey = 'BrightnessContrastEffect' + +export const BrightnessContrastEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new BrightnessContrastEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const brightnessContrastAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: BrightnessContrastEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.SRC, + brightness: 0.0, + contrast: 0.0 + }, + schema: { + brightness: { propertyType: PropertyTypes.Number, name: 'Brightness', min: -1, max: 1, step: 0.01 }, + contrast: { propertyType: PropertyTypes.Number, name: 'Contrast', min: -1, max: 1, step: 0.01 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/ChromaticAberrationEffect.tsx b/packages/engine/src/postprocessing/ChromaticAberrationEffect.tsx new file mode 100644 index 0000000000..a394294e8b --- /dev/null +++ b/packages/engine/src/postprocessing/ChromaticAberrationEffect.tsx @@ -0,0 +1,93 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { ChromaticAberrationEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { Vector2 } from 'three' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + ChromaticAberrationEffect: ChromaticAberrationEffect + } +} + +const effectKey = 'ChromaticAberrationEffect' + +export const ChromaticAberrationEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new ChromaticAberrationEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const chromaticAberrationAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: ChromaticAberrationEffectProcessReactor, + defaultValues: { + isActive: false, + offset: new Vector2(1e-3, 5e-4), + radialModulation: false, + modulationOffset: 0.15 + }, + schema: { + offset: { propertyType: PropertyTypes.Vector2, name: 'Offset' }, + radialModulation: { propertyType: PropertyTypes.Boolean, name: 'Radial Modulation' }, + modulationOffset: { propertyType: PropertyTypes.Number, name: 'Modulation Offset', min: 0, max: 10, step: 0.01 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/ColorAverageEffect.tsx b/packages/engine/src/postprocessing/ColorAverageEffect.tsx new file mode 100644 index 0000000000..84d6239bf0 --- /dev/null +++ b/packages/engine/src/postprocessing/ColorAverageEffect.tsx @@ -0,0 +1,88 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, ColorAverageEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + ColorAverageEffect: ColorAverageEffect + } +} + +const effectKey = 'ColorAverageEffect' + +export const ColorAverageEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new ColorAverageEffect(effectData[effectKey].value.blendFunction) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const colorAverageAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: ColorAverageEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.NORMAL + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/ColorDepthEffect.tsx b/packages/engine/src/postprocessing/ColorDepthEffect.tsx new file mode 100644 index 0000000000..268af45c2f --- /dev/null +++ b/packages/engine/src/postprocessing/ColorDepthEffect.tsx @@ -0,0 +1,90 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, ColorDepthEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + ColorDepthEffect: ColorDepthEffect + } +} + +const effectKey = 'ColorDepthEffect' + +export const ColorDepthEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new ColorDepthEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const colorDepthAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: ColorDepthEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.NORMAL, + bits: 16 + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + bits: { propertyType: PropertyTypes.Number, name: 'Bits', min: 8, max: 256, step: 8 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/DepthOfFieldEffect.tsx b/packages/engine/src/postprocessing/DepthOfFieldEffect.tsx new file mode 100644 index 0000000000..2fa09fd1fc --- /dev/null +++ b/packages/engine/src/postprocessing/DepthOfFieldEffect.tsx @@ -0,0 +1,103 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity, useComponent } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { CameraComponent } from '@etherealengine/spatial/src/camera/components/CameraComponent' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, DepthOfFieldEffect, Resolution } from 'postprocessing' +import React, { useEffect } from 'react' +import { ArrayCamera } from 'three' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + DepthOfFieldEffect: DepthOfFieldEffect + } +} + +const effectKey = 'DepthOfFieldEffect' + +export const DepthOfFieldEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const camera = useComponent(rendererEntity, CameraComponent) + const eff = new DepthOfFieldEffect(camera.value as ArrayCamera, effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const depthOfFieldAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: DepthOfFieldEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.NORMAL, + focusDistance: 0.0, + focalLength: 0.1, + focusRange: 0.1, + bokehScale: 1.0, + resolutionScale: 1.0, + resolutionX: Resolution.AUTO_SIZE, + resolutionY: Resolution.AUTO_SIZE + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + bokehScale: { propertyType: PropertyTypes.Number, name: 'Bokeh Scale', min: -10, max: 10, step: 0.01 }, + focalLength: { propertyType: PropertyTypes.Number, name: 'Focal Length', min: 0, max: 1, step: 0.01 }, + focalRange: { propertyType: PropertyTypes.Number, name: 'Focal Range', min: 0, max: 1, step: 0.01 }, + focusDistance: { propertyType: PropertyTypes.Number, name: 'Focus Distance', min: 0, max: 1, step: 0.01 }, + resolutionScale: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: -10, max: 10, step: 0.01 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/DotScreenEffect.tsx b/packages/engine/src/postprocessing/DotScreenEffect.tsx new file mode 100644 index 0000000000..eb475231c4 --- /dev/null +++ b/packages/engine/src/postprocessing/DotScreenEffect.tsx @@ -0,0 +1,92 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, DotScreenEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + DotScreenEffect: DotScreenEffect + } +} + +const effectKey = 'DotScreenEffect' + +export const DotScreenEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new DotScreenEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const dotScreenAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: DotScreenEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.NORMAL, + angle: Math.PI * 0.5, + scale: 1.0 + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + angle: { propertyType: PropertyTypes.Number, name: 'Angle', min: 0, max: 360, step: 0.1 }, + scale: { propertyType: PropertyTypes.Number, name: 'Scale', min: 0, max: 10, step: 0.1 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/FXAAEffect.tsx b/packages/engine/src/postprocessing/FXAAEffect.tsx new file mode 100644 index 0000000000..f2e47bba76 --- /dev/null +++ b/packages/engine/src/postprocessing/FXAAEffect.tsx @@ -0,0 +1,88 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, FXAAEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + FXAAEffect: FXAAEffect + } +} + +const effectKey = 'FXAAEffect' + +export const FXAAEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new FXAAEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const fxaaAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: FXAAEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.SRC + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/GlitchEffect.tsx b/packages/engine/src/postprocessing/GlitchEffect.tsx new file mode 100644 index 0000000000..dad2ab6ac8 --- /dev/null +++ b/packages/engine/src/postprocessing/GlitchEffect.tsx @@ -0,0 +1,105 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, GlitchEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { Vector2 } from 'three' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + GlitchEffect: GlitchEffect + } +} + +const effectKey = 'GlitchEffect' + +export const GlitchEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new GlitchEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const glitchAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: GlitchEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.NORMAL, + chromaticAberrationOffset: undefined, + delay: new Vector2(1.5, 3.5), + duration: new Vector2(0.6, 1.0), + strength: new Vector2(0.3, 1.0), + perturbationMap: undefined, + dtSize: 64, + columns: 0.05, + ratio: 0.85 + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + chromaticAberrationOffset: { propertyType: PropertyTypes.Vector2, name: 'Chromatic Aberration Offset' }, + delay: { propertyType: PropertyTypes.Vector2, name: 'Delay' }, + duration: { propertyType: PropertyTypes.Vector2, name: 'Duration' }, + strength: { propertyType: PropertyTypes.Vector2, name: 'Strength' }, + perturbationMap: { propertyType: PropertyTypes.Texture, name: 'Perturbation Map' }, + dtSize: { propertyType: PropertyTypes.Number, name: 'DT Size', min: 0, max: 10, step: 0.1 }, + columns: { propertyType: PropertyTypes.Number, name: 'Columns', min: 0, max: 10, step: 0.1 }, + ratio: { propertyType: PropertyTypes.Number, name: 'Ratio', min: 0, max: 10, step: 0.1 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/GridEffect.tsx b/packages/engine/src/postprocessing/GridEffect.tsx new file mode 100644 index 0000000000..07db1d08b2 --- /dev/null +++ b/packages/engine/src/postprocessing/GridEffect.tsx @@ -0,0 +1,92 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, GridEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + GridEffect: GridEffect + } +} + +const effectKey = 'GridEffect' + +export const GridEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new GridEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const gridAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: GridEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.OVERLAY, + scale: 1.0, + lineWidth: 0.0 + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + scale: { propertyType: PropertyTypes.Number, name: 'Scale', min: 0, max: 10, step: 0.1 }, + lineWidth: { propertyType: PropertyTypes.Number, name: 'Line Width', min: 0, max: 10, step: 0.1 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/HueSaturationEffect.tsx b/packages/engine/src/postprocessing/HueSaturationEffect.tsx new file mode 100644 index 0000000000..a0034a432c --- /dev/null +++ b/packages/engine/src/postprocessing/HueSaturationEffect.tsx @@ -0,0 +1,92 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, HueSaturationEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + HueSaturationEffect: HueSaturationEffect + } +} + +const effectKey = 'HueSaturationEffect' + +export const HueSaturationEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new HueSaturationEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const hueSaturationAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: HueSaturationEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.SRC, + hue: 0, + saturation: 0.0 + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + hue: { propertyType: PropertyTypes.Number, name: 'Hue', min: -1, max: 1, step: 0.01 }, + saturation: { propertyType: PropertyTypes.Number, name: 'Saturation', min: -1, max: 1, step: 0.01 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/LUT1DEffect.tsx b/packages/engine/src/postprocessing/LUT1DEffect.tsx new file mode 100644 index 0000000000..a1f496bfb5 --- /dev/null +++ b/packages/engine/src/postprocessing/LUT1DEffect.tsx @@ -0,0 +1,97 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, LUT1DEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { useTexture } from '../assets/functions/resourceLoaderHooks' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + LUT1DEffect: LUT1DEffect + } +} + +const effectKey = 'LUT1DEffect' + +export const LUT1DEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + const [lut1DEffectTexture, lut1DEffectTextureError] = useTexture(effectData[effectKey].value?.lutPath!) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + + if (lut1DEffectTexture) { + const eff = new LUT1DEffect(lut1DEffectTexture, effectData[effectKey].value) + effects[effectKey].set(eff) + } + return () => { + effects[effectKey].set(none) + } + }, [isActive, effectData[effectKey], lut1DEffectTexture]) + + return null +} + +export const lut1DAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: LUT1DEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.SRC, + lutPath: undefined, + lut: undefined + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + lutPath: { propertyType: PropertyTypes.Texture, name: 'LUT' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/LUT3DEffect.tsx b/packages/engine/src/postprocessing/LUT3DEffect.tsx new file mode 100644 index 0000000000..8e4a80b95f --- /dev/null +++ b/packages/engine/src/postprocessing/LUT3DEffect.tsx @@ -0,0 +1,97 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, LUT3DEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { useTexture } from '../assets/functions/resourceLoaderHooks' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + LUT3DEffect: LUT3DEffect + } +} + +const effectKey = 'LUT3DEffect' + +export const LUT3DEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + const [lut3DEffectTexture, lut3DEffectTextureError] = useTexture(effectData[effectKey].value?.lutPath!) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + + if (lut3DEffectTexture) { + const eff = new LUT3DEffect(lut3DEffectTexture, effectData[effectKey].value) + effects[effectKey].set(eff) + } + return () => { + effects[effectKey].set(none) + } + }, [isActive, effectData[effectKey], lut3DEffectTexture]) + + return null +} + +export const lut3DAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: LUT3DEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.SRC, + lutPath: undefined, + lut: undefined + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + lutPath: { propertyType: PropertyTypes.Texture, name: 'LUT' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/LensDistortionEffect.tsx b/packages/engine/src/postprocessing/LensDistortionEffect.tsx new file mode 100644 index 0000000000..ea714ed091 --- /dev/null +++ b/packages/engine/src/postprocessing/LensDistortionEffect.tsx @@ -0,0 +1,95 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { LensDistortionEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { Vector2 } from 'three' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + LensDistortionEffect: LensDistortionEffect + } +} + +const effectKey = 'LensDistortionEffect' + +export const LensDistortionEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new LensDistortionEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const lensDistortionAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: LensDistortionEffectProcessReactor, + defaultValues: { + isActive: false, + distortion: new Vector2(0, 0), + principalPoint: new Vector2(0, 0), + focalLength: new Vector2(1, 1), + skew: 0 + }, + schema: { + distortion: { propertyType: PropertyTypes.Vector2, name: 'Distortion' }, + principalPoint: { propertyType: PropertyTypes.Vector2, name: 'Principal Point' }, + focalLength: { propertyType: PropertyTypes.Vector2, name: 'Focal Length' }, + skew: { propertyType: PropertyTypes.Number, name: 'Skew', min: 0, max: 10, step: 0.05 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/LinearTosRGBEffect.tsx b/packages/engine/src/postprocessing/LinearTosRGBEffect.tsx new file mode 100644 index 0000000000..f483556140 --- /dev/null +++ b/packages/engine/src/postprocessing/LinearTosRGBEffect.tsx @@ -0,0 +1,85 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { LinearTosRGBEffect } from '@etherealengine/spatial/src/renderer/effects/LinearTosRGBEffect' +import React, { useEffect } from 'react' + +declare module 'postprocessing' { + interface EffectComposer { + LinearTosRGBEffect: LinearTosRGBEffect + } +} + +const effectKey = 'LinearTosRGBEffect' + +export const LinearTosRGBEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new LinearTosRGBEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const linearTosRGBAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: LinearTosRGBEffectProcessReactor, + defaultValues: { + isActive: false, + skew: 0 + }, + schema: {} + } + }) +} diff --git a/packages/engine/src/postprocessing/MotionBlurEffect.tsx b/packages/engine/src/postprocessing/MotionBlurEffect.tsx new file mode 100644 index 0000000000..664d78efb2 --- /dev/null +++ b/packages/engine/src/postprocessing/MotionBlurEffect.tsx @@ -0,0 +1,98 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity, useComponent } from '@etherealengine/ecs' +import { getMutableState, getState, none, useHookstate } from '@etherealengine/hyperflux' +import { CameraComponent } from '@etherealengine/spatial/src/camera/components/CameraComponent' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import React, { useEffect } from 'react' +import { MotionBlurEffect, VelocityDepthNormalPass } from 'realism-effects' +import { Scene } from 'three' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + MotionBlurEffect: MotionBlurEffect + } +} + +const effectKey = 'MotionBlurEffect' + +export const MotionBlurEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + const camera = useComponent(rendererEntity, CameraComponent) + const scene = useHookstate(() => new Scene()) + const velocityDepthNormalPass = useHookstate(new VelocityDepthNormalPass(scene, camera)) + const useVelocityDepthNormalPass = useHookstate(false) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new MotionBlurEffect(velocityDepthNormalPass, effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const motionBlurAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: MotionBlurEffectProcessReactor, + defaultValues: { + isActive: false, + intensity: 1, + jitter: 1, + samples: 16 + }, + schema: { + intensity: { propertyType: PropertyTypes.Number, name: 'Intensity', min: 0, max: 10, step: 0.01 }, + jitter: { propertyType: PropertyTypes.Number, name: 'Jitter', min: 0, max: 10, step: 0.01 }, + samples: { propertyType: PropertyTypes.Number, name: 'Samples', min: 1, max: 64, step: 1 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/NoiseEffect.tsx b/packages/engine/src/postprocessing/NoiseEffect.tsx new file mode 100644 index 0000000000..9f7686f71f --- /dev/null +++ b/packages/engine/src/postprocessing/NoiseEffect.tsx @@ -0,0 +1,90 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, NoiseEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + NoiseEffect: NoiseEffect + } +} + +const effectKey = 'NoiseEffect' + +export const NoiseEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new NoiseEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const noiseAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: NoiseEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.SCREEN, + premultiply: false + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + premultiply: { propertyType: PropertyTypes.Boolean, name: 'Premultiply' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/OutlineEffect.tsx b/packages/engine/src/postprocessing/OutlineEffect.tsx new file mode 100644 index 0000000000..38bf738d18 --- /dev/null +++ b/packages/engine/src/postprocessing/OutlineEffect.tsx @@ -0,0 +1,110 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, KernelSize, OutlineEffect, Resolution } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +const effectKey = 'OutlineEffect' + +export const OutlineEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new OutlineEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const outlineAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: OutlineEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.SCREEN, + patternScale: 1.0, + edgeStrength: 1.0, + pulseSpeed: 0.0, + visibleEdgeColor: 0xffffff, + hiddenEdgeColor: 0x22090a, + multisampling: 0, + resolutionScale: 0.5, + resolutionX: Resolution.AUTO_SIZE, + resolutionY: Resolution.AUTO_SIZE, + width: Resolution.AUTO_SIZE, + height: 480, + kernelSize: KernelSize.VERY_SMALL, + blur: false, + xRay: true + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + patternScale: { propertyType: PropertyTypes.Number, name: 'Pattern Scale', min: 0, max: 10, step: 0.01 }, + edgeStrength: { propertyType: PropertyTypes.Number, name: 'Edge Strength', min: 0, max: 10, step: 0.01 }, + pulseSpeed: { propertyType: PropertyTypes.Number, name: 'Pulse Speed', min: 0, max: 10, step: 0.01 }, + visibleEdgeColor: { propertyType: PropertyTypes.Color, name: 'Visible Edge Color' }, + hiddenEdgeColor: { propertyType: PropertyTypes.Color, name: 'Hidden Edge Color' }, + multisampling: { propertyType: PropertyTypes.Number, name: 'Multisampling', min: 0, max: 10, step: 0.01 }, + resolutionScale: { propertyType: PropertyTypes.Number, name: 'ResolutionScale', min: 0, max: 10, step: 0.01 }, + blur: { propertyType: PropertyTypes.Boolean, name: 'Blur' }, + xRay: { propertyType: PropertyTypes.Boolean, name: 'xRay' }, + kernelSize: { propertyType: PropertyTypes.KernelSize, name: 'KernelSize' } + //resolutionX: Resolution.AUTO_SIZE, + //resolutionY: Resolution.AUTO_SIZE, + //width: Resolution.AUTO_SIZE, + //height: 480, + } + } + }) +} diff --git a/packages/engine/src/postprocessing/PixelationEffect.tsx b/packages/engine/src/postprocessing/PixelationEffect.tsx new file mode 100644 index 0000000000..ea5bfe504d --- /dev/null +++ b/packages/engine/src/postprocessing/PixelationEffect.tsx @@ -0,0 +1,88 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { PixelationEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + PixelationEffect: PixelationEffect + } +} + +const effectKey = 'PixelationEffect' + +export const PixelationEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new PixelationEffect(effectData[effectKey].granularity.value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const pixelationAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: PixelationEffectProcessReactor, + defaultValues: { + isActive: false, + granularity: 30 + }, + schema: { + granularity: { propertyType: PropertyTypes.Number, name: 'Granularity', min: 0, max: 1000, step: 1 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/PopulateEffectRegistry.tsx b/packages/engine/src/postprocessing/PopulateEffectRegistry.tsx new file mode 100644 index 0000000000..feb31869c8 --- /dev/null +++ b/packages/engine/src/postprocessing/PopulateEffectRegistry.tsx @@ -0,0 +1,98 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { PresentationSystemGroup } from '@etherealengine/ecs' +import { defineSystem } from '@etherealengine/ecs/src/SystemFunctions' +import { useEffect } from 'react' +import { bloomAddToEffectRegistry } from './BloomEffect' +import { brightnessContrastAddToEffectRegistry } from './BrightnessContrastEffect' +import { chromaticAberrationAddToEffectRegistry } from './ChromaticAberrationEffect' +import { colorAverageAddToEffectRegistry } from './ColorAverageEffect' +import { colorDepthAddToEffectRegistry } from './ColorDepthEffect' +import { depthOfFieldAddToEffectRegistry } from './DepthOfFieldEffect' +import { dotScreenAddToEffectRegistry } from './DotScreenEffect' +import { fxaaAddToEffectRegistry } from './FXAAEffect' +import { glitchAddToEffectRegistry } from './GlitchEffect' +import { gridAddToEffectRegistry } from './GridEffect' +import { hueSaturationAddToEffectRegistry } from './HueSaturationEffect' +import { lut1DAddToEffectRegistry } from './LUT1DEffect' +import { lut3DAddToEffectRegistry } from './LUT3DEffect' +import { lensDistortionAddToEffectRegistry } from './LensDistortionEffect' +import { linearTosRGBAddToEffectRegistry } from './LinearTosRGBEffect' +import { motionBlurAddToEffectRegistry } from './MotionBlurEffect' +import { noiseAddToEffectRegistry } from './NoiseEffect' +import { pixelationAddToEffectRegistry } from './PixelationEffect' +import { ssaoAddToEffectRegistry } from './SSAOEffect' +import { ssgiAddToEffectRegistry } from './SSGIEffect' +import { ssrAddToEffectRegistry } from './SSREffect' +import { scanlineAddToEffectRegistry } from './ScanlineEffect' +import { shockWaveAddToEffectRegistry } from './ShockWaveEffect' +import { traaAddToEffectRegistry } from './TRAAEffect' +import { textureAddToEffectRegistry } from './TextureEffect' +import { tiltShiftAddToEffectRegistry } from './TiltShiftEffect' +import { toneMappingAddToEffectRegistry } from './ToneMappingEffect' +import { vignetteAddToEffectRegistry } from './VignetteEffect' + +export const populateEffectRegistry = () => { + // registers the effects + bloomAddToEffectRegistry() + brightnessContrastAddToEffectRegistry() + chromaticAberrationAddToEffectRegistry() + colorAverageAddToEffectRegistry() + colorDepthAddToEffectRegistry() + depthOfFieldAddToEffectRegistry() + dotScreenAddToEffectRegistry() + fxaaAddToEffectRegistry() + glitchAddToEffectRegistry() + //GodRaysEffect + gridAddToEffectRegistry() + hueSaturationAddToEffectRegistry() + lensDistortionAddToEffectRegistry() + linearTosRGBAddToEffectRegistry() + lut1DAddToEffectRegistry() + lut3DAddToEffectRegistry() + motionBlurAddToEffectRegistry() + noiseAddToEffectRegistry() + pixelationAddToEffectRegistry() + scanlineAddToEffectRegistry() + shockWaveAddToEffectRegistry() + ssaoAddToEffectRegistry() + ssrAddToEffectRegistry() + ssgiAddToEffectRegistry() + textureAddToEffectRegistry() + tiltShiftAddToEffectRegistry() + toneMappingAddToEffectRegistry() + traaAddToEffectRegistry() + vignetteAddToEffectRegistry() +} + +export const PostProcessingRegisterSystem = defineSystem({ + uuid: 'ee.engine.PostProcessingRegisterSystem', + insert: { before: PresentationSystemGroup }, + reactor: () => { + useEffect(() => populateEffectRegistry(), []) + return null + } +}) diff --git a/packages/engine/src/postprocessing/PostProcessingRegister.tsx b/packages/engine/src/postprocessing/PostProcessingRegister.tsx new file mode 100644 index 0000000000..cabe696f3c --- /dev/null +++ b/packages/engine/src/postprocessing/PostProcessingRegister.tsx @@ -0,0 +1,40 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +export enum PropertyTypes { + BlendFunction, + Number, + Boolean, + Color, + ColorSpace, + KernelSize, + SMAAPreset, + EdgeDetectionMode, + PredicationMode, + Texture, + Vector2, + Vector3, + VignetteTechnique +} diff --git a/packages/engine/src/postprocessing/SMAAEffect.tsx b/packages/engine/src/postprocessing/SMAAEffect.tsx new file mode 100644 index 0000000000..a89c39e99a --- /dev/null +++ b/packages/engine/src/postprocessing/SMAAEffect.tsx @@ -0,0 +1,84 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { EdgeDetectionMode, PredicationMode, SMAAEffect, SMAAPreset } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +const effectKey = 'SMAAEffect' + +export const SMAAEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new SMAAEffect(effectData[effectKey].granularity.value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const smaaAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: SMAAEffectProcessReactor, + defaultValues: { + isActive: false, + preset: SMAAPreset.MEDIUM, + edgeDetectionMode: EdgeDetectionMode.COLOR, + predicationMode: PredicationMode.DISABLED + }, + schema: { + preset: { propertyType: PropertyTypes.SMAAPreset, name: 'Preset' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/SSAOEffect.tsx b/packages/engine/src/postprocessing/SSAOEffect.tsx new file mode 100644 index 0000000000..7856026c98 --- /dev/null +++ b/packages/engine/src/postprocessing/SSAOEffect.tsx @@ -0,0 +1,116 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity, useComponent } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { CameraComponent } from '@etherealengine/spatial/src/camera/components/CameraComponent' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, Resolution, SSAOEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { ArrayCamera } from 'three' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + SSAOEffect: SSAOEffect + } +} + +const effectKey = 'SSAOEffect' + +export const SSAOEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const camera = useComponent(rendererEntity, CameraComponent) + const eff = new SSAOEffect(camera.value as ArrayCamera, effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const ssaoAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: SSAOEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.MULTIPLY, + distanceScaling: true, + depthAwareUpsampling: true, + normalDepthBuffer: undefined, + samples: 9, + rings: 7, + // worldDistanceThreshold: 0.97, + // worldDistanceFalloff: 0.03, + // worldProximityThreshold: 0.0005, + // worldProximityFalloff: 0.001, + distanceThreshold: 0.97, // Render up to a distance of ~20 world units + distanceFalloff: 0.03, // with an additional ~2.5 units of falloff. + rangeThreshold: 0.0005, + rangeFalloff: 0.001, + minRadiusScale: 0.1, + luminanceInfluence: 0.7, + bias: 0.025, + radius: 0.1825, + intensity: 1.0, + fade: 0.01, + color: undefined, + resolutionScale: 1.0, + resolutionX: Resolution.AUTO_SIZE, + resolutionY: Resolution.AUTO_SIZE, + width: Resolution.AUTO_SIZE, + height: Resolution.AUTO_SIZE + }, + schema: { + preset: { propertyType: PropertyTypes.SMAAPreset, name: 'Preset' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/SSGIEffect.tsx b/packages/engine/src/postprocessing/SSGIEffect.tsx new file mode 100644 index 0000000000..47f9a305d5 --- /dev/null +++ b/packages/engine/src/postprocessing/SSGIEffect.tsx @@ -0,0 +1,131 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import React, { useEffect } from 'react' +import { SSGIEffect } from 'realism-effects' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + SSGIEffect: SSGIEffect + } +} + +const effectKey = 'SSGIEffect' + +export const SSGIEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + + const eff = new SSGIEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const ssgiAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: SSGIEffectProcessReactor, + defaultValues: { + isActive: false, + distance: 10, + thickness: 10, + denoiseIterations: 1, + denoiseKernel: 2, + denoiseDiffuse: 10, + denoiseSpecular: 10, + radius: 3, + phi: 0.5, + lumaPhi: 5, + depthPhi: 2, + normalPhi: 50, + roughnessPhi: 50, + specularPhi: 50, + envBlur: 0.5, + importanceSampling: true, + steps: 20, + refineSteps: 5, + resolutionScale: 1, + missedRays: false + }, + schema: { + distance: { propertyType: PropertyTypes.Number, name: 'Distance', min: 0.001, max: 10, step: 0.01 }, + thickness: { propertyType: PropertyTypes.Number, name: 'Thickness', min: 0, max: 5, step: 0.01 }, + denoiseIterations: { propertyType: PropertyTypes.Number, name: 'Denoise Iterations', min: 0, max: 5, step: 1 }, + denoiseKernel: { propertyType: PropertyTypes.Number, name: 'Denoise Kernel', min: 1, max: 5, step: 1 }, + denoiseDiffuse: { propertyType: PropertyTypes.Number, name: 'Denoise Diffuse', min: 0, max: 50, step: 0.01 }, + denoiseSpecular: { propertyType: PropertyTypes.Number, name: 'Denoise Specular', min: 0, max: 50, step: 0.01 }, + radius: { propertyType: PropertyTypes.Number, name: 'Radius', min: 0, max: 50, step: 0.01 }, + phi: { propertyType: PropertyTypes.Number, name: 'Phi', min: 0, max: 50, step: 0.01 }, + lumaPhi: { propertyType: PropertyTypes.Number, name: 'Denoise Specular', min: 0, max: 50, step: 0.01 }, + depthPhi: { propertyType: PropertyTypes.Number, name: 'luminosity Phi', min: 0, max: 15, step: 0.001 }, + normalPhi: { propertyType: PropertyTypes.Number, name: 'Normal Phi', min: 0, max: 50, step: 0.001 }, + roughnessPhi: { propertyType: PropertyTypes.Number, name: 'Roughness Phi', min: 0, max: 100, step: 0.001 }, + specularPhi: { propertyType: PropertyTypes.Number, name: 'Specular Phi', min: 0, max: 50, step: 0.01 }, + envBlur: { propertyType: PropertyTypes.Number, name: 'Environment Blur', min: 0, max: 1, step: 0.01 }, + importanceSampling: { propertyType: PropertyTypes.Boolean, name: 'Importance Sampling' }, + steps: { propertyType: PropertyTypes.Number, name: 'Steps', min: 0, max: 256, step: 1 }, + refineSteps: { propertyType: PropertyTypes.Number, name: 'Refine Steps', min: 0, max: 16, step: 1 }, + resolutionScale: { + propertyType: PropertyTypes.Number, + name: 'Resolution Scale', + min: 0.25, + max: 1, + step: 0.25 + }, + missedRays: { propertyType: PropertyTypes.Boolean, name: 'Missed Rays' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/SSREffect.tsx b/packages/engine/src/postprocessing/SSREffect.tsx new file mode 100644 index 0000000000..22912d2709 --- /dev/null +++ b/packages/engine/src/postprocessing/SSREffect.tsx @@ -0,0 +1,141 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity, useComponent } from '@etherealengine/ecs' +import { getMutableState, getState, none, useHookstate } from '@etherealengine/hyperflux' +import { CameraComponent } from '@etherealengine/spatial/src/camera/components/CameraComponent' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { EffectComposer } from 'postprocessing' +import React, { useEffect } from 'react' +import { SSREffect, VelocityDepthNormalPass } from 'realism-effects' +import { Scene } from 'three' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + SSREffect: SSREffect + } +} + +const effectKey = 'SSREffect' + +export const SSREffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects + composer: EffectComposer + scene: Scene +}) => { + const { isActive, rendererEntity, effectData, effects, composer, scene } = props + const effectState = getState(PostProcessingEffectState) + + const camera = useComponent(rendererEntity, CameraComponent) + const velocityDepthNormalPass = useHookstate(new VelocityDepthNormalPass(scene, camera)) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new SSREffect(composer, scene, camera.value, { + ...effectData[effectKey].value, + velocityDepthNormalPass + }) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const ssrAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: SSREffectProcessReactor, + defaultValues: { + isActive: false, + distance: 10, + thickness: 10, + denoiseIterations: 1, + denoiseKernel: 2, + denoiseDiffuse: 10, + denoiseSpecular: 10, + radius: 3, + phi: 0.5, + lumaPhi: 5, + depthPhi: 2, + normalPhi: 50, + roughnessPhi: 50, + specularPhi: 50, + envBlur: 0.5, + importanceSampling: true, + steps: 20, + refineSteps: 5, + resolutionScale: 1, + missedRays: false + }, + schema: { + distance: { propertyType: PropertyTypes.Number, name: 'Distance', min: 0.001, max: 10, step: 0.01 }, + thickness: { propertyType: PropertyTypes.Number, name: 'Thickness', min: 0, max: 5, step: 0.01 }, + denoiseIterations: { propertyType: PropertyTypes.Number, name: 'Denoise Iterations', min: 0, max: 5, step: 1 }, + denoiseKernel: { propertyType: PropertyTypes.Number, name: 'Denoise Kernel', min: 1, max: 5, step: 1 }, + denoiseDiffuse: { propertyType: PropertyTypes.Number, name: 'Denoise Diffuse', min: 0, max: 50, step: 0.01 }, + denoiseSpecular: { propertyType: PropertyTypes.Number, name: 'Denoise Specular', min: 0, max: 50, step: 0.01 }, + radius: { propertyType: PropertyTypes.Number, name: 'Radius', min: 0, max: 50, step: 0.01 }, + phi: { propertyType: PropertyTypes.Number, name: 'Phi', min: 0, max: 50, step: 0.01 }, + lumaPhi: { propertyType: PropertyTypes.Number, name: 'Denoise Specular', min: 0, max: 50, step: 0.01 }, + depthPhi: { propertyType: PropertyTypes.Number, name: 'luminosity Phi', min: 0, max: 15, step: 0.001 }, + normalPhi: { propertyType: PropertyTypes.Number, name: 'Normal Phi', min: 0, max: 50, step: 0.001 }, + roughnessPhi: { propertyType: PropertyTypes.Number, name: 'Roughness Phi', min: 0, max: 100, step: 0.001 }, + specularPhi: { propertyType: PropertyTypes.Number, name: 'Specular Phi', min: 0, max: 50, step: 0.01 }, + envBlur: { propertyType: PropertyTypes.Number, name: 'Environment Blur', min: 0, max: 1, step: 0.01 }, + importanceSampling: { propertyType: PropertyTypes.Boolean, name: 'Importance Sampling' }, + steps: { propertyType: PropertyTypes.Number, name: 'Steps', min: 0, max: 256, step: 1 }, + refineSteps: { propertyType: PropertyTypes.Number, name: 'Refine Steps', min: 0, max: 16, step: 1 }, + resolutionScale: { + propertyType: PropertyTypes.Number, + name: 'Resolution Scale', + min: 0.25, + max: 1, + step: 0.25 + }, + missedRays: { propertyType: PropertyTypes.Boolean, name: 'Missed Rays' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/ScanlineEffect.tsx b/packages/engine/src/postprocessing/ScanlineEffect.tsx new file mode 100644 index 0000000000..e9dde840be --- /dev/null +++ b/packages/engine/src/postprocessing/ScanlineEffect.tsx @@ -0,0 +1,92 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, ScanlineEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + ScanlineEffect: ScanlineEffect + } +} + +const effectKey = 'ScanlineEffect' + +export const ScanlineEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new ScanlineEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const scanlineAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: ScanlineEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.OVERLAY, + density: 1.25, + scrollSpeed: 0.0 + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + density: { propertyType: PropertyTypes.Number, name: 'Density', min: 0, max: 10, step: 0.05 }, + scrollSpeed: { propertyType: PropertyTypes.Number, name: 'Scroll Speed', min: 0, max: 10, step: 0.05 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/ShockWaveEffect.tsx b/packages/engine/src/postprocessing/ShockWaveEffect.tsx new file mode 100644 index 0000000000..f62d3a9cd8 --- /dev/null +++ b/packages/engine/src/postprocessing/ShockWaveEffect.tsx @@ -0,0 +1,97 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { ShockWaveEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { Vector3 } from 'three' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + ShockWaveEffect: ShockWaveEffect + } +} + +const effectKey = 'ShockWaveEffect' + +export const ShockWaveEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new ShockWaveEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} + +export const shockWaveAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: ShockWaveEffectProcessReactor, + defaultValues: { + isActive: false, + position: new Vector3(0, 0, 0), + speed: 2.0, + maxRadius: 1.0, + waveSize: 0.2, + amplitude: 0.05 + }, + schema: { + position: { propertyType: PropertyTypes.Vector3, name: 'Position' }, + speed: { propertyType: PropertyTypes.Number, name: 'Speed', min: 0, max: 10, step: 0.05 }, + maxRadius: { propertyType: PropertyTypes.Number, name: 'Max Radius', min: 0, max: 10, step: 0.05 }, + waveSize: { propertyType: PropertyTypes.Number, name: 'Wave Size', min: 0, max: 10, step: 0.05 }, + amplitude: { propertyType: PropertyTypes.Number, name: 'Amplitude', min: 0, max: 10, step: 0.05 } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/TRAAEffect.tsx b/packages/engine/src/postprocessing/TRAAEffect.tsx new file mode 100644 index 0000000000..843e5ec297 --- /dev/null +++ b/packages/engine/src/postprocessing/TRAAEffect.tsx @@ -0,0 +1,112 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity, useComponent } from '@etherealengine/ecs' +import { getMutableState, getState, none, useHookstate } from '@etherealengine/hyperflux' +import { CameraComponent } from '@etherealengine/spatial/src/camera/components/CameraComponent' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import React, { useEffect } from 'react' +import { TRAAEffect, VelocityDepthNormalPass } from 'realism-effects' +import { Scene } from 'three' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + TRAAEffect: TRAAEffect + } +} + +const effectKey = 'TRAAEffect' + +export const TRAAEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects + scene: Scene +}) => { + const { isActive, rendererEntity, effectData, effects, scene } = props + const effectState = getState(PostProcessingEffectState) + const camera = useComponent(rendererEntity, CameraComponent) + const velocityDepthNormalPass = useHookstate(new VelocityDepthNormalPass(scene, camera)) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + + // todo support more than 1 texture + const textureCount = 1 + + const eff = new TRAAEffect(scene, camera.value, velocityDepthNormalPass, textureCount, effectData[effectKey].value) + effects[effectKey].set(eff) + + return () => { + effects[effectKey].set(none) + } + }, [isActive, effectData[effectKey], scene, velocityDepthNormalPass]) + + return null +} + +export const traaAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: TRAAEffectProcessReactor, + defaultValues: { + isActive: false, + blend: 0.8, + constantBlend: true, + dilation: true, + blockySampling: false, + logTransform: false, // ! TODO: check if can use logTransform withoutt artifacts + depthDistance: 10, + worldDistance: 5, + neighborhoodClamping: true + }, + schema: { + blend: { propertyType: PropertyTypes.Number, name: 'Blend', min: 0, max: 1, step: 0.001 }, + constantBlend: { propertyType: PropertyTypes.Boolean, name: 'Constant Blend' }, + dilation: { propertyType: PropertyTypes.Boolean, name: 'Dilation' }, + blockySampling: { propertyType: PropertyTypes.Boolean, name: 'Blocky Sampling' }, + logTransform: { propertyType: PropertyTypes.Boolean, name: 'Log Transform' }, + depthDistance: { propertyType: PropertyTypes.Number, name: 'Depth Distance', min: 0.01, max: 100, step: 0.01 }, + worldDistance: { propertyType: PropertyTypes.Number, name: 'World Distance', min: 0.01, max: 100, step: 0.01 }, + neighborhoodClamping: { propertyType: PropertyTypes.Boolean, name: 'Neighborhood Clamping' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/TextureEffect.tsx b/packages/engine/src/postprocessing/TextureEffect.tsx new file mode 100644 index 0000000000..3ea1c24a99 --- /dev/null +++ b/packages/engine/src/postprocessing/TextureEffect.tsx @@ -0,0 +1,100 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { NO_PROXY, getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, TextureEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { useTexture } from '../assets/functions/resourceLoaderHooks' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + TextureEffect: TextureEffect + } +} + +const effectKey = 'TextureEffect' + +export const TextureEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + const [textureEffectTexture, textureEffectTextureError] = useTexture(effectData[effectKey].value?.texturePath!) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + if (textureEffectTexture) { + let data = effectData[effectKey].get(NO_PROXY) + data.texture = textureEffectTexture + const eff = new TextureEffect(data) + effects[effectKey].set(eff) + } + return () => { + effects[effectKey].set(none) + } + }, [isActive, effectData[effectKey], textureEffectTexture]) + + return null +} + +export const textureAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: TextureEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.NORMAL, + texturePath: undefined, + texture: undefined, + aspectCorrection: false + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + texturePath: { propertyType: PropertyTypes.Texture, name: 'Texture' }, + aspectCorrection: { propertyType: PropertyTypes.Boolean, name: 'Aspect Correction' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/TiltShiftEffect.tsx b/packages/engine/src/postprocessing/TiltShiftEffect.tsx new file mode 100644 index 0000000000..835418bad4 --- /dev/null +++ b/packages/engine/src/postprocessing/TiltShiftEffect.tsx @@ -0,0 +1,106 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, KernelSize, Resolution, TiltShiftEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + TiltShiftEffect: TiltShiftEffect + } +} + +const effectKey = 'TiltShiftEffect' + +export const TiltShiftEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + + const eff = new TiltShiftEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + + return () => { + effects[effectKey].set(none) + } + }, [isActive, effectData[effectKey]]) + + return null +} + +export const tiltShiftAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: TiltShiftEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.NORMAL, + offset: 0.0, + rotation: 0.0, + focusArea: 0.4, + feather: 0.3, + kernelSize: KernelSize.MEDIUM, + resolutionScale: 0.5, + resolutionX: Resolution.AUTO_SIZE, + resolutionY: Resolution.AUTO_SIZE + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + offset: { propertyType: PropertyTypes.Number, name: 'Offset', min: 0, max: 10, step: 0.1 }, + rotation: { propertyType: PropertyTypes.Number, name: 'Rotation', min: 0, max: 360, step: 0.1 }, + focusArea: { propertyType: PropertyTypes.Number, name: 'Focus Area', min: 0, max: 10, step: 0.1 }, + feather: { propertyType: PropertyTypes.Number, name: 'Feather', min: 0, max: 10, step: 0.1 }, + kernelSize: { propertyType: PropertyTypes.KernelSize, name: 'KernelSize' }, + resolutionScale: { propertyType: PropertyTypes.Number, name: 'Resolution Scale', min: 0, max: 10, step: 0.1 } + //resolutionX: Resolution.AUTO_SIZE, + //resolutionY: Resolution.AUTO_SIZE + } + } + }) +} diff --git a/packages/engine/src/postprocessing/ToneMappingEffect.tsx b/packages/engine/src/postprocessing/ToneMappingEffect.tsx new file mode 100644 index 0000000000..4fd6560250 --- /dev/null +++ b/packages/engine/src/postprocessing/ToneMappingEffect.tsx @@ -0,0 +1,113 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, ToneMappingEffect, ToneMappingMode } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + ToneMappingEffect: ToneMappingEffect + } +} + +const effectKey = 'ToneMappingEffect' + +export const ToneMappingEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + + const eff = new ToneMappingEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + + return () => { + effects[effectKey].set(none) + } + }, [isActive, effectData[effectKey]]) + + return null +} + +export const toneMappingAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: ToneMappingEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.SRC, + adaptive: false, + mode: ToneMappingMode.AGX, + resolution: 256, + maxLuminance: 4.0, + whitePoint: 4.0, + middleGrey: 0.6, + minLuminance: 0.01, + averageLuminance: 1.0, + adaptationRate: 1.0 + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + adaptive: { propertyType: PropertyTypes.Boolean, name: 'Adaptive' }, + adaptationRate: { propertyType: PropertyTypes.Number, name: 'Adaptation Rate', min: -1, max: 1, step: 0.01 }, + averageLuminance: { + propertyType: PropertyTypes.Number, + name: 'Average Luminance', + min: -1, + max: 1, + step: 0.01 + }, + maxLuminance: { propertyType: PropertyTypes.Number, name: 'Max Luminance', min: -1, max: 1, step: 0.01 }, + middleGrey: { propertyType: PropertyTypes.Number, name: 'Middle Grey', min: -1, max: 1, step: 0.01 }, + resolution: { propertyType: PropertyTypes.Number, name: 'Resolution' }, + whitePoint: { propertyType: PropertyTypes.Number, name: 'Resolution' }, + minLuminance: { propertyType: PropertyTypes.Number, name: 'Resolution' } + } + } + }) +} diff --git a/packages/engine/src/postprocessing/VignetteEffect.tsx b/packages/engine/src/postprocessing/VignetteEffect.tsx new file mode 100644 index 0000000000..8609aae834 --- /dev/null +++ b/packages/engine/src/postprocessing/VignetteEffect.tsx @@ -0,0 +1,101 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ + +import { Entity } from '@etherealengine/ecs' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { + EffectReactorProps, + PostProcessingEffectState +} from '@etherealengine/spatial/src/renderer/effects/EffectRegistry' +import { BlendFunction, VignetteEffect, VignetteTechnique } from 'postprocessing' +import React, { useEffect } from 'react' +import { PropertyTypes } from './PostProcessingRegister' + +declare module 'postprocessing' { + interface EffectComposer { + VignetteEffect: VignetteEffect + } +} + +const effectKey = 'VignetteEffect' + +export const VignetteEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + + // todo support more than 1 texture + const textureCount = 1 + + const eff = new VignetteEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + + return () => { + effects[effectKey].set(none) + } + }, [isActive, effectData[effectKey]]) + + return null +} + +export const vignetteAddToEffectRegistry = () => { + // registers the effect + + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: VignetteEffectProcessReactor, + defaultValues: { + isActive: false, + blendFunction: BlendFunction.NORMAL, + technique: VignetteTechnique.DEFAULT, + eskil: false, + offset: 0.5, + darkness: 0.5 + }, + schema: { + blendFunction: { propertyType: PropertyTypes.BlendFunction, name: 'Blend Function' }, + technique: { propertyType: PropertyTypes.VignetteTechnique, name: 'Technique' }, + eskil: { propertyType: PropertyTypes.Boolean, name: 'Eskil' }, + offset: { propertyType: PropertyTypes.Number, name: 'Offset', min: 0, max: 10, step: 0.1 }, + darkness: { propertyType: PropertyTypes.Number, name: 'Darkness', min: 0, max: 10, step: 0.1 } + } + } + }) +} diff --git a/packages/spatial/src/renderer/passes/CustomNormalPass.js b/packages/engine/src/postprocessing/passes/CustomNormalPass.js similarity index 98% rename from packages/spatial/src/renderer/passes/CustomNormalPass.js rename to packages/engine/src/postprocessing/passes/CustomNormalPass.js index 58a2fc86e8..424ef3bc85 100644 --- a/packages/spatial/src/renderer/passes/CustomNormalPass.js +++ b/packages/engine/src/postprocessing/passes/CustomNormalPass.js @@ -27,7 +27,7 @@ Ethereal Engine. All Rights Reserved. import { Color, MeshNormalMaterial, NearestFilter, WebGLRenderTarget } from "three"; import { Resolution, RenderPass, Pass } from "postprocessing"; -import { ObjectLayers } from "../constants/ObjectLayers"; +import { ObjectLayers } from "@etherealengine/spatial/src/renderer/constants/ObjectLayers"; /** * A pass that renders the normals of a given scene. */ diff --git a/packages/spatial/package.json b/packages/spatial/package.json index d2b06a44ca..fc888776a6 100755 --- a/packages/spatial/package.json +++ b/packages/spatial/package.json @@ -24,14 +24,13 @@ "bitecs": "0.3.40", "lodash": "4.17.21", "noisejs": "2.1.0", - "postprocessing": "6.34.1", "react": "18.2.0", "react-dom": "18.2.0", - "realism-effects": "1.1.2", "three": "0.158.0", "three-mesh-bvh": "^0.7.1", "ts-matches": "5.3.0", - "web-worker": "1.2.0" + "web-worker": "1.2.0", + "postprocessing": "6.34.1" }, "devDependencies": { "@types/mocha": "10.0.1", diff --git a/packages/spatial/src/renderer/components/PostProcessingComponent.test.tsx b/packages/spatial/src/renderer/components/PostProcessingComponent.test.tsx new file mode 100644 index 0000000000..65d3dc1e19 --- /dev/null +++ b/packages/spatial/src/renderer/components/PostProcessingComponent.test.tsx @@ -0,0 +1,201 @@ +// /* +// CPAL-1.0 License + +// The contents of this file are subject to the Common Public Attribution License +// Version 1.0. (the "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +// The License is based on the Mozilla Public License Version 1.1, but Sections 14 +// and 15 have been added to cover use of software over a computer network and +// provide for limited attribution for the Original Developer. In addition, +// Exhibit A has been modified to be consistent with Exhibit B. + +// Software distributed under the License is distributed on an "AS IS" basis, +// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +// specific language governing rights and limitations under the License. + +// The Original Code is Ethereal Engine. + +// The Original Developer is the Initial Developer. The Initial Developer of the +// Original Code is the Ethereal Engine team. + +// All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +// Ethereal Engine. All Rights Reserved. +// */ + +import assert from 'assert' +import { MathUtils } from 'three' + +import { + Entity, + EntityUUID, + UUIDComponent, + getComponent, + getMutableComponent, + hasComponent, + setComponent +} from '@etherealengine/ecs' +import { destroyEngine } from '@etherealengine/ecs/src/Engine' +import { createEntity, removeEntity } from '@etherealengine/ecs/src/EntityFunctions' +import { getMutableState, getState, none } from '@etherealengine/hyperflux' +import { CameraComponent } from '@etherealengine/spatial/src/camera/components/CameraComponent' +import { createEngine } from '@etherealengine/spatial/src/initializeEngine' +import { RendererComponent } from '@etherealengine/spatial/src/renderer/WebGLRendererSystem' +import { SceneComponent } from '@etherealengine/spatial/src/renderer/components/SceneComponents' +import { EntityTreeComponent } from '@etherealengine/spatial/src/transform/components/EntityTree' +import { act, render } from '@testing-library/react' +import { BlendFunction, EffectComposer, NoiseEffect } from 'postprocessing' +import React, { useEffect } from 'react' +import { RendererState } from '../RendererState' +import { EffectReactorProps, PostProcessingEffectState } from '../effects/EffectRegistry' +import { PostProcessingComponent } from './PostProcessingComponent' + +describe('PostProcessingComponent', () => { + let rootEntity: Entity + let entity: Entity + + const mockCanvas = () => { + return { + getDrawingBufferSize: () => 0 + } as any as HTMLCanvasElement + } + + beforeEach(() => { + createEngine() + + rootEntity = createEntity() + setComponent(rootEntity, UUIDComponent, MathUtils.generateUUID() as EntityUUID) + setComponent(rootEntity, EntityTreeComponent) + setComponent(rootEntity, CameraComponent) + setComponent(rootEntity, SceneComponent) + setComponent(rootEntity, RendererComponent, { canvas: mockCanvas() }) + + entity = createEntity() + setComponent(entity, UUIDComponent, MathUtils.generateUUID() as EntityUUID) + getMutableState(RendererState).usePostProcessing.set(true) + setComponent(entity, PostProcessingComponent, { enabled: true }) + setComponent(entity, EntityTreeComponent) + + //set data to test + setComponent(rootEntity, SceneComponent, { children: [entity] }) + + //override addpass to test data without dependency on Browser + let addPassCount = 0 + EffectComposer.prototype.addPass = () => { + addPassCount++ + } + }) + + afterEach(() => { + return destroyEngine() + }) + + it('Create default post processing component', () => { + const postProcessingComponent = getComponent(entity, PostProcessingComponent) + assert(postProcessingComponent, 'post processing component exists') + }) + + it('Test Effect Composure amd Highlight Effect', async () => { + const effectKey = 'OutlineEffect' + + //force nested reactors to run + const { rerender, unmount } = render(<>) + + const postProcessingComponent = getMutableComponent(entity, PostProcessingComponent) + await act(() => rerender(<>)) + + const effectComposer = getComponent(rootEntity, RendererComponent).effectComposer + //test that the effect composer is setup + assert(getComponent(rootEntity, RendererComponent).effectComposer, 'effect composer is setup') + + //test that the effect pass has the the effect set + // @ts-ignore + const effects = getComponent(rootEntity, RendererComponent).effectComposer.EffectPass.effects + assert(effects.find((el) => el.name == effectKey)) + + unmount() + }) + + it('Test Effect Add and Remove', async () => { + const effectKey = 'NoiseEffect' + getMutableState(PostProcessingEffectState).merge({ + [effectKey]: { + reactor: NoiseEffectProcessReactor, + defaultValues: { + isActive: true, + blendFunction: BlendFunction.SCREEN, + premultiply: false + }, + schema: { + blendFunction: { propertyType: 0, name: 'Blend Function' }, + premultiply: { propertyType: 2, name: 'Premultiply' } + } + } + }) + + const { rerender, unmount } = render(<>) + + await act(() => { + rerender(<>) + }) + + assert(hasComponent(entity, PostProcessingComponent)) + + const postProcessingComponent = getMutableComponent(entity, PostProcessingComponent) + postProcessingComponent.effects[effectKey]['isActive'].set(true) + + await act(() => { + rerender(<>) + }) + + // @ts-ignore + let effects = getComponent(rootEntity, RendererComponent).effectComposer.EffectPass.effects + assert( + effects.find((el) => el.name == effectKey), + ' Effect turned on' + ) + + postProcessingComponent.effects[effectKey]['isActive'].set(false) + + await act(() => { + rerender(<>) + }) + + // @ts-ignore + effects = getComponent(rootEntity, RendererComponent).effectComposer.EffectPass.effects + assert(!effects.find((el) => el.name == effectKey), ' Effect turned off') + + removeEntity(entity) + unmount() + }) +}) + +const effectKey = 'NoiseEffect' +export const NoiseEffectProcessReactor: React.FC = (props: { + isActive + rendererEntity: Entity + effectData + effects +}) => { + const { isActive, rendererEntity, effectData, effects } = props + const effectState = getState(PostProcessingEffectState) + + useEffect(() => { + if (effectData[effectKey].value) return + effectData[effectKey].set(effectState[effectKey].defaultValues) + }, []) + + useEffect(() => { + if (!isActive?.value) { + if (effects[effectKey].value) effects[effectKey].set(none) + return + } + const eff = new NoiseEffect(effectData[effectKey].value) + effects[effectKey].set(eff) + return () => { + effects[effectKey].set(none) + } + }, [isActive]) + + return null +} diff --git a/packages/spatial/src/renderer/components/PostProcessingComponent.tsx b/packages/spatial/src/renderer/components/PostProcessingComponent.tsx old mode 100755 new mode 100644 index e5ea82faa5..2a04e642e0 --- a/packages/spatial/src/renderer/components/PostProcessingComponent.tsx +++ b/packages/spatial/src/renderer/components/PostProcessingComponent.tsx @@ -23,15 +23,40 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20 Ethereal Engine. All Rights Reserved. */ -import { useEffect } from 'react' - -import { defineComponent, useComponent, useEntityContext } from '@etherealengine/ecs' -import { NO_PROXY_STEALTH } from '@etherealengine/hyperflux' - -import { defaultPostProcessingSchema, EffectPropsSchema } from '../effects/PostProcessing' -import { configureEffectComposer } from '../functions/configureEffectComposer' +import { Entity, defineComponent, useComponent, useEntityContext } from '@etherealengine/ecs' +import { ErrorBoundary, NO_PROXY, getState, useHookstate } from '@etherealengine/hyperflux' +import { + EdgeDetectionMode, + Effect, + EffectComposer, + EffectPass, + OutlineEffect, + RenderPass, + SMAAEffect +} from 'postprocessing' +import React, { Suspense, useEffect } from 'react' +import { ArrayCamera, Scene, WebGLRenderer } from 'three' +import { CameraComponent } from '../../camera/components/CameraComponent' +import { HighlightState } from '../HighlightState' +import { RendererState } from '../RendererState' +import { RenderSettingsState, RendererComponent } from '../WebGLRendererSystem' +import { ObjectLayers } from '../constants/ObjectLayers' +import { PostProcessingEffectState } from '../effects/EffectRegistry' import { useScene } from './SceneComponents' +declare module 'postprocessing' { + interface EffectComposer { + // passes + EffectPass: EffectPass + // effects + SMAAEffect: SMAAEffect + OutlineEffect: OutlineEffect + } + interface Effect { + isActive: boolean + } +} + export const PostProcessingComponent = defineComponent({ name: 'PostProcessingComponent', jsonID: 'EE_postprocessing', @@ -39,13 +64,12 @@ export const PostProcessingComponent = defineComponent({ onInit(entity) { return { enabled: false, - effects: defaultPostProcessingSchema + effects: {} as Record // effect name, parameters } }, onSet: (entity, component, json) => { if (!json) return - if (typeof json.enabled === 'boolean') component.enabled.set(json.enabled) if (typeof json.effects === 'object') component.merge({ effects: json.effects }) }, @@ -61,18 +85,88 @@ export const PostProcessingComponent = defineComponent({ reactor: () => { const entity = useEntityContext() const rendererEntity = useScene(entity) - const postprocessingComponent = useComponent(entity, PostProcessingComponent) - - useEffect(() => { - if (!rendererEntity) return - configureEffectComposer( - rendererEntity, - postprocessingComponent.enabled.value - ? (postprocessingComponent.effects.get(NO_PROXY_STEALTH) as EffectPropsSchema) - : undefined - ) - }, [rendererEntity, postprocessingComponent.enabled, postprocessingComponent.effects]) - - return null + + if (!rendererEntity) return null + + return } }) + +const PostProcessingReactor = (props: { entity: Entity; rendererEntity: Entity }) => { + const { entity, rendererEntity } = props + const postProcessingComponent = useComponent(entity, PostProcessingComponent) + const EffectRegistry = getState(PostProcessingEffectState) + const effects = useHookstate>({}) + const renderer = useComponent(rendererEntity, RendererComponent) + const renderSettings = getState(RendererState) + const camera = useComponent(rendererEntity, CameraComponent) + const scene = new Scene() + const composer = new EffectComposer(renderer.value.renderer as WebGLRenderer) + + useEffect(() => { + renderer.effectComposer.set(composer) + const renderPass = new RenderPass() + renderer.value.effectComposer.addPass(renderPass) + renderer.renderPass.set(renderPass) + }, []) + + useEffect(() => { + const effectsVal = effects.get(NO_PROXY) as Record + + if (renderSettings.usePostProcessing && postProcessingComponent.enabled.value) { + for (const key in effectsVal) { + const val = effectsVal[key] + renderer.value.effectComposer[key] = val + } + } else { + renderer.value.effectComposer.removePass(renderer.value.effectComposer.EffectPass as EffectPass) + } + + //always have the smaa effect + const smaaPreset = getState(RenderSettingsState).smaaPreset + const smaaEffect = new SMAAEffect({ + preset: smaaPreset, + edgeDetectionMode: EdgeDetectionMode.COLOR + }) + effectsVal['SMAAEffect'] = smaaEffect + renderer.effectComposer['SMAAEffect'].set(smaaEffect) + + // //always have the outline effect for the highlight selection + const outlineEffect = new OutlineEffect(scene as Scene, camera.value as ArrayCamera, getState(HighlightState)) + outlineEffect.selectionLayer = ObjectLayers.HighlightEffect + effectsVal['OutlineEffect'] = outlineEffect + renderer.effectComposer['OutlineEffect'].set(outlineEffect) + + if (renderer.value.effectComposer.EffectPass) { + renderer.value.effectComposer.removePass(renderer.value.effectComposer.EffectPass as EffectPass) + } + + const effectArray = Object.values(effectsVal) + renderer.effectComposer.EffectPass.set(new EffectPass(camera.value as ArrayCamera, ...effectArray)) + renderer.value.effectComposer.addPass(renderer.value.effectComposer.EffectPass as EffectPass) + }, [effects, postProcessingComponent.enabled]) + + // for each effect specified in our postProcessingComponent, we mount a sub-reactor based on the effect registry for that effect ID + return ( + <> + {Object.keys(EffectRegistry).map((key) => { + const effect = EffectRegistry[key] // get effect registry entry + if (!effect) return null + return ( + + + + + + ) + })} + + ) +} diff --git a/packages/spatial/src/renderer/effects/EffectRegistry.ts b/packages/spatial/src/renderer/effects/EffectRegistry.ts new file mode 100644 index 0000000000..135a19094f --- /dev/null +++ b/packages/spatial/src/renderer/effects/EffectRegistry.ts @@ -0,0 +1,66 @@ +/* +CPAL-1.0 License + +The contents of this file are subject to the Common Public Attribution License +Version 1.0. (the "License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at +https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. +The License is based on the Mozilla Public License Version 1.1, but Sections 14 +and 15 have been added to cover use of software over a computer network and +provide for limited attribution for the Original Developer. In addition, +Exhibit A has been modified to be consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, +WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the +specific language governing rights and limitations under the License. + +The Original Code is Ethereal Engine. + +The Original Developer is the Initial Developer. The Initial Developer of the +Original Code is the Ethereal Engine team. + +All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 +Ethereal Engine. All Rights Reserved. +*/ +import { Entity } from '@etherealengine/ecs' +import { State, defineState } from '@etherealengine/hyperflux' +import { EffectComposer } from 'postprocessing' +import React from 'react' +import { Scene } from 'three' + +export type EffectReactorProps = { + isActive: State + rendererEntity: Entity + effectData: any + effects: any + composer: EffectComposer + scene: Scene +} + +/** Interface for dynamic effect Registry + * @param reactor reactor for effect + * @param defaultValues specifies the default values for the effect adhering to schema + * @param schema specifies a schema for the editor to generate UI for each effect. (@todo Eventually can generate from default values) + * @example + * { + reactor: ChromaticAberrationEffectProcessReactor, + defaultValues: { + hue: 1, + saturation: 1 + }, + schema: { + hue: { propertyType: PropertyTypes.Number, name: 'Hue', min: -1, max: 1, step: 0.01 }, + saturation: { propertyType: PropertyTypes.Number, name: 'Saturation', min: -1, max: 1, step: 0.01 } + } + } + */ +export interface EffectRegistryEntry { + reactor: React.FC + defaultValues: any + schema: any +} + +export const PostProcessingEffectState = defineState({ + name: 'PostProcessingEffectState', + initial: {} as Record +}) diff --git a/packages/spatial/src/renderer/effects/PostProcessing.ts b/packages/spatial/src/renderer/effects/PostProcessing.ts deleted file mode 100644 index 43400e621a..0000000000 --- a/packages/spatial/src/renderer/effects/PostProcessing.ts +++ /dev/null @@ -1,716 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { - BlendFunction, - BloomEffect, - BloomEffectOptions, - BrightnessContrastEffect, - ColorDepthEffect, - DepthOfFieldEffect, - EdgeDetectionMode, - HueSaturationEffect, - KernelSize, - OutlineEffect, - PredicationMode, - Resolution, - SMAAEffect, - SMAAPreset, - SSAOEffect, - ToneMappingEffect, - ToneMappingMode, - VignetteEffect, - VignetteTechnique -} from 'postprocessing' -import { Color, ColorSpace, Texture, TextureEncoding, Vector2 } from 'three' - -import { LinearTosRGBEffect } from '../../renderer/effects/LinearTosRGBEffect' - -export const Effects = { - SMAAEffect: 'SMAAEffect' as const, - OutlineEffect: 'OutlineEffect' as const, - SSAOEffect: 'SSAOEffect' as const, - SSREffect: 'SSREffect' as const, - DepthOfFieldEffect: 'DepthOfFieldEffect' as const, - BloomEffect: 'BloomEffect' as const, - ToneMappingEffect: 'ToneMappingEffect' as const, - BrightnessContrastEffect: 'BrightnessContrastEffect' as const, - HueSaturationEffect: 'HueSaturationEffect' as const, - ColorDepthEffect: 'ColorDepthEffect' as const, - LinearTosRGBEffect: 'LinearTosRGBEffect' as const, - SSGIEffect: 'SSGIEffect' as const, - TRAAEffect: 'TRAAEffect' as const, - // ChromaticAberrationEffect: 'ChromaticAberrationEffect' as const, - MotionBlurEffect: 'MotionBlurEffect' as const, - // ColorAverageEffect: 'ColorAverageEffect' as const, - // DotScreenEffect: 'DotScreenEffect' as const, - // TiltShiftEffect: 'TiltShiftEffect' as const, - // GlitchEffect: 'GlitchEffect' as const, - // GodRaysEffect: 'GodRaysEffect' as const, - // GridEffect: 'GridEffect' as const, - // LUT1DEffect: 'LUT1DEffect' as const, - // LUT3DEffect: 'LUT3DEffect' as const, - // NoiseEffect: 'NoiseEffect' as const, - // PixelationEffect: 'PixelationEffect' as const, - // ScanlineEffect: 'ScanlineEffect' as const, - // ShockWaveEffect: 'ShockWaveEffect' as const, - // FXAAEffect: 'FXAAEffect' as const, - // TextureEffect: 'TextureEffect' as const, - VignetteEffect: 'VignetteEffect' as const - // LensDistortionEffect: 'LensDistortionEffect' as const -} - -export const EffectMap = { - [Effects.SMAAEffect]: SMAAEffect, - [Effects.OutlineEffect]: OutlineEffect, - [Effects.SSAOEffect]: SSAOEffect, - [Effects.DepthOfFieldEffect]: DepthOfFieldEffect, - [Effects.BloomEffect]: BloomEffect, - [Effects.ToneMappingEffect]: ToneMappingEffect, - [Effects.BrightnessContrastEffect]: BrightnessContrastEffect, - [Effects.HueSaturationEffect]: HueSaturationEffect, - [Effects.ColorDepthEffect]: ColorDepthEffect, - [Effects.LinearTosRGBEffect]: LinearTosRGBEffect, - [Effects.VignetteEffect]: VignetteEffect -} - -declare module 'postprocessing' { - interface EffectComposer { - // passes - EffectPass: EffectPass - // effects - SMAAEffect: SMAAEffect - OutlineEffect: OutlineEffect - SSAOEffect: SSAOEffect - DepthOfFieldEffect: DepthOfFieldEffect - BloomEffect: BloomEffect - ToneMappingEffect: ToneMappingEffect - BrightnessContrastEffect: BrightnessContrastEffect - HueSaturationEffect: HueSaturationEffect - ColorDepthEffect: ColorDepthEffect - LinearTosRGBEffect: LinearTosRGBEffect - VignetteEffect: VignetteEffect - } -} - -export type EffectMapType = (typeof EffectMap)[keyof typeof EffectMap] - -export type EffectProps = { - isActive: boolean -} - -export type SMAAEffectProps = EffectProps & { - preset?: SMAAPreset - edgeDetectionMode?: EdgeDetectionMode - predicationMode?: PredicationMode -} - -export type OutlineEffectProps = EffectProps & { - blendFunction?: BlendFunction - patternTexture?: Texture - patternScale?: number - edgeStrength?: number - pulseSpeed?: number - visibleEdgeColor?: number - hiddenEdgeColor?: number - multisampling?: number - resolutionScale?: number - resolutionX?: number - resolutionY?: number - width?: number - height?: number - kernelSize?: KernelSize - blur?: boolean - xRay?: boolean -} - -export type SSAOEffectProps = EffectProps & { - blendFunction?: BlendFunction - distanceScaling?: boolean - depthAwareUpsampling?: boolean - normalDepthBuffer?: Texture - samples?: number - rings?: number - worldDistanceThreshold?: number - worldDistanceFalloff?: number - worldProximityThreshold?: number - worldProximityFalloff?: number - distanceThreshold?: number - distanceFalloff?: number - rangeThreshold?: number - rangeFalloff?: number - minRadiusScale?: number - luminanceInfluence?: number - radius?: number - intensity?: number - bias?: number - fade?: number - color?: Color - resolutionScale?: number - resolutionX?: number - resolutionY?: number - width?: number - height?: number - blur?: boolean - kernelSize?: KernelSize -} - -const defaultSSROptions = { - distance: 10, - thickness: 10, - denoiseIterations: 1, - denoiseKernel: 2, - denoiseDiffuse: 10, - denoiseSpecular: 10, - radius: 3, - phi: 0.5, - lumaPhi: 5, - depthPhi: 2, - normalPhi: 50, - roughnessPhi: 50, - specularPhi: 50, - envBlur: 0.5, - importanceSampling: true, - steps: 20, - refineSteps: 5, - resolutionScale: 1, - missedRays: false -} - -export type SSREffectProps = EffectProps & typeof defaultSSROptions - -export type DepthOfFieldEffectProps = EffectProps & { - blendFunction?: BlendFunction - focusDistance?: number - focalLength?: number - focusRange?: number - bokehScale?: number - resolutionScale?: number - resolutionX?: number - resolutionY?: number -} - -export type BloomEffectProps = EffectProps & BloomEffectOptions - -export type ToneMappingEffectProps = EffectProps & { - blendFunction?: BlendFunction - adaptive?: boolean - mode?: ToneMappingMode - resolution?: number - maxLuminance?: number - whitePoint?: number - middleGrey?: number - minLuminance?: number - averageLuminance?: number - adaptationRate?: number -} - -export type BrightnessContrastEffectProps = EffectProps & { - blendFunction?: BlendFunction - brightness?: number - contrast?: number -} - -export type HueSaturationEffectProps = EffectProps & { - blendFunction?: BlendFunction - hue?: number - saturation?: number -} - -export type ColorDepthEffectProps = EffectProps & { - blendFunction?: BlendFunction - bits?: number -} - -export type LinearTosRGBEffectProps = EffectProps & { blendFunction?: BlendFunction } - -export type SSGIEffectProps = EffectProps & { - distance: number - thickness: number - denoiseIterations: number - denoiseKernel: number - denoiseDiffuse: number - denoiseSpecular: number - radius: number - phi: number - lumaPhi: number - depthPhi: number - normalPhi: number - roughnessPhi: number - specularPhi: number - envBlur: number - importanceSampling: boolean - steps: number - refineSteps: number - resolutionScale: number - missedRays: boolean -} - -export type TRAAEffectProps = EffectProps & { - blend: number - constantBlend: boolean - dilation: boolean - blockySampling: boolean - logTransform: boolean - depthDistance: number - worldDistance: number - neighborhoodClamping: boolean -} - -export type MotionBlurEffectProps = EffectProps & { - intensity: 1 - jitter: 1 - samples: 16 -} - -export type ChromaticAberrationEffectProps = EffectProps & { - blendFunction?: BlendFunction - offset?: Vector2 - radialModulation: boolean - modulationOffset: number -} -export type ColorAverageEffectProps = EffectProps & { - blendFunction?: BlendFunction - bits?: number -} -export type DotScreenEffectProps = EffectProps & { - blendFunction?: BlendFunction - angle?: number - scale?: number -} -export type TiltShiftEffectProps = EffectProps & { - blendFunction?: BlendFunction - offset?: number - rotation?: number - focusArea?: number - feather?: number - bias?: number - kernelSize?: KernelSize - resolutionScale?: number - resolutionX?: number - resolutionY?: number -} -export type GlitchEffectProps = EffectProps & { - blendFunction?: BlendFunction - chromaticAberrationOffset?: Vector2 - delay?: Vector2 - duration?: Vector2 - strength?: Vector2 - perturbationMap?: Texture - dtSize?: number - columns?: number - ratio?: number -} -export type GodRaysEffectProps = EffectProps & { - blendFunction?: BlendFunction - samples?: number - density?: number - decay?: number - weight?: number - exposure?: number - clampMax?: number - resolutionScale?: number - resolutionX?: number - resolutionY?: number - width?: number - height?: number - kernelSize?: KernelSize - blur?: boolean -} -export type GridEffectProps = EffectProps & { - blendFunction?: BlendFunction - scale?: number - lineWidth?: number -} -export type LUT1DEffectProps = EffectProps & { blendFunction?: BlendFunction } -export type LUT3DEffectProps = EffectProps & { - blendFunction?: BlendFunction - tetrahedralInterpolation?: boolean - inputEncoding?: TextureEncoding - inputColorSpace?: ColorSpace -} -export type NoiseEffectProps = EffectProps & { - blendFunction?: BlendFunction - premultiply?: boolean -} -export type PixelationEffectProps = EffectProps & { granularity?: number } -export type ScanlineEffectProps = EffectProps & { - blendFunction?: BlendFunction - density?: number -} -export type ShockWaveEffectProps = EffectProps & { - speed?: number - maxRadius?: number - waveSize?: number - amplitude?: number -} -export type FXAAEffectProps = EffectProps & { blendFunction?: BlendFunction } -export type TextureEffectProps = EffectProps & { - blendFunction?: BlendFunction - texture?: Texture - aspectCorrection?: boolean -} -export type VignetteEffectProps = EffectProps & { - blendFunction?: BlendFunction - technique?: VignetteTechnique - eskil?: boolean - offset?: number - darkness?: number -} -export type LensDistortionEffectProps = EffectProps & { - distortion: Vector2 - principalPoint: Vector2 - focalLength: Vector2 - skew?: number -} - -export type EffectPropsSchema = { - // [Effects.SMAAEffect]: SMAAEffectProps - // [Effects.OutlineEffect]: OutlineEffectProps - [Effects.SSAOEffect]: SSAOEffectProps - [Effects.SSREffect]: SSREffectProps - [Effects.DepthOfFieldEffect]: DepthOfFieldEffectProps - [Effects.BloomEffect]: BloomEffectProps - [Effects.ToneMappingEffect]: ToneMappingEffectProps - [Effects.BrightnessContrastEffect]: BrightnessContrastEffectProps - [Effects.HueSaturationEffect]: HueSaturationEffectProps - [Effects.ColorDepthEffect]: ColorDepthEffectProps - [Effects.LinearTosRGBEffect]: LinearTosRGBEffectProps - [Effects.SSGIEffect]: SSGIEffectProps - [Effects.TRAAEffect]: TRAAEffectProps - [Effects.MotionBlurEffect]: MotionBlurEffectProps - // [Effects.ChromaticAberrationEffect]: ChromaticAberrationEffectProps - // [Effects.ColorAverageEffect]: ColorAverageEffectProps - // [Effects.DotScreenEffect]: DotScreenEffectProps - // [Effects.TiltShiftEffect]: TiltShiftEffectProps - // [Effects.GlitchEffect]: GlitchEffectProps - // [Effects.GodRaysEffect]: GodRaysEffectProps - // [Effects.GridEffect]: GridEffectProps - // [Effects.LUT1DEffect]: LUT1DEffectProps - // [Effects.LUT3DEffect]: LUT3DEffectProps - // [Effects.NoiseEffect]: NoiseEffectProps - // [Effects.PixelationEffect]: PixelationEffectProps - // [Effects.ScanlineEffect]: ScanlineEffectProps - // [Effects.ShockWaveEffect]: ShockWaveEffectProps - // [Effects.FXAAEffect]: FXAAEffectProps - // [Effects.TextureEffect]: TextureEffectProps - [Effects.VignetteEffect]: VignetteEffectProps - // [Effects.LensDistortionEffect]: LensDistortionEffectProps -} - -export type EffectPropsSchemaType = (typeof defaultPostProcessingSchema)[keyof typeof defaultPostProcessingSchema] - -export const defaultPostProcessingSchema: EffectPropsSchema = { - // [Effects.SMAAEffect]: { - // isActive: false, - // preset: SMAAPreset.MEDIUM, - // edgeDetectionMode: EdgeDetectionMode.COLOR, - // predicationMode: PredicationMode.DISABLED - // }, - // [Effects.OutlineEffect]: { - // isActive: false, - // blendFunction: BlendFunction.SCREEN, - // patternScale: 1.0, - // edgeStrength: 1.0, - // pulseSpeed: 0.0, - // visibleEdgeColor: 0xffffff, - // hiddenEdgeColor: 0x22090a, - // multisampling: 0, - // resolutionScale: 0.5, - // resolutionX: Resolution.AUTO_SIZE, - // resolutionY: Resolution.AUTO_SIZE, - // width: Resolution.AUTO_SIZE, - // height: 480, - // kernelSize: KernelSize.VERY_SMALL, - // blur: false, - // xRay: true - // }, - [Effects.SSREffect]: { - isActive: false, - ...defaultSSROptions - }, - [Effects.SSGIEffect]: { - isActive: false, - distance: 10, - thickness: 10, - denoiseIterations: 1, - denoiseKernel: 2, - denoiseDiffuse: 10, - denoiseSpecular: 10, - radius: 3, - phi: 0.5, - lumaPhi: 5, - depthPhi: 2, - normalPhi: 50, - roughnessPhi: 50, - specularPhi: 50, - envBlur: 0.5, - importanceSampling: true, - steps: 20, - refineSteps: 5, - resolutionScale: 1, - missedRays: false - }, - [Effects.SSAOEffect]: { - isActive: false, - blendFunction: BlendFunction.MULTIPLY, - distanceScaling: true, - depthAwareUpsampling: true, - normalDepthBuffer: undefined, - samples: 9, - rings: 7, - // worldDistanceThreshold: 0.97, - // worldDistanceFalloff: 0.03, - // worldProximityThreshold: 0.0005, - // worldProximityFalloff: 0.001, - distanceThreshold: 0.125, // Render up to a distance of ~20 world units - distanceFalloff: 0.02, // with an additional ~2.5 units of falloff. - rangeThreshold: 0.0005, - rangeFalloff: 0.001, - minRadiusScale: 0.1, - luminanceInfluence: 0.7, - bias: 0.025, - radius: 0.1825, - intensity: 1.0, - fade: 0.01, - color: undefined, - resolutionScale: 1.0, - resolutionX: Resolution.AUTO_SIZE, - resolutionY: Resolution.AUTO_SIZE, - width: Resolution.AUTO_SIZE, - height: Resolution.AUTO_SIZE, - kernelSize: KernelSize.SMALL, - blur: true - }, - [Effects.DepthOfFieldEffect]: { - isActive: false, - blendFunction: BlendFunction.NORMAL, - focusDistance: 0.1, - focalLength: 0.1, - focusRange: 0.1, - bokehScale: 1.0, - resolutionScale: 1.0, - resolutionX: Resolution.AUTO_SIZE, - resolutionY: Resolution.AUTO_SIZE - }, - [Effects.BloomEffect]: { - isActive: true, - blendFunction: BlendFunction.SCREEN, - kernelSize: KernelSize.LARGE, - luminanceThreshold: 0.9, - luminanceSmoothing: 0.025, - mipmapBlur: false, - intensity: 1.0, - radius: 0.85, - levels: 8 - }, - [Effects.ToneMappingEffect]: { - isActive: false, - blendFunction: BlendFunction.NORMAL, - adaptive: false, - mode: ToneMappingMode.ACES_FILMIC, - resolution: 256, - maxLuminance: 4.0, - whitePoint: 4.0, - middleGrey: 0.6, - minLuminance: 0.01, - averageLuminance: 1.0, - adaptationRate: 1.0 - }, - [Effects.BrightnessContrastEffect]: { - isActive: false, - blendFunction: BlendFunction.NORMAL, - brightness: 0.0, - contrast: 0.0 - }, - [Effects.HueSaturationEffect]: { - isActive: false, - blendFunction: BlendFunction.NORMAL, - hue: 0, - saturation: 0.0 - }, - [Effects.ColorDepthEffect]: { - isActive: false, - blendFunction: BlendFunction.NORMAL, - bits: 16 - }, - [Effects.LinearTosRGBEffect]: { - isActive: false - }, - [Effects.TRAAEffect]: { - isActive: false, - blend: 0.8, - constantBlend: true, - dilation: true, - blockySampling: false, - logTransform: false, // ! TODO: check if can use logTransform withoutt artifacts - depthDistance: 10, - worldDistance: 5, - neighborhoodClamping: true - }, - [Effects.MotionBlurEffect]: { - isActive: false, - intensity: 1, - jitter: 1, - samples: 16 - }, - // [Effects.ChromaticAberrationEffect]: { - // isActive: false, - // blendFunction: BlendFunction.NORMAL, - // offset: undefined, - // radialModulation: false, - // modulationOffset: 0.15 - // }, - // [Effects.ColorAverageEffect]: { - // isActive: false, - // blendFunction: BlendFunction.NORMAL - // }, - // [Effects.DotScreenEffect]: { isActive: false, blendFunction: BlendFunction.NORMAL, angle: 1.57, scale: 1.0 }, - // [Effects.TiltShiftEffect]: { - // isActive: false, - // blendFunction: BlendFunction.NORMAL, - // offset: 0.0, - // rotation: 0.0, - // focusArea: 0.4, - // feather: 0.3, - // bias: 0.06, - // kernelSize: KernelSize.MEDIUM, - // resolutionScale: 0.5, - // resolutionX: Resolution.AUTO_SIZE, - // resolutionY: Resolution.AUTO_SIZE - // }, - // [Effects.GlitchEffect]: { - // isActive: false, - // blendFunction: BlendFunction.NORMAL, - // chromaticAberrationOffset: undefined, - // delay: undefined, - // duration: undefined, - // strength: undefined, - // perturbationMap: undefined, - // dtSize: 64, - // columns: 0.05, - // ratio: 0.85 - // }, - // [Effects.GodRaysEffect]: { - // isActive: false, - // blendFunction: BlendFunction.SCREEN, - // samples: 60.0, - // density: 0.96, - // decay: 0.9, - // weight: 0.4, - // exposure: 0.6, - // clampMax: 1.0, - // resolutionScale: 0.5, - // resolutionX: Resolution.AUTO_SIZE, - // resolutionY: Resolution.AUTO_SIZE, - // width: Resolution.AUTO_SIZE, - // height: Resolution.AUTO_SIZE, - // kernelSize: KernelSize.SMALL, - // blur: true - // }, - // [Effects.GridEffect]: { - // isActive: false, - // blendFunction: BlendFunction.OVERLAY, - // scale: 1.0, - // lineWidth: 0.0 - // }, - // [Effects.LUT1DEffect]: { isActive: false, blendFunction: BlendFunction.SET }, - // [Effects.LUT3DEffect]: { isActive: false, blendFunction: BlendFunction.SET, tetrahedralInterpolation: false, inputEncoding: sRGBEncoding, inputColorSpace: SRGBColorSpace }, - // [Effects.NoiseEffect]: { isActive: false, blendFunction: BlendFunction.SCREEN, premultiply: false }, - // [Effects.PixelationEffect]: { isActive: false, granularity: 30 }, - // [Effects.ScanlineEffect]: { isActive: false, blendFunction: BlendFunction.OVERLAY, density: 1.25}, - // [Effects.ShockWaveEffect]: { - // isActive: false, - // speed: 2.0, - // maxRadius: 1.0, - // waveSize: 0.2, - // amplitude: 0.05, - // }, - // [Effects.FXAAEffect]: { isActive: false, blendFunction: BlendFunction.SRC }, - // [Effects.TextureEffect]: { - // isActive: false, - // blendFunction: BlendFunction.NORMAL, - // texture: undefined, - // aspectCorrection: false - // }, - [Effects.VignetteEffect]: { - isActive: false, - blendFunction: BlendFunction.NORMAL, - technique: VignetteTechnique.DEFAULT, - eskil: false, - offset: 0.5, - darkness: 0.5 - } - // [Effects.LensDistortionEffect]: { - // isActive: false, - // distortion: new Vector2(0,0), - // principalPoint: new Vector2(0,0), - // focalLength: new Vector2(0,0), - // skew: 0 - // } -} - -/** - * Based on Unity's post processing pipelines - * - https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@17.0/manual/rendering-excecution-order.html - * - https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@17.0/manual/EffectList.html - * - https://docs.unity3d.com/Packages/com.unity.render-pipelines.core@17.0/manual - */ - -/** 1. input aliasing */ -// Effects.SMAAEffect, -// Effects.OutlineEffect, - -export const effectInOrder = [ - /** 2. world effects */ - // Effects.PaniniProjection, - Effects.DepthOfFieldEffect, - Effects.SSAOEffect, // TODO- add option to use HBAO - Effects.SSREffect, - Effects.SSGIEffect, - // Effects.GodRaysEffect, - - /** 3. camera effects */ - // Effects.LensDistortionEffect, - //Effects.LensFlareEffect, - // Effects.ChromaticAberrationEffect, - Effects.MotionBlurEffect, - Effects.BloomEffect, - Effects.VignetteEffect, - - /** 4. color grading */ - Effects.ToneMappingEffect, - // maybe replace with Shadows/Midtones/Highlights ? - Effects.BrightnessContrastEffect, - Effects.HueSaturationEffect, - Effects.ColorDepthEffect, - // Effects.LUT1DEffect, - // Effects.LUT3DEffect, - - /** 5. final fix, aliasing and noise passes */ - Effects.LinearTosRGBEffect, // should this just be always on? - Effects.TRAAEffect - // Effects.FXAAEffect -] diff --git a/packages/spatial/src/renderer/functions/configureEffectComposer.ts b/packages/spatial/src/renderer/functions/configureEffectComposer.ts deleted file mode 100644 index 34c6cca823..0000000000 --- a/packages/spatial/src/renderer/functions/configureEffectComposer.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* -CPAL-1.0 License - -The contents of this file are subject to the Common Public Attribution License -Version 1.0. (the "License"); you may not use this file except in compliance -with the License. You may obtain a copy of the License at -https://github.com/EtherealEngine/etherealengine/blob/dev/LICENSE. -The License is based on the Mozilla Public License Version 1.1, but Sections 14 -and 15 have been added to cover use of software over a computer network and -provide for limited attribution for the Original Developer. In addition, -Exhibit A has been modified to be consistent with Exhibit B. - -Software distributed under the License is distributed on an "AS IS" basis, -WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the -specific language governing rights and limitations under the License. - -The Original Code is Ethereal Engine. - -The Original Developer is the Initial Developer. The Initial Developer of the -Original Code is the Ethereal Engine team. - -All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023 -Ethereal Engine. All Rights Reserved. -*/ - -import { - BlendFunction, - DepthDownsamplingPass, - EdgeDetectionMode, - EffectComposer, - EffectPass, - OutlineEffect, - RenderPass, - SMAAEffect, - TextureEffect -} from 'postprocessing' -import { VelocityDepthNormalPass } from 'realism-effects' -import { Scene } from 'three' - -import { Entity, getComponent } from '@etherealengine/ecs' -import { getState } from '@etherealengine/hyperflux' - -import { CameraComponent } from '../../camera/components/CameraComponent' -import { EngineState } from '../../EngineState' -import { ObjectLayers } from '../../renderer/constants/ObjectLayers' -import { defaultPostProcessingSchema, EffectMap, Effects } from '../effects/PostProcessing' -import { HighlightState } from '../HighlightState' -import { CustomNormalPass } from '../passes/CustomNormalPass' -import { RendererState } from '../RendererState' -import { RendererComponent, RenderSettingsState } from '../WebGLRendererSystem' -import { changeRenderMode } from './changeRenderMode' - -export const configureEffectComposer = (entity: Entity, schema?: typeof defaultPostProcessingSchema): void => { - const renderer = getComponent(entity, RendererComponent) - const camera = getComponent(entity, CameraComponent) - if (!renderer || !camera) return - - const scene = new Scene() - - if (renderer.effectComposer) { - renderer.effectComposer.dispose() - renderer.renderPass = null! - } - - const composer = new EffectComposer(renderer.renderer) - renderer.effectComposer = composer - - // we always want to have at least the render pass enabled - const renderPass = new RenderPass() - renderer.effectComposer.addPass(renderPass) - renderer.renderPass = renderPass - - const renderSettings = getState(RendererState) - if (!renderSettings.usePostProcessing) return - - const effects: any[] = [] - - const smaaPreset = getState(RenderSettingsState).smaaPreset - const smaaEffect = new SMAAEffect({ - preset: smaaPreset, - edgeDetectionMode: EdgeDetectionMode.COLOR - }) - composer.SMAAEffect = smaaEffect - - const outlineEffect = new OutlineEffect(scene, camera, getState(HighlightState)) - outlineEffect.selectionLayer = ObjectLayers.HighlightEffect - composer.OutlineEffect = outlineEffect - - const SmaaEffectPass = new EffectPass(camera, smaaEffect) - const OutlineEffectPass = new EffectPass(camera, outlineEffect) - composer.addPass(OutlineEffectPass) //outlines don't follow camera - composer.addPass(SmaaEffectPass) - - const effectKeys = Object.keys(EffectMap) - - const normalPass = new CustomNormalPass(scene, camera) - - const depthDownsamplingPass = new DepthDownsamplingPass({ - normalBuffer: normalPass.texture, - resolutionScale: 0.5 - }) - - if (!schema) return - - const velocityDepthNormalPass = new VelocityDepthNormalPass(scene, camera) - let useVelocityDepthNormalPass = false - let useDepthDownsamplingPass = false - - for (const key of effectKeys) { - const effectOptions = schema[key] - - if (!effectOptions || !effectOptions.isActive) continue - const EffectClass = EffectMap[key] - - if (!EffectClass) continue - - if (key === Effects.SSAOEffect) { - const eff = new EffectClass(camera, normalPass.texture, { - ...effectOptions, - normalDepthBuffer: depthDownsamplingPass.texture - }) - useDepthDownsamplingPass = true - composer[key] = eff - effects.push(eff) - } else if (key === Effects.SSREffect || key === Effects.SSGIEffect) { - // SSR is just a mode of SSGI, and can't both be run at the same time - const eff = new EffectClass(composer, scene, camera, { ...effectOptions, velocityDepthNormalPass }) - useVelocityDepthNormalPass = true - composer[key] = eff - effects.push(eff) - } else if (key === Effects.DepthOfFieldEffect) { - const eff = new EffectClass(camera, effectOptions) - composer[key] = eff - effects.push(eff) - } else if (key === Effects.TRAAEffect) { - // todo support more than 1 texture - const textureCount = 1 - const eff = new EffectClass(scene, camera, velocityDepthNormalPass, textureCount, effectOptions) - useVelocityDepthNormalPass = true - composer[key] = eff - effects.push(eff) - } else if (key === Effects.MotionBlurEffect) { - const eff = new EffectClass(velocityDepthNormalPass, effectOptions) - useVelocityDepthNormalPass = true - composer[key] = eff - effects.push(eff) - } else { - const eff = new EffectClass(effectOptions) - composer[key] = eff - effects.push(eff) - } - } - if (effects.length) { - if (useVelocityDepthNormalPass) composer.addPass(velocityDepthNormalPass) - - if (useDepthDownsamplingPass) { - composer.addPass(normalPass) - composer.addPass(depthDownsamplingPass) - const textureEffect = new TextureEffect({ - blendFunction: BlendFunction.SKIP, - texture: depthDownsamplingPass.texture - }) - effects.push(textureEffect) - } - - composer.EffectPass = new EffectPass(camera, ...effects) - composer.addPass(composer.EffectPass) - } - if (getState(EngineState).isEditor) changeRenderMode() -}