1
0
mirror of https://github.com/troisjs/trois.git synced 2024-11-24 04:12:02 +08:00

wip: effects

This commit is contained in:
Kevin Levron 2021-04-20 12:41:04 +02:00
parent 1da2a90c67
commit 81852fee78
23 changed files with 346 additions and 350 deletions

View File

@ -1,44 +0,0 @@
import { defineComponent } from 'vue';
import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';
import EffectPass from './EffectPass.js';
export default defineComponent({
extends: EffectPass,
props: {
focus: {
type: Number,
default: 1,
},
aperture: {
type: Number,
default: 0.025,
},
maxblur: {
type: Number,
default: 0.01,
},
},
watch: {
focus() { this.pass.uniforms.focus.value = this.focus; },
aperture() { this.pass.uniforms.aperture.value = this.aperture; },
maxblur() { this.pass.uniforms.maxblur.value = this.maxblur; },
},
mounted() {
if (!this.three.scene) {
console.error('Missing Scene');
}
if (!this.three.camera) {
console.error('Missing Camera');
}
const params = {
focus: this.focus,
aperture: this.aperture,
maxblur: this.maxblur,
width: this.three.size.width,
height: this.three.size.height,
};
const pass = new BokehPass(this.three.scene, this.three.camera, params);
this.completePass(pass);
},
__hmrId: 'BokehPass',
});

42
src/effects/BokehPass.ts Normal file
View File

@ -0,0 +1,42 @@
import { defineComponent, watch } from 'vue'
import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js'
import EffectPass from './EffectPass'
const props = {
focus: { type: Number, default: 1 },
aperture: { type: Number, default: 0.025 },
maxblur: { type: Number, default: 0.01 },
}
export default defineComponent({
extends: EffectPass,
props,
created() {
if (!this.three.scene) {
console.error('Missing Scene')
return
}
if (!this.three.camera) {
console.error('Missing Camera')
return
}
const params = {
focus: this.focus,
aperture: this.aperture,
maxblur: this.maxblur,
width: this.three.size.width,
height: this.three.size.height,
}
const pass = new BokehPass(this.three.scene, this.three.camera, params)
Object.keys(props).forEach(p => {
// @ts-ignore
watch(() => this[p], (value) => { pass.uniforms[p].value = value })
})
this.initEffectPass(pass)
},
__hmrId: 'BokehPass',
})

View File

@ -1,41 +0,0 @@
import { defineComponent } from 'vue';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
export default defineComponent({
setup() {
return {
passes: [],
};
},
inject: ['three'],
provide() {
return {
passes: this.passes,
};
},
mounted() {
this.three.onAfterInit(() => {
this.composer = new EffectComposer(this.three.renderer);
this.three.renderer.autoClear = false;
this.passes.forEach(pass => {
this.composer.addPass(pass);
});
this.three.composer = this.composer;
this.resize();
this.three.onAfterResize(this.resize);
});
},
unmounted() {
this.three.offAfterResize(this.resize);
},
methods: {
resize() {
this.composer.setSize(this.three.size.width, this.three.size.height);
},
},
render() {
return this.$slots.default();
},
__hmrId: 'EffectComposer',
});

View File

@ -0,0 +1,59 @@
import { defineComponent, inject } from 'vue'
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { ThreeInterface } from '../core/useThree'
import { Pass } from 'three/examples/jsm/postprocessing/Pass'
interface EffectComposerSetupInterface {
three: ThreeInterface
// passes: Pass[]
composer?: EffectComposer
}
export interface EffectComposerInterface extends EffectComposerSetupInterface {
addPass(pass: Pass): void
removePass(pass: Pass): void
}
export default defineComponent({
setup(): EffectComposerSetupInterface {
const three = inject('three') as ThreeInterface
return {
three,
// passes: [],
}
},
provide() {
return {
composer: this,
}
},
created() {
const composer = new EffectComposer(this.three.renderer)
this.composer = composer
this.three.composer = composer
this.three.onAfterInit(() => {
this.three.renderer.autoClear = false
this.resize()
this.three.onAfterResize(this.resize)
})
},
unmounted() {
this.three.offAfterResize(this.resize)
},
methods: {
addPass(pass: Pass) {
this.composer?.addPass(pass)
},
removePass(pass: Pass) {
this.composer?.removePass(pass)
},
resize() {
this.composer?.setSize(this.three.size.width, this.three.size.height)
},
},
render() {
return this.$slots.default ? this.$slots.default() : []
},
__hmrId: 'EffectComposer',
})

