import * as THREE from 'three' import type { ThreeSharedRenderer } from './ThreeSharedRenderer' import type { ThreePBR } from './ThreePBR' import type { ThreePointLight } from './ThreePointLight' import type { AudioAnalyzer } from './AudioAnalyzer' import { nx, nx3js, ny, ny3js, nz, nz3js, px, px3js, py, py3js, pz, pz3js, rect } from '../assets' import { blobFrag, blobVert, skyboxFrag, skyboxVert } from '../shaders' export class NoiseBlob { private textSprite!: THREE.Texture private renderer: ThreeSharedRenderer private isInit: boolean private readonly showHdr: boolean private w: number private h: number private shaderCubeMap!: THREE.ShaderMaterial private pbr!: ThreePBR private scene!: THREE.Scene private shadowScene!: THREE.Scene private shaderMesh!: THREE.ShaderMaterial private shaderWire!: THREE.ShaderMaterial private shaderPoints!: THREE.ShaderMaterial private shaderShadow!: THREE.ShaderMaterial private shaderPopPoints!: THREE.ShaderMaterial private shaderPopWire!: THREE.ShaderMaterial private shaderPopPointsOut!: THREE.ShaderMaterial private shaderPopWireOut!: THREE.ShaderMaterial private light: ThreePointLight private cubeMapB!: THREE.CubeTexture private cubeMap!: THREE.CubeTexture private analyzer: AudioAnalyzer private timer = 0 constructor (renderer: ThreeSharedRenderer, analyzer: AudioAnalyzer, light: ThreePointLight) { this.renderer = renderer this.analyzer = analyzer this.light = light this.isInit = false this.showHdr = true this.w = renderer.container.clientWidth this.h = renderer.container.clientHeight this.initTexture() this.initShader() this.initScene() this.initCubeMap() } destroy () { this.renderer.destroy() this.light.destroy() this.textSprite?.dispose() this.shaderCubeMap?.dispose() this.shaderMesh?.dispose() this.shaderWire?.dispose() this.shaderPoints?.dispose() this.shaderShadow?.dispose() this.shaderPopPoints?.dispose() this.shaderPopWire?.dispose() this.shaderPopPointsOut?.dispose() this.shaderPopWireOut?.dispose() this.cubeMapB?.dispose() this.cubeMap?.dispose() } initTexture () { this.textSprite = new THREE.TextureLoader().load(rect) this.textSprite.wrapS = THREE.ClampToEdgeWrapping this.textSprite.wrapT = THREE.ClampToEdgeWrapping this.textSprite.magFilter = THREE.LinearFilter this.textSprite.minFilter = THREE.LinearFilter } initShader () { const screenRes = `vec2( ${this.w.toFixed(1)}, ${this.h.toFixed(1)})` const load = (_vert, _frag) => new THREE.ShaderMaterial({ defines: { SCREEN_RES: screenRes, }, uniforms: { u_t: { value: 0 }, u_is_init: { value: false }, u_audio_high: { value: 0.0 }, u_audio_mid: { value: 0.0 }, u_audio_bass: { value: 0.0 }, u_audio_level: { value: 0.0 }, u_audio_history: { value: 0.0 }, }, vertexShader: _vert, fragmentShader: _frag, }) this.shaderCubeMap = new THREE.ShaderMaterial( { defines: { SCREEN_RES: screenRes, }, uniforms: { u_cubemap: { value: this.cubeMap }, u_cubemap_b: { value: this.cubeMapB }, u_exposure: { value: 2.0 }, u_gamma: { value: 2.2 }, }, vertexShader: skyboxVert, fragmentShader: skyboxFrag, }, ) this.shaderMesh = load(blobVert, blobFrag) this.shaderWire = load(blobVert, blobFrag) this.shaderPoints = load(blobVert, blobFrag) this.shaderShadow = load(blobVert, blobFrag) this.shaderPopPoints = load(blobVert, blobFrag) this.shaderPopWire = load(blobVert, blobFrag) this.shaderPopPointsOut = load(blobVert, blobFrag) this.shaderPopWireOut = load(blobVert, blobFrag) this.shaderMesh.extensions.derivatives = true this.shaderMesh.defines.IS_MESH = 'true' this.shaderMesh.defines.HAS_SHADOW = 'true' this.shaderWire.defines.IS_WIRE = 'true' this.shaderPoints.defines.IS_POINTS = 'true' this.shaderShadow.defines.IS_SHADOW = 'true' this.shaderPopPoints.defines.IS_POINTS = 'true' this.shaderPopPoints.defines.IS_POP = 'true' this.shaderPopWire.defines.IS_WIRE = 'true' this.shaderPopWire.defines.IS_POP = 'true' this.shaderPopPointsOut.defines.IS_POINTS = 'true' this.shaderPopPointsOut.defines.IS_POP_OUT = 'true' this.shaderPopWireOut.defines.IS_WIRE = 'true' this.shaderPopWireOut.defines.IS_POP_OUT = 'true' const lightPosition = this.light.getLightPosition() lightPosition.applyMatrix4(this.renderer.camera.modelViewMatrix) const shadowMatrix = new THREE.Matrix4() shadowMatrix.identity() shadowMatrix.multiplyMatrices( this.light.getLight().projectionMatrix, this.light.getLight().modelViewMatrix, ) this.shaderMesh.uniforms.u_light_pos = { value: lightPosition } this.shaderMesh.uniforms.u_shadow_matrix = { value: shadowMatrix } this.shaderMesh.uniforms.u_shadow_map = { value: this.light.getShadowMap() } this.shaderMesh.uniforms.u_debug_shadow = { value: false } this.shaderPoints.uniforms.textSprite = { value: this.textSprite } this.shaderPopPoints.uniforms.textSprite = { value: this.textSprite } this.shaderPopWire.uniforms.textSprite = { value: this.textSprite } this.shaderPopPointsOut.uniforms.textSprite = { value: this.textSprite } this.shaderPopWireOut.uniforms.textSprite = { value: this.textSprite } this.shaderPoints.blending = THREE.AdditiveBlending this.shaderWire.blending = THREE.AdditiveBlending this.shaderPopPoints.blending = THREE.AdditiveBlending this.shaderPopWire.blending = THREE.AdditiveBlending this.shaderPopPointsOut.blending = THREE.AdditiveBlending this.shaderPopWireOut.blending = THREE.AdditiveBlending this.shaderWire.transparent = true this.shaderPoints.transparent = true this.shaderPopPoints.transparent = true this.shaderPopWire.transparent = true this.shaderPopPointsOut.transparent = true this.shaderPopWireOut.transparent = true this.shaderWire.depthTest = false this.shaderPoints.depthTest = false this.shaderPopPoints.depthTest = false this.shaderPopWire.depthTest = false this.shaderPopPointsOut.depthTest = false this.shaderPopWireOut.depthTest = false } initScene () { const _sphere_size = 0.7 const _geom = new THREE.SphereGeometry(_sphere_size, 128, 128) const _geom_lowres = new THREE.SphereGeometry(_sphere_size, 64, 64) this.scene = new THREE.Scene() this.shadowScene = new THREE.Scene() const _mesh = new THREE.Mesh(_geom, this.shaderMesh) const _wire = new THREE.Line(_geom_lowres, this.shaderWire) const _points = new THREE.Points(_geom, this.shaderPoints) const _shadow_mesh = new THREE.Mesh(_geom, this.shaderShadow) const _pop_points = new THREE.Points(_geom_lowres, this.shaderPopPoints) const _pop_wire = new THREE.Line(_geom_lowres, this.shaderPopWire) const _pop_points_out = new THREE.Points(_geom_lowres, this.shaderPopPointsOut) const _pop_wire_out = new THREE.Line(_geom_lowres, this.shaderPopWireOut) this.scene.add(_mesh) this.scene.add(_wire) this.scene.add(_points) this.scene.add(_pop_points) this.scene.add(_pop_wire) this.scene.add(_pop_points_out) this.scene.add(_pop_wire_out) this.shadowScene.add(_shadow_mesh) const boxGeometry = new THREE.BoxGeometry(100, 100, 100) const mesh = new THREE.Mesh(boxGeometry, this.shaderCubeMap) const mS = (new THREE.Matrix4()).identity() mS.elements[0] = -1 mS.elements[5] = -1 mS.elements[10] = -1 boxGeometry.applyMatrix4(mS) this.scene.add(mesh) } initCubeMap () { this.cubeMap = new THREE.CubeTextureLoader().load([ px3js, nx3js, py3js, ny3js, pz3js, nz3js, ]) this.cubeMap.format = THREE.RGBAFormat this.cubeMapB = new THREE.CubeTextureLoader().load([ px, nx, py, ny, pz, nz, ]) this.cubeMapB.format = THREE.RGBAFormat this.shaderMesh.uniforms.cubemap = { value: this.cubeMap } this.shaderCubeMap.uniforms.u_cubemap.value = this.cubeMap this.shaderMesh.uniforms.cubemap_b = { value: this.cubeMapB } this.shaderCubeMap.uniforms.u_cubemap_b.value = this.cubeMapB this.shaderCubeMap.uniforms.u_show_cubemap = { value: this.showHdr } this.shaderMesh.defines.HAS_CUBEMAP = 'true' } updateCubeMap () { const crossFader = 0.0 this.shaderMesh.uniforms.cross_fader = { value: crossFader } this.shaderCubeMap.uniforms.cross_fader = { value: crossFader } this.shaderCubeMap.uniforms.u_exposure.value = this.pbr.getExposure() this.shaderCubeMap.uniforms.u_gamma.value = this.pbr.getGamma() } update () { const shaders = [ this.shaderMesh, this.shaderWire, this.shaderPoints, this.shaderPopPoints, this.shaderPopWire, this.shaderPopPointsOut, this.shaderPopWireOut, this.shaderShadow, ] for (let i = 0; i < shaders.length; i++) { shaders[i].uniforms.u_is_init.value = this.isInit shaders[i].uniforms.u_t.value = this.timer shaders[i].uniforms.u_audio_high.value = this.analyzer.getHigh() shaders[i].uniforms.u_audio_mid.value = this.analyzer.getMid() shaders[i].uniforms.u_audio_bass.value = this.analyzer.getBass() shaders[i].uniforms.u_audio_level.value = this.analyzer.getLevel() shaders[i].uniforms.u_audio_history.value = this.analyzer.getHistory() } this.updateCubeMap() const _cam = this.renderer.getCamera() this.renderer.renderer.render(this.scene, _cam) if (!this.isInit) { this.isInit = true } this.timer = this.renderer.getTimer() } setRetina () { this.w *= 0.5 this.h *= 0.5 } setPBR (pbr: ThreePBR) { this.pbr = pbr this.shaderMesh.uniforms.tex_normal = { value: this.pbr.getNormalMap() } this.shaderMesh.uniforms.tex_roughness = { value: this.pbr.getRoughnessMap() } this.shaderMesh.uniforms.tex_metallic = { value: this.pbr.getMetallicMap() } this.shaderMesh.uniforms.u_normal = { value: this.pbr.getNormal() } this.shaderMesh.uniforms.u_roughness = { value: this.pbr.getRoughness() } this.shaderMesh.uniforms.u_metallic = { value: this.pbr.getMetallic() } this.shaderMesh.uniforms.u_exposure = { value: this.pbr.getExposure() } this.shaderMesh.uniforms.u_gamma = { value: this.pbr.getGamma() } this.shaderMesh.uniforms.u_view_matrix_inverse = { value: this.renderer.getInverseMatrix() } this.shaderMesh.defines.IS_PBR = 'true' } updatePBR () { this.shaderMesh.uniforms.u_normal.value = this.pbr.getNormal() this.shaderMesh.uniforms.u_roughness.value = this.pbr.getRoughness() this.shaderMesh.uniforms.u_metallic.value = this.pbr.getMetallic() this.shaderMesh.uniforms.u_exposure.value = this.pbr.getExposure() this.shaderMesh.uniforms.u_gamma.value = this.pbr.getGamma() this.shaderMesh.uniforms.u_view_matrix_inverse.value = this.renderer.getInverseMatrix() } }