feat: add Asteroid visualizer (#1577)

This commit is contained in:
Phan An 2022-11-07 15:23:59 +01:00 committed by GitHub
parent bc2cc89788
commit 27bfe31391
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1340 additions and 3 deletions

View file

@ -34,5 +34,14 @@ export const visualizers: Visualizer[] = [
author: 'Radik (@H2xDev)',
url: 'https://codepen.io/H2xDev/pen/rRRGbv'
}
},
{
id: 'asteroid',
name: 'Asteroid',
init: async (container) => (await import('@/visualizers/asteroid/scripts')).init(container),
credits: {
author: 'JH (@jhugheswebdev)',
url: 'https://github.com/jhugheswebdev/sound-equalizer-threejs'
}
}
]

View file

@ -27,8 +27,7 @@ export const audioService = {
this.source = this.context.createMediaElementSource(this.element)
this.analyzer = this.context.createAnalyser()
this.source.connect(this.analyzer)
this.analyzer.connect(this.preampGainNode)
this.source.connect(this.preampGainNode)
const config = equalizerStore.getConfig()
@ -62,7 +61,10 @@ export const audioService = {
})
})
prevFilter!.connect(this.context.destination)
prevFilter!.connect(this.analyzer)
// connect the analyzer node last, so that changes to the equalizer affect the visualizer as well
this.analyzer.connect(this.context.destination)
this.unlockAudioContext()
},

View file

@ -0,0 +1,35 @@
import nx3js from './nx_3js.jpg'
import ny3js from './ny_3js.jpg'
import nz3js from './nz_3js.jpg'
import px3js from './px_3js.jpg'
import py3js from './py_3js.jpg'
import pz3js from './pz_3js.jpg'
import nx from './nx.jpg'
import ny from './ny.jpg'
import nz from './nz.jpg'
import px from './px.jpg'
import py from './py.jpg'
import pz from './pz.jpg'
import rect from './sprite_additive_rect.png'
import normalTexture from './normal.jpg'
import roughnessTexture from './roughness.jpg'
import metallicTexture from './metallic.jpg'
export {
nx3js,
ny3js,
nz3js,
px3js,
py3js,
pz3js,
nx,
ny,
nz,
px,
py,
pz,
rect,
normalTexture,
roughnessTexture,
metallicTexture
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,85 @@
import { audioService } from '@/services'
export class AudioAnalyzer {
private bass = 0.
private mid = 0.
private high = 0.
private level = 0.
private history = 0.
private frame = 0
private readonly frequencyBinCount: number
// [!] this can't be read-only regardless of IDE's suggestion
private audioBuffer: Uint8Array
private analyzer: AnalyserNode
constructor () {
this.analyzer = audioService.analyzer
this.analyzer.fftSize = 128
this.frequencyBinCount = this.analyzer.frequencyBinCount
this.audioBuffer = new Uint8Array(this.frequencyBinCount)
}
update () {
this.analyzer.getByteFrequencyData(this.audioBuffer)
let bass = 0., mid = 0., high = 0.
if (this.audioBuffer[0] === 0) {
// create a "pulse" effect on audio idle
if (this.frame % 40 == (Math.floor(Math.random() * 40.))) {
bass = Math.random()
mid = Math.random()
high = Math.random()
}
} else {
const passSize = this.frequencyBinCount / 3.
for (let i = 0; i < this.frequencyBinCount; i++) {
const val = Math.pow(this.audioBuffer[i] / 196., 3.)
if (i < passSize)
bass += val
else if (i >= passSize && i < passSize * 2)
mid += val
else if (i >= passSize * 2)
high += val
}
bass /= passSize
mid /= passSize
high /= passSize
}
this.bass = this.bass > bass ? this.bass * .96 : bass
this.mid = this.mid > mid ? this.mid * .96 : mid
this.high = this.high > high ? this.high * .96 : high
this.level = (this.bass + this.mid + this.high) / 3.
this.history += this.level * .01 + .005
this.frame++
}
getBass () {
return this.bass || 0.
}
getMid () {
return this.mid || 0.
}
getHigh () {
return this.high || 0.
}
getLevel () {
return this.level || 0.
}
getHistory () {
return this.history || 0.
}
}

View file

@ -0,0 +1,6 @@
export class DeviceChecker {
isRetina () {
const m = window.matchMedia('only screen and (min--moz-device-pixel-ratio: 1.3), only screen and (-o-min-device-pixel-ratio: 2.6/2), only screen and (-webkit-min-device-pixel-ratio: 1.3), only screen and (min-device-pixel-ratio: 1.3), only screen and (min-resolution: 1.3dppx)')
return (m && m.matches || (window.devicePixelRatio > 1))
}
}

View file

@ -0,0 +1,325 @@
import * as THREE from 'three'
import { ThreeSharedRenderer } from './ThreeSharedRenderer'
import { ThreePBR } from './ThreePBR'
import { ThreePointLight } from './ThreePointLight'
import { 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. },
u_audio_mid: { value: 0. },
u_audio_bass: { value: 0. },
u_audio_level: { value: 0. },
u_audio_history: { value: 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. },
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 = .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.
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 *= .5
this.h *= .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()
}
}

