mirror of
https://github.com/troisjs/trois.git
synced 2024-11-24 04:12:02 +08:00
remove components
This commit is contained in:
parent
fff36604c4
commit
ce7958201a
@ -1,204 +0,0 @@
|
|||||||
import {
|
|
||||||
FloatType,
|
|
||||||
Mesh,
|
|
||||||
NearestFilter,
|
|
||||||
OrthographicCamera,
|
|
||||||
PlaneGeometry,
|
|
||||||
RGBAFormat,
|
|
||||||
ShaderMaterial,
|
|
||||||
Uniform,
|
|
||||||
Vector2,
|
|
||||||
WebGLRenderTarget,
|
|
||||||
} from 'three'
|
|
||||||
|
|
||||||
// shaders from https://github.com/evanw/webgl-water
|
|
||||||
function LiquidEffect(renderer) {
|
|
||||||
this.renderer = renderer
|
|
||||||
this.width = 512
|
|
||||||
this.height = 512
|
|
||||||
// this.delta = new Vector2(this.width / Math.pow(width, 2), this.height / Math.pow(height, 2));
|
|
||||||
this.delta = new Vector2(1 / this.width, 1 / this.height)
|
|
||||||
|
|
||||||
const targetOptions = {
|
|
||||||
minFilter: NearestFilter,
|
|
||||||
magFilter: NearestFilter,
|
|
||||||
type: FloatType,
|
|
||||||
format: RGBAFormat,
|
|
||||||
depthBuffer: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.hMap = new WebGLRenderTarget(this.width, this.height, targetOptions)
|
|
||||||
this.hMap1 = new WebGLRenderTarget(this.width, this.height, targetOptions)
|
|
||||||
this.fsQuad = new FullScreenQuad()
|
|
||||||
|
|
||||||
this.initShaders()
|
|
||||||
}
|
|
||||||
|
|
||||||
LiquidEffect.prototype.initShaders = function () {
|
|
||||||
const defaultVertexShader = `
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
this.copyMat = new ShaderMaterial({
|
|
||||||
uniforms: { tDiffuse: { value: null } },
|
|
||||||
vertexShader: defaultVertexShader,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform sampler2D tDiffuse;
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = texture2D(tDiffuse, vUv);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.updateMat = new ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
tDiffuse: { value: null },
|
|
||||||
delta: new Uniform(this.delta),
|
|
||||||
},
|
|
||||||
vertexShader: defaultVertexShader,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform sampler2D tDiffuse;
|
|
||||||
uniform vec2 delta;
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
vec4 texel = texture2D(tDiffuse, vUv);
|
|
||||||
|
|
||||||
vec2 dx = vec2(delta.x, 0.0);
|
|
||||||
vec2 dy = vec2(0.0, delta.y);
|
|
||||||
float average = (
|
|
||||||
texture2D(tDiffuse, vUv - dx).r +
|
|
||||||
texture2D(tDiffuse, vUv - dy).r +
|
|
||||||
texture2D(tDiffuse, vUv + dx).r +
|
|
||||||
texture2D(tDiffuse, vUv + dy).r
|
|
||||||
) * 0.25;
|
|
||||||
texel.g += (average - texel.r) * 2.0;
|
|
||||||
texel.g *= 0.995;
|
|
||||||
texel.r += texel.g;
|
|
||||||
|
|
||||||
gl_FragColor = texel;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.normalsMat = new ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
tDiffuse: { value: null },
|
|
||||||
delta: new Uniform(this.delta),
|
|
||||||
},
|
|
||||||
vertexShader: defaultVertexShader,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform sampler2D tDiffuse;
|
|
||||||
uniform vec2 delta;
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
vec4 texel = texture2D(tDiffuse, vUv);
|
|
||||||
vec3 dx = vec3(delta.x, texture2D(tDiffuse, vec2(vUv.x + delta.x, vUv.y)).r - texel.r, 0.0);
|
|
||||||
vec3 dy = vec3(0.0, texture2D(tDiffuse, vec2(vUv.x, vUv.y + delta.y)).r - texel.r, delta.y);
|
|
||||||
texel.ba = normalize(cross(dy, dx)).xz;
|
|
||||||
gl_FragColor = texel;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.dropMat = new ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
tDiffuse: { value: null },
|
|
||||||
center: new Uniform(new Vector2()),
|
|
||||||
radius: { value: 0.05 },
|
|
||||||
strength: { value: 0.5 },
|
|
||||||
},
|
|
||||||
vertexShader: defaultVertexShader,
|
|
||||||
fragmentShader: `
|
|
||||||
const float PI = 3.1415926535897932384626433832795;
|
|
||||||
uniform sampler2D tDiffuse;
|
|
||||||
uniform vec2 center;
|
|
||||||
uniform float radius;
|
|
||||||
uniform float strength;
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
vec4 texel = texture2D(tDiffuse, vUv);
|
|
||||||
float drop = max(0.0, 1.0 - length(center * 0.5 + 0.5 - vUv) / radius);
|
|
||||||
drop = 0.5 - cos(drop * PI) * 0.5;
|
|
||||||
texel.r += drop * strength;
|
|
||||||
// texel.r = clamp(texel.r, -2.0, 2.0);
|
|
||||||
gl_FragColor = texel;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
LiquidEffect.prototype.update = function () {
|
|
||||||
this.updateHMap()
|
|
||||||
// this.updateHMap();
|
|
||||||
this.updateHMapNormals()
|
|
||||||
}
|
|
||||||
|
|
||||||
LiquidEffect.prototype.updateHMap = function () {
|
|
||||||
this.updateMat.uniforms.tDiffuse.value = this.hMap.texture
|
|
||||||
this.renderShaderMat(this.updateMat, this.hMap1)
|
|
||||||
this.swapBuffers()
|
|
||||||
}
|
|
||||||
|
|
||||||
LiquidEffect.prototype.updateHMapNormals = function () {
|
|
||||||
this.normalsMat.uniforms.tDiffuse.value = this.hMap.texture
|
|
||||||
this.renderShaderMat(this.normalsMat, this.hMap1)
|
|
||||||
this.swapBuffers()
|
|
||||||
}
|
|
||||||
|
|
||||||
LiquidEffect.prototype.addDrop = function (x, y, radius, strength) {
|
|
||||||
this.dropMat.uniforms.tDiffuse.value = this.hMap.texture
|
|
||||||
this.dropMat.uniforms.center.value.set(x, y)
|
|
||||||
this.dropMat.uniforms.radius.value = radius
|
|
||||||
this.dropMat.uniforms.strength.value = strength
|
|
||||||
this.renderShaderMat(this.dropMat, this.hMap1)
|
|
||||||
this.swapBuffers()
|
|
||||||
}
|
|
||||||
|
|
||||||
// LiquidEffect.prototype.renderBuffer = function (buffer, target) {
|
|
||||||
// this.copyMat.uniforms.tDiffuse.value = buffer.texture;
|
|
||||||
// this.renderShaderMat(this.copyMat, target);
|
|
||||||
// };
|
|
||||||
|
|
||||||
LiquidEffect.prototype.renderShaderMat = function (mat, target) {
|
|
||||||
this.fsQuad.material = mat
|
|
||||||
const oldTarget = this.renderer.getRenderTarget()
|
|
||||||
this.renderer.setRenderTarget(target)
|
|
||||||
this.fsQuad.render(this.renderer)
|
|
||||||
this.renderer.setRenderTarget(oldTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
LiquidEffect.prototype.swapBuffers = function () {
|
|
||||||
const temp = this.hMap
|
|
||||||
this.hMap = this.hMap1
|
|
||||||
this.hMap1 = temp
|
|
||||||
}
|
|
||||||
|
|
||||||
// from https://threejs.org/examples/js/postprocessing/EffectComposer.js
|
|
||||||
const FullScreenQuad = (function () {
|
|
||||||
const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1)
|
|
||||||
const geometry = new PlaneGeometry(2, 2)
|
|
||||||
|
|
||||||
const FullScreenQuad = function (material) {
|
|
||||||
this._mesh = new Mesh(geometry, material)
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(FullScreenQuad.prototype, 'material', {
|
|
||||||
get: function () { return this._mesh.material },
|
|
||||||
set: function (value) { this._mesh.material = value },
|
|
||||||
})
|
|
||||||
|
|
||||||
Object.assign(FullScreenQuad.prototype, {
|
|
||||||
render: function (renderer) {
|
|
||||||
renderer.render(this._mesh, camera)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return FullScreenQuad
|
|
||||||
})()
|
|
||||||
|
|
||||||
export default LiquidEffect
|
|
@ -1,54 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue'
|
|
||||||
import { DoubleSide, Mesh, MeshStandardMaterial, PlaneGeometry } from 'three'
|
|
||||||
import { bindProps, Object3D } from '../../../build/trois.module.js'
|
|
||||||
import LiquidEffect from './LiquidEffect.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Object3D,
|
|
||||||
props: {
|
|
||||||
width: { type: Number, default: 10 },
|
|
||||||
height: { type: Number, default: 10 },
|
|
||||||
widthSegments: { type: Number, default: 200 },
|
|
||||||
heightSegments: { type: Number, default: 200 },
|
|
||||||
color: { type: [Number, String], default: '#ffffff' },
|
|
||||||
metalness: { type: Number, default: 0.75 },
|
|
||||||
roughness: { type: Number, default: 0.25 },
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.liquidEffect = new LiquidEffect(this.renderer.renderer)
|
|
||||||
this.renderer.onMounted(() => {
|
|
||||||
this.liquidEffect.renderer = this.renderer.renderer
|
|
||||||
this.renderer.onBeforeRender(this.update)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.material = new MeshStandardMaterial({
|
|
||||||
color: this.color, side: DoubleSide, metalness: this.metalness, roughness: this.roughness,
|
|
||||||
onBeforeCompile: shader => {
|
|
||||||
shader.uniforms.hmap = { value: this.liquidEffect.hMap.texture }
|
|
||||||
shader.vertexShader = "uniform sampler2D hmap;\n" + shader.vertexShader
|
|
||||||
const token = '#include <begin_vertex>'
|
|
||||||
const customTransform = `
|
|
||||||
vec3 transformed = vec3(position);
|
|
||||||
vec4 info = texture2D(hmap, uv);
|
|
||||||
vNormal = vec3(info.b, sqrt(1.0 - dot(info.ba, info.ba)), info.a).xzy;
|
|
||||||
transformed.z = 20. * info.r;
|
|
||||||
`
|
|
||||||
shader.vertexShader = shader.vertexShader.replace(token, customTransform)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
bindProps(this, ['metalness', 'roughness'], this.material)
|
|
||||||
watch(() => this.color, (value) => this.material.color.set(value))
|
|
||||||
|
|
||||||
this.geometry = new PlaneGeometry(this.width, this.height, this.widthSegments, this.heightSegments)
|
|
||||||
this.mesh = new Mesh(this.geometry, this.material)
|
|
||||||
this.initObject3D(this.mesh)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.renderer.offBeforeRender(this.update)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
update() {
|
|
||||||
this.liquidEffect.update()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
@ -1,73 +0,0 @@
|
|||||||
import { defineComponent } from 'vue'
|
|
||||||
import {
|
|
||||||
BackSide,
|
|
||||||
CubeCamera,
|
|
||||||
FrontSide,
|
|
||||||
LinearMipmapLinearFilter,
|
|
||||||
Mesh as TMesh,
|
|
||||||
RGBFormat,
|
|
||||||
WebGLCubeRenderTarget,
|
|
||||||
} from 'three'
|
|
||||||
|
|
||||||
import { bindProp, Mesh } from '../../../build/trois.module.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Mesh,
|
|
||||||
props: {
|
|
||||||
cubeRTSize: { type: Number, default: 256 },
|
|
||||||
cubeCameraNear: { type: Number, default: 0.1 },
|
|
||||||
cubeCameraFar: { type: Number, default: 2000 },
|
|
||||||
autoUpdate: Boolean,
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.initGem()
|
|
||||||
if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT)
|
|
||||||
else this.renderer.onMounted(this.updateCubeRT)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.renderer.offBeforeRender(this.updateCubeRT)
|
|
||||||
if (this.cubeCamera) this.removeFromParent(this.cubeCamera)
|
|
||||||
if (this.meshBack) this.removeFromParent(this.meshBack)
|
|
||||||
if (this.materialBack) this.materialBack.dispose()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initGem() {
|
|
||||||
const cubeRT = new WebGLCubeRenderTarget(this.cubeRTSize, { format: RGBFormat, generateMipmaps: true, minFilter: LinearMipmapLinearFilter })
|
|
||||||
this.cubeCamera = new CubeCamera(this.cubeCameraNear, this.cubeCameraFar, cubeRT)
|
|
||||||
bindProp(this, 'position', this.cubeCamera)
|
|
||||||
this.addToParent(this.cubeCamera)
|
|
||||||
|
|
||||||
this.material.side = FrontSide
|
|
||||||
this.material.envMap = cubeRT.texture
|
|
||||||
this.material.envMapIntensity = 10
|
|
||||||
this.material.metalness = 0
|
|
||||||
this.material.roughness = 0
|
|
||||||
this.material.opacity = 0.75
|
|
||||||
this.material.transparent = true
|
|
||||||
this.material.premultipliedAlpha = true
|
|
||||||
this.material.needsUpdate = true
|
|
||||||
|
|
||||||
this.materialBack = this.material.clone()
|
|
||||||
this.materialBack.side = BackSide
|
|
||||||
this.materialBack.envMapIntensity = 5
|
|
||||||
this.materialBack.metalness = 1
|
|
||||||
this.materialBack.roughness = 0
|
|
||||||
this.materialBack.opacity = 0.5
|
|
||||||
|
|
||||||
this.meshBack = new TMesh(this.geometry, this.materialBack)
|
|
||||||
|
|
||||||
bindProp(this, 'position', this.meshBack)
|
|
||||||
bindProp(this, 'rotation', this.meshBack)
|
|
||||||
bindProp(this, 'scale', this.meshBack)
|
|
||||||
this.addToParent(this.meshBack)
|
|
||||||
},
|
|
||||||
updateCubeRT() {
|
|
||||||
this.mesh.visible = false
|
|
||||||
this.meshBack.visible = false
|
|
||||||
this.cubeCamera.update(this.renderer.renderer, this.scene)
|
|
||||||
this.mesh.visible = true
|
|
||||||
this.meshBack.visible = true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
__hmrId: 'Gem',
|
|
||||||
})
|
|
@ -1,44 +0,0 @@
|
|||||||
import { defineComponent } from 'vue'
|
|
||||||
import {
|
|
||||||
CubeCamera,
|
|
||||||
LinearMipmapLinearFilter,
|
|
||||||
RGBFormat,
|
|
||||||
WebGLCubeRenderTarget,
|
|
||||||
} from 'three'
|
|
||||||
|
|
||||||
import { Mesh } from '../../../build/trois.module.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Mesh,
|
|
||||||
props: {
|
|
||||||
cubeRTSize: { type: Number, default: 256 },
|
|
||||||
cubeCameraNear: { type: Number, default: 0.1 },
|
|
||||||
cubeCameraFar: { type: Number, default: 2000 },
|
|
||||||
autoUpdate: Boolean,
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.initMirrorMesh()
|
|
||||||
if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT)
|
|
||||||
else this.renderer.onMounted(this.updateCubeRT)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.renderer.offBeforeRender(this.updateCubeRT)
|
|
||||||
if (this.cubeCamera) this.removeFromParent(this.cubeCamera)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initMirrorMesh() {
|
|
||||||
const cubeRT = new WebGLCubeRenderTarget(this.cubeRTSize, { format: RGBFormat, generateMipmaps: true, minFilter: LinearMipmapLinearFilter })
|
|
||||||
this.cubeCamera = new CubeCamera(this.cubeCameraNear, this.cubeCameraFar, cubeRT)
|
|
||||||
this.addToParent(this.cubeCamera)
|
|
||||||
|
|
||||||
this.material.envMap = cubeRT.texture
|
|
||||||
this.material.needsUpdate = true
|
|
||||||
},
|
|
||||||
updateCubeRT() {
|
|
||||||
this.mesh.visible = false
|
|
||||||
this.cubeCamera.update(this.renderer.renderer, this.scene)
|
|
||||||
this.mesh.visible = true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
__hmrId: 'MirrorMesh',
|
|
||||||
})
|
|
@ -1,48 +0,0 @@
|
|||||||
import { defineComponent } from 'vue'
|
|
||||||
import {
|
|
||||||
CubeCamera,
|
|
||||||
CubeRefractionMapping,
|
|
||||||
LinearMipmapLinearFilter,
|
|
||||||
RGBFormat,
|
|
||||||
WebGLCubeRenderTarget,
|
|
||||||
} from 'three'
|
|
||||||
|
|
||||||
import { bindProp, Mesh } from '../../../build/trois.module.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Mesh,
|
|
||||||
props: {
|
|
||||||
cubeRTSize: { type: Number, default: 256 },
|
|
||||||
cubeCameraNear: { type: Number, default: 0.1 },
|
|
||||||
cubeCameraFar: { type: Number, default: 2000 },
|
|
||||||
refractionRatio: { type: Number, default: 0.98 },
|
|
||||||
autoUpdate: Boolean,
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.initMirrorMesh()
|
|
||||||
if (this.autoUpdate) this.renderer.onBeforeRender(this.updateCubeRT)
|
|
||||||
else this.renderer.onMounted(this.updateCubeRT)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.renderer.offBeforeRender(this.updateCubeRT)
|
|
||||||
if (this.cubeCamera) this.removeFromParent(this.cubeCamera)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
initMirrorMesh() {
|
|
||||||
const cubeRT = new WebGLCubeRenderTarget(this.cubeRTSize, { mapping: CubeRefractionMapping, format: RGBFormat, generateMipmaps: true, minFilter: LinearMipmapLinearFilter })
|
|
||||||
this.cubeCamera = new CubeCamera(this.cubeCameraNear, this.cubeCameraFar, cubeRT)
|
|
||||||
bindProp(this, 'position', this.cubeCamera)
|
|
||||||
this.addToParent(this.cubeCamera)
|
|
||||||
|
|
||||||
this.material.envMap = cubeRT.texture
|
|
||||||
this.material.refractionRatio = this.refractionRatio
|
|
||||||
this.material.needsUpdate = true
|
|
||||||
},
|
|
||||||
updateCubeRT() {
|
|
||||||
this.mesh.visible = false
|
|
||||||
this.cubeCamera.update(this.renderer.renderer, this.scene)
|
|
||||||
this.mesh.visible = true
|
|
||||||
},
|
|
||||||
},
|
|
||||||
__hmrId: 'RefractionMesh',
|
|
||||||
})
|
|
@ -1,47 +0,0 @@
|
|||||||
import Stats from 'stats.js'
|
|
||||||
import { RendererInjectionKey } from '../../../build/trois.module.js'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
noSetup: { type: Boolean, default: false },
|
|
||||||
},
|
|
||||||
emits: ['created'],
|
|
||||||
inject: { renderer: RendererInjectionKey },
|
|
||||||
setup({ noSetup }) {
|
|
||||||
const stats = new Stats()
|
|
||||||
if (!noSetup) {
|
|
||||||
stats.showPanel(0) // 0: fps, 1: ms, 2: mb, 3+: custom
|
|
||||||
document.body.appendChild(stats.dom)
|
|
||||||
}
|
|
||||||
return { stats }
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (!this.noSetup) {
|
|
||||||
this.renderer.onBeforeRender(this.begin)
|
|
||||||
this.renderer.onAfterRender(this.end)
|
|
||||||
}
|
|
||||||
this.$emit('created', { stats: this.stats })
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
begin() {
|
|
||||||
if (this.stats) {
|
|
||||||
this.stats.begin()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
end() {
|
|
||||||
if (this.stats) {
|
|
||||||
this.stats.end()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
if (this.stats && this.stats.dom) {
|
|
||||||
this.stats.dom.parentElement.removeChild(this.stats.dom)
|
|
||||||
}
|
|
||||||
this.renderer.offBeforeRender(this.begin)
|
|
||||||
this.renderer.offAfterRender(this.end)
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
return this.$slots.default ? this.$slots.default() : []
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
<template>
|
|
||||||
<button @click="onClick">{{ message }}</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// from threejs/examples/jsm/webxr/VRButton.js
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
enterMessage: { type: String, default: 'ENTER VR' },
|
|
||||||
exitMessage: { type: String, default: 'EXIT VR' },
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
error: '',
|
|
||||||
xrSupport: false,
|
|
||||||
currentSession: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
message() {
|
|
||||||
if (this.xrSupport) {
|
|
||||||
return this.currentSession ? this.exitMessage : this.enterMessage
|
|
||||||
} else if (this.error) {
|
|
||||||
return this.error
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if ('xr' in navigator) {
|
|
||||||
navigator.xr.isSessionSupported('immersive-vr').then((supported) => {
|
|
||||||
this.xrSupport = supported
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if (window.isSecureContext === false) {
|
|
||||||
this.error = 'WEBXR NEEDS HTTPS'
|
|
||||||
} else {
|
|
||||||
this.error = 'WEBXR NOT AVAILABLE'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init(renderer) {
|
|
||||||
this.renderer = renderer
|
|
||||||
},
|
|
||||||
onClick() {
|
|
||||||
if (!this.xrSupport) return
|
|
||||||
if (!this.renderer) return
|
|
||||||
|
|
||||||
if (this.currentSession) {
|
|
||||||
this.currentSession.end()
|
|
||||||
} else {
|
|
||||||
const sessionInit = { optionalFeatures: ['local-floor', 'bounded-floor', 'hand-tracking'] }
|
|
||||||
navigator.xr.requestSession('immersive-vr', sessionInit).then(this.onSessionStarted)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async onSessionStarted(session) {
|
|
||||||
session.addEventListener('end', this.onSessionEnded)
|
|
||||||
await this.renderer.xr.setSession(session)
|
|
||||||
this.currentSession = session
|
|
||||||
},
|
|
||||||
onSessionEnded() {
|
|
||||||
this.currentSession.removeEventListener('end', this.onSessionEnded)
|
|
||||||
this.currentSession = null
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
position: absolute;
|
|
||||||
width: 100px;
|
|
||||||
left: calc(50% - 50px);
|
|
||||||
bottom: 20px;
|
|
||||||
padding: 12px 6px;
|
|
||||||
border: 1px solid #fff;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: rgba(0,0,0,0.1);
|
|
||||||
color: #fff;
|
|
||||||
font: normal 13px sans-serif;
|
|
||||||
text-align: center;
|
|
||||||
opacity: 0.5;
|
|
||||||
outline: none;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,86 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue'
|
|
||||||
import { Image } from '../../../build/trois.module.js'
|
|
||||||
import snoise2 from '../../glsl/snoise2.glsl.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Image,
|
|
||||||
props: {
|
|
||||||
widthSegments: { type: Number, default: 20 },
|
|
||||||
heightSegments: { type: Number, default: 20 },
|
|
||||||
timeCoef: { type: Number, default: 0.001 },
|
|
||||||
noiseCoef: { type: Number, default: 1 },
|
|
||||||
zCoef: { type: Number, default: 5 },
|
|
||||||
dispCoef: { type: Number, default: 0.05 },
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
// uniforms
|
|
||||||
const uTime = { value: 0 }
|
|
||||||
const uNoiseCoef = { value: props.noiseCoef }
|
|
||||||
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value })
|
|
||||||
const uZCoef = { value: props.zCoef }
|
|
||||||
watch(() => props.zCoef, (value) => { uZCoef.value = value })
|
|
||||||
const uDispCoef = { value: props.dispCoef }
|
|
||||||
watch(() => props.dispCoef, (value) => { uDispCoef.value = value })
|
|
||||||
|
|
||||||
return {
|
|
||||||
uTime, uNoiseCoef, uZCoef, uDispCoef,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.tweakMaterial()
|
|
||||||
|
|
||||||
this.startTime = Date.now()
|
|
||||||
this.renderer.onBeforeRender(this.updateTime)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.renderer.offBeforeRender(this.updateTime)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
tweakMaterial() {
|
|
||||||
this.material.onBeforeCompile = (shader) => {
|
|
||||||
shader.uniforms.uTime = this.uTime
|
|
||||||
shader.uniforms.uNoiseCoef = this.uNoiseCoef
|
|
||||||
shader.uniforms.uZCoef = this.uZCoef
|
|
||||||
shader.uniforms.uDispCoef = this.uDispCoef
|
|
||||||
shader.vertexShader = `
|
|
||||||
uniform float uTime;
|
|
||||||
uniform float uNoiseCoef;
|
|
||||||
uniform float uZCoef;
|
|
||||||
varying float vNoise;
|
|
||||||
${snoise2}
|
|
||||||
` + shader.vertexShader
|
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace(
|
|
||||||
'#include <begin_vertex>',
|
|
||||||
`
|
|
||||||
vec3 p = vec3(position * uNoiseCoef);
|
|
||||||
p.x += uTime;
|
|
||||||
vNoise = snoise(p.xy);
|
|
||||||
vec3 transformed = vec3(position);
|
|
||||||
transformed.z += vNoise * uZCoef;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
shader.fragmentShader = `
|
|
||||||
uniform float uDispCoef;
|
|
||||||
varying float vNoise;
|
|
||||||
` + shader.fragmentShader
|
|
||||||
|
|
||||||
shader.fragmentShader = shader.fragmentShader.replace(
|
|
||||||
'#include <map_fragment>',
|
|
||||||
`
|
|
||||||
vec4 texelColor = texture2D(map, vUv);
|
|
||||||
vec4 dispTexel = texture2D(map, vUv + vec2(vNoise * uDispCoef, 0));
|
|
||||||
texelColor.r = dispTexel.r;
|
|
||||||
diffuseColor = texelColor;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
this.materialShader = shader
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateTime() {
|
|
||||||
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef
|
|
||||||
},
|
|
||||||
},
|
|
||||||
__hmrId: 'NoisyImage',
|
|
||||||
})
|
|
@ -1,129 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue'
|
|
||||||
import { ObjectSpaceNormalMap, ShaderMaterial, Vector2, WebGLRenderTarget } from 'three'
|
|
||||||
import { Pass } from 'three/examples/jsm/postprocessing/Pass.js'
|
|
||||||
import { Plane } from '../../../build/trois.module.js'
|
|
||||||
import snoise3 from '../../glsl/snoise3.glsl.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Plane,
|
|
||||||
props: {
|
|
||||||
timeCoef: { type: Number, default: 0.001 },
|
|
||||||
noiseCoef: { type: Number, default: 5 },
|
|
||||||
deltaCoef: { type: Number, default: 1 / 512 },
|
|
||||||
displacementScale: { type: Number, default: 5 },
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
// uniforms
|
|
||||||
const uTime = { value: 0 }
|
|
||||||
const uNoiseCoef = { value: props.noiseCoef }
|
|
||||||
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value })
|
|
||||||
const uDelta = { value: new Vector2(props.deltaCoef, props.deltaCoef) }
|
|
||||||
watch(() => props.deltaCoef, (value) => { uDelta.value.set(value, value) })
|
|
||||||
|
|
||||||
return {
|
|
||||||
uTime, uNoiseCoef, uDelta,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.init()
|
|
||||||
|
|
||||||
watch(() => this.displacementScale, (value) => { this.material.displacementScale = value })
|
|
||||||
|
|
||||||
this.startTime = Date.now()
|
|
||||||
this.renderer.onBeforeRender(this.update)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.renderer.offBeforeRender(this.update)
|
|
||||||
this.fsQuad.dispose()
|
|
||||||
this.dispRT.dispose()
|
|
||||||
this.dispMat.dispose()
|
|
||||||
this.normRT.dispose()
|
|
||||||
this.normMat.dispose()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init() {
|
|
||||||
this.fsQuad = new Pass.FullScreenQuad()
|
|
||||||
|
|
||||||
// displacement map
|
|
||||||
this.dispRT = new WebGLRenderTarget(512, 512, { depthBuffer: false, stencilBuffer: false })
|
|
||||||
this.dispMat = new ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
uTime: this.uTime,
|
|
||||||
uNoiseCoef: this.uNoiseCoef,
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
// gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
gl_Position = vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform float uTime;
|
|
||||||
uniform float uNoiseCoef;
|
|
||||||
varying vec2 vUv;
|
|
||||||
${snoise3}
|
|
||||||
void main() {
|
|
||||||
vec2 p = vec2(vUv * uNoiseCoef);
|
|
||||||
float noise = (snoise(vec3(p.x, p.y, uTime)) + 1.0) / 2.0;
|
|
||||||
gl_FragColor = vec4(noise, 0.0, 0.0, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
|
|
||||||
// normal map
|
|
||||||
this.normRT = new WebGLRenderTarget(512, 512, { depthBuffer: false, stencilBuffer: false })
|
|
||||||
this.normMat = new ShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
dispMap: { value: this.dispRT.texture },
|
|
||||||
delta: this.uDelta,
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
// gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
gl_Position = vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
uniform sampler2D dispMap;
|
|
||||||
uniform vec2 delta;
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
// gl_FragColor = vec4(0.5, 0.5, 1.0, 0.0);
|
|
||||||
float x1 = texture2D(dispMap, vec2(vUv.x - delta.x, vUv.y)).r;
|
|
||||||
float x2 = texture2D(dispMap, vec2(vUv.x + delta.x, vUv.y)).r;
|
|
||||||
float y1 = texture2D(dispMap, vec2(vUv.x, vUv.y - delta.y)).r;
|
|
||||||
float y2 = texture2D(dispMap, vec2(vUv.x, vUv.y + delta.y)).r;
|
|
||||||
gl_FragColor = vec4(0.5 + (x1 - x2), 0.5 + (y1 - y2), 1.0, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
|
|
||||||
this.material.displacementMap = this.dispRT.texture
|
|
||||||
this.material.displacementScale = this.displacementScale
|
|
||||||
this.material.normalMap = this.normRT.texture
|
|
||||||
this.material.normalMapType = ObjectSpaceNormalMap
|
|
||||||
// this.material.needsUpdate = true;
|
|
||||||
},
|
|
||||||
update() {
|
|
||||||
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef
|
|
||||||
this.renderDisp()
|
|
||||||
},
|
|
||||||
renderDisp() {
|
|
||||||
this.renderMat(this.dispMat, this.dispRT)
|
|
||||||
this.renderMat(this.normMat, this.normRT)
|
|
||||||
},
|
|
||||||
renderMat(mat, target) {
|
|
||||||
const renderer = this.renderer.renderer
|
|
||||||
this.fsQuad.material = mat
|
|
||||||
const oldTarget = renderer.getRenderTarget()
|
|
||||||
renderer.setRenderTarget(target)
|
|
||||||
this.fsQuad.render(renderer)
|
|
||||||
renderer.setRenderTarget(oldTarget)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
__hmrId: 'NoisyPlane',
|
|
||||||
})
|
|
@ -1,68 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue'
|
|
||||||
import { Sphere } from '../../../build/trois.module.js'
|
|
||||||
import snoise4 from '../../glsl/snoise4.glsl.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Sphere,
|
|
||||||
props: {
|
|
||||||
radius: { type: Number, default: 20 },
|
|
||||||
widthSegments: { type: Number, default: 128 },
|
|
||||||
heightSegments: { type: Number, default: 128 },
|
|
||||||
timeCoef: { type: Number, default: 0.001 },
|
|
||||||
noiseCoef: { type: Number, default: 0.05 },
|
|
||||||
dispCoef: { type: Number, default: 5 },
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
// uniforms
|
|
||||||
const uTime = { value: 0 }
|
|
||||||
const uNoiseCoef = { value: props.noiseCoef }
|
|
||||||
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value })
|
|
||||||
const uDispCoef = { value: props.dispCoef }
|
|
||||||
watch(() => props.dispCoef, (value) => { uDispCoef.value = value })
|
|
||||||
|
|
||||||
return {
|
|
||||||
uTime, uNoiseCoef, uDispCoef,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.updateMaterial()
|
|
||||||
|
|
||||||
this.startTime = Date.now()
|
|
||||||
this.renderer.onBeforeRender(this.updateTime)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.renderer.offBeforeRender(this.updateTime)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateMaterial() {
|
|
||||||
this.material.onBeforeCompile = (shader) => {
|
|
||||||
shader.uniforms.uTime = this.uTime
|
|
||||||
shader.uniforms.uNoiseCoef = this.uNoiseCoef
|
|
||||||
shader.uniforms.uDispCoef = this.uDispCoef
|
|
||||||
shader.vertexShader = `
|
|
||||||
uniform float uTime;
|
|
||||||
uniform float uNoiseCoef;
|
|
||||||
uniform float uDispCoef;
|
|
||||||
varying float vNoise;
|
|
||||||
${snoise4}
|
|
||||||
` + shader.vertexShader
|
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace(
|
|
||||||
'#include <begin_vertex>',
|
|
||||||
`
|
|
||||||
vec4 p = vec4(vec3(position * uNoiseCoef), uTime);
|
|
||||||
vNoise = snoise(p);
|
|
||||||
vec3 transformed = vec3(position);
|
|
||||||
transformed += normalize(position) * vNoise * uDispCoef;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
this.materialShader = shader
|
|
||||||
}
|
|
||||||
this.material.needsupdate = true
|
|
||||||
},
|
|
||||||
updateTime() {
|
|
||||||
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef
|
|
||||||
},
|
|
||||||
},
|
|
||||||
__hmrId: 'NoisySphere',
|
|
||||||
})
|
|
@ -1,65 +0,0 @@
|
|||||||
import { defineComponent, watch } from 'vue'
|
|
||||||
import { Text } from '../../../build/trois.module.js'
|
|
||||||
import snoise2 from '../../glsl/snoise2.glsl.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
extends: Text,
|
|
||||||
props: {
|
|
||||||
timeCoef: { type: Number, default: 0.001 },
|
|
||||||
noiseCoef: { type: Number, default: 0.015 },
|
|
||||||
zCoef: { type: Number, default: 10 },
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
// uniforms
|
|
||||||
const uTime = { value: 0 }
|
|
||||||
const uNoiseCoef = { value: props.noiseCoef }
|
|
||||||
watch(() => props.noiseCoef, (value) => { uNoiseCoef.value = value })
|
|
||||||
const uZCoef = { value: props.zCoef }
|
|
||||||
watch(() => props.zCoef, (value) => { uZCoef.value = value })
|
|
||||||
|
|
||||||
return {
|
|
||||||
uTime, uNoiseCoef, uZCoef,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.updateMaterial()
|
|
||||||
|
|
||||||
this.startTime = Date.now()
|
|
||||||
this.renderer.onBeforeRender(this.updateTime)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.renderer.offBeforeRender(this.updateTime)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
updateMaterial() {
|
|
||||||
this.material.onBeforeCompile = (shader) => {
|
|
||||||
shader.uniforms.uTime = this.uTime
|
|
||||||
shader.uniforms.uNoiseCoef = this.uNoiseCoef
|
|
||||||
shader.uniforms.uZCoef = this.uZCoef
|
|
||||||
shader.vertexShader = `
|
|
||||||
uniform float uTime;
|
|
||||||
uniform float uNoiseCoef;
|
|
||||||
uniform float uZCoef;
|
|
||||||
${snoise2}
|
|
||||||
` + shader.vertexShader
|
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace(
|
|
||||||
'#include <begin_vertex>',
|
|
||||||
`
|
|
||||||
vec3 p = vec3(position * uNoiseCoef);
|
|
||||||
p.x += uTime;
|
|
||||||
float noise = snoise(p.xy);
|
|
||||||
vec3 transformed = vec3(position);
|
|
||||||
transformed.z += noise * uZCoef;
|
|
||||||
`
|
|
||||||
)
|
|
||||||
this.materialShader = shader
|
|
||||||
}
|
|
||||||
this.material.needsupdate = true
|
|
||||||
},
|
|
||||||
updateTime() {
|
|
||||||
this.uTime.value = (Date.now() - this.startTime) * this.timeCoef
|
|
||||||
},
|
|
||||||
},
|
|
||||||
__hmrId: 'NoisyText',
|
|
||||||
})
|
|
@ -1,66 +0,0 @@
|
|||||||
import { defineComponent } from 'vue'
|
|
||||||
import useCannon from './useCannon.js'
|
|
||||||
import { RendererInjectionKey, SceneInjectionKey } from '../../../build/trois.module.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
inject: {
|
|
||||||
renderer: RendererInjectionKey,
|
|
||||||
scene: SceneInjectionKey,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
gravity: { type: Object, default: () => ({ x: 0, y: 0, z: -9.82 }) },
|
|
||||||
broadphase: { type: String },
|
|
||||||
onBeforeStep: Function,
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this._parent = this.getParent()
|
|
||||||
if (!this._parent) console.error('Missing parent (Scene, Group...)')
|
|
||||||
|
|
||||||
this.cannon = useCannon({ gravity: this.gravity, broadphase: this.broadphase })
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.renderer.onBeforeRender(this.step)
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.renderer.offBeforeRender(this.step)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
step() {
|
|
||||||
this.onBeforeStep?.(this.cannon)
|
|
||||||
this.cannon.step()
|
|
||||||
},
|
|
||||||
add(o) {
|
|
||||||
this.addToParent(o)
|
|
||||||
this.cannon.addMesh(o)
|
|
||||||
},
|
|
||||||
remove(o) {
|
|
||||||
this.removeFromParent(o)
|
|
||||||
this.cannon.removeMesh(o)
|
|
||||||
},
|
|
||||||
getParent() {
|
|
||||||
let parent = this.$parent
|
|
||||||
while (parent) {
|
|
||||||
if (parent.add) return parent
|
|
||||||
parent = parent.$parent
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
addToParent(o) {
|
|
||||||
if (this._parent) {
|
|
||||||
this._parent.add(o)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
removeFromParent(o) {
|
|
||||||
if (this._parent) {
|
|
||||||
this._parent.remove(o)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
return this.$slots.default ? this.$slots.default() : []
|
|
||||||
},
|
|
||||||
})
|
|
@ -1,208 +0,0 @@
|
|||||||
import {
|
|
||||||
Box, Cylinder, Plane, Sphere,
|
|
||||||
Body, World,
|
|
||||||
SAPBroadphase,
|
|
||||||
Quaternion, Vec3,
|
|
||||||
} from 'cannon'
|
|
||||||
|
|
||||||
export default function useCannon(options) {
|
|
||||||
const {
|
|
||||||
broadphase = null,
|
|
||||||
gravity = new Vec3(0, 0, -9.82),
|
|
||||||
// solverIterations = 10,
|
|
||||||
} = options
|
|
||||||
|
|
||||||
const world = new World()
|
|
||||||
world.gravity.set(gravity.x, gravity.y, gravity.z)
|
|
||||||
|
|
||||||
if (broadphase === 'sap') {
|
|
||||||
world.broadphase = new SAPBroadphase(world)
|
|
||||||
}
|
|
||||||
// world.solver.iterations = solverIterations;
|
|
||||||
|
|
||||||
const meshes = []
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
world,
|
|
||||||
addMesh,
|
|
||||||
removeMesh,
|
|
||||||
step,
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
|
|
||||||
function addMesh(mesh) {
|
|
||||||
const shape = getShape(mesh.geometry)
|
|
||||||
if (shape) {
|
|
||||||
if (mesh.isInstancedMesh) {
|
|
||||||
handleInstancedMesh(mesh, shape)
|
|
||||||
} else if (mesh.isMesh) {
|
|
||||||
handleMesh(mesh, shape)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn(`Unhandled Mesh geometry ${mesh.geometry.type}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeMesh(mesh) {
|
|
||||||
const index = meshes.indexOf(mesh)
|
|
||||||
if (index !== -1) {
|
|
||||||
meshes.splice(index, 1)
|
|
||||||
}
|
|
||||||
if (mesh.userData.bodies) {
|
|
||||||
mesh.userData.bodies.forEach(body => {
|
|
||||||
world.removeBody(body)
|
|
||||||
})
|
|
||||||
mesh.userData.bodies = []
|
|
||||||
}
|
|
||||||
if (mesh.userData.body) {
|
|
||||||
world.removeBody(mesh.userData.body)
|
|
||||||
delete mesh.userData.body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function step() {
|
|
||||||
world.step(1 / 60)
|
|
||||||
for (let i = 0, l = meshes.length; i < l; i++) {
|
|
||||||
const mesh = meshes[i]
|
|
||||||
if (mesh.isInstancedMesh) {
|
|
||||||
const iMatrix = mesh.instanceMatrix.array
|
|
||||||
const bodies = mesh.userData.bodies
|
|
||||||
for (let j = 0; j < bodies.length; j++) {
|
|
||||||
const body = bodies[j]
|
|
||||||
compose(body.position, body.quaternion, mesh.userData.scales[j], iMatrix, j * 16)
|
|
||||||
}
|
|
||||||
mesh.instanceMatrix.needsUpdate = true
|
|
||||||
} else if (mesh.isMesh) {
|
|
||||||
mesh.position.copy(mesh.userData.body.position)
|
|
||||||
mesh.quaternion.copy(mesh.userData.body.quaternion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getShape(geometry) {
|
|
||||||
const parameters = geometry.parameters
|
|
||||||
switch (geometry.type) {
|
|
||||||
case 'BoxGeometry':
|
|
||||||
return new Box(new Vec3(
|
|
||||||
parameters.width / 2,
|
|
||||||
parameters.height / 2,
|
|
||||||
parameters.depth / 2
|
|
||||||
))
|
|
||||||
|
|
||||||
case 'PlaneGeometry':
|
|
||||||
return new Plane()
|
|
||||||
|
|
||||||
case 'SphereGeometry':
|
|
||||||
return new Sphere(parameters.radius)
|
|
||||||
|
|
||||||
case 'CylinderGeometry':
|
|
||||||
return new Cylinder(parameters.radiusTop, parameters.radiusBottom, parameters.height, parameters.radialSegments)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMesh(mesh, shape) {
|
|
||||||
const position = new Vec3()
|
|
||||||
position.copy(mesh.position)
|
|
||||||
|
|
||||||
const quaternion = new Quaternion()
|
|
||||||
quaternion.copy(mesh.quaternion)
|
|
||||||
|
|
||||||
const mass = mesh.userData.mass ? mesh.userData.mass : 0
|
|
||||||
const damping = mesh.userData.damping ? mesh.userData.damping : 0.01
|
|
||||||
|
|
||||||
const velocity = mesh.userData.velocity ? new Vec3(
|
|
||||||
mesh.userData.velocity.x,
|
|
||||||
mesh.userData.velocity.y,
|
|
||||||
mesh.userData.velocity.z) : new Vec3(0, 0, 0)
|
|
||||||
|
|
||||||
const body = new Body({ shape, position, velocity, quaternion, mass, linearDamping: damping, angularDamping: damping })
|
|
||||||
world.addBody(body)
|
|
||||||
|
|
||||||
mesh.userData.body = body
|
|
||||||
if (mesh.userData.mass > 0) {
|
|
||||||
meshes.push(mesh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleInstancedMesh(mesh, shape) {
|
|
||||||
const iMatrix = mesh.instanceMatrix.array
|
|
||||||
const bodies = []
|
|
||||||
for (let i = 0; i < mesh.count; i++) {
|
|
||||||
const index = i * 16
|
|
||||||
|
|
||||||
const position = new Vec3()
|
|
||||||
position.set(iMatrix[index + 12], iMatrix[index + 13], iMatrix[index + 14])
|
|
||||||
|
|
||||||
// handle instance scale
|
|
||||||
let scale = 1
|
|
||||||
if (mesh.userData.scales?.[i]) scale = mesh.userData.scales?.[i]
|
|
||||||
const geoParams = mesh.geometry.parameters
|
|
||||||
if (mesh.geometry.type === 'SphereGeometry') {
|
|
||||||
shape = new Sphere(scale * geoParams.radius)
|
|
||||||
} else if (mesh.geometry.type === 'BoxGeometry') {
|
|
||||||
shape = new Box(new Vec3(
|
|
||||||
scale * geoParams.width / 2,
|
|
||||||
scale * geoParams.height / 2,
|
|
||||||
scale * geoParams.depth / 2
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
console.warn(`Unhandled InstancedMesh geometry ${mesh.geometry.type}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let mass = 0
|
|
||||||
if (mesh.userData.masses?.[i]) mass = mesh.userData.masses[i]
|
|
||||||
else if (mesh.userData.mass) mass = mesh.userData.mass
|
|
||||||
|
|
||||||
let damping = 0.01
|
|
||||||
if (mesh.userData.dampings?.[i]) damping = mesh.userData.dampings?.[i]
|
|
||||||
else if (mesh.userData.damping) damping = mesh.userData.damping
|
|
||||||
|
|
||||||
let velocity = new Vec3(0, 0, 0)
|
|
||||||
if (mesh.userData.velocities?.[i]) velocity = new Vec3(
|
|
||||||
mesh.userData.velocities?.[i].x,
|
|
||||||
mesh.userData.velocities?.[i].y,
|
|
||||||
mesh.userData.velocities?.[i].z)
|
|
||||||
else if (mesh.userData.velocities) velocity = new Vec3(
|
|
||||||
mesh.userData.velocities.x,
|
|
||||||
mesh.userData.velocities.y,
|
|
||||||
mesh.userData.velocities.z)
|
|
||||||
|
|
||||||
const body = new Body({ shape, position, velocity, mass, linearDamping: damping, angularDamping: damping })
|
|
||||||
world.addBody(body)
|
|
||||||
bodies.push(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
mesh.userData.bodies = bodies
|
|
||||||
meshes.push(mesh)
|
|
||||||
}
|
|
||||||
|
|
||||||
function compose(position, quaternion, scale, iMatrix, index) {
|
|
||||||
const x = quaternion.x, y = quaternion.y, z = quaternion.z, w = quaternion.w
|
|
||||||
const x2 = x + x, y2 = y + y, z2 = z + z
|
|
||||||
const xx = x * x2, xy = x * y2, xz = x * z2
|
|
||||||
const yy = y * y2, yz = y * z2, zz = z * z2
|
|
||||||
const wx = w * x2, wy = w * y2, wz = w * z2
|
|
||||||
|
|
||||||
iMatrix[index + 0] = (1 - (yy + zz)) * scale
|
|
||||||
iMatrix[index + 1] = (xy + wz) * scale
|
|
||||||
iMatrix[index + 2] = (xz - wy) * scale
|
|
||||||
iMatrix[index + 3] = 0
|
|
||||||
|
|
||||||
iMatrix[index + 4] = (xy - wz) * scale
|
|
||||||
iMatrix[index + 5] = (1 - (xx + zz)) * scale
|
|
||||||
iMatrix[index + 6] = (yz + wx) * scale
|
|
||||||
iMatrix[index + 7] = 0
|
|
||||||
|
|
||||||
iMatrix[index + 8] = (xz + wy) * scale
|
|
||||||
iMatrix[index + 9] = (yz - wx) * scale
|
|
||||||
iMatrix[index + 10] = (1 - (xx + yy)) * scale
|
|
||||||
iMatrix[index + 11] = 0
|
|
||||||
|
|
||||||
iMatrix[index + 12] = position.x
|
|
||||||
iMatrix[index + 13] = position.y
|
|
||||||
iMatrix[index + 14] = position.z
|
|
||||||
iMatrix[index + 15] = 1
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,113 +0,0 @@
|
|||||||
import { defineComponent, inject, onMounted, onUnmounted, watch } from 'vue'
|
|
||||||
import { LoadingManager } from 'three'
|
|
||||||
import { BloomEffect, DepthOfFieldEffect, EdgeDetectionMode, GodRaysEffect, SMAAEffect, SMAAImageLoader, SMAAPreset } from 'postprocessing'
|
|
||||||
import { EffectPassInjectionKey } from './EffectPass'
|
|
||||||
|
|
||||||
// type EffectTypes = 'bloom' | 'dof' | 'godrays' | 'smaa'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
props: {
|
|
||||||
type: { type: String, required: true },
|
|
||||||
options: { type: Object, default: () => ({}) },
|
|
||||||
onReady: Function,
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const effectPass = inject(EffectPassInjectionKey)
|
|
||||||
if (!effectPass) {
|
|
||||||
console.error('EffectPass not found')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let effect
|
|
||||||
const effectIndex = effectPass.getEffectIndex()
|
|
||||||
|
|
||||||
const initEffect = (params) => {
|
|
||||||
effect = createEffect(effectPass, props, params)
|
|
||||||
if (!effect) {
|
|
||||||
console.error('Invalid effect type')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (props.onReady) props.onReady(effect)
|
|
||||||
effectPass.addEffect(effect, effectIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (props.type === 'smaa') {
|
|
||||||
const smaaImageLoader = new SMAAImageLoader(new LoadingManager())
|
|
||||||
smaaImageLoader.load(([search, area]) => {
|
|
||||||
initEffect({ smaaSearch: search, smaaArea: area })
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
initEffect()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (effect) {
|
|
||||||
effectPass.removeEffect(effect)
|
|
||||||
effect.dispose()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
render() { return [] },
|
|
||||||
})
|
|
||||||
|
|
||||||
function createEffect(effectPass, props, assets) {
|
|
||||||
let effect
|
|
||||||
switch (props.type) {
|
|
||||||
case 'bloom' :
|
|
||||||
effect = createBloomEffect(props)
|
|
||||||
break
|
|
||||||
case 'dof' :
|
|
||||||
effect = createDepthOfFieldEffect(effectPass, props)
|
|
||||||
break
|
|
||||||
case 'godrays' :
|
|
||||||
effect = createGodraysEffect(effectPass, props)
|
|
||||||
break
|
|
||||||
case 'smaa' :
|
|
||||||
effect = createSmaaEffect(props, assets)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return effect
|
|
||||||
}
|
|
||||||
|
|
||||||
function createBloomEffect(props) {
|
|
||||||
const effect = new BloomEffect(props.options)
|
|
||||||
watch(() => props.options.luminanceThreshold, (value) => { effect.luminanceMaterial.threshold = value })
|
|
||||||
watch(() => props.options.luminanceSmoothing, (value) => { effect.luminanceMaterial.smoothing = value })
|
|
||||||
watch(() => props.options.intensity, (value) => { effect.intensity = value })
|
|
||||||
return effect
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDepthOfFieldEffect(effectPass, props) {
|
|
||||||
const effect = new DepthOfFieldEffect(effectPass.composer.renderer, props.options)
|
|
||||||
const uniforms = effect.circleOfConfusionMaterial.uniforms
|
|
||||||
watch(() => props.options.focusDistance, (value) => { uniforms.focusDistance.value = value })
|
|
||||||
watch(() => props.options.focalLength, (value) => { uniforms.focalLength.value = value })
|
|
||||||
watch(() => props.options.bokehScale, (value) => { effect.bokehScale = value })
|
|
||||||
return effect
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSmaaEffect(props, assets) {
|
|
||||||
const { smaaSearch, smaaArea } = assets
|
|
||||||
const params = [props.options.preset ?? SMAAPreset.HIGH, props.options.edgeDetectionMode ?? EdgeDetectionMode.COLOR]
|
|
||||||
return new SMAAEffect(smaaSearch, smaaArea, ...params)
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGodraysEffect(effectPass, props) {
|
|
||||||
const opts = { ...props.options }
|
|
||||||
const { lightSource } = props.options
|
|
||||||
if (typeof lightSource !== 'string') {
|
|
||||||
console.error('Invalid lightSource')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
delete opts.lightSource
|
|
||||||
|
|
||||||
const lightSourceComp = effectPass.renderer.$root.$refs[lightSource]
|
|
||||||
if (!lightSourceComp) {
|
|
||||||
console.error('Invalid lightSource ref')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GodRaysEffect(effectPass.composer.renderer.camera, lightSourceComp.mesh, opts)
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
import { inject, onUnmounted, provide } from 'vue'
|
|
||||||
import { Clock } from 'three'
|
|
||||||
import { EffectComposer } from 'postprocessing'
|
|
||||||
import { RendererInjectionKey } from '../../../../build/trois.module.js'
|
|
||||||
// import { RendererInjectionKey } from '../../../core'
|
|
||||||
|
|
||||||
export const ComposerInjectionKey = Symbol('Composer')
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup() {
|
|
||||||
const renderer = inject(RendererInjectionKey)
|
|
||||||
if (!renderer) {
|
|
||||||
console.error('Renderer not found')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const composer = new 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() : []
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
import { inject, onUnmounted, provide } from 'vue'
|
|
||||||
import { EffectPass } from 'postprocessing'
|
|
||||||
import { ComposerInjectionKey } from './EffectComposer.js'
|
|
||||||
|
|
||||||
export const EffectPassInjectionKey = Symbol('EffectPass')
|
|
||||||
|
|
||||||
export default {
|
|
||||||
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
|
|
||||||
|
|
||||||
const effects = []
|
|
||||||
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 EffectPass(composer.renderer.camera, ...effects)
|
|
||||||
composer.composer.addPass(effectPass, passIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
const addEffect = (effect, index) => {
|
|
||||||
effects.splice(index, 1, effect)
|
|
||||||
refreshEffectPass()
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeEffect = (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() : [] },
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
import { inject, onUnmounted } from 'vue'
|
|
||||||
import { BlurPass, RenderPass } from 'postprocessing'
|
|
||||||
import { ComposerInjectionKey } from './EffectComposer'
|
|
||||||
|
|
||||||
// type PassTypes = 'render' | 'blur'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
type: { type: String, 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
|
|
||||||
const passIndex = composer.getPassIndex()
|
|
||||||
|
|
||||||
const initPass = () => {
|
|
||||||
pass = createPass(composer.renderer, props.type, props.options)
|
|
||||||
if (!pass) {
|
|
||||||
console.error('Invalid pass type')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
pass.renderToScreen = props.renderToScreen
|
|
||||||
if (props.onReady) props.onReady(pass)
|
|
||||||
composer.composer.addPass(pass, passIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (pass) {
|
|
||||||
composer.composer.removePass(pass)
|
|
||||||
pass.dispose()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
initPass()
|
|
||||||
},
|
|
||||||
render() { return [] },
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPass(renderer, type, options) {
|
|
||||||
let pass
|
|
||||||
switch (type) {
|
|
||||||
case 'render' :
|
|
||||||
pass = new RenderPass(renderer.scene, renderer.camera)
|
|
||||||
break
|
|
||||||
case 'blur' :
|
|
||||||
pass = new BlurPass(options)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return pass
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
export { default as EffectComposer } from './EffectComposer'
|
|
||||||
export { default as Pass } from './Pass'
|
|
||||||
export { default as EffectPass } from './EffectPass'
|
|
||||||
export { default as Effect } from './Effect'
|
|
@ -1,199 +0,0 @@
|
|||||||
import {
|
|
||||||
DoubleSide,
|
|
||||||
InstancedBufferAttribute,
|
|
||||||
InstancedMesh,
|
|
||||||
MathUtils,
|
|
||||||
MeshBasicMaterial,
|
|
||||||
Object3D,
|
|
||||||
Vector2,
|
|
||||||
Vector3,
|
|
||||||
} from 'three'
|
|
||||||
|
|
||||||
import { Geometry, Face3 } from 'three/examples/jsm/deprecated/Geometry.js'
|
|
||||||
|
|
||||||
export default class AnimatedPlane {
|
|
||||||
constructor(params) {
|
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
|
||||||
this[key] = value
|
|
||||||
})
|
|
||||||
|
|
||||||
this.o3d = new Object3D()
|
|
||||||
this.uProgress = { value: 0 }
|
|
||||||
this.uvScale = new Vector2()
|
|
||||||
|
|
||||||
this.initMaterial()
|
|
||||||
this.initPlane()
|
|
||||||
}
|
|
||||||
|
|
||||||
initMaterial() {
|
|
||||||
this.material = new MeshBasicMaterial({
|
|
||||||
side: DoubleSide,
|
|
||||||
transparent: true,
|
|
||||||
map: this.texture,
|
|
||||||
onBeforeCompile: shader => {
|
|
||||||
shader.uniforms.progress = this.uProgress
|
|
||||||
shader.uniforms.uvScale = { value: this.uvScale }
|
|
||||||
shader.vertexShader = `
|
|
||||||
uniform float progress;
|
|
||||||
uniform vec2 uvScale;
|
|
||||||
|
|
||||||
attribute vec3 offset;
|
|
||||||
attribute vec3 rotation;
|
|
||||||
attribute vec2 uvOffset;
|
|
||||||
|
|
||||||
mat3 rotationMatrixXYZ(vec3 r)
|
|
||||||
{
|
|
||||||
float cx = cos(r.x);
|
|
||||||
float sx = sin(r.x);
|
|
||||||
float cy = cos(r.y);
|
|
||||||
float sy = sin(r.y);
|
|
||||||
float cz = cos(r.z);
|
|
||||||
float sz = sin(r.z);
|
|
||||||
|
|
||||||
return mat3(
|
|
||||||
cy * cz, cx * sz + sx * sy * cz, sx * sz - cx * sy * cz,
|
|
||||||
-cy * sz, cx * cz - sx * sy * sz, sx * cz + cx * sy * sz,
|
|
||||||
sy, -sx * cy, cx * cy
|
|
||||||
);
|
|
||||||
}
|
|
||||||
` + shader.vertexShader
|
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace('#include <uv_vertex>', `
|
|
||||||
#include <uv_vertex>
|
|
||||||
vUv = vUv * uvScale + uvOffset;
|
|
||||||
`)
|
|
||||||
|
|
||||||
shader.vertexShader = shader.vertexShader.replace('#include <project_vertex>', `
|
|
||||||
mat3 rotMat = rotationMatrixXYZ(progress * rotation);
|
|
||||||
transformed = rotMat * transformed;
|
|
||||||
|
|
||||||
vec4 mvPosition = vec4(transformed, 1.0);
|
|
||||||
#ifdef USE_INSTANCING
|
|
||||||
mvPosition = instanceMatrix * mvPosition;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
mvPosition.xyz += progress * offset;
|
|
||||||
|
|
||||||
mvPosition = modelViewMatrix * mvPosition;
|
|
||||||
gl_Position = projectionMatrix * mvPosition;
|
|
||||||
`)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
initPlane() {
|
|
||||||
const { width, wWidth, wHeight } = this.screen
|
|
||||||
this.wSize = this.size * wWidth / width
|
|
||||||
this.nx = Math.ceil(wWidth / this.wSize) + 1
|
|
||||||
this.ny = Math.ceil(wHeight / this.wSize) + 1
|
|
||||||
this.icount = this.nx * this.ny
|
|
||||||
|
|
||||||
this.initGeometry()
|
|
||||||
this.initUV()
|
|
||||||
this.initAnimAttributes()
|
|
||||||
|
|
||||||
if (this.imesh) {
|
|
||||||
this.o3d.remove(this.imesh)
|
|
||||||
}
|
|
||||||
this.imesh = new InstancedMesh(this.bGeometry, this.material, this.icount)
|
|
||||||
this.o3d.add(this.imesh)
|
|
||||||
|
|
||||||
const dummy = new Object3D()
|
|
||||||
let index = 0
|
|
||||||
let x = -(wWidth - (wWidth - this.nx * this.wSize)) / 2 + this.dx
|
|
||||||
for (let i = 0; i < this.nx; i++) {
|
|
||||||
let y = -(wHeight - (wHeight - this.ny * this.wSize)) / 2 + this.dy
|
|
||||||
for (let j = 0; j < this.ny; j++) {
|
|
||||||
dummy.position.set(x, y, 0)
|
|
||||||
dummy.updateMatrix()
|
|
||||||
this.imesh.setMatrixAt(index++, dummy.matrix)
|
|
||||||
y += this.wSize
|
|
||||||
}
|
|
||||||
x += this.wSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initGeometry() {
|
|
||||||
// square
|
|
||||||
const geometry = new Geometry()
|
|
||||||
geometry.vertices.push(new Vector3(0, 0, 0))
|
|
||||||
geometry.vertices.push(new Vector3(this.wSize, 0, 0))
|
|
||||||
geometry.vertices.push(new Vector3(0, this.wSize, 0))
|
|
||||||
geometry.vertices.push(new Vector3(this.wSize, this.wSize, 0))
|
|
||||||
geometry.faces.push(new Face3(0, 2, 1))
|
|
||||||
geometry.faces.push(new Face3(2, 3, 1))
|
|
||||||
|
|
||||||
geometry.faceVertexUvs[0].push([
|
|
||||||
new Vector2(0, 0),
|
|
||||||
new Vector2(0, 1),
|
|
||||||
new Vector2(1, 0),
|
|
||||||
])
|
|
||||||
geometry.faceVertexUvs[0].push([
|
|
||||||
new Vector2(0, 1),
|
|
||||||
new Vector2(1, 1),
|
|
||||||
new Vector2(1, 0),
|
|
||||||
])
|
|
||||||
|
|
||||||
// geometry.computeFaceNormals();
|
|
||||||
// geometry.computeVertexNormals();
|
|
||||||
|
|
||||||
// center
|
|
||||||
this.dx = this.wSize / 2
|
|
||||||
this.dy = this.wSize / 2
|
|
||||||
geometry.translate(-this.dx, -this.dy, 0)
|
|
||||||
|
|
||||||
this.bGeometry = geometry.toBufferGeometry()
|
|
||||||
}
|
|
||||||
|
|
||||||
initAnimAttributes() {
|
|
||||||
const { randFloat: rnd, randFloatSpread: rndFS } = MathUtils
|
|
||||||
const v3 = new Vector3()
|
|
||||||
|
|
||||||
const offsets = new Float32Array(this.icount * 3)
|
|
||||||
for (let i = 0; i < offsets.length; i += 3) {
|
|
||||||
if (this.anim === 1) v3.set(rndFS(10), rnd(50, 100), rnd(20, 50)).toArray(offsets, i)
|
|
||||||
else v3.set(rndFS(20), rndFS(20), rnd(20, 200)).toArray(offsets, i)
|
|
||||||
}
|
|
||||||
this.bGeometry.setAttribute('offset', new InstancedBufferAttribute(offsets, 3))
|
|
||||||
|
|
||||||
const rotations = new Float32Array(this.icount * 3)
|
|
||||||
const angle = Math.PI * 4
|
|
||||||
for (let i = 0; i < rotations.length; i += 3) {
|
|
||||||
rotations[i] = rndFS(angle)
|
|
||||||
rotations[i + 1] = rndFS(angle)
|
|
||||||
rotations[i + 2] = rndFS(angle)
|
|
||||||
}
|
|
||||||
this.bGeometry.setAttribute('rotation', new InstancedBufferAttribute(rotations, 3))
|
|
||||||
}
|
|
||||||
|
|
||||||
initUV() {
|
|
||||||
const ratio = this.nx / this.ny
|
|
||||||
const tRatio = this.texture.image.width / this.texture.image.height
|
|
||||||
if (ratio > tRatio) this.uvScale.set(1 / this.nx, (tRatio / ratio) / this.ny)
|
|
||||||
else this.uvScale.set((ratio / tRatio) / this.nx, 1 / this.ny)
|
|
||||||
const nW = this.uvScale.x * this.nx
|
|
||||||
const nH = this.uvScale.y * this.ny
|
|
||||||
|
|
||||||
const v2 = new Vector2()
|
|
||||||
const uvOffsets = new Float32Array(this.icount * 2)
|
|
||||||
for (let i = 0; i < this.nx; i++) {
|
|
||||||
for (let j = 0; j < this.ny; j++) {
|
|
||||||
v2.set(
|
|
||||||
this.uvScale.x * i + (1 - nW) / 2,
|
|
||||||
this.uvScale.y * j + (1 - nH) / 2
|
|
||||||
).toArray(uvOffsets, (i * this.ny + j) * 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.bGeometry.setAttribute('uvOffset', new InstancedBufferAttribute(uvOffsets, 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
setTexture(texture) {
|
|
||||||
this.texture = texture
|
|
||||||
this.material.map = texture
|
|
||||||
this.initUV()
|
|
||||||
}
|
|
||||||
|
|
||||||
resize() {
|
|
||||||
this.initPlane()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,162 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Renderer ref="renderer" antialias resize>
|
|
||||||
<Camera ref="camera" :position="{ z: 150 }"></Camera>
|
|
||||||
<Scene ref="scene">
|
|
||||||
</Scene>
|
|
||||||
</Renderer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { Object3D } from 'three'
|
|
||||||
import { gsap, Power4 } from 'gsap'
|
|
||||||
|
|
||||||
import { lerp, useTextures, Camera, Renderer, Scene } from '../../../build/trois.module.js'
|
|
||||||
import AnimatedPlane from './AnimatedPlane.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { Camera, Renderer, Scene },
|
|
||||||
props: {
|
|
||||||
images: Array,
|
|
||||||
events: { type: Object, default: () => { return { wheel: true, click: true, keyup: true } } },
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const loader = useTextures()
|
|
||||||
return {
|
|
||||||
loader,
|
|
||||||
progress: 0,
|
|
||||||
targetProgress: 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.renderer = this.$refs.renderer
|
|
||||||
this.three = this.renderer.three
|
|
||||||
|
|
||||||
if (this.images.length < 2) {
|
|
||||||
console.error('This slider needs at least 2 images.')
|
|
||||||
} else {
|
|
||||||
this.loader.loadTextures(this.images, this.init)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.loader.dispose()
|
|
||||||
const domElement = this.renderer.renderer.domElement
|
|
||||||
domElement.removeEventListener('click', this.onClick)
|
|
||||||
domElement.removeEventListener('wheel', this.onWheel)
|
|
||||||
document.removeEventListener('keyup', this.onKeyup)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init() {
|
|
||||||
this.initScene()
|
|
||||||
|
|
||||||
gsap.fromTo(this.plane1.uProgress,
|
|
||||||
{
|
|
||||||
value: -2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
duration: 2.5,
|
|
||||||
ease: Power4.easeOut,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const domElement = this.renderer.renderer.domElement
|
|
||||||
if (this.events.click) domElement.addEventListener('click', this.onClick)
|
|
||||||
if (this.events.wheel) domElement.addEventListener('wheel', this.onWheel)
|
|
||||||
if (this.events.keyup) document.addEventListener('keyup', this.onKeyup)
|
|
||||||
this.renderer.onBeforeRender(this.updateProgress)
|
|
||||||
this.renderer.onResize(this.onResize)
|
|
||||||
},
|
|
||||||
initScene() {
|
|
||||||
const renderer = this.renderer.renderer
|
|
||||||
const scene = this.$refs.scene.scene
|
|
||||||
|
|
||||||
this.plane1 = new AnimatedPlane({
|
|
||||||
renderer, screen: this.renderer.size,
|
|
||||||
size: 10,
|
|
||||||
anim: 1,
|
|
||||||
texture: this.loader.textures[0],
|
|
||||||
})
|
|
||||||
|
|
||||||
this.plane2 = new AnimatedPlane({
|
|
||||||
renderer, screen: this.renderer.size,
|
|
||||||
size: 10,
|
|
||||||
anim: 2,
|
|
||||||
texture: this.loader.textures[1],
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setPlanesProgress(0)
|
|
||||||
this.planes = new Object3D()
|
|
||||||
this.planes.add(this.plane1.o3d)
|
|
||||||
this.planes.add(this.plane2.o3d)
|
|
||||||
scene.add(this.planes)
|
|
||||||
},
|
|
||||||
onResize() {
|
|
||||||
this.plane1.resize()
|
|
||||||
this.plane2.resize()
|
|
||||||
},
|
|
||||||
onWheel(e) {
|
|
||||||
// e.preventDefault()
|
|
||||||
if (e.deltaY > 0) {
|
|
||||||
this.setTargetProgress(this.targetProgress + 1 / 20)
|
|
||||||
} else {
|
|
||||||
this.setTargetProgress(this.targetProgress - 1 / 20)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick(e) {
|
|
||||||
if (e.clientY < this.renderer.size.height / 2) {
|
|
||||||
this.navPrevious()
|
|
||||||
} else {
|
|
||||||
this.navNext()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyup(e) {
|
|
||||||
if (e.keyCode === 37 || e.keyCode === 38) {
|
|
||||||
this.navPrevious()
|
|
||||||
} else if (e.keyCode === 39 || e.keyCode === 40) {
|
|
||||||
this.navNext()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
navNext() {
|
|
||||||
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress + 1)
|
|
||||||
else this.setTargetProgress(Math.ceil(this.targetProgress))
|
|
||||||
},
|
|
||||||
navPrevious() {
|
|
||||||
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress - 1)
|
|
||||||
else this.setTargetProgress(Math.floor(this.targetProgress))
|
|
||||||
},
|
|
||||||
setTargetProgress(value) {
|
|
||||||
this.targetProgress = value
|
|
||||||
if (this.targetProgress < 0) {
|
|
||||||
this.progress += this.images.length
|
|
||||||
this.targetProgress += this.images.length
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateProgress() {
|
|
||||||
const progress1 = lerp(this.progress, this.targetProgress, 0.1)
|
|
||||||
const pdiff = progress1 - this.progress
|
|
||||||
if (pdiff === 0) return
|
|
||||||
|
|
||||||
const p0 = this.progress % 1
|
|
||||||
const p1 = progress1 % 1
|
|
||||||
if ((pdiff > 0 && p1 < p0) || (pdiff < 0 && p0 < p1)) {
|
|
||||||
const i = Math.floor(progress1) % this.images.length
|
|
||||||
const j = (i + 1) % this.images.length
|
|
||||||
this.plane1.setTexture(this.loader.textures[i])
|
|
||||||
this.plane2.setTexture(this.loader.textures[j])
|
|
||||||
}
|
|
||||||
|
|
||||||
this.progress = progress1
|
|
||||||
this.setPlanesProgress(this.progress % 1)
|
|
||||||
},
|
|
||||||
setPlanesProgress(progress) {
|
|
||||||
this.plane1.uProgress.value = progress
|
|
||||||
this.plane2.uProgress.value = -1 + progress
|
|
||||||
this.plane1.material.opacity = 1 - progress
|
|
||||||
this.plane2.material.opacity = progress
|
|
||||||
this.plane1.o3d.position.z = progress
|
|
||||||
this.plane2.o3d.position.z = progress - 1
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
@ -1,157 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Renderer ref="renderer" antialias resize pointer>
|
|
||||||
<OrthographicCamera ref="camera" :position="{ z: 10 }" />
|
|
||||||
<Scene ref="scene" />
|
|
||||||
</Renderer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { defineComponent } from 'vue'
|
|
||||||
import { Vector2 } from 'three'
|
|
||||||
import { gsap, Power4 } from 'gsap'
|
|
||||||
|
|
||||||
import { lerp, useTextures, OrthographicCamera, Renderer, Scene } from '../../../build/trois.module.js'
|
|
||||||
import ZoomBlurImage from './ZoomBlurImage.js'
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: { OrthographicCamera, Renderer, Scene },
|
|
||||||
props: {
|
|
||||||
images: Array,
|
|
||||||
events: { type: Object, default: () => { return { wheel: true, click: true, keyup: true } } },
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const center = new Vector2()
|
|
||||||
const loader = useTextures()
|
|
||||||
|
|
||||||
return {
|
|
||||||
loader,
|
|
||||||
center,
|
|
||||||
progress: 0,
|
|
||||||
targetProgress: 0,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.renderer = this.$refs.renderer
|
|
||||||
this.three = this.renderer.three
|
|
||||||
|
|
||||||
if (this.images.length < 2) {
|
|
||||||
console.error('This slider needs at least 2 images.')
|
|
||||||
} else {
|
|
||||||
this.loader.loadTextures(this.images, this.init)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
unmounted() {
|
|
||||||
this.loader.dispose()
|
|
||||||
const domElement = this.renderer.renderer.domElement
|
|
||||||
domElement.removeEventListener('click', this.onClick)
|
|
||||||
domElement.removeEventListener('wheel', this.onWheel)
|
|
||||||
document.removeEventListener('keyup', this.onKeyup)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
init() {
|
|
||||||
this.initScene()
|
|
||||||
gsap.fromTo(this.image1.uStrength,
|
|
||||||
{
|
|
||||||
value: -2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 0,
|
|
||||||
duration: 2.5,
|
|
||||||
ease: Power4.easeOut,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const domElement = this.renderer.renderer.domElement
|
|
||||||
if (this.events.click) domElement.addEventListener('click', this.onClick)
|
|
||||||
if (this.events.wheel) domElement.addEventListener('wheel', this.onWheel)
|
|
||||||
if (this.events.keyup) document.addEventListener('keyup', this.onKeyup)
|
|
||||||
this.renderer.onBeforeRender(this.animate)
|
|
||||||
this.renderer.onResize(this.onResize)
|
|
||||||
},
|
|
||||||
initScene() {
|
|
||||||
const scene = this.$refs.scene.scene
|
|
||||||
|
|
||||||
this.image1 = new ZoomBlurImage(this.renderer)
|
|
||||||
this.image1.setMap(this.loader.textures[0])
|
|
||||||
this.image2 = new ZoomBlurImage(this.renderer)
|
|
||||||
this.image2.setMap(this.loader.textures[1])
|
|
||||||
this.setImagesProgress(0)
|
|
||||||
|
|
||||||
scene.add(this.image1.mesh)
|
|
||||||
scene.add(this.image2.mesh)
|
|
||||||
},
|
|
||||||
animate() {
|
|
||||||
const { positionN } = this.renderer.three.pointer
|
|
||||||
this.center.copy(positionN).divideScalar(2).addScalar(0.5)
|
|
||||||
this.image1.uCenter.value.lerp(this.center, 0.1)
|
|
||||||
this.image2.uCenter.value.lerp(this.center, 0.1)
|
|
||||||
// lerpv2(this.image1.uCenter.value, this.center, 0.1)
|
|
||||||
// lerpv2(this.image2.uCenter.value, this.center, 0.1)
|
|
||||||
|
|
||||||
this.updateProgress()
|
|
||||||
},
|
|
||||||
onResize() {
|
|
||||||
this.image1.updateUV()
|
|
||||||
this.image2.updateUV()
|
|
||||||
},
|
|
||||||
onWheel(e) {
|
|
||||||
// e.preventDefault()
|
|
||||||
if (e.deltaY > 0) {
|
|
||||||
this.setTargetProgress(this.targetProgress + 1 / 20)
|
|
||||||
} else {
|
|
||||||
this.setTargetProgress(this.targetProgress - 1 / 20)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onClick(e) {
|
|
||||||
if (e.clientY < this.renderer.size.height / 2) {
|
|
||||||
this.navPrevious()
|
|
||||||
} else {
|
|
||||||
this.navNext()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onKeyup(e) {
|
|
||||||
if (e.keyCode === 37 || e.keyCode === 38) {
|
|
||||||
this.navPrevious()
|
|
||||||
} else if (e.keyCode === 39 || e.keyCode === 40) {
|
|
||||||
this.navNext()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
navNext() {
|
|
||||||
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress + 1)
|
|
||||||
else this.setTargetProgress(Math.ceil(this.targetProgress))
|
|
||||||
},
|
|
||||||
navPrevious() {
|
|
||||||
if (Number.isInteger(this.targetProgress)) this.setTargetProgress(this.targetProgress - 1)
|
|
||||||
else this.setTargetProgress(Math.floor(this.targetProgress))
|
|
||||||
},
|
|
||||||
setTargetProgress(value) {
|
|
||||||
this.targetProgress = value
|
|
||||||
if (this.targetProgress < 0) {
|
|
||||||
this.progress += this.images.length
|
|
||||||
this.targetProgress += this.images.length
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateProgress() {
|
|
||||||
const progress1 = lerp(this.progress, this.targetProgress, 0.1)
|
|
||||||
const pdiff = progress1 - this.progress
|
|
||||||
if (pdiff === 0) return
|
|
||||||
|
|
||||||
const p0 = this.progress % 1
|
|
||||||
const p1 = progress1 % 1
|
|
||||||
if ((pdiff > 0 && p1 < p0) || (pdiff < 0 && p0 < p1)) {
|
|
||||||
const i = Math.floor(progress1) % this.images.length
|
|
||||||
const j = (i + 1) % this.images.length
|
|
||||||
this.image1.setMap(this.loader.textures[i])
|
|
||||||
this.image2.setMap(this.loader.textures[j])
|
|
||||||
}
|
|
||||||
|
|
||||||
this.progress = progress1
|
|
||||||
this.setImagesProgress(this.progress % 1)
|
|
||||||
},
|
|
||||||
setImagesProgress(progress) {
|
|
||||||
this.image1.uStrength.value = progress
|
|
||||||
this.image2.uStrength.value = -1 + progress
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
@ -1,109 +0,0 @@
|
|||||||
import {
|
|
||||||
Mesh,
|
|
||||||
PlaneGeometry,
|
|
||||||
ShaderMaterial,
|
|
||||||
Vector2,
|
|
||||||
} from 'three'
|
|
||||||
|
|
||||||
export default function ZoomBlurImage(renderer) {
|
|
||||||
let geometry, material, mesh
|
|
||||||
|
|
||||||
const uMap = { value: null }
|
|
||||||
const uCenter = { value: new Vector2(0.5, 0.5) }
|
|
||||||
const uStrength = { value: 0 }
|
|
||||||
const uUVOffset = { value: new Vector2(0, 0) }
|
|
||||||
const uUVScale = { value: new Vector2(1, 1) }
|
|
||||||
|
|
||||||
init()
|
|
||||||
|
|
||||||
return { geometry, material, mesh, uCenter, uStrength, setMap, updateUV }
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
geometry = new PlaneGeometry(2, 2, 1, 1)
|
|
||||||
|
|
||||||
material = new ShaderMaterial({
|
|
||||||
transparent: true,
|
|
||||||
uniforms: {
|
|
||||||
map: uMap,
|
|
||||||
center: uCenter,
|
|
||||||
strength: uStrength,
|
|
||||||
uvOffset: uUVOffset,
|
|
||||||
uvScale: uUVScale,
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
varying vec2 vUv;
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
// adapted from https://github.com/evanw/glfx.js
|
|
||||||
fragmentShader: `
|
|
||||||
uniform sampler2D map;
|
|
||||||
uniform vec2 center;
|
|
||||||
uniform float strength;
|
|
||||||
uniform vec2 uvOffset;
|
|
||||||
uniform vec2 uvScale;
|
|
||||||
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() {
|
|
||||||
vec2 tUv = vUv * uvScale + uvOffset;
|
|
||||||
if (abs(strength) > 0.001) {
|
|
||||||
vec4 color = vec4(0.0);
|
|
||||||
float total = 0.0;
|
|
||||||
vec2 toCenter = center * uvScale + uvOffset - tUv;
|
|
||||||
|
|
||||||
/* 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 <= 20.0; t++) {
|
|
||||||
float percent = (t + offset) / 20.0;
|
|
||||||
float weight = 2.0 * (percent - percent * percent);
|
|
||||||
vec4 texel = texture2D(map, tUv + toCenter * percent * strength);
|
|
||||||
|
|
||||||
/* 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;
|
|
||||||
gl_FragColor.a = 1.0 - abs(strength);
|
|
||||||
} else {
|
|
||||||
gl_FragColor = texture2D(map, tUv);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
})
|
|
||||||
|
|
||||||
mesh = new Mesh(geometry, material)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMap(value) {
|
|
||||||
uMap.value = value
|
|
||||||
updateUV()
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateUV() {
|
|
||||||
const ratio = renderer.size.ratio
|
|
||||||
const iRatio = uMap.value.image.width / uMap.value.image.height
|
|
||||||
uUVOffset.value.set(0, 0)
|
|
||||||
uUVScale.value.set(1, 1)
|
|
||||||
if (iRatio > ratio) {
|
|
||||||
uUVScale.value.x = ratio / iRatio
|
|
||||||
uUVOffset.value.x = (1 - uUVScale.value.x) / 2
|
|
||||||
} else {
|
|
||||||
uUVScale.value.y = iRatio / ratio
|
|
||||||
uUVOffset.value.y = (1 - uUVScale.value.y) / 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user