diff --git a/src/components/postprocessing/vanruesc/Effect.ts b/src/components/postprocessing/vanruesc/Effect.ts new file mode 100644 index 0000000..5568446 --- /dev/null +++ b/src/components/postprocessing/vanruesc/Effect.ts @@ -0,0 +1,103 @@ +import { defineComponent, inject, onMounted, onUnmounted, PropType } from 'vue' +import { LoadingManager } from 'three' +// @ts-ignore +import * as PP from 'postprocessing' +import { EffectPassInjectionKey, EffectPassInterface } from './EffectPass' + +type EffectTypes = 'bloom' | 'dof' | 'godrays' | 'smaa' + +export default defineComponent({ + props: { + type: { type: String as PropType, required: true }, + options: { type: Object, default: () => ({}) }, + onReady: Function, + }, + setup(props) { + const effectPass = inject(EffectPassInjectionKey) + if (!effectPass) { + console.error('EffectPass not found') + return + } + + let effect: undefined | PP.Effect // not useful + const effectIndex = effectPass.getEffectIndex() + + const initEffect = (params: any = undefined) => { + effect = createEffect(effectPass, props.type, props.options, params) + if (!effect) { + console.error('Invalid effect type') + return + } + props.onReady?.(effect) + effectPass.addEffect(effect, effectIndex) + } + + onMounted(() => { + if (props.type === 'smaa') { + const smaaImageLoader = new PP.SMAAImageLoader(new LoadingManager()) + smaaImageLoader.load(([search, area]: [HTMLImageElement, HTMLImageElement]) => { + initEffect({ smaaSearch: search, smaaArea: area }) + }) + } else { + initEffect() + } + }) + + onUnmounted(() => { + if (effect) { + effectPass.removeEffect(effect) + effect.dispose() + } + }) + }, + render() { return [] }, +}) + +function createEffect( + effectPass: EffectPassInterface, + type: string, + options: Record, + assets: any = undefined +): PP.Effect { + let effect + switch (type) { + case 'bloom' : + effect = new PP.BloomEffect(options) + break + case 'dof' : + effect = new PP.DepthOfFieldEffect(effectPass.composer.renderer, options) + break + case 'godrays' : + effect = createGodraysEffect(effectPass, options) + break + case 'smaa' : + effect = createSmaaEffect(options, assets) + break + } + return effect +} + +function createSmaaEffect(options: Record, assets: any): PP.Pass { + const { smaaSearch, smaaArea } = assets + // TODO : options + const params = [options.preset ?? PP.SMAAPreset.HIGH, options.edgeDetectionMode ?? PP.EdgeDetectionMode.COLOR] + return new PP.SMAAEffect(smaaSearch, smaaArea, ...params) +} + +function createGodraysEffect(effectPass: EffectPassInterface, options: Record): PP.Pass { + const opts = { ...options } + const { lightSource } = options + if (typeof lightSource !== 'string') { + console.error('Invalid lightSource') + return + } + delete opts.lightSource + + const lightSourceComp = effectPass.renderer.$root?.$refs[lightSource] as any + if (!lightSourceComp) { + console.error('Invalid lightSource ref') + return + } + + return new PP.GodRaysEffect(effectPass.composer.renderer.camera, lightSourceComp.mesh, opts) +} diff --git a/src/components/postprocessing/vanruesc/EffectComposer.ts b/src/components/postprocessing/vanruesc/EffectComposer.ts new file mode 100644 index 0000000..e6ee76e --- /dev/null +++ b/src/components/postprocessing/vanruesc/EffectComposer.ts @@ -0,0 +1,49 @@ +import { defineComponent, inject, InjectionKey, onUnmounted, provide } from 'vue' +import { Clock } from 'three' +// @ts-ignore +import * as PP from 'postprocessing' +// import { RendererInjectionKey, RendererPublicInterface } from '../../../build/trois' +import { RendererInjectionKey, RendererPublicInterface } from '../../../export' + +export interface EffectComposerInterface { + renderer: RendererPublicInterface + composer: PP.EffectComposer + getPassIndex: {(): number} +} + +export const ComposerInjectionKey: InjectionKey = Symbol('Composer') + +export default defineComponent({ + setup() { + const renderer = inject(RendererInjectionKey) + if (!renderer) { + console.error('Renderer not found') + return + } + + const composer = new PP.EffectComposer(renderer.renderer) + const clock = new Clock() + const render = () => { composer.render(clock.getDelta()) } + const setSize = () => { composer.setSize(renderer.size.width, renderer.size.height) } + + let passIndex = 0 + const getPassIndex = () => { return passIndex++ } + + renderer.onInit(() => { + renderer.renderer.autoClear = false + renderer.renderFn = render + setSize() + renderer.onResize(setSize) + }) + + onUnmounted(() => { + renderer.offResize(setSize) + composer.dispose() + }) + + provide(ComposerInjectionKey, { renderer, composer, getPassIndex }) + }, + render() { + return this.$slots.default ? this.$slots.default() : [] + }, +}) diff --git a/src/components/postprocessing/vanruesc/EffectPass.ts b/src/components/postprocessing/vanruesc/EffectPass.ts new file mode 100644 index 0000000..68bb56a --- /dev/null +++ b/src/components/postprocessing/vanruesc/EffectPass.ts @@ -0,0 +1,78 @@ +import { defineComponent, inject, InjectionKey, onUnmounted, provide } from 'vue' +// @ts-ignore +import * as PP from 'postprocessing' +import { ComposerInjectionKey, EffectComposerInterface } from './EffectComposer' +import { RendererPublicInterface } from '../../../export' + +export interface EffectPassInterface { + composer: EffectComposerInterface + renderer: RendererPublicInterface + effectPass: PP.EffectPass + effects: Array + getEffectIndex: {(): number} + addEffect: {(effect: PP.Effect, index: number): number} + removeEffect: {(effect: PP.Effect): number} +} + +export const EffectPassInjectionKey: InjectionKey = Symbol('Composer') + +export default defineComponent({ + props: { + // needsSwap: { type: Boolean, default: false }, + renderToScreen: { type: Boolean, default: false }, + }, + setup() { + const composer = inject(ComposerInjectionKey) + if (!composer) { + console.error('Composer not found') + return {} + } + + const passIndex = composer.getPassIndex() + let effectPass: PP.EffectPass + + const effects: Array = [] + let effectIndex = 0 + const getEffectIndex = () => { return effectIndex++ } + + const refreshEffectPass = () => { + // we have to recreate EffectPass (modifying effectPass.effects don't work) + if (effectPass) { + composer.composer.removePass(effectPass) + effectPass.dispose() + } + effectPass = new PP.EffectPass(composer.renderer.camera, ...effects) + composer.composer.addPass(effectPass, passIndex) + } + + const addEffect = (effect: PP.Effect, index: number) => { + effects.splice(index, 1, effect) + refreshEffectPass() + } + + const removeEffect = (effect: PP.Effect) => { + const index = effects.indexOf(effect) + if (index >= 0) { + effects.splice(index, 1) + refreshEffectPass() + } + } + + onUnmounted(() => { + if (effectPass) { + composer.composer.removePass(effectPass) + effectPass.dispose() + } + }) + + provide(EffectPassInjectionKey, { + composer, + renderer: composer.renderer, + effectPass, + effects, + getEffectIndex, + addEffect, removeEffect, + }) + }, + render() { return this.$slots.default ? this.$slots.default() : [] }, +}) diff --git a/src/components/postprocessing/vanruesc/Pass.ts b/src/components/postprocessing/vanruesc/Pass.ts new file mode 100644 index 0000000..5b8895f --- /dev/null +++ b/src/components/postprocessing/vanruesc/Pass.ts @@ -0,0 +1,66 @@ +import { defineComponent, inject, onUnmounted, PropType } from 'vue' +// @ts-ignore +import * as PP from 'postprocessing' +import { ComposerInjectionKey } from './EffectComposer' +// import { RendererPublicInterface } from '../../../build/trois' +import { RendererPublicInterface } from '../../../export' + +type PassTypes = 'render' | 'blur' + +export default defineComponent({ + props: { + type: { type: String as PropType, required: true }, + options: { type: Object, default: () => ({}) }, + // needsSwap: { type: Boolean, default: false }, + renderToScreen: { type: Boolean, default: false }, + onReady: Function, + }, + setup(props) { + const composer = inject(ComposerInjectionKey) + if (!composer || !composer.renderer) { + console.error('Composer/Renderer not found') + return {} + } + + let pass: undefined | PP.Pass + const passIndex = composer.getPassIndex() + + const initPass = (params: any = undefined) => { + pass = createPass(composer.renderer, props.type, props.options) + if (!pass) { + console.error('Invalid pass type') + return + } + pass.renderToScreen = props.renderToScreen + props.onReady?.(pass) + composer.composer.addPass(pass, passIndex) + } + + onUnmounted(() => { + if (pass) { + composer.composer.removePass(pass) + pass.dispose() + } + }) + + initPass() + }, + render() { return [] }, +}) + +function createPass( + renderer: RendererPublicInterface, + type: string, + options: Record +): PP.Pass { + let pass + switch (type) { + case 'render' : + pass = new PP.RenderPass(renderer.scene, renderer.camera) + break + case 'blur' : + pass = new PP.BlurPass(options) + break + } + return pass +} diff --git a/src/components/postprocessing/vanruesc/index.ts b/src/components/postprocessing/vanruesc/index.ts new file mode 100644 index 0000000..98b1c8e --- /dev/null +++ b/src/components/postprocessing/vanruesc/index.ts @@ -0,0 +1,4 @@ +export { default as EffectComposer } from './EffectComposer' +export { default as Pass } from './Pass' +export { default as EffectPass } from './EffectPass' +export { default as Effect } from './Effect'