View file

@ -0,0 +1,65 @@
import * as THREE from 'three'
import { metallicTexture, normalTexture, roughnessTexture } from '../assets'
export class ThreePBR {
private readonly normalMap: THREE.Texture
private readonly roughnessMap: THREE.Texture
private readonly metallicMap: THREE.Texture
private readonly normal = 1.
private readonly roughness = .0
private readonly metallic = 1.
private readonly exposure = 2.
private readonly gamma = 2.2
constructor () {
this.normalMap = new THREE.TextureLoader().load(normalTexture)
this.normalMap.wrapS = THREE.ClampToEdgeWrapping
this.normalMap.wrapT = THREE.ClampToEdgeWrapping
this.normalMap.magFilter = THREE.LinearFilter
this.normalMap.minFilter = THREE.LinearFilter
this.roughnessMap = new THREE.TextureLoader().load(roughnessTexture)
this.roughnessMap.wrapS = THREE.ClampToEdgeWrapping
this.roughnessMap.wrapT = THREE.ClampToEdgeWrapping
this.roughnessMap.magFilter = THREE.LinearFilter
this.roughnessMap.minFilter = THREE.LinearFilter
this.metallicMap = new THREE.TextureLoader().load(metallicTexture)
this.metallicMap.wrapS = THREE.ClampToEdgeWrapping
this.metallicMap.wrapT = THREE.ClampToEdgeWrapping
this.metallicMap.magFilter = THREE.LinearFilter
this.metallicMap.minFilter = THREE.LinearFilter
}
getNormalMap () {
return this.normalMap
}
getRoughnessMap () {
return this.roughnessMap
}
getMetallicMap () {
return this.metallicMap
}
getExposure () {
return this.exposure
}
getGamma () {
return this.gamma
}
getNormal () {
return this.normal
}
getRoughness () {
return this.roughness
}
getMetallic () {
return this.metallic
}
}

View file

@ -0,0 +1,44 @@
import * as THREE from 'three'
export class ThreePointLight {
private readonly shadowBuffer: THREE.WebGLRenderTarget
private readonly light: THREE.PerspectiveCamera
constructor () {
this.shadowBuffer = new THREE.WebGLRenderTarget(2048., 2048.)
this.shadowBuffer.depthBuffer = true
this.shadowBuffer.depthTexture = new THREE.DepthTexture(0, 0)
this.light = new THREE.PerspectiveCamera(35., this.shadowBuffer.width / this.shadowBuffer.height, .1, 1000.)
this.light.lookAt(0., 0., 0.)
}
ziggle (frame: number) {
const e = frame * 10.
this.light.position.copy(new THREE.Vector3(
this.light.position.x * Math.sin(e),
this.light.position.y,
this.light.position.z * Math.cos(e)
))
this.light.lookAt(0., 0., 0.)
this.light.updateProjectionMatrix()
}
getLight () {
return this.light
}
getLightPosition () {
return this.light.position
}
getShadowMap () {
return this.shadowBuffer.depthTexture
}
destroy () {
this.shadowBuffer.dispose()
}
}

View file