View File

@ -1,25 +0,0 @@
import { defineComponent } from 'vue';
export default defineComponent({
inject: ['three', 'passes'],
emits: ['ready'],
beforeMount() {
if (!this.passes) {
console.error('Missing parent EffectComposer');
}
},
unmounted() {
if (this.pass.dispose) this.pass.dispose();
},
methods: {
completePass(pass) {
this.passes.push(pass);
this.pass = pass;
this.$emit('ready', pass);
},
},
render() {
return [];
},
__hmrId: 'EffectPass',
});

42
src/effects/EffectPass.ts Normal file
View File

@ -0,0 +1,42 @@
import { Pass } from 'three/examples/jsm/postprocessing/Pass'
import { defineComponent, inject } from 'vue'
import { ThreeInterface } from '../core/useThree'
import { EffectComposerInterface } from './EffectComposer'
interface EffectSetupInterface {
three: ThreeInterface
composer: EffectComposerInterface
pass?: Pass
}
export default defineComponent({
inject: ['three', 'composer'],
emits: ['ready'],
setup(): EffectSetupInterface {
const three = inject('three') as ThreeInterface
const composer = inject('composer') as EffectComposerInterface
return { three, composer }
},
created() {
if (!this.composer) {
console.error('Missing parent EffectComposer')
}
},
unmounted() {
if (this.pass) {
this.composer.removePass(this.pass);
(this.pass as any).dispose?.()
}
},
methods: {
initEffectPass(pass: Pass) {
this.pass = pass
this.composer.addPass(pass)
this.$emit('ready', pass)
},
},
render() {
return []
},
__hmrId: 'EffectPass',
})

View File

@ -1,26 +0,0 @@
import { defineComponent } from 'vue';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js';
import EffectPass from './EffectPass.js';
export default defineComponent({
extends: EffectPass,
mounted() {
const pass = new ShaderPass(FXAAShader);
this.completePass(pass);
// resize will be called in three init
this.three.onAfterResize(this.resize);
},
unmounted() {
this.three.offAfterResize(this.resize);
},
methods: {
resize() {
const { resolution } = this.pass.material.uniforms;
resolution.value.x = 1 / this.three.size.width;
resolution.value.y = 1 / this.three.size.height;
},
},
__hmrId: 'FXAAPass',
});

30
src/effects/FXAAPass.ts Normal file
View File

@ -0,0 +1,30 @@
import { defineComponent } from 'vue'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'
import EffectPass from './EffectPass'
import { ThreeResizeEventInterface } from '../core/useThree'
export default defineComponent({
extends: EffectPass,
created() {
const pass = new ShaderPass(FXAAShader)
// resize will be called in three init
this.three.onAfterResize(this.resize)
this.initEffectPass(pass)
},
unmounted() {
this.three.offAfterResize(this.resize)
},
methods: {
resize({ size }: ThreeResizeEventInterface) {
if (this.pass) {
const { resolution } = (this.pass as ShaderPass).material.uniforms
resolution.value.x = 1 / size.width
resolution.value.y = 1 / size.height
}
},
},
__hmrId: 'FXAAPass',
})

View File

@ -1,24 +0,0 @@
import { defineComponent } from 'vue';
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass.js';
import EffectPass from './EffectPass.js';
export default defineComponent({
extends: EffectPass,
props: {
noiseIntensity: { type: Number, default: 0.5 },
scanlinesIntensity: { type: Number, default: 0.05 },
scanlinesCount: { type: Number, default: 4096 },
grayscale: { type: Number, default: 0 },
},
watch: {
noiseIntensity() { this.pass.uniforms.nIntensity.value = this.noiseIntensity; },
scanlinesIntensity() { this.pass.uniforms.sIntensity.value = this.scanlinesIntensity; },
scanlinesCount() { this.pass.uniforms.sCount.value = this.scanlinesCount; },
grayscale() { this.pass.uniforms.grayscale.value = this.grayscale; },
},
mounted() {
const pass = new FilmPass(this.noiseIntensity, this.scanlinesIntensity, this.scanlinesCount, this.grayscale);
this.completePass(pass);
},
__hmrId: 'FilmPass',
});

