diff --git a/src/effects/TiltShiftPass.js b/src/effects/TiltShiftPass.js new file mode 100644 index 0000000..e9c9ac1 --- /dev/null +++ b/src/effects/TiltShiftPass.js @@ -0,0 +1,53 @@ +import { Vector2 } from 'three'; +import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; +import { watch } from 'vue'; +import EffectPass from './EffectPass.js'; +import TiltShift from '../shaders/TiltShift.js'; +import useBindPropValue from '../use/useBindPropValue.js'; + +export default { + 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; + + useBindPropValue(this, 'blurRadius', uniforms.blurRadius); + useBindPropValue(this, 'gradientRadius', uniforms.gradientRadius); + + this.updateFocusLine(); + ['start', 'end'].forEach(p => { + watch(() => this[p], this.updateFocusLine); + }); + + this.pass.setSize = (width, height) => { + uniforms.texSize.value.set(width, height); + }; + }, + 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', +}; diff --git a/src/effects/ZoomBlurPass.js b/src/effects/ZoomBlurPass.js new file mode 100644 index 0000000..4330a0e --- /dev/null +++ b/src/effects/ZoomBlurPass.js @@ -0,0 +1,26 @@ +import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'; +import EffectPass from './EffectPass.js'; +import ZoomBlur from '../shaders/ZoomBlur.js'; +import useBindProp from '../use/useBindProp.js'; +import useBindPropValue from '../use/useBindPropValue.js'; + +export default { + extends: EffectPass, + props: { + center: { type: Object, default: { x: 0, y: 0 } }, + strength: { type: Number, default: 0.5 }, + }, + mounted() { + this.pass = new ShaderPass(ZoomBlur); + this.passes.push(this.pass); + + const uniforms = this.uniforms = this.pass.uniforms; + useBindProp(this, 'center', uniforms.center.value); + useBindPropValue(this, 'strength', uniforms.strength); + + this.pass.setSize = (width, height) => { + uniforms.texSize.value.set(width, height); + }; + }, + __hmrId: 'ZoomBlurPass', +}; diff --git a/src/effects/index.js b/src/effects/index.js index a607a04..5eb259b 100644 --- a/src/effects/index.js +++ b/src/effects/index.js @@ -7,4 +7,6 @@ export { default as FXAAPass } from './FXAAPass.js'; export { default as HalftonePass } from './HalftonePass.js'; export { default as SAOPass } from './SAOPass.js'; export { default as SMAAPass } from './SMAAPass.js'; +export { default as TiltShiftPass } from './TiltShiftPass.js'; export { default as UnrealBloomPass } from './UnrealBloomPass.js'; +export { default as ZoomBlurPass } from './ZoomBlurPass.js'; diff --git a/src/plugin.js b/src/plugin.js index ecf15ac..df24801 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -76,7 +76,9 @@ export const TroisJSVuePlugin = { 'RenderPass', 'SAOPass', 'SMAAPass', + 'TiltShiftPass', 'UnrealBloomPass', + 'ZoomBlurPass', 'GLTFViewer', ]; diff --git a/src/shaders/TiltShift.js b/src/shaders/TiltShift.js new file mode 100644 index 0000000..2566eb3 --- /dev/null +++ b/src/shaders/TiltShift.js @@ -0,0 +1,60 @@ +// From https://github.com/evanw/glfx.js +import { Vector2 } from 'three'; +import DefaultShader from './default'; + +export default { + uniforms: { + tDiffuse: { value: null }, + blurRadius: { value: 0 }, + gradientRadius: { value: 0 }, + start: { value: new Vector2() }, + end: { value: new Vector2() }, + delta: { value: new Vector2() }, + texSize: { value: new Vector2() }, + }, + vertexShader: DefaultShader.vertexShader, + fragmentShader: ` + uniform sampler2D tDiffuse; + uniform float blurRadius; + uniform float gradientRadius; + uniform vec2 start; + uniform vec2 end; + uniform vec2 delta; + uniform vec2 texSize; + varying vec2 vUv; + + float random(vec3 scale, float seed) { + /* use the fragment position for a different seed per-pixel */ + return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed); + } + + void main() { + vec4 color = vec4(0.0); + float total = 0.0; + + /* randomize the lookup values to hide the fixed number of samples */ + float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0); + + vec2 normal = normalize(vec2(start.y - end.y, end.x - start.x)); + float radius = smoothstep(0.0, 1.0, abs(dot(vUv * texSize - start, normal)) / gradientRadius) * blurRadius; + for (float t = -30.0; t <= 30.0; t++) { + float percent = (t + offset - 0.5) / 30.0; + float weight = 1.0 - abs(percent); + vec4 texel = texture2D(tDiffuse, vUv + delta / texSize * percent * radius); + // vec4 texel2 = texture2D(tDiffuse, vUv + vec2(-delta.y, delta.x) / texSize * percent * radius); + + /* switch to pre-multiplied alpha to correctly blur transparent images */ + texel.rgb *= texel.a; + // texel2.rgb *= texel2.a; + + color += texel * weight; + total += 2.0 * weight; + } + + gl_FragColor = color / total; + + /* switch back from pre-multiplied alpha */ + gl_FragColor.rgb /= gl_FragColor.a + 0.00001; + } + `, +}; diff --git a/src/shaders/ZoomBlur.js b/src/shaders/ZoomBlur.js new file mode 100644 index 0000000..f96f87b --- /dev/null +++ b/src/shaders/ZoomBlur.js @@ -0,0 +1,51 @@ +// From https://github.com/evanw/glfx.js +import { Vector2 } from 'three'; +import DefaultShader from './default'; + +export default { + uniforms: { + tDiffuse: { value: null }, + center: { value: new Vector2() }, + strength: { value: 0 }, + texSize: { value: new Vector2() }, + }, + vertexShader: DefaultShader.vertexShader, + fragmentShader: ` + uniform sampler2D tDiffuse; + uniform vec2 center; + uniform float strength; + uniform vec2 texSize; + varying vec2 vUv; + + float random(vec3 scale, float seed) { + /* use the fragment position for a different seed per-pixel */ + return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed); + } + + void main() { + vec4 color = vec4(0.0); + float total = 0.0; + vec2 toCenter = center - vUv * texSize; + + /* randomize the lookup values to hide the fixed number of samples */ + float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0); + + for (float t = 0.0; t <= 40.0; t++) { + float percent = (t + offset) / 40.0; + float weight = 4.0 * (percent - percent * percent); + vec4 texel = texture2D(tDiffuse, vUv + toCenter * percent * strength / texSize); + + /* switch to pre-multiplied alpha to correctly blur transparent images */ + texel.rgb *= texel.a; + + color += texel * weight; + total += weight; + } + + gl_FragColor = color / total; + + /* switch back from pre-multiplied alpha */ + gl_FragColor.rgb /= gl_FragColor.a + 0.00001; + } + `, +}; diff --git a/src/shaders/default.js b/src/shaders/default.js new file mode 100644 index 0000000..a4d2818 --- /dev/null +++ b/src/shaders/default.js @@ -0,0 +1,16 @@ +export default { + uniforms: {}, + vertexShader: ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragmentShader: ` + varying vec2 vUv; + void main() { + gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); + } + `, +}; diff --git a/src/use/useBindPropValue.js b/src/use/useBindPropValue.js new file mode 100644 index 0000000..2650c09 --- /dev/null +++ b/src/use/useBindPropValue.js @@ -0,0 +1,10 @@ +import { watch } from 'vue'; + +export default function useBindPropValue(src, srcProp, dst, dstProp = 'value') { + if (src[srcProp]) { + dst[dstProp] = src[srcProp]; + watch(() => src[srcProp], (value) => { + dst[dstProp] = value; + }); + } +};