@ -0,0 +1,89 @@
import * as THREE from 'three'
export class ThreeSharedRenderer {
public readonly camera: THREE.PerspectiveCamera
private timer: number
public renderer!: THREE.WebGLRenderer
public container: HTMLElement
private readonly resizeHandler: () => void
constructor (container: HTMLElement) {
this.container = container
this.camera = new THREE.PerspectiveCamera(45, this.container.clientWidth / this.container.clientHeight, .1, 100)
this.camera.position.z = 5
this.camera.updateProjectionMatrix()
this.timer = 0
this.initRenderer()
this.resizeHandler = this.resize.bind(this)
window.addEventListener('resize', this.resizeHandler, false)
}
resize () {
this.camera.aspect = this.container.clientWidth / this.container.clientHeight
this.camera.updateProjectionMatrix()
this.renderer.setPixelRatio(window.devicePixelRatio)
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight)
}
initRenderer () {
this.renderer = new THREE.WebGLRenderer()
this.renderer.setPixelRatio(window.devicePixelRatio)
this.renderer.setSize(this.container.clientWidth, this.container.clientHeight)
this.renderer.autoClear = true
this.renderer.shadowMap.enabled = true
this.renderer.shadowMap.type = THREE.PCFShadowMap
this.container.appendChild(this.renderer.domElement)
}
render (queue: Closure[]) {
for (let i = 0; i < queue.length; i++) {
this.renderer.clearDepth()
queue[i]()
}
this.timer += .001
if (this.timer > 999999.) {
this.timer = 0.
}
}
ziggleCam (frame: number) {
const e = frame
const nLoc = new THREE.Vector3(
Math.sin(e),
Math.cos(e * .9) * Math.sin(e * .7),
Math.cos(e)).normalize()
nLoc.multiplyScalar(8. + 2. * Math.sin(2. * e))
this.camera.position.copy(nLoc)
this.camera.lookAt(0., 0., 0.)
this.camera.updateProjectionMatrix()
}
getInverseMatrix () {
return this.camera.matrixWorldInverse
}
getTimer () {
return this.timer == undefined ? 0. : this.timer
}
getCamera () {
return this.camera
}
destroy () {
window.removeEventListener('resize', this.resizeHandler, false)
this.renderer.dispose()
}
}

View file

@ -0,0 +1,58 @@
import { ThreeSharedRenderer } from './ThreeSharedRenderer'
import { ThreePBR } from './ThreePBR'
import { ThreePointLight } from './ThreePointLight'
import { NoiseBlob } from './NoiseBlob'
import { AudioAnalyzer } from './AudioAnalyzer'
import { DeviceChecker } from './DeviceChecker'
let analyzer: AudioAnalyzer
let renderer: ThreeSharedRenderer
let renderQueue
let blob
let pbr
let light
let deviceChecker
export const init = (container: HTMLElement) => {
deviceChecker = new DeviceChecker()
const isRetina = deviceChecker.isRetina()
analyzer = new AudioAnalyzer()
renderer = new ThreeSharedRenderer(container)
pbr = new ThreePBR()
light = new ThreePointLight()
blob = new NoiseBlob(renderer, analyzer, light)
blob.setPBR(pbr)
isRetina && blob.setRetina()
renderQueue = [
blob.update.bind(blob)
]
update()
return () => {
// this will destroy the renderers and related class instances
blob.destroy()
}
}
const update = () => {
requestAnimationFrame(update)
analyzer.update()
// update blob
blob.updatePBR()
// update pbr
pbr.exposure = 5. + 30. * analyzer.getLevel()
light.ziggle(renderer.getTimer())
renderer.ziggleCam(renderer.getTimer())
renderer.render(renderQueue)
}

View file