26
src/effects/FilmPass.ts Normal file
View File

@ -0,0 +1,26 @@
import { defineComponent, watch } from 'vue'
import { FilmPass } from 'three/examples/jsm/postprocessing/FilmPass.js'
import EffectPass from './EffectPass'
const props = {
noiseIntensity: { type: Number, default: 0.5 },
scanlinesIntensity: { type: Number, default: 0.05 },
scanlinesCount: { type: Number, default: 4096 },
grayscale: { type: Number, default: 0 },
}
export default defineComponent({
extends: EffectPass,
props,
created() {
const pass = new FilmPass(this.noiseIntensity, this.scanlinesIntensity, this.scanlinesCount, this.grayscale)
Object.keys(props).forEach(p => {
// @ts-ignore
watch(() => this[p], (value) => { pass.uniforms[p].value = value })
})
this.initEffectPass(pass)
},
__hmrId: 'FilmPass',
})

View File

@ -1,28 +0,0 @@
import { defineComponent, watch } from 'vue';
import { HalftonePass } from 'three/examples/jsm/postprocessing/HalftonePass.js';
import EffectPass from './EffectPass.js';
export default defineComponent({
extends: EffectPass,
props: {
shape: { type: Number, default: 1 },
radius: { type: Number, default: 4 },
rotateR: { type: Number, default: Math.PI / 12 * 1 },
rotateG: { type: Number, default: Math.PI / 12 * 2 },
rotateB: { type: Number, default: Math.PI / 12 * 3 },
scatter: { type: Number, default: 0 },
},
mounted() {
const pass = new HalftonePass(this.three.size.width, this.three.size.height, {});
['shape', 'radius', 'rotateR', 'rotateG', 'rotateB', 'scatter'].forEach(p => {
pass.uniforms[p].value = this[p];
watch(() => this[p], () => {
pass.uniforms[p].value = this[p];
});
});
this.completePass(pass);
},
__hmrId: 'HalftonePass',
});

View File

@ -0,0 +1,30 @@
import { defineComponent, watch } from 'vue'
import { HalftonePass } from 'three/examples/jsm/postprocessing/HalftonePass.js'
import EffectPass from './EffectPass'
const props = {
shape: { type: Number, default: 1 },
radius: { type: Number, default: 4 },
rotateR: { type: Number, default: Math.PI / 12 * 1 },
rotateG: { type: Number, default: Math.PI / 12 * 2 },
rotateB: { type: Number, default: Math.PI / 12 * 3 },
scatter: { type: Number, default: 0 },
}
export default defineComponent({
extends: EffectPass,
props,
created() {
const pass = new HalftonePass(this.three.size.width, this.three.size.height, {})
Object.keys(props).forEach(p => {
// @ts-ignore
pass.uniforms[p].value = this[p]
// @ts-ignore
watch(() => this[p], (value) => { pass.uniforms[p].value = value })
})
this.initEffectPass(pass)
},
__hmrId: 'HalftonePass',
})

View File

@ -1,18 +1,20 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'; import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import EffectPass from './EffectPass.js'; import EffectPass from './EffectPass'
export default defineComponent({ export default defineComponent({
extends: EffectPass, extends: EffectPass,
mounted() { created() {
if (!this.three.scene) { if (!this.three.scene) {
console.error('Missing Scene'); console.error('Missing Scene')
return
} }
if (!this.three.camera) { if (!this.three.camera) {
console.error('Missing Camera'); console.error('Missing Camera')
return
} }
const pass = new RenderPass(this.three.scene, this.three.camera); const pass = new RenderPass(this.three.scene, this.three.camera)
this.completePass(pass); this.initEffectPass(pass)
}, },
__hmrId: 'RenderPass', __hmrId: 'RenderPass',
}); })

View File