@ -0,0 +1,300 @@
export const blobFrag =
`
varying float v_noise;
uniform float u_audio_high;
uniform float u_audio_mid;
uniform float u_audio_bass;
uniform float u_audio_level;
uniform float u_audio_history;
vec3 norm(in vec3 _v){
return length(_v) > .0 ? normalize(_v) : vec3(.0);
}
#if defined(IS_POINTS)
uniform sampler2D tex_sprite;
#endif
#if defined(IS_PBR) && defined(HAS_CUBEMAP)
uniform samplerCube cubemap;
uniform samplerCube cubemap_b;
uniform float cross_fader;
uniform sampler2D tex_normal;
uniform sampler2D tex_roughness;
uniform sampler2D tex_metallic;
uniform float u_normal;
uniform float u_roughness;
uniform float u_metallic;
uniform float u_exposure;
uniform float u_gamma;
varying vec3 v_world_normal;
varying vec3 v_object_pos;
varying vec3 v_eye_pos;
varying vec3 v_pos;
varying vec3 v_normal;
varying vec3 v_world_pos;
varying vec2 v_uv;
#define PI 3.1415926535897932384626433832795
// Filmic tonemapping from
// http://filmicgames.com/archives/75
const float A = 0.15;
const float B = 0.50;
const float C = 0.10;
const float D = 0.20;
const float E = 0.02;
const float F = 0.30;
vec3 Uncharted2Tonemap( vec3 x )
{
return ((x*(A*x+C*B)+D*E)/((x*(A*x+B)+D*F) + .00001))-E/F;
}
// https://www.unrealengine.com/blog/physically-based-shading-on-mobile
vec3 EnvBRDFApprox( vec3 SpecularColor, float Roughness, float NoV )
{
const vec4 c0 = vec4( -1, -0.0275, -0.572, 0.022 );
const vec4 c1 = vec4( 1, 0.0425, 1.04, -0.04 );
vec4 r = Roughness * c0 + c1;
float a004 = min( r.x * r.x, exp2( -9.28 * NoV ) ) * r.x + r.y;
vec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;
return SpecularColor * AB.x + AB.y;
}
// http://the-witness.net/news/2012/02/seamless-cube-map-filtering/
vec3 fix_cube_lookup( vec3 v, float cube_size, float lod ) {
float M = max(max(abs(v.x), abs(v.y)), abs(v.z));
float scale = 1. - exp2(lod) / (cube_size + .00001);
if (abs(v.x) != M) v.x *= scale;
if (abs(v.y) != M) v.y *= scale;
if (abs(v.z) != M) v.z *= scale;
return v;
}
// Normal Blending
// Source adapted from http://blog.selfshadow.com/publications/blending-in-detail/
vec3 blendNormalsUnity( vec3 baseNormal, vec3 detailsNormal )
{
vec3 n1 = baseNormal;
vec3 n2 = detailsNormal;
mat3 nBasis = mat3(
vec3(n1.z, n1.y, -n1.x), // +90 degree rotation around y axis
vec3(n1.x, n1.z, -n1.y), // -90 degree rotation around x axis
vec3(n1.x, n1.y, n1.z));
return norm(n2.x*nBasis[0] + n2.y*nBasis[1] + n2.z*nBasis[2]);
}
vec3 blendNormals( vec3 n1, vec3 n2 )
{
return blendNormalsUnity( n1, n2 );
}
#endif
#if defined(HAS_SHADOW)
uniform sampler2D u_shadow_map;
uniform vec3 u_light_pos;
uniform bool u_debug_shadow;
varying vec4 v_shadow_coord;
float sample_shadow( vec4 sc )
{
float s = 1./1024.;
vec2 unproj2D = vec2 (sc.s / (sc.q + .00001),
sc.t / (sc.q + .00001));
float shadow = 0.0;
shadow += texture2D( u_shadow_map, unproj2D + vec2(-s,-s) ).r;
shadow += texture2D( u_shadow_map, unproj2D + vec2(-s, 0.) ).r;
shadow += texture2D( u_shadow_map, unproj2D + vec2(-s, s) ).r;
shadow += texture2D( u_shadow_map, unproj2D + vec2( 0.,-s) ).r;
shadow += texture2D( u_shadow_map, unproj2D + vec2( 0., 0.) ).r;
shadow += texture2D( u_shadow_map, unproj2D + vec2( 0., s) ).r;
shadow += texture2D( u_shadow_map, unproj2D + vec2( s,-s) ).r;
shadow += texture2D( u_shadow_map, unproj2D + vec2( s, 0.) ).r;
shadow += texture2D( u_shadow_map, unproj2D + vec2( s, s) ).r;
return shadow/9.0;;
}
#endif
void main(){
float m_noise = v_noise;
float m_noise_inv = 1.-v_noise;
vec3 m_diffuse = vec3(0.);
m_diffuse.r += m_noise_inv + m_noise;
m_diffuse.g += m_noise*1.5;
//m_diffuse.b += m_noise;
m_diffuse -= pow(abs(1.-m_noise), 4.)*.95; //<- darken peak
m_diffuse = clamp(m_diffuse, vec3(0.), vec3(2.));
m_diffuse *= pow(u_audio_level, 2.);
vec3 m_col = m_diffuse;
#if defined(IS_SHADOW)
gl_FragColor = vec4(m_col, 1.);
return;
#endif
#if defined(IS_PBR) && defined(HAS_CUBEMAP)
vec3 N = norm( v_world_normal );
// blend with PBR's
N = blendNormals( N, texture2D( tex_normal, v_uv ).xyz );
vec3 V = norm( v_eye_pos );
// fresnel
float m_fresnel = pow(1. + dot(norm(v_world_pos - v_eye_pos), v_world_normal), 8.);
#if defined(HAS_SHADOW)
// Light direction
vec3 L = norm( u_light_pos - v_world_pos.xyz );
// Surface reflection vector
vec3 R = norm( -reflect( L, N ) );
#endif
// sample the roughness and metallic textures
float roughnessMask = texture2D( tex_roughness, v_uv ).r;
float metallicMask = texture2D( tex_metallic, v_uv ).r;
// deduce the diffuse and specular color from the baseColor and how metallic the material is
vec3 m_specular_col = vec3(m_diffuse)*8.;
vec3 m_diffuse_col = vec3(m_diffuse)*8.;
vec3 diffuseColor = m_diffuse_col - m_diffuse_col * u_metallic * metallicMask;
vec3 specularColor = mix( vec3( 0.08 * m_specular_col ), m_diffuse_col, u_metallic * metallicMask );
// sample the pre-filtered cubemap at the corresponding mipmap level
int numMips = 6;
float mip = float(numMips) - 1. + log2( u_roughness * roughnessMask );
vec3 lookup = -reflect( V, N );
vec3 cube_a_rad = pow( abs(textureCube( cubemap, fix_cube_lookup( lookup, 2048., mip ) ).rgb), vec3( 2.2 ) );
vec3 cube_b_rad = pow( abs(textureCube( cubemap_b, fix_cube_lookup( lookup, 2048., mip ) ).rgb), vec3( 2.2 ) );
vec3 cube_a_irr = pow( abs(textureCube( cubemap, fix_cube_lookup( N, 2048., 0. ) ).rgb), vec3( 2.2 ) );
vec3 cube_b_irr = pow( abs(textureCube( cubemap_b, fix_cube_lookup( N, 2048., 0. ) ).rgb), vec3( 2.2 ) );
vec3 radiance = mix(cube_a_rad, cube_b_rad, cross_fader);
vec3 irradiance = mix(cube_a_irr, cube_b_irr, cross_fader);
// get the approximate reflectance
// float NoV = saturate( dot( N, V ) );
float NoV = clamp( dot( N, V ), 0., 1. );
vec3 reflectance = EnvBRDFApprox( specularColor, pow( abs(u_roughness * roughnessMask), 4.0 ), NoV );
// combine the specular IBL and the BRDF
vec3 diffuse = diffuseColor * radiance;
vec3 specular = radiance * reflectance;
m_col = (diffuse + specular)*u_audio_level*(1.-min(m_fresnel, .99));
#if defined(HAS_SHADOW)
// from light source
vec3 m_light_diffuse_color = vec3(m_diffuse)*3.;
vec3 m_light_specular_color = vec3(m_diffuse)*3.;
float m_light_diffuse_intensity = 30.;
float m_light_specular_intensity = 30.;
float m_light_diffuse_pow = 150.;
float m_light_specular_pow = 120.;
// Diffuse factor
float NdotL = max( dot( N, L ), 0.0 );
vec3 D = vec3( NdotL );
D = pow(abs(D), vec3(m_light_diffuse_pow));
D *= m_light_diffuse_color * m_light_diffuse_intensity;
// Specular factor
vec3 S = pow( max( dot( R, V ), 0.0 ), m_light_specular_pow ) * vec3(1.);
S *= m_light_specular_color * m_light_specular_intensity;
m_col += (D + S)*u_audio_level*(1.-min(m_fresnel, .99));
// cal shadow
float m_shadow = 1.;
vec4 m_shadow_coord = v_shadow_coord;
m_shadow_coord.z += .0003; // <- bias
m_shadow = sample_shadow(m_shadow_coord);
m_col *= (m_shadow + m_col*.2 + m_diffuse*.5);
#endif
// add noise diffuse
m_col += pow(abs(m_diffuse), vec3(10.))*8.;
// apply the tone-mapping
m_col = Uncharted2Tonemap( m_col * u_exposure );
// white balance
m_col = m_col * ( 1. / (Uncharted2Tonemap( vec3( 20. ) ) + .00001) );
// gamma correction
m_col = pow( abs(m_col), vec3( 1. / (u_gamma + .00001) ) );
#endif
#if defined(IS_WIRE) || defined(IS_POINTS)
m_col.b -= m_col.g;
// inner ziggle
m_col *= .4 * pow(abs(m_noise), 6.);
// outter ziggle
m_col.rg += .2 * pow(abs(m_noise_inv), 4.);
m_col.g *= .5;
// treble burn
m_col += pow(abs(u_audio_high), 3.) * 1.;
// on&off
m_col *= u_audio_level;
#if defined(IS_WIRE)
m_col *= .7;
#endif
#endif
gl_FragColor = vec4(m_col, 1.);
#if defined(HAS_SHADOW)
if(u_debug_shadow)
gl_FragColor = vec4(vec3(m_shadow), 1.);
#endif
#if defined(IS_POINTS)
gl_FragColor *= texture2D(tex_sprite, gl_PointCoord);
#endif
#if defined(IS_POP) || defined(IS_POP_OUT)
gl_FragColor.rgb = pow(abs(gl_FragColor.rgb), vec3(1.2));
#if defined(IS_POINTS) && defined(IS_POP)
gl_FragColor.rgb *= pow(u_audio_level, 2.);
gl_FragColor.rgb *= 50.;
#endif
#if defined(IS_WIRE)
gl_FragColor.rgb *= 2.;
#if defined(IS_POP_OUT)
gl_FragColor.rgb *= .2;
#endif
#endif
#endif
}
`