@ -1,13 +1,13 @@
import { defineComponent } from 'vue'; import { defineComponent } from 'vue'
import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js'; import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js'
import EffectPass from './EffectPass.js'; import EffectPass from './EffectPass'
export default defineComponent({ export default defineComponent({
extends: EffectPass, extends: EffectPass,
mounted() { created() {
// three size is not set yet, but this pass will be resized by effect composer // three size is not set yet, but this pass will be resized by effect composer
const pass = new SMAAPass(this.three.size.width, this.three.size.height); const pass = new SMAAPass(this.three.size.width, this.three.size.height)
this.completePass(pass); this.initEffectPass(pass)
}, },
__hmrId: 'SMAAPass', __hmrId: 'SMAAPass',
}); })

View File

@ -1,30 +0,0 @@
import { defineComponent } from 'vue';
import { SSAOPass } from 'three/examples/jsm/postprocessing/SSAOPass.js';
import EffectPass from './EffectPass.js';
export default defineComponent({
extends: EffectPass,
props: {
scene: null,
camera: null,
options: {
type: Object,
default: () => ({}),
},
},
mounted() {
const pass = new SSAOPass(
this.scene || this.three.scene,
this.camera || this.three.camera,
this.three.size.width,
this.three.size.height
);
for (const key of Object.keys(this.options)) {
pass[key] = this.options[key];
}
this.completePass(pass);
},
__hmrId: 'SSAOPass',
});

38
src/effects/SSAOPass.ts Normal file
View File

@ -0,0 +1,38 @@
import { defineComponent } from 'vue'
import { SSAOPass } from 'three/examples/jsm/postprocessing/SSAOPass.js'
import EffectPass from './EffectPass'
export default defineComponent({
extends: EffectPass,
props: {
options: {
type: Object,
default: () => ({}),
},
},
created() {
if (!this.three.scene) {
console.error('Missing Scene')
return
}
if (!this.three.camera) {
console.error('Missing Camera')
return
}
const pass = new SSAOPass(
this.three.scene,
this.three.camera,
this.three.size.width,
this.three.size.height
)
Object.keys(this.options).forEach(key => {
// @ts-ignore
pass[key] = this.options[key]
})
this.initEffectPass(pass)
},
__hmrId: 'SSAOPass',
})

View File

@ -1,57 +0,0 @@
import { defineComponent, watch } from 'vue';
import { Vector2 } from 'three';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import EffectPass from './EffectPass.js';
import TiltShift from '../shaders/TiltShift.js';
import { bindProp } from '../tools';
export default defineComponent({
extends: EffectPass,
props: {
blurRadius: { type: Number, default: 10 },
gradientRadius: { type: Number, default: 100 },
start: { type: Object, default: { x: 0, y: 100 } },
end: { type: Object, default: { x: 10, y: 100 } },
},
mounted() {
this.pass = new ShaderPass(TiltShift);
this.passes.push(this.pass);
this.pass1 = new ShaderPass(TiltShift);
this.passes.push(this.pass1);
const uniforms = this.uniforms = this.pass.uniforms;
const uniforms1 = this.uniforms1 = this.pass1.uniforms;
uniforms1.blurRadius = uniforms.blurRadius;
uniforms1.gradientRadius = uniforms.gradientRadius;
uniforms1.start = uniforms.start;
uniforms1.end = uniforms.end;
uniforms1.texSize = uniforms.texSize;
bindProp(this, 'blurRadius', uniforms.blurRadius, 'value');
bindProp(this, 'gradientRadius', uniforms.gradientRadius, 'value');
this.updateFocusLine();
['start', 'end'].forEach(p => {
watch(() => this[p], this.updateFocusLine, { deep: true });
});
this.pass.setSize = (width, height) => {
uniforms.texSize.value.set(width, height);
};
// emit ready event with two passes - do so manually in this file instead
// of calling `completePass` like in other effect types
this.$emit('ready', [this.pass, this.pass1]);
},
methods: {
updateFocusLine() {
this.uniforms.start.value.copy(this.start);
this.uniforms.end.value.copy(this.end);
const dv = new Vector2().copy(this.end).sub(this.start).normalize();
this.uniforms.delta.value.copy(dv);
this.uniforms1.delta.value.set(-dv.y, dv.x);
},
},
__hmrId: 'TiltShiftPass',
});

View File

@ -1,24 +0,0 @@
import { defineComponent } from 'vue';
import { Vector2 } from 'three';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import EffectPass from './EffectPass.js';
export default defineComponent({
extends: EffectPass,
props: {
strength: { type: Number, default: 1.5 },
radius: { type: Number, default: 0 },
threshold: { type: Number, default: 0 },
},
watch: {
strength() { this.pass.strength = this.strength; },
radius() { this.pass.radius = this.radius; },
threshold() { this.pass.threshold = this.threshold; },
},
mounted() {
const size = new Vector2(this.three.size.width, this.three.size.height);
const pass = new UnrealBloomPass(size, this.strength, this.radius, this.threshold);
this.completePass(pass);
},
__hmrId: 'UnrealBloomPass',
});

View File

@ -0,0 +1,27 @@
import { defineComponent, watch } from 'vue'
import { Vector2 } from 'three'
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'
import EffectPass from './EffectPass'
const props = {
strength: { type: Number, default: 1.5 },
radius: { type: Number, default: 0 },
threshold: { type: Number, default: 0 },
}
export default defineComponent({
extends: EffectPass,
props,
created() {
const size = new Vector2(this.three.size.width, this.three.size.height)
const pass = new UnrealBloomPass(size, this.strength, this.radius, this.threshold)
Object.keys(props).forEach(p => {
// @ts-ignore
watch(() => this[p], (value) => { pass.uniforms[p].value = value })
})
this.initEffectPass(pass)
},
__hmrId: 'UnrealBloomPass',
})

View File

@ -1,23 +0,0 @@
import { defineComponent } from 'vue';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import EffectPass from './EffectPass.js';
import ZoomBlur from '../shaders/ZoomBlur.js';
import { bindProp } from '../tools';
export default defineComponent({
extends: EffectPass,
props: {
center: { type: Object, default: { x: 0.5, y: 0.5 } },
strength: { type: Number, default: 0.5 },
},
mounted() {
const pass = new ShaderPass(ZoomBlur);
const uniforms = this.uniforms = pass.uniforms;
bindProp(this, 'center', uniforms.center, 'value');
bindProp(this, 'strength', uniforms.strength, 'value');
this.completePass(pass);
},
__hmrId: 'ZoomBlurPass',
});

View File

@ -0,0 +1,22 @@
import { defineComponent } from 'vue'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import EffectPass from './EffectPass'
import ZoomBlur from '../shaders/ZoomBlur'
import { bindProp } from '../tools'
export default defineComponent({
extends: EffectPass,
props: {
center: { type: Object, default: () => ({ x: 0.5, y: 0.5 }) },
strength: { type: Number, default: 0.5 },
},
created() {
const pass = new ShaderPass(ZoomBlur)
bindProp(this, 'center', pass.uniforms.center, 'value')
bindProp(this, 'strength', pass.uniforms.strength, 'value')
this.initEffectPass(pass)
},
__hmrId: 'ZoomBlurPass',
})

View File

@ -1,12 +0,0 @@
export { default as EffectComposer } from './EffectComposer.js';
export { default as RenderPass } from './RenderPass.js';
export { default as BokehPass } from './BokehPass.js';
export { default as FilmPass } from './FilmPass.js';
export { default as FXAAPass } from './FXAAPass.js';
export { default as HalftonePass } from './HalftonePass.js';
export { default as SMAAPass } from './SMAAPass.js';
export { default as SSAOPass } from './SSAOPass.js';
export { default as TiltShiftPass } from './TiltShiftPass.js';
export { default as UnrealBloomPass } from './UnrealBloomPass.js';
export { default as ZoomBlurPass } from './ZoomBlurPass.js';

12
src/effects/index.ts Normal file
View File

@ -0,0 +1,12 @@
export { default as EffectComposer } from './EffectComposer'
export { default as RenderPass } from './RenderPass'
export { default as BokehPass } from './BokehPass'
export { default as FilmPass } from './FilmPass'
export { default as FXAAPass } from './FXAAPass'
export { default as HalftonePass } from './HalftonePass'
export { default as SMAAPass } from './SMAAPass'
export { default as SSAOPass } from './SSAOPass'
export { default as TiltShiftPass } from './TiltShiftPass'
export { default as UnrealBloomPass } from './UnrealBloomPass'
export { default as ZoomBlurPass } from './ZoomBlurPass'