View file

@ -0,0 +1,266 @@
export const blobVert =
`
uniform vec2 u_mouse;
uniform vec2 u_mouse_delta;
uniform float u_t;
uniform bool u_is_init;
uniform float u_audio_high;
uniform float u_audio_mid;
uniform float u_audio_bass;
uniform float u_audio_level;
uniform float u_audio_history;
varying float v_noise;
#if defined(IS_PBR) && defined(HAS_CUBEMAP)
uniform mat4 u_view_matrix_inverse;
varying vec3 v_world_normal;
varying vec3 v_eye_pos;
varying vec3 v_object_pos;
varying vec3 v_pos;
varying vec3 v_normal;
varying vec3 v_world_pos;
varying vec2 v_uv;
#endif
#if defined(HAS_SHADOW)
uniform mat4 u_shadow_matrix;
varying vec4 v_shadow_coord;
const mat4 biasMat = mat4( 0.5, 0.0, 0.0, 0.0,
0.0, 0.5, 0.0, 0.0,
0.0, 0.0, 0.5, 0.0,
0.5, 0.5, 0.5, 1.0 );
#endif
// (Keijiro) This shader was slightly modified from the original version.
// It's recommended to use the original version for other purposes.
//
// Description : Array and textureless GLSL 2D/3D/4D simplex
// noise functions.
// Author : Ian McEwan, Ashima Arts.
// Maintainer : ijm
// Lastmod : 20110822 (ijm)
// License : Copyright (C) 2011 Ashima Arts. All rights reserved.
// Distributed under the MIT License. See LICENSE file.
// https://github.com/ashima/webgl-noise
//
vec3 mod289(vec3 x)
{
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 mod289(vec4 x) {
return x - floor(x * (1.0 / 289.0)) * 289.0;
}
vec4 permute(vec4 x)
{
return mod289((x * 34.0 + 1.0) * x);
}
vec4 taylorInvSqrt(vec4 r)
{
return 1.79284291400159 - 0.85373472095314 * r;
}
float snoise(vec3 v)
{
const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0);
// First corner
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v - i + dot(i, C.xxx);
// Other corners
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
// x1 = x0 - i1 + 1.0 * C.xxx;
// x2 = x0 - i2 + 2.0 * C.xxx;
// x3 = x0 - 1.0 + 3.0 * C.xxx;
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy;
vec3 x3 = x0 - 0.5;
// Permutations
i = mod289(i); // Avoid truncation effects in permutation
vec4 p =
permute(permute(permute(i.z + vec4(0.0, i1.z, i2.z, 1.0))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
// Gradients: 7x7 points over a square, mapped onto an octahedron.
// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
vec4 j = p - 49.0 * floor(p * (1.0 / 49.0)); // mod(p,7*7)
vec4 x_ = floor(j * (1.0 / 7.0));
vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N)
vec4 x = x_ * (2.0 / 7.0) + 0.5 / 7.0 - 1.0;
vec4 y = y_ * (2.0 / 7.0) + 0.5 / 7.0 - 1.0;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
//vec4 s0 = vec4(lessThan(b0, 0.0)) * 2.0 - 1.0;
//vec4 s1 = vec4(lessThan(b1, 0.0)) * 2.0 - 1.0;
vec4 s0 = floor(b0) * 2.0 + 1.0;
vec4 s1 = floor(b1) * 2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
vec3 g0 = vec3(a0.xy, h.x);
vec3 g1 = vec3(a0.zw, h.y);
vec3 g2 = vec3(a1.xy, h.z);
vec3 g3 = vec3(a1.zw, h.w);
// Normalise gradients
vec4 norm = taylorInvSqrt(vec4(dot(g0, g0), dot(g1, g1), dot(g2, g2), dot(g3, g3)));
g0 *= norm.x;
g1 *= norm.y;
g2 *= norm.z;
g3 *= norm.w;
// Mix final noise value
vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), 0.0);
m = m * m;
m = m * m;
vec4 px = vec4(dot(x0, g0), dot(x1, g1), dot(x2, g2), dot(x3, g3));
return (42.0 * dot(m, px) + 1.) * .5;
}
vec3 norm(in vec3 _v){
return length(_v) > .0 ? normalize(_v) : vec3(.0);
}
mat4 rotationMatrix(vec3 axis, float angle)
{
axis = norm(axis);
float s = sin(angle);
float c = cos(angle);
float oc = 1.0 - c;
return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
0.0, 0.0, 0.0, 1.0);
}
void main(){
float m_bass = u_audio_bass;
float m_mid = u_audio_mid;
float m_high = u_audio_high;
float m_level = u_audio_level;
float m_history = u_audio_history;
vec3 m_noise_seed = position.xyz;
float m_noise_complexity = .6;
float m_noise_time = u_audio_history * .3;
float m_noise_scale = 1.2 + m_level;
vec3 m_tangent_vector = .00001 * norm(cross(position, vec3(1., 0., 0.))
+ cross(position, vec3(0., 1., 0.)));
vec3 m_bitangent_vector = .00001 * norm(cross(m_tangent_vector, position));
float m_fbm = 0.;
float m_fbm_tangent = 0.;
float m_fbm_bitangent = 0.;
const int m_noise_oct = 5;
for(int i = 0; i < m_noise_oct; i++){
m_fbm += snoise(
(m_noise_seed) * m_noise_complexity * float(i) +
m_noise_time * float(i)
);
m_fbm_tangent += snoise(
(m_noise_seed + m_tangent_vector) * m_noise_complexity * float(i) +
m_noise_time * float(i)
);
m_fbm_bitangent += snoise(
(m_noise_seed + m_bitangent_vector) * m_noise_complexity * float(i) +
m_noise_time * float(i)
);
}
m_fbm /= (float(m_noise_oct));
m_fbm_tangent /= (float(m_noise_oct));
m_fbm_bitangent /= (float(m_noise_oct));
vec3 m_pos = position + norm(position) * m_fbm * m_noise_scale;
vec3 m_pos_tangent = (position + m_tangent_vector) + norm(position + m_tangent_vector) * m_fbm * m_noise_scale;
vec3 m_pos_bitangent = (position + m_bitangent_vector) + norm(position + m_bitangent_vector) * m_fbm * m_noise_scale;
vec3 m_normal = norm(cross( (m_pos_tangent - m_pos), (m_pos_bitangent - m_pos)));
// get color
float m_noise_col = pow(abs(1.-m_fbm), 3.5);
v_noise = m_noise_col + m_noise_col * m_level * 2.2;
// rand direction
float _dirx = snoise(m_pos.zyx * 4. + m_noise_time * .01);
float _diry = snoise(m_pos.yzx * 4. + m_noise_time * .01);
float _dirz = snoise(m_pos.zxy * 4. + m_noise_time * .01);
vec3 _rand_point_dir = vec3(_dirx, _diry, _dirz);
_rand_point_dir = 1.-2.*_rand_point_dir;
#if defined(IS_WIRE) || defined(IS_POINTS)
// size
gl_PointSize = pow(abs(m_fbm), 6.) * 1000. * m_high;
m_pos += (_rand_point_dir * .3 * m_level);
#endif
#if defined(IS_POP)
gl_PointSize *= .5;
m_pos *= 1.1 * m_fbm;
m_pos = vec3(rotationMatrix(vec3(.3,1.,.2), .5*m_history) * vec4(m_pos, 1.));
#endif
#if defined(IS_POP_OUT)
gl_PointSize *= .5;
m_pos *= 1.2;
m_pos += (_rand_point_dir*_rand_point_dir * .2 * m_high);
m_pos = vec3(rotationMatrix(vec3(1.,.2,.3), -.5*m_history) * vec4(m_pos, 1.));
#endif
#if defined(IS_PBR) && defined(HAS_CUBEMAP)
vec4 _world_pos = modelMatrix * vec4(m_pos, 1.);
vec4 _view_pos = viewMatrix * _world_pos;
v_object_pos = m_pos;
v_pos = _view_pos.xyz;
v_normal = normalMatrix * m_normal;
v_world_pos = _world_pos.xyz;
v_world_normal = vec3(u_view_matrix_inverse * vec4(v_normal, 0.));
v_eye_pos = -1. * vec3(u_view_matrix_inverse * (_view_pos - vec4(0.,0.,0.,1.)) );
v_uv = uv;
#endif
#if defined(HAS_SHADOW)
v_shadow_coord = (biasMat * u_shadow_matrix) * vec4(m_pos, 1.);
#endif
gl_Position = projectionMatrix * modelViewMatrix * vec4(m_pos, 1.);
}
`

View file

@ -0,0 +1,4 @@
export * from './skybox.vert'
export * from './skybox.frag'
export * from './blob.vert'
export * from './blob.frag'

View file

@ -0,0 +1,41 @@
export const skyboxFrag =
`
#define A 0.15
#define B 0.50
#define C 0.10
#define D 0.20
#define E 0.02
#define F 0.30
uniform samplerCube u_cubemap;
uniform samplerCube u_cubemap_b;
uniform float cross_fader;
uniform float u_exposure;
uniform float u_gamma;
uniform bool u_show_cubemap;
varying vec3 v_direction;
vec3 Uncharted2Tonemap( vec3 x ){
return ((x*(A*x+C*B)+D*E)/(x*(A*x+B)+D*F))-E/F;
}
void main( void ){
vec3 cube_a = pow( abs(textureCube( u_cubemap, v_direction ).rgb), vec3( 2.2 ) );
vec3 cube_b = pow( abs(textureCube( u_cubemap_b, v_direction ).rgb), vec3( 2.2 ) );
vec3 color = mix(cube_a, cube_b, cross_fader);
// apply the tone-mapping
// color = Uncharted2Tonemap( color * u_exposure );
// white balance
// color = color * ( 1. / Uncharted2Tonemap( vec3( 20. ) ) );
// gamma correction
// color = pow( color, vec3( 1. / u_gamma ) );
color *= u_show_cubemap ? 1. : 0.;
gl_FragColor = vec4( color, 1. );
}
`

View file

@ -0,0 +1,8 @@
export const skyboxVert =
`
varying vec3 v_direction;
void main(){
v_direction = position.xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position.xyz, 1.);
}
`