├── .github └── FUNDING.yml ├── README.md ├── basics ├── particleSystem │ ├── assets │ │ ├── cloudNoise.jpg │ │ ├── cloudShape.jpg │ │ ├── dot.png │ │ ├── fbmNoise.png │ │ ├── flame.png │ │ ├── mario-sprite.png │ │ ├── message.html │ │ ├── perlinNoise.png │ │ ├── snowflake.png │ │ ├── starship.glb │ │ └── venice-sunset.hdr │ ├── components │ │ ├── cloud.js │ │ ├── drawcalls.js │ │ ├── environment-map.js │ │ ├── fireworks.js │ │ ├── galaxy.js │ │ ├── particle-system-explosion.js │ │ ├── particle-system-instanced.js │ │ ├── particle-system-naive-wireframe.js │ │ ├── particle-system-naive.js │ │ ├── particle-system-rocket-exhaust.js │ │ ├── particle-system-shell.js │ │ └── swap-entities.js │ └── index.html └── quad │ ├── assets │ ├── explosion.png │ ├── mario-sprite.png │ └── message.html │ ├── components │ ├── explosion-sprite-sheet.js │ ├── quad-texture.js │ ├── quad-wireframe.js │ ├── sprite-animation.js │ ├── sprite-strip.js │ └── uv-values.js │ └── index.html ├── index.html ├── js ├── components │ ├── info-message.js │ └── slideshow.js ├── systems │ └── wireframe.js └── vendor │ ├── OrbitControls.js │ ├── PMREMGenerator.js │ ├── RGBELoader.js │ ├── aframe-master.min.js │ └── aframe-master.min.js.map └── wind-waker ├── fire-dynamic ├── OrbitControls.js ├── index.html └── main.js ├── fire-static ├── OrbitControls.js ├── index.html └── main.js ├── water ├── assets │ ├── clouds.png │ ├── island.glb │ ├── king-of-red-lions.glb │ ├── message.html │ └── water.png ├── components │ ├── tile.js │ ├── tiles-animation.js │ ├── vignette-background.js │ ├── water-v1.js │ ├── water-v2.js │ ├── water-v3.js │ └── water.js └── index.html └── wind ├── aframe.html ├── components └── wind-v1.js ├── index.html └── main.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [dmarcos] 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

A-Frame FX

2 | 3 |

Notes, studies, components, examples and snippets about 2D and 3D special effects that I share as I learn. Not a library, not a framework.

4 | 5 | ## Effects 6 | 7 | - [Quad](https://diegomarcos.com/aframe-fx/basics/quad/#3) 8 | - [Wind Waker Water](https://diegomarcos.com/aframe-fx/wind-waker/water/#4) 9 | - [Particle Systems](https://diegomarcos.com/aframe-fx/basics/particleSystem/) 10 | 11 | -------------------------------------------------------------------------------- /basics/particleSystem/assets/cloudNoise.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/cloudNoise.jpg -------------------------------------------------------------------------------- /basics/particleSystem/assets/cloudShape.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/cloudShape.jpg -------------------------------------------------------------------------------- /basics/particleSystem/assets/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/dot.png -------------------------------------------------------------------------------- /basics/particleSystem/assets/fbmNoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/fbmNoise.png -------------------------------------------------------------------------------- /basics/particleSystem/assets/flame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/flame.png -------------------------------------------------------------------------------- /basics/particleSystem/assets/mario-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/mario-sprite.png -------------------------------------------------------------------------------- /basics/particleSystem/assets/message.html: -------------------------------------------------------------------------------- 1 |

References

2 | 3 |

Galaxy Shader by Fabrice Neyret

4 | 5 |

Starship model by AkiroXR

6 | 7 |

Procedural clouds by Codrops

-------------------------------------------------------------------------------- /basics/particleSystem/assets/perlinNoise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/perlinNoise.png -------------------------------------------------------------------------------- /basics/particleSystem/assets/snowflake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/snowflake.png -------------------------------------------------------------------------------- /basics/particleSystem/assets/starship.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/starship.glb -------------------------------------------------------------------------------- /basics/particleSystem/assets/venice-sunset.hdr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/particleSystem/assets/venice-sunset.hdr -------------------------------------------------------------------------------- /basics/particleSystem/components/cloud.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('cloud', { 2 | schema: {startTime: {default: 0}}, 3 | 4 | vertexShader:` 5 | varying vec2 vUv; 6 | 7 | void main() { 8 | vUv = uv; 9 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 10 | } 11 | `, 12 | 13 | fragmentShader:` 14 | varying vec2 vUv; 15 | uniform sampler2D uMapShape; 16 | uniform sampler2D uMapNoise; 17 | uniform float uTime; 18 | 19 | vec4 gammaCorrect(vec4 color, float gamma){ 20 | return pow(color, vec4(1.0 / gamma)); 21 | } 22 | 23 | vec4 levelRange(vec4 color, float minInput, float maxInput){ 24 | return min(max(color - vec4(minInput), vec4(0.0)) / (vec4(maxInput) - vec4(minInput)), vec4(1.0)); 25 | } 26 | 27 | vec4 levels(vec4 color, float minInput, float gamma, float maxInput){ 28 | return gammaCorrect(levelRange(color, minInput, maxInput), gamma); 29 | } 30 | 31 | vec4 mod289(vec4 x) { 32 | return x - floor(x * (1.0 / 289.0)) * 289.0; 33 | } 34 | 35 | vec3 mod289(vec3 x) { 36 | return x - floor(x * (1.0 / 289.0)) * 289.0; 37 | } 38 | 39 | float mod289(float x) { 40 | return x - floor(x * (1.0 / 289.0)) * 289.0; 41 | } 42 | 43 | vec4 permute(vec4 x) { 44 | return mod289(((x*34.0)+1.0)*x); 45 | } 46 | 47 | float permute(float x) { 48 | return mod289(((x*34.0)+1.0)*x); 49 | } 50 | 51 | vec4 taylorInvSqrt(vec4 r) 52 | { 53 | return 1.79284291400159 - 0.85373472095314 * r; 54 | } 55 | 56 | float taylorInvSqrt(float r) 57 | { 58 | return 1.79284291400159 - 0.85373472095314 * r; 59 | } 60 | 61 | vec4 grad4(float j, vec4 ip) 62 | { 63 | const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); 64 | vec4 p,s; 65 | 66 | p.xyz = floor( fract (vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; 67 | p.w = 1.5 - dot(abs(p.xyz), ones.xyz); 68 | s = vec4(lessThan(p, vec4(0.0))); 69 | p.xyz = p.xyz + (s.xyz*2.0 - 1.0) * s.www; 70 | 71 | return p; 72 | } 73 | 74 | // (sqrt(5) - 1)/4 = F4, used once below 75 | #define F4 0.309016994374947451 76 | 77 | float snoise4D(vec4 v) 78 | { 79 | const vec4 C = vec4( 0.138196601125011, // (5 - sqrt(5))/20 G4 80 | 0.276393202250021, // 2 * G4 81 | 0.414589803375032, // 3 * G4 82 | -0.447213595499958); // -1 + 4 * G4 83 | 84 | // First corner 85 | vec4 i = floor(v + dot(v, vec4(F4)) ); 86 | vec4 x0 = v - i + dot(i, C.xxxx); 87 | 88 | // Other corners 89 | 90 | // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) 91 | vec4 i0; 92 | vec3 isX = step( x0.yzw, x0.xxx ); 93 | vec3 isYZ = step( x0.zww, x0.yyz ); 94 | 95 | // i0.x = dot( isX, vec3( 1.0 ) ); 96 | i0.x = isX.x + isX.y + isX.z; 97 | i0.yzw = 1.0 - isX; 98 | 99 | // i0.y += dot( isYZ.xy, vec2( 1.0 ) ); 100 | i0.y += isYZ.x + isYZ.y; 101 | i0.zw += 1.0 - isYZ.xy; 102 | i0.z += isYZ.z; 103 | i0.w += 1.0 - isYZ.z; 104 | 105 | // i0 now contains the unique values 0,1,2,3 in each channel 106 | vec4 i3 = clamp( i0, 0.0, 1.0 ); 107 | vec4 i2 = clamp( i0-1.0, 0.0, 1.0 ); 108 | vec4 i1 = clamp( i0-2.0, 0.0, 1.0 ); 109 | 110 | // x0 = x0 - 0.0 + 0.0 * C.xxxx 111 | // x1 = x0 - i1 + 1.0 * C.xxxx 112 | // x2 = x0 - i2 + 2.0 * C.xxxx 113 | // x3 = x0 - i3 + 3.0 * C.xxxx 114 | // x4 = x0 - 1.0 + 4.0 * C.xxxx 115 | vec4 x1 = x0 - i1 + C.xxxx; 116 | vec4 x2 = x0 - i2 + C.yyyy; 117 | vec4 x3 = x0 - i3 + C.zzzz; 118 | vec4 x4 = x0 + C.wwww; 119 | 120 | // Permutations 121 | i = mod289(i); 122 | float j0 = permute( permute( permute( permute(i.w) + i.z) + i.y) + i.x); 123 | vec4 j1 = permute( permute( permute( permute ( 124 | i.w + vec4(i1.w, i2.w, i3.w, 1.0 )) 125 | + i.z + vec4(i1.z, i2.z, i3.z, 1.0 )) 126 | + i.y + vec4(i1.y, i2.y, i3.y, 1.0 )) 127 | + i.x + vec4(i1.x, i2.x, i3.x, 1.0 )); 128 | 129 | // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope 130 | // 7*7*6 = 294, which is close to the ring size 17*17 = 289. 131 | vec4 ip = vec4(1.0/294.0, 1.0/49.0, 1.0/7.0, 0.0) ; 132 | 133 | vec4 p0 = grad4(j0, ip); 134 | vec4 p1 = grad4(j1.x, ip); 135 | vec4 p2 = grad4(j1.y, ip); 136 | vec4 p3 = grad4(j1.z, ip); 137 | vec4 p4 = grad4(j1.w, ip); 138 | 139 | // Normalise gradients 140 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 141 | p0 *= norm.x; 142 | p1 *= norm.y; 143 | p2 *= norm.z; 144 | p3 *= norm.w; 145 | p4 *= taylorInvSqrt(dot(p4,p4)); 146 | 147 | // Mix contributions from the five corners 148 | vec3 m0 = max(0.6 - vec3(dot(x0,x0), dot(x1,x1), dot(x2,x2)), 0.0); 149 | vec2 m1 = max(0.6 - vec2(dot(x3,x3), dot(x4,x4) ), 0.0); 150 | m0 = m0 * m0; 151 | m1 = m1 * m1; 152 | return 49.0 * ( dot(m0*m0, vec3( dot( p0, x0 ), dot( p1, x1 ), dot( p2, x2 ))) 153 | + dot(m1*m1, vec2( dot( p3, x3 ), dot( p4, x4 ) ) ) ) ; 154 | 155 | } 156 | 157 | float snoise3D(vec3 v) 158 | { 159 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 160 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 161 | 162 | // First corner 163 | vec3 i = floor(v + dot(v, C.yyy) ); 164 | vec3 x0 = v - i + dot(i, C.xxx) ; 165 | 166 | // Other corners 167 | vec3 g = step(x0.yzx, x0.xyz); 168 | vec3 l = 1.0 - g; 169 | vec3 i1 = min( g.xyz, l.zxy ); 170 | vec3 i2 = max( g.xyz, l.zxy ); 171 | 172 | // x0 = x0 - 0.0 + 0.0 * C.xxx; 173 | // x1 = x0 - i1 + 1.0 * C.xxx; 174 | // x2 = x0 - i2 + 2.0 * C.xxx; 175 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 176 | vec3 x1 = x0 - i1 + C.xxx; 177 | vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y 178 | vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y 179 | 180 | // Permutations 181 | i = mod289(i); 182 | vec4 p = permute( permute( permute( 183 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 184 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 185 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 186 | 187 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 188 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 189 | float n_ = 0.142857142857; // 1.0/7.0 190 | vec3 ns = n_ * D.wyz - D.xzx; 191 | 192 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) 193 | 194 | vec4 x_ = floor(j * ns.z); 195 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 196 | 197 | vec4 x = x_ *ns.x + ns.yyyy; 198 | vec4 y = y_ *ns.x + ns.yyyy; 199 | vec4 h = 1.0 - abs(x) - abs(y); 200 | 201 | vec4 b0 = vec4( x.xy, y.xy ); 202 | vec4 b1 = vec4( x.zw, y.zw ); 203 | 204 | //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; 205 | //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; 206 | vec4 s0 = floor(b0)*2.0 + 1.0; 207 | vec4 s1 = floor(b1)*2.0 + 1.0; 208 | vec4 sh = -step(h, vec4(0.0)); 209 | 210 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 211 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 212 | 213 | vec3 p0 = vec3(a0.xy,h.x); 214 | vec3 p1 = vec3(a0.zw,h.y); 215 | vec3 p2 = vec3(a1.xy,h.z); 216 | vec3 p3 = vec3(a1.zw,h.w); 217 | 218 | //Normalise gradients 219 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 220 | p0 *= norm.x; 221 | p1 *= norm.y; 222 | p2 *= norm.z; 223 | p3 *= norm.w; 224 | 225 | // Mix final noise value 226 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 227 | m = m * m; 228 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 229 | dot(p2,x2), dot(p3,x3) ) ); 230 | } 231 | 232 | float fbm3d(vec3 x, const in int it) { 233 | float v = 0.0; 234 | float a = 0.5; 235 | vec3 shift = vec3(100); 236 | 237 | for (int i = 0; i < 32; ++i) { 238 | if(i this.data.particleRate) { 142 | for (var i = 0; i < this.particlesInfo.length; i++) { 143 | particleInfo = this.particlesInfo[i]; 144 | 145 | // Skip if the particle is in use. 146 | if (particleInfo.particleLifeTime) { continue; } 147 | 148 | particleInfo.particleLifeTime = this.data.particleLifeTime; 149 | particleInfo.object3D.position.y = 1; 150 | particleInfo.object3D.position.x = particleInfo.xPosition; 151 | particleInfo.object3D.updateMatrix(); 152 | 153 | this.visibleAttribute.setX(i, 1.0); 154 | this.el.emit('particlestarted'); 155 | this.instancedMesh.setMatrixAt(i, particleInfo.object3D.matrix); 156 | break; 157 | } 158 | this.lastParticleDelta = 0; 159 | } 160 | 161 | this.instancedMesh.instanceMatrix.needsUpdate = true; 162 | this.visibleAttribute.needsUpdate = true; 163 | }, 164 | 165 | update: function (oldData) { 166 | if (oldData.src !== this.data.src) { this.loadTextureImage(); } 167 | }, 168 | 169 | loadTextureImage: function () { 170 | var src = this.data.src; 171 | var self = this; 172 | this.el.sceneEl.systems.material.loadTexture(src, {src: src}, function textureLoaded (texture) { 173 | self.el.sceneEl.renderer.initTexture(texture); 174 | texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping; 175 | texture.magFilfer = THREE.LinearFilter; 176 | self.shader.uniforms.uMap.value = texture; 177 | }); 178 | }, 179 | 180 | initQuadShader: function() { 181 | var uniforms = { 182 | uMap: {type: 't', value: null} 183 | }; 184 | var shader = this.shader = new THREE.ShaderMaterial({ 185 | uniforms: uniforms, 186 | vertexShader: this.vertexShader, 187 | fragmentShader: this.fragmentShader, 188 | transparent: true 189 | }); 190 | return shader; 191 | } 192 | }); -------------------------------------------------------------------------------- /basics/particleSystem/components/particle-system-naive-wireframe.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('particle-system-naive-wireframe', { 2 | schema: { 3 | particleRate: {default: 50}, 4 | particleSize: {default: 0.1}, 5 | particleSpeed: {default: 0.005}, 6 | particleLifeTime: {default: 1000}, 7 | }, 8 | 9 | vertexShader:` 10 | attribute vec3 barycentric; 11 | varying vec3 vDistanceBarycenter; 12 | 13 | void main() { 14 | vDistanceBarycenter = barycentric; 15 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 16 | } 17 | `, 18 | 19 | fragmentShader:` 20 | #if __VERSION__ == 100 21 | #extension GL_OES_standard_derivatives : enable 22 | #endif 23 | 24 | varying vec3 vDistanceBarycenter; 25 | 26 | // This is like 27 | float aastep (float threshold, float dist) { 28 | float afwidth = fwidth(dist) * 0.5; 29 | return smoothstep(threshold - afwidth, threshold + afwidth, dist); 30 | } 31 | 32 | void main() { 33 | float thickness = 0.05; 34 | // this will be our signed distance for the wireframe edge 35 | float d = min(min(vDistanceBarycenter.x, vDistanceBarycenter.y), vDistanceBarycenter.z); 36 | // compute the anti-aliased stroke edge 37 | float edge = 1.0 - aastep(thickness, d); 38 | // now compute the final color of the mesh 39 | vec4 lineColor = vec4(0.1, 0.1, 0.1, 1.0); 40 | vec4 fillColor = vec4(1.0, 1.0, 1.0, 1.0); 41 | gl_FragColor = vec4(mix(fillColor, lineColor, edge)); 42 | }`, 43 | 44 | init: function () { 45 | var positionX; 46 | var positionY; 47 | var sign; 48 | this.quads = []; 49 | this.particles = []; 50 | this.positions = []; 51 | this.initQuadShader(); 52 | for (var i = 0; i < 200; i++) { 53 | sign = Math.floor(Math.random() * 2) === 0 ? 1 : -1; 54 | positionX = sign * Math.random(); 55 | sign = Math.floor(Math.random() * 2) === 0 ? 1 : -1; 56 | positionY = sign * Math.random(); 57 | this.addQuad(positionX / 2, 1, 0); 58 | } 59 | }, 60 | 61 | addQuad: function(x, y, z) { 62 | var mesh; 63 | var quad = {}; 64 | var geometry = this.initQuadGeometry(x, y, quad); 65 | var mesh = new THREE.Mesh(geometry, this.shader); 66 | 67 | quad.mesh = mesh; 68 | quad.lifeTime = 0; 69 | 70 | this.quads.push(quad); 71 | mesh.visible = false; 72 | this.el.setObject3D('quad' + (this.quads.length - 1), mesh); 73 | }, 74 | 75 | initQuadGeometry: function (x, y, quad) { 76 | var geometry = new THREE.BufferGeometry(); 77 | var vertexCoordinateSize = 3; // 3 floats to represent x,y,z coordinates. 78 | var wireframeSystem = this.el.sceneEl.systems.wireframe; 79 | 80 | var quadSize = this.data.particleSize; 81 | var quadHalfSize = quadSize / 2.0; 82 | 83 | var uvCoordinateSize = 2; // 2 float to represent u,v coordinates. 84 | // No indexed 85 | var positions = quad.positions = [ 86 | // Top left triangle 87 | quadHalfSize + x, quadHalfSize + y, 0.0, 88 | -quadHalfSize + x, quadHalfSize + y, 0.0, 89 | -quadHalfSize + x, -quadHalfSize + y, 0.0, 90 | // Bottom right triangle 91 | -quadHalfSize + x, -quadHalfSize + y, 0.0, 92 | quadHalfSize + x, -quadHalfSize + y, 0.0, 93 | quadHalfSize + x, quadHalfSize + y, 0.0 94 | ]; 95 | 96 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, vertexCoordinateSize)); 97 | wireframeSystem.calculateBarycenters(geometry); 98 | 99 | return geometry; 100 | }, 101 | 102 | tick: function (time, delta) { 103 | var geometry; 104 | var positions; 105 | var quad; 106 | var particleSpeed = this.data.particleSpeed; 107 | 108 | this.lastParticleDelta = this.lastParticleDelta || 0; 109 | this.lastParticleDelta += delta; 110 | for (var i = 0; i < this.quads.length; i++) { 111 | quad = this.quads[i]; 112 | geometry = quad.mesh.geometry; 113 | positions = geometry.attributes.position.array; 114 | if (!quad.mesh.visible) { continue; } 115 | 116 | positions[1] = positions[1] - particleSpeed; 117 | positions[4] = positions[4] - particleSpeed; 118 | positions[7] = positions[7] - particleSpeed; 119 | 120 | positions[10] = positions[10] - particleSpeed; 121 | positions[13] = positions[13] - particleSpeed; 122 | positions[16] = positions[16] - particleSpeed; 123 | 124 | quad.particleLifeTime -= delta; 125 | 126 | if (quad.particleLifeTime <= 0) { quad.mesh.visible = false } 127 | geometry.attributes.position.needsUpdate = true; 128 | } 129 | 130 | // Emits a new particle. 131 | if (this.lastParticleDelta > this.data.particleRate) { 132 | for (var i = 0; i < this.quads.length; i++) { 133 | quad = this.quads[i]; 134 | if (quad.mesh.visible) { continue; } 135 | quad.mesh.visible = true; 136 | positions = quad.mesh.geometry.attributes.position.array; 137 | positions[1] = quad.positions[1]; 138 | positions[4] = quad.positions[4]; 139 | positions[7] = quad.positions[7]; 140 | 141 | positions[10] = quad.positions[10]; 142 | positions[13] = quad.positions[13]; 143 | positions[16] = quad.positions[16]; 144 | quad.particleLifeTime = this.data.particleLifeTime; 145 | break; 146 | } 147 | this.lastParticleDelta = 0; 148 | } 149 | }, 150 | 151 | initQuadShader: function() { 152 | var shader = this.shader = new THREE.ShaderMaterial({ 153 | vertexShader: this.vertexShader, 154 | fragmentShader: this.fragmentShader, 155 | }); 156 | } 157 | }); -------------------------------------------------------------------------------- /basics/particleSystem/components/particle-system-naive.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('particle-system-naive', { 2 | schema: { 3 | particleSize: {default: 0.1}, 4 | particleSpeed: {default: 0.005}, 5 | particleLifeTime: {default: 1000}, 6 | src: {type: 'map'} 7 | }, 8 | vertexShader:` 9 | varying vec2 vUv; 10 | 11 | void main() { 12 | vUv = uv; 13 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 14 | } 15 | `, 16 | 17 | fragmentShader:` 18 | varying vec2 vUv; 19 | uniform sampler2D uMap; 20 | 21 | void main() { 22 | gl_FragColor = texture2D(uMap, vUv); 23 | }`, 24 | 25 | init: function () { 26 | var positionX; 27 | var positionY; 28 | var sign; 29 | this.quads = []; 30 | this.particles = []; 31 | this.positions = []; 32 | this.initQuadShader(); 33 | for (var i = 0; i < 100; i++) { 34 | sign = Math.floor(Math.random() * 2) === 0 ? 1 : -1; 35 | positionX = sign * Math.random(); 36 | sign = Math.floor(Math.random() * 2) === 0 ? 1 : -1; 37 | positionY = sign * Math.random(); 38 | this.addQuad(positionX / 2, 1, 0); 39 | } 40 | }, 41 | 42 | addQuad: function(x, y, z) { 43 | var mesh; 44 | var quad = {}; 45 | var geometry = this.initQuadGeometry(x, y, quad); 46 | var mesh = new THREE.Mesh(geometry, this.shader); 47 | 48 | quad.mesh = mesh; 49 | quad.lifeTime = 0; 50 | 51 | this.quads.push(quad); 52 | mesh.visible = false; 53 | this.el.setObject3D('quad' + (this.quads.length - 1), mesh); 54 | }, 55 | 56 | initQuadGeometry: function (x, y, quad) { 57 | var geometry = new THREE.BufferGeometry(); 58 | var vertexCoordinateSize = 3; // 3 floats to represent x,y,z coordinates. 59 | 60 | var quadSize = this.data.particleSize; 61 | var quadHalfSize = quadSize / 2.0; 62 | 63 | var uvCoordinateSize = 2; // 2 float to represent u,v coordinates. 64 | // No indexed 65 | var positions = quad.positions = [ 66 | // Top left triangle 67 | quadHalfSize + x, quadHalfSize + y, 0.0, 68 | -quadHalfSize + x, quadHalfSize + y, 0.0, 69 | -quadHalfSize + x, -quadHalfSize + y, 0.0, 70 | // Bottom right triangle 71 | -quadHalfSize + x, -quadHalfSize + y, 0.0, 72 | quadHalfSize + x, -quadHalfSize + y, 0.0, 73 | quadHalfSize + x, quadHalfSize + y, 0.0 74 | ]; 75 | 76 | var uvs = [ 77 | 1, 1, 78 | 0, 1, 79 | 0, 0, 80 | 81 | 0, 0, 82 | 1, 0, 83 | 1, 1, 84 | ]; 85 | 86 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, vertexCoordinateSize)); 87 | geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, uvCoordinateSize)); 88 | 89 | return geometry; 90 | }, 91 | 92 | tick: function (time, delta) { 93 | var geometry; 94 | var positions; 95 | var quad; 96 | var particleSpeed = this.data.particleSpeed; 97 | 98 | this.lastParticleDelta = this.lastParticleDelta || 0; 99 | this.lastParticleDelta += delta; 100 | for (var i = 0; i < this.quads.length; i++) { 101 | quad = this.quads[i]; 102 | geometry = quad.mesh.geometry; 103 | positions = geometry.attributes.position.array; 104 | if (!quad.mesh.visible) { continue; } 105 | 106 | positions[1] = positions[1] - particleSpeed; 107 | positions[4] = positions[4] - particleSpeed; 108 | positions[7] = positions[7] - particleSpeed; 109 | 110 | positions[10] = positions[10] - particleSpeed; 111 | positions[13] = positions[13] - particleSpeed; 112 | positions[16] = positions[16] - particleSpeed; 113 | 114 | quad.particleLifeTime -= delta; 115 | 116 | if (quad.particleLifeTime <= 0) { quad.mesh.visible = false } 117 | geometry.attributes.position.needsUpdate = true; 118 | } 119 | 120 | if (this.lastParticleDelta > 50) { 121 | for (var i = 0; i < this.quads.length; i++) { 122 | quad = this.quads[i]; 123 | if (quad.mesh.visible) { continue; } 124 | quad.mesh.visible = true; 125 | positions = quad.mesh.geometry.attributes.position.array; 126 | positions[1] = quad.positions[1]; 127 | positions[4] = quad.positions[4]; 128 | positions[7] = quad.positions[7]; 129 | 130 | positions[10] = quad.positions[10]; 131 | positions[13] = quad.positions[13]; 132 | positions[16] = quad.positions[16]; 133 | quad.particleLifeTime = this.data.particleLifeTime; 134 | break; 135 | } 136 | this.lastParticleDelta = 0; 137 | } 138 | }, 139 | 140 | update: function (oldData) { 141 | if (oldData.src !== this.data.src) { this.loadQuadImage(); } 142 | }, 143 | 144 | loadQuadImage: function () { 145 | var src = this.data.src; 146 | var self = this; 147 | this.el.sceneEl.systems.material.loadTexture(src, {src: src}, function textureLoaded (texture) { 148 | self.el.sceneEl.renderer.initTexture(texture); 149 | texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping; 150 | texture.magFilfer = THREE.LinearFilter; 151 | self.shader.uniforms.uMap.value = texture; 152 | }); 153 | }, 154 | 155 | initQuadShader: function() { 156 | var uniforms = {uMap: {type: 't', value: null}}; 157 | var shader = this.shader = new THREE.ShaderMaterial({ 158 | uniforms: uniforms, 159 | vertexShader: this.vertexShader, 160 | fragmentShader: this.fragmentShader, 161 | transparent: true 162 | }); 163 | } 164 | }); -------------------------------------------------------------------------------- /basics/particleSystem/components/particle-system-rocket-exhaust.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('particle-system-rocket-exhaust', { 2 | schema: { 3 | particlesNumber: {default: 100}, 4 | particleSize: {default: 0.1}, 5 | particleSpeed: {default: 0.005}, 6 | particleLifeTime: {default: 1000}, 7 | src: {type: 'map'}, 8 | color: {default: 'white', type: 'color'}, 9 | endColor: {default: 'white', type: 'color'}, 10 | animateColor: {default: false}, 11 | shockDiamonds: {default: false}, 12 | endScaleFactor: {default: 1.0} 13 | }, 14 | 15 | multiple: true, 16 | 17 | vertexShader:` 18 | attribute float visible; 19 | attribute vec4 color; 20 | 21 | varying vec2 vUv; 22 | varying float vVisible; 23 | varying vec4 vColor; 24 | 25 | void main() { 26 | vUv = uv; 27 | vVisible = visible; 28 | vColor = color; 29 | 30 | vec4 mvPosition = instanceMatrix * vec4( position, 1.0 ); 31 | vec4 modelViewPosition = modelViewMatrix * mvPosition; 32 | gl_Position = projectionMatrix * modelViewPosition; 33 | } 34 | `, 35 | 36 | fragmentShader:` 37 | varying float vVisible; 38 | varying vec2 vUv; 39 | varying vec4 vColor; 40 | uniform sampler2D uMap; 41 | uniform vec3 uColor; 42 | 43 | void main() { 44 | if (vVisible == 1.0) { 45 | gl_FragColor = texture2D(uMap, vUv)* vec4(vColor.xyz, 0.4); 46 | } else { 47 | discard; 48 | } 49 | 50 | }`, 51 | 52 | init: function () { 53 | var positionX; 54 | var positionY; 55 | var sign; 56 | var particlesNumber = this.data.particlesNumber; 57 | 58 | var geometry = this.initQuadGeometry(); 59 | var shader = this.initQuadShader(); 60 | var mesh = this.instancedMesh = new THREE.InstancedMesh(geometry, shader, particlesNumber); 61 | 62 | this.quads = []; 63 | for (var i = 0; i < particlesNumber; i++) { 64 | sign = Math.floor(Math.random() * 2) === 0 ? 1 : -1; 65 | positionX = sign * Math.random(); 66 | sign = Math.floor(Math.random() * 2) === 0 ? 1 : -1; 67 | positionY = sign * Math.random(); 68 | this.addQuad(0, 1, 0); 69 | } 70 | 71 | this.el.setObject3D('particleInstanced' + this.attrName, mesh); 72 | }, 73 | 74 | addQuad: function(x, y, z) { 75 | var mesh; 76 | var quad = {}; 77 | 78 | quad.object3D = new THREE.Object3D(); 79 | quad.object3D.position.set(x, y, z); 80 | quad.lifeTime = 0; 81 | this.quads.push(quad); 82 | }, 83 | 84 | initQuadGeometry: function () { 85 | var geometry = new THREE.BufferGeometry(); 86 | var vertexCoordinateSize = 3; // 3 floats to represent x,y,z coordinates. 87 | 88 | var quadSize = this.data.particleSize; 89 | var quadHalfSize = quadSize / 2.0; 90 | 91 | var uvCoordinateSize = 2; // 2 float to represent u,v coordinates. 92 | // No indexed 93 | var positions = [ 94 | // Top left triangle 95 | quadHalfSize, quadHalfSize, 0.0, 96 | -quadHalfSize, quadHalfSize, 0.0, 97 | -quadHalfSize, -quadHalfSize, 0.0, 98 | // Bottom right triangle 99 | -quadHalfSize, -quadHalfSize, 0.0, 100 | quadHalfSize, -quadHalfSize, 0.0, 101 | quadHalfSize, quadHalfSize, 0.0 102 | ]; 103 | 104 | var uvs = [ 105 | 1, 1, 106 | 0, 1, 107 | 0, 0, 108 | 109 | 0, 0, 110 | 1, 0, 111 | 1, 1, 112 | ]; 113 | 114 | var visible = Array(this.data.particlesNumber).fill(0.0); 115 | var color = Array(this.data.particlesNumber*4).fill(0.0); 116 | 117 | var visibleAttribute = this.visibleAttribute = new THREE.InstancedBufferAttribute(new Float32Array(visible), 1); 118 | var colorAttribute = this.colorAttribute = new THREE.InstancedBufferAttribute(new Float32Array(color), 4); 119 | 120 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, vertexCoordinateSize)); 121 | geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, uvCoordinateSize)); 122 | geometry.setAttribute('visible', visibleAttribute); 123 | geometry.setAttribute('color', colorAttribute); 124 | return geometry; 125 | }, 126 | 127 | tick: function (time, delta) { 128 | var geometry; 129 | var positions; 130 | var quad; 131 | var particleSpeed = this.data.particleSpeed; 132 | var scale; 133 | 134 | this.lastParticleDelta = this.lastParticleDelta || 0; 135 | this.lastParticleDelta += delta; 136 | for (var i = 0; i < this.quads.length; i++) { 137 | quad = this.quads[i]; 138 | quad.particleLifeTime -= delta; 139 | 140 | // Reset particle. 141 | if (quad.particleLifeTime < 0) { 142 | quad.particleLifeTime = 0; 143 | this.visibleAttribute.setX(i, 0.0); 144 | } 145 | 146 | 147 | if (quad.particleLifeTime) { 148 | if (this.data.shockDiamonds) { 149 | quad.object3D.scale.x = 0.8 * (quad.particleLifeTime / this.data.particleLifeTime) * (1 + Math.sin(2 * Math.PI * 8 * (quad.particleLifeTime / 1600))); 150 | } 151 | if (this.data.animateColor) { 152 | quad.lerpColor.lerp(this.endColor, 1 - (quad.particleLifeTime / this.data.particleLifeTime)); 153 | this.colorAttribute.setX(i, quad.lerpColor.r); 154 | this.colorAttribute.setY(i, quad.lerpColor.g); 155 | this.colorAttribute.setZ(i, quad.lerpColor.b); 156 | quad.lerpColor = this.startColor.clone(); 157 | } 158 | if (this.data.endScaleFactor !== 1.0) { 159 | scale = (1 - this.data.endScaleFactor) * (quad.particleLifeTime / this.data.particleLifeTime) + this.data.endScaleFactor; 160 | quad.object3D.scale.x = scale; 161 | quad.object3D.scale.y = scale; 162 | } 163 | } 164 | quad.object3D.position.y -= particleSpeed; 165 | quad.object3D.updateMatrix(); 166 | this.instancedMesh.setMatrixAt(i, quad.object3D.matrix); 167 | } 168 | 169 | if (this.lastParticleDelta > 40) { 170 | for (var i = 0; i < this.quads.length; i++) { 171 | quad = this.quads[i]; 172 | if (quad.particleLifeTime) { continue; } 173 | 174 | this.velocityAttribute 175 | quad.particleLifeTime = this.data.particleLifeTime; 176 | quad.lerpColor = this.startColor.clone(); 177 | quad.object3D.position.y = 1; 178 | quad.object3D.updateMatrix(); 179 | 180 | this.visibleAttribute.setX(i, 1.0); 181 | 182 | this.colorAttribute.setX(i, this.startColor.r); 183 | this.colorAttribute.setY(i, this.startColor.g); 184 | this.colorAttribute.setZ(i, this.startColor.b); 185 | this.colorAttribute.needsUpdate = true; 186 | this.instancedMesh.setMatrixAt(i, quad.object3D.matrix); 187 | break; 188 | } 189 | this.lastParticleDelta = 0; 190 | } 191 | 192 | this.instancedMesh.instanceMatrix.needsUpdate = true; 193 | this.visibleAttribute.needsUpdate = true; 194 | }, 195 | 196 | update: function (oldData) { 197 | if (oldData.src !== this.data.src) { this.loadQuadImage(); } 198 | this.startColor = new THREE.Color(this.data.color); 199 | this.endColor = new THREE.Color(this.data.endColor); 200 | this.shader.uniforms.uColor.value = this.startColor; 201 | }, 202 | 203 | loadQuadImage: function () { 204 | var src = this.data.src; 205 | var self = this; 206 | this.el.sceneEl.systems.material.loadTexture(src, {src: src}, function textureLoaded (texture) { 207 | self.el.sceneEl.renderer.initTexture(texture); 208 | texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping; 209 | texture.magFilfer = THREE.LinearFilter; 210 | self.shader.uniforms.uMap.value = texture; 211 | }); 212 | }, 213 | 214 | initQuadShader: function() { 215 | var uniforms = { 216 | uMap: {type: 't', value: null}, 217 | uColor: {type: 'v3', value: new THREE.Vector3()} 218 | }; 219 | var shader = this.shader = new THREE.ShaderMaterial({ 220 | uniforms: uniforms, 221 | vertexShader: this.vertexShader, 222 | fragmentShader: this.fragmentShader, 223 | transparent: true 224 | }); 225 | return shader; 226 | } 227 | }); -------------------------------------------------------------------------------- /basics/particleSystem/components/particle-system-shell.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('particle-system-shell', { 2 | schema: { 3 | particlesNumber: {default: 100}, 4 | particleSize: {default: 0.1}, 5 | particleSpeed: {default: 0.02}, 6 | particleLifeTime: {default: 500}, 7 | src: {type: 'map'} 8 | }, 9 | vertexShader:` 10 | attribute float visible; 11 | varying vec2 vUv; 12 | varying float vVisible; 13 | 14 | void main() { 15 | vUv = uv; 16 | vVisible = visible; 17 | vec4 mvPosition = instanceMatrix * vec4(position, 1.0); 18 | vec4 modelViewPosition = modelViewMatrix * mvPosition; 19 | gl_Position = projectionMatrix * modelViewPosition; 20 | } 21 | `, 22 | 23 | fragmentShader:` 24 | varying float vVisible; 25 | varying vec2 vUv; 26 | uniform sampler2D uMap; 27 | uniform vec3 uColor; 28 | 29 | void main() { 30 | if (vVisible == 1.0) { 31 | gl_FragColor = texture2D(uMap, vUv) * vec4(1.0, 1.0, 1.0, 1.0); 32 | } else { 33 | discard; 34 | } 35 | 36 | }`, 37 | 38 | init: function () { 39 | var positionX; 40 | var positionY; 41 | var sign; 42 | var particlesNumber = this.data.particlesNumber; 43 | 44 | var geometry = this.initQuadGeometry(); 45 | var shader = this.initQuadShader(); 46 | var mesh = this.instancedMesh = new THREE.InstancedMesh(geometry, shader, particlesNumber); 47 | 48 | this.quads = []; 49 | for (var i = 0; i < particlesNumber; i++) { this.addQuad(); } 50 | 51 | this.el.setObject3D('particleShell', mesh); 52 | }, 53 | 54 | addQuad: function() { 55 | var quad = {}; 56 | quad.object3D = new THREE.Object3D(); 57 | quad.object3D.position.set(0, 0, 0); 58 | quad.lifeTime = 0; 59 | this.quads.push(quad); 60 | }, 61 | 62 | initQuadGeometry: function () { 63 | var geometry = new THREE.BufferGeometry(); 64 | var vertexCoordinateSize = 3; // 3 floats to represent x,y,z coordinates. 65 | 66 | var quadSize = this.data.particleSize; 67 | var quadHalfSize = quadSize / 2.0; 68 | 69 | var uvCoordinateSize = 2; // 2 float to represent u,v coordinates. 70 | // No indexed 71 | var positions = [ 72 | // Top left triangle 73 | quadHalfSize, quadHalfSize, 0.0, 74 | -quadHalfSize, quadHalfSize, 0.0, 75 | -quadHalfSize, -quadHalfSize, 0.0, 76 | // Bottom right triangle 77 | -quadHalfSize, -quadHalfSize, 0.0, 78 | quadHalfSize, -quadHalfSize, 0.0, 79 | quadHalfSize, quadHalfSize, 0.0 80 | ]; 81 | 82 | var uvs = [ 83 | 1, 1, 84 | 0, 1, 85 | 0, 0, 86 | 87 | 0, 0, 88 | 1, 0, 89 | 1, 1, 90 | ]; 91 | 92 | var visible = Array(this.data.particlesNumber).fill(0.0); 93 | 94 | var visibleAttribute = this.visibleAttribute = new THREE.InstancedBufferAttribute(new Float32Array(visible), 1); 95 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, vertexCoordinateSize)); 96 | geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, uvCoordinateSize)); 97 | geometry.setAttribute('visible', visibleAttribute); 98 | return geometry; 99 | }, 100 | 101 | tick: function (time, delta) { 102 | var geometry; 103 | var quad; 104 | 105 | for (var i = 0; i < this.quads.length; i++) { 106 | quad = this.quads[i]; 107 | 108 | if (!quad.isActive) { continue; } 109 | 110 | quad.particleLifeTime -= delta; 111 | 112 | if (quad.particleLifeTime < 0) { 113 | quad.particleLifeTime = 0; 114 | quad.isActive = false; 115 | this.el.emit('particleended', { 116 | position: { 117 | x: quad.object3D.position.x, 118 | y: quad.object3D.position.y 119 | } 120 | }); 121 | this.visibleAttribute.setX(i, 0.0); 122 | } 123 | 124 | quad.object3D.position.y += this.data.particleSpeed; 125 | quad.object3D.updateMatrix(); 126 | this.instancedMesh.setMatrixAt(i, quad.object3D.matrix); 127 | } 128 | 129 | this.instancedMesh.instanceMatrix.needsUpdate = true; 130 | this.visibleAttribute.needsUpdate = true; 131 | }, 132 | 133 | update: function (oldData) { 134 | var data = this.data; 135 | if (oldData.src !== data.src) { this.loadQuadImage(); } 136 | }, 137 | 138 | startShell: function (x, y) { 139 | for (var i = 0; i < this.quads.length; i++) { 140 | quad = this.quads[i]; 141 | if (quad.isActive) { continue; } 142 | 143 | quad.isActive = true; 144 | quad.particleLifeTime = this.data.particleLifeTime; 145 | quad.object3D.position.x = x; 146 | quad.object3D.position.y = y; 147 | quad.object3D.updateMatrix(); 148 | 149 | this.visibleAttribute.setX(i, 1.0); 150 | this.el.emit('particlestarted'); 151 | this.instancedMesh.setMatrixAt(i, quad.object3D.matrix); 152 | break; 153 | } 154 | 155 | this.instancedMesh.instanceMatrix.needsUpdate = true; 156 | this.visibleAttribute.needsUpdate = true; 157 | }, 158 | 159 | loadQuadImage: function () { 160 | var src = this.data.src; 161 | var self = this; 162 | this.el.sceneEl.systems.material.loadTexture(src, {src: src}, function textureLoaded (texture) { 163 | self.el.sceneEl.renderer.initTexture(texture); 164 | texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping; 165 | texture.magFilfer = THREE.LinearFilter; 166 | self.shader.uniforms.uMap.value = texture; 167 | }); 168 | }, 169 | 170 | initQuadShader: function() { 171 | var uniforms = { 172 | uMap: {type: 't', value: null} 173 | }; 174 | var shader = this.shader = new THREE.ShaderMaterial({ 175 | uniforms: uniforms, 176 | vertexShader: this.vertexShader, 177 | fragmentShader: this.fragmentShader, 178 | transparent: true 179 | }); 180 | return shader; 181 | } 182 | }); -------------------------------------------------------------------------------- /basics/particleSystem/components/swap-entities.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('swap-entities', { 2 | schema: { 3 | toggleButton: {type: 'selector'}, 4 | entity1: {type: 'selector'}, 5 | entity2: {type: 'selector'} 6 | }, 7 | init: function () { 8 | var entity1El = this.data.entity1; 9 | var entity2El = this.data.entity2; 10 | this.data.toggleButton.addEventListener('click', function (evt) { 11 | entity1El.object3D.visible = !entity1El.object3D.visible; 12 | entity2El.object3D.visible = !entity2El.object3D.visible; 13 | evt.preventDefault(); 14 | }); 15 | } 16 | }); -------------------------------------------------------------------------------- /basics/particleSystem/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Particle Systems • A-Frame 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |

Particle System

42 |

Particle systems represent smoke, fire, rain, fireworks... Usually phenomena composed of many small discrete elements that combined appear as a single entity or follow some common dynamics.

43 | 44 |

The emitter is a location where the particles are created. Usually each particle has a time counter. When the timer expires the particle is recycled and eventually reemitted as a new particle. 45 |

46 | 47 |

48 | We can render particles in multiple ways. This example uses individual quads. This results in a drawcall per particle which is very inefficient but keeps the code simple for illustration purposes.

49 | 50 |

Draw Calls: 0

51 |
52 |
53 | 54 | 55 | 58 | 59 |
60 | 61 | 62 |
63 |
64 |

Particles Texture

65 |

Apply a texture to a particle to change its appereance and simulate the desired effect.

66 | 67 |

This example renders the quads using a technique called instanced drawing to reduce the number of draw calls. Compare the number with the previous example.

68 | 69 |

Draw Calls: 0

70 |
71 |
72 | 73 | 74 | 77 | 78 |
79 | 80 | 81 |
82 |
83 |

Combine Particle Systems

84 |

This is a simple representation of the SpaceX Raptor engine exhaust. It combines 3 particle systems: One for the brighter exhaust coming out from the nozzle, one for the purple plume and a last one to represent the shock diamonds

85 |
86 |
87 | 88 | 89 | 90 | 91 | 95 | 100 | 101 | 102 | 103 | 104 |
105 | 106 | 107 |
108 |
109 |

Chaining Particle Systems

110 |

When the timer expires for the shell particle an event triggers the firework sparks

111 |
112 |
113 | 114 | 115 | 118 | 119 |
120 | 121 | 122 |
123 |
124 |

Complex Particle Behavior

125 |

Redefine the init and tick methods to animate the particles. A few lines of code can describe very complex behavior

126 |

This particle system is a simplification of the Density Wave Theory that describes the formation of spiral galaxies. Notice that the galaxy rotates while the arms remain stationary. Arms are formed by areas with higher density of matter (planets, stars, dust...) due to overlapping eliptical orbits. Astronomers initially thought the spiral arms were material but it turns it's just areas of congested traffic of orbiting objects. A cosmic traffic jam!

127 |

TOGGLE ORBITS

128 |
129 |
130 | 131 | 132 | 133 | 137 | 141 | 142 | 143 |
144 |
145 | 146 | -------------------------------------------------------------------------------- /basics/quad/assets/explosion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/quad/assets/explosion.png -------------------------------------------------------------------------------- /basics/quad/assets/mario-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/basics/quad/assets/mario-sprite.png -------------------------------------------------------------------------------- /basics/quad/assets/message.html: -------------------------------------------------------------------------------- 1 |

Explosion animation by Sinestesia Studio

2 | 3 |

Frog Mario by Nintendo

4 | -------------------------------------------------------------------------------- /basics/quad/components/explosion-sprite-sheet.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('explosion-sprite-sheet', { 2 | schema: { 3 | spriteWidth: {default: 256}, 4 | spriteHeight: {default: 256}, 5 | duration: {default: 1000} 6 | }, 7 | vertexShader:` 8 | varying vec2 vUv; 9 | 10 | void main() { 11 | vUv = uv; 12 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 13 | }`, 14 | 15 | fragmentShader:` 16 | varying vec2 vUv; 17 | uniform sampler2D uMap; 18 | // Sprite index in the grid. 19 | // 0 is the top left corner. 20 | // numOfFrames - 1 is the bottom right corner. 21 | uniform float uSpriteIndex; 22 | // Each sprite width and height in uv coordinates [0, 1] 23 | uniform float uSpriteWidth; 24 | uniform float uSpriteHeight; 25 | 26 | void main() { 27 | // Current sprite column in uv coordinates. 28 | float uMin = uSpriteIndex * uSpriteWidth; 29 | // We take the decimal part. 30 | // It represents the colum within the current row. 31 | uMin = uMin - floor(uMin); 32 | // End of current sprite column is the column coordinate + the size of one sprite. 33 | float uMax = uMin + uSpriteWidth; 34 | // Current sprite row in uv coordinates. 35 | // We take the integer part. We're interested in the row not the column. 36 | float vMin = floor(uSpriteIndex * uSpriteWidth); 37 | // We need to invert the y-coordinate because the uv origin (0, 0) is in the 38 | // bottom left and the first frame is in the top-left corner of the sprite sheet. 39 | vMin = 1.0 - vMin * uSpriteHeight - uSpriteHeight; 40 | // End of row is the row coordinate + the size of one sprite. 41 | float vMax = vMin + uSpriteHeight; 42 | 43 | vec4 color = texture2D(uMap, vUv); 44 | vec4 tintColor = vec4(1.0, 0.0, 0.0, 1.0); 45 | float tintAmount = 0.5; 46 | // We tint red if it's the UV coordinate falls 47 | // in the selected cell. We tint gray otherwise. 48 | if (vUv.x >= uMin && vUv.x < uMax && 49 | vUv.y >= vMin && vUv.y < vMax) { 50 | tintColor = vec4(1.0, 0.0, 0.0, 1.0); 51 | } else { 52 | tintAmount = 0.02; 53 | tintColor = vec4(0.72, 0.72, 0.72, 1.0); 54 | } 55 | // Tint the texture value. 56 | gl_FragColor = mix(color, tintColor, tintAmount); 57 | }`, 58 | 59 | init: function () { 60 | var mesh; 61 | this.initGeometry(); 62 | this.initShader(); 63 | 64 | mesh = new THREE.Mesh(this.geometry, this.shader); 65 | this.el.setObject3D('mesh', mesh); 66 | }, 67 | 68 | initGeometry: function () { 69 | var geometry = this.geometry = new THREE.BufferGeometry(); 70 | var vertexCoordinateSize = 3; // 3 floats to represent x,y,z coordinates. 71 | var uvCoordinateSize = 2; // 2 float to represent u,v coordinates. 72 | var quadSize = 0.8; 73 | var quadHalfSize = quadSize / 2.0; 74 | var wireframeSystem = this.el.sceneEl.systems.wireframe; 75 | 76 | var positions = [ 77 | -quadHalfSize, -quadHalfSize, 0.0, // bottom-left 78 | quadHalfSize, -quadHalfSize, 0.0, // bottom-right 79 | -quadHalfSize, quadHalfSize, 0.0, // top-left 80 | quadHalfSize, quadHalfSize, 0.0 // top-right 81 | ]; 82 | 83 | var uvs = [ 84 | 0, 0, 85 | 1, 0, 86 | 0, 1, 87 | 1, 1, 88 | ]; 89 | 90 | // Counter-clockwise triangle winding. 91 | geometry.setIndex([ 92 | 3, 2, 0, // top-left triangle. 93 | 0, 1, 3 // bottom-right triangle. 94 | ]); 95 | 96 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, vertexCoordinateSize)); 97 | geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, uvCoordinateSize)); 98 | 99 | wireframeSystem.unindexBufferGeometry(geometry); 100 | wireframeSystem.calculateBarycenters(geometry); 101 | }, 102 | 103 | initShader: function() { 104 | var self = this; 105 | var uniforms = { 106 | uMap: {type: 't', value: null}, 107 | uSpriteIndex: {type: 'f', value: 0}, 108 | uSpriteWidth: {type: 'f', value: 0}, 109 | uSpriteHeight: {type: 'f', value: 0} 110 | }; 111 | var shader = this.shader = new THREE.ShaderMaterial({ 112 | uniforms: uniforms, 113 | vertexShader: this.vertexShader, 114 | fragmentShader: this.fragmentShader, 115 | transparent: true 116 | }); 117 | 118 | var textureLoader = new THREE.TextureLoader(); 119 | textureLoader.load('assets/explosion.png', function (texture) { 120 | var spriteHeight = self.data.spriteHeight; 121 | var spriteWidth = self.data.spriteWidth; 122 | var imageWidth = texture.image.width; 123 | var imageHeight = texture.image.height; 124 | shader.uniforms.uSpriteWidth.value = spriteWidth / imageWidth; 125 | shader.uniforms.uSpriteHeight.value = spriteHeight / imageHeight; 126 | shader.uniforms.uMap.value = texture; 127 | 128 | texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping; 129 | texture.magFilfer = THREE.NearestFilter; 130 | }); 131 | }, 132 | 133 | tick: function (time) { 134 | var startTime = this.startTime = this.startTime || time; 135 | var elapsedTime = time - this.startTime; 136 | var duration = this.data.duration; 137 | if (elapsedTime > duration) { 138 | elapsedTime = 0; 139 | this.startTime = time; 140 | } 141 | var spriteIndex = Math.floor((elapsedTime / duration) * 64); 142 | this.shader.uniforms.uSpriteIndex.value = spriteIndex; 143 | } 144 | }); -------------------------------------------------------------------------------- /basics/quad/components/quad-texture.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('quad-texture', { 2 | vertexShader:` 3 | varying vec2 vUv; 4 | 5 | void main() { 6 | vUv = uv; 7 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 8 | } 9 | `, 10 | 11 | fragmentShader:` 12 | varying vec2 vUv; 13 | uniform sampler2D uMap; 14 | uniform float uSpriteIndex; 15 | 16 | void main() { 17 | float numberOfFrames = 3.0; 18 | // Min uv is the position of the current frame in uv coordinates [0, 1] 19 | float uMin = uSpriteIndex / numberOfFrames; 20 | // Max uv is the min uv + the size of one frame. 21 | float uMax = uMin + 1.0 / numberOfFrames; 22 | // Texture lookup between the min and max u values. 23 | float u = mix(uMin, uMax, vUv.x); 24 | vec2 uv = vec2(u, vUv.y); 25 | gl_FragColor = texture2D(uMap, uv); 26 | }`, 27 | 28 | init: function () { 29 | var mesh; 30 | this.initGeometry(); 31 | this.initShader(); 32 | 33 | mesh = new THREE.Mesh(this.geometry, this.shader); 34 | this.el.setObject3D('mesh', mesh); 35 | }, 36 | 37 | initGeometry: function () { 38 | var geometry = this.geometry = new THREE.BufferGeometry(); 39 | var vertexCoordinateSize = 3; // 3 floats to represent x,y,z coordinates. 40 | var uvCoordinateSize = 2; // 2 float to represent u,v coordinates. 41 | var quadSize = 0.8; 42 | var quadHalfSize = quadSize / 2.0; 43 | var wireframeSystem = this.el.sceneEl.systems.wireframe; 44 | 45 | var positions = [ 46 | -quadHalfSize, -quadHalfSize, 0.0, // bottom-left 47 | quadHalfSize, -quadHalfSize, 0.0, // bottom-right 48 | -quadHalfSize, quadHalfSize, 0.0, // top-left 49 | quadHalfSize, quadHalfSize, 0.0 // top-right 50 | ]; 51 | 52 | var uvs = [ 53 | 0, 0, 54 | 1, 0, 55 | 0, 1, 56 | 1, 1, 57 | ]; 58 | 59 | // Counter-clockwise triangle winding. 60 | geometry.setIndex([ 61 | 3, 2, 0, // top-left triangle. 62 | 0, 1, 3 // bottom-right triangle. 63 | ]); 64 | 65 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, vertexCoordinateSize)); 66 | geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, uvCoordinateSize)); 67 | 68 | wireframeSystem.unindexBufferGeometry(geometry); 69 | wireframeSystem.calculateBarycenters(geometry); 70 | }, 71 | 72 | initShader: function() { 73 | var uniforms = { 74 | uMap: {type: 't', value: null}, 75 | uSpriteIndex: {type: 'f', value: 0} 76 | }; 77 | var shader = this.shader = new THREE.ShaderMaterial({ 78 | uniforms: uniforms, 79 | vertexShader: this.vertexShader, 80 | fragmentShader: this.fragmentShader, 81 | }); 82 | 83 | var textureLoader = new THREE.TextureLoader(); 84 | textureLoader.load('assets/mario-sprite.png', function (texture) { 85 | shader.uniforms.uMap.value = texture; 86 | texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping; 87 | texture.magFilfer = THREE.LinearFilter; 88 | }); 89 | }, 90 | 91 | tick: function (time) { 92 | var startTime = this.startTime = this.startTime || time; 93 | var elapsedTime = time - this.startTime; 94 | var spriteIndex = Math.floor((elapsedTime / 500) % 3); 95 | this.shader.uniforms.uSpriteIndex.value = spriteIndex; 96 | } 97 | }); -------------------------------------------------------------------------------- /basics/quad/components/quad-wireframe.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('quad-wireframe', { 2 | vertexShader:` 3 | attribute vec3 barycentric; 4 | varying vec3 vDistanceBarycenter; 5 | 6 | void main() { 7 | vDistanceBarycenter = barycentric; 8 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 9 | } 10 | `, 11 | 12 | fragmentShader:` 13 | #if __VERSION__ == 100 14 | #extension GL_OES_standard_derivatives : enable 15 | #endif 16 | 17 | varying vec3 vDistanceBarycenter; 18 | 19 | // This is like 20 | float aastep (float threshold, float dist) { 21 | float afwidth = fwidth(dist) * 0.5; 22 | return smoothstep(threshold - afwidth, threshold + afwidth, dist); 23 | } 24 | 25 | void main() { 26 | float thickness = 0.015; 27 | // this will be our signed distance for the wireframe edge 28 | float d = min(min(vDistanceBarycenter.x, vDistanceBarycenter.y), vDistanceBarycenter.z); 29 | // compute the anti-aliased stroke edge 30 | float edge = 1.0 - aastep(thickness, d); 31 | // now compute the final color of the mesh 32 | vec4 lineColor = vec4(0.1, 0.1, 0.1, 1.0); 33 | vec4 fillColor = vec4(1.0, 1.0, 1.0, 1.0); 34 | gl_FragColor = vec4(mix(fillColor, lineColor, edge)); 35 | }`, 36 | 37 | init: function () { 38 | var mesh; 39 | this.initGeometry(); 40 | this.initShader(); 41 | 42 | mesh = new THREE.Mesh(this.geometry, this.shader); 43 | this.el.setObject3D('mesh', mesh); 44 | }, 45 | 46 | initGeometry: function () { 47 | var geometry = this.geometry = new THREE.BufferGeometry(); 48 | var vertexCoordinateSize = 3; // 3 floats to represent x,y,z coordinates. 49 | var uvCoordinateSize = 2; // 2 float to represent u,v coordinates. 50 | var quadSize = 0.8; 51 | var quadHalfSize = quadSize / 2.0; 52 | var wireframeSystem = this.el.sceneEl.systems.wireframe; 53 | 54 | // No indexed 55 | var positions = [ 56 | // Top left triangle 57 | quadHalfSize, quadHalfSize, 0.0, 58 | -quadHalfSize, quadHalfSize, 0.0, 59 | -quadHalfSize, -quadHalfSize, 0.0, 60 | // Bottom right triangle 61 | -quadHalfSize, -quadHalfSize, 0.0, 62 | quadHalfSize, -quadHalfSize, 0.0, 63 | quadHalfSize, quadHalfSize, 0.0 64 | ]; 65 | 66 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, vertexCoordinateSize)); 67 | wireframeSystem.calculateBarycenters(geometry); 68 | }, 69 | 70 | initShader: function() { 71 | var shader = this.shader = new THREE.ShaderMaterial({ 72 | vertexShader: this.vertexShader, 73 | fragmentShader: this.fragmentShader 74 | }); 75 | } 76 | }); -------------------------------------------------------------------------------- /basics/quad/components/sprite-animation.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('sprite-animation', { 2 | schema: { 3 | spriteWidth: {default: 256}, 4 | spriteHeight: {default: 256}, 5 | duration: {default: 1000} 6 | }, 7 | vertexShader:` 8 | varying vec2 vUv; 9 | 10 | void main() { 11 | vUv = uv; 12 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 13 | } 14 | `, 15 | 16 | fragmentShader:` 17 | varying vec2 vUv; 18 | uniform sampler2D uMap; 19 | uniform float uSpriteIndex; 20 | uniform float uSpriteWidth; 21 | uniform float uSpriteHeight; 22 | 23 | void main() { 24 | float u = uSpriteIndex * uSpriteWidth; 25 | u = u - floor(u); 26 | float v = floor(uSpriteIndex * uSpriteWidth); 27 | v = v * uSpriteHeight; 28 | vec2 uv = vec2(u + vUv.x * uSpriteWidth, 1.0 - (v + uSpriteHeight - vUv.y * uSpriteHeight)); 29 | 30 | gl_FragColor = texture2D(uMap, uv); 31 | }`, 32 | 33 | init: function () { 34 | var mesh; 35 | this.initGeometry(); 36 | this.initShader(); 37 | 38 | mesh = new THREE.Mesh(this.geometry, this.shader); 39 | this.el.setObject3D('mesh', mesh); 40 | }, 41 | 42 | initGeometry: function () { 43 | var geometry = this.geometry = new THREE.BufferGeometry(); 44 | var vertexCoordinateSize = 3; // 3 floats to represent x,y,z coordinates. 45 | var uvCoordinateSize = 2; // 2 float to represent u,v coordinates. 46 | var quadSize = 0.8; 47 | var quadHalfSize = quadSize / 2.0; 48 | var wireframeSystem = this.el.sceneEl.systems.wireframe; 49 | 50 | var positions = [ 51 | -quadHalfSize, -quadHalfSize, 0.0, // bottom-left 52 | quadHalfSize, -quadHalfSize, 0.0, // bottom-right 53 | -quadHalfSize, quadHalfSize, 0.0, // top-left 54 | quadHalfSize, quadHalfSize, 0.0 // top-right 55 | ]; 56 | 57 | var uvs = [ 58 | 0, 0, 59 | 1, 0, 60 | 0, 1, 61 | 1, 1, 62 | ]; 63 | 64 | // Counter-clockwise triangle winding. 65 | geometry.setIndex([ 66 | 3, 2, 0, // top-left triangle. 67 | 0, 1, 3 // bottom-right triangle. 68 | ]); 69 | 70 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, vertexCoordinateSize)); 71 | geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, uvCoordinateSize)); 72 | 73 | wireframeSystem.unindexBufferGeometry(geometry); 74 | wireframeSystem.calculateBarycenters(geometry); 75 | }, 76 | 77 | initShader: function() { 78 | var self = this; 79 | var uniforms = { 80 | uMap: {type: 't', value: null}, 81 | uSpriteIndex: {type: 'f', value: 0}, 82 | uSpriteWidth: {type: 'f', value: 0}, 83 | uSpriteHeight: {type: 'f', value: 0} 84 | }; 85 | var shader = this.shader = new THREE.ShaderMaterial({ 86 | uniforms: uniforms, 87 | vertexShader: this.vertexShader, 88 | fragmentShader: this.fragmentShader, 89 | transparent: true 90 | }); 91 | 92 | var textureLoader = new THREE.TextureLoader(); 93 | textureLoader.load('assets/explosion.png', function (texture) { 94 | var spriteHeight = self.data.spriteHeight; 95 | var spriteWidth = self.data.spriteWidth; 96 | var imageWidth = texture.image.width; 97 | var imageHeight = texture.image.height; 98 | shader.uniforms.uSpriteWidth.value = spriteWidth / imageWidth; 99 | shader.uniforms.uSpriteHeight.value = spriteHeight / imageHeight; 100 | shader.uniforms.uMap.value = texture; 101 | 102 | texture.wrapS = texture.wrapT = THREE.ClampToEdgeWrapping; 103 | texture.magFilfer = THREE.NearestFilter; 104 | }); 105 | }, 106 | 107 | tick: function (time) { 108 | var startTime = this.startTime = this.startTime || time; 109 | var elapsedTime = time - this.startTime; 110 | var duration = this.data.duration; 111 | if (elapsedTime > duration) { 112 | elapsedTime = 0; 113 | this.startTime = time; 114 | } 115 | var spriteIndex = Math.floor((elapsedTime / duration) * 64); 116 | this.shader.uniforms.uSpriteIndex.value = spriteIndex; 117 | } 118 | }); -------------------------------------------------------------------------------- /basics/quad/components/sprite-strip.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('sprite-strip', { 2 | vertexShader:` 3 | varying vec2 vUv; 4 | 5 | void main() { 6 | vUv = uv; 7 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 8 | }`, 9 | 10 | fragmentShader:` 11 | varying vec2 vUv; 12 | uniform sampler2D uMap; 13 | uniform float uSpriteIndex; 14 | 15 | void main() { 16 | vec4 color = texture2D(uMap, vUv); 17 | vec4 tintColor = vec4(1.0, 0.0, 0.0, 1.0); 18 | float tintAmount = 0.0; 19 | float uMin = uSpriteIndex * 1.0 / 3.0; 20 | float uMax = uMin + 1.0 / 3.0; 21 | if (vUv.x >= uMin && vUv.x <= uMax) { 22 | tintAmount = 0.5; 23 | } 24 | color = mix(color, tintColor, tintAmount); 25 | gl_FragColor = vec4(color.rgb, 1.0); 26 | }`, 27 | 28 | init: function () { 29 | var mesh; 30 | this.initGeometry(); 31 | this.initShader(); 32 | 33 | mesh = new THREE.Mesh(this.geometry, this.shader); 34 | this.el.setObject3D('mesh', mesh); 35 | }, 36 | 37 | initGeometry: function () { 38 | var geometry = this.geometry = new THREE.BufferGeometry(); 39 | var vertexCoordinateSize = 3; // 3 floats to represent x,y,z coordinates. 40 | var uvCoordinateSize = 2; // 2 float to represent u,v coordinates. 41 | var quadSize = 0.8; 42 | var quadHalfSize = quadSize / 2.0; 43 | var wireframeSystem = this.el.sceneEl.systems.wireframe; 44 | 45 | var positions = [ 46 | -quadHalfSize, -quadHalfSize, 0.0, // bottom-left 47 | quadHalfSize, -quadHalfSize, 0.0, // bottom-right 48 | -quadHalfSize, quadHalfSize, 0.0, // top-left 49 | quadHalfSize, quadHalfSize, 0.0 // top-right 50 | ]; 51 | 52 | var uvs = [ 53 | 0, 0, 54 | 1, 0, 55 | 0, 1, 56 | 1, 1, 57 | ]; 58 | 59 | // Counter-clockwise triangle winding. 60 | geometry.setIndex([ 61 | 3, 2, 0, // top-left triangle. 62 | 0, 1, 3 // bottom-right triangle. 63 | ]); 64 | 65 | geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, vertexCoordinateSize)); 66 | geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, uvCoordinateSize)); 67 | 68 | wireframeSystem.unindexBufferGeometry(geometry); 69 | wireframeSystem.calculateBarycenters(geometry); 70 | }, 71 | 72 | initShader: function() { 73 | var uniforms = { 74 | uMap: {type: 't', value: null}, 75 | uSpriteIndex: {type: 'f', value: 0} 76 | }; 77 | var shader = this.shader = new THREE.ShaderMaterial({ 78 | uniforms: uniforms, 79 | vertexShader: this.vertexShader, 80 | fragmentShader: this.fragmentShader, 81 | }); 82 | 83 | var textureLoader = new THREE.TextureLoader(); 84 | textureLoader.load('assets/mario-sprite.png', function (texture) { 85 | shader.uniforms.uMap.value = texture; 86 | texture.wrapS = texture.wrapT = THREE.REPEAT_WRAPPING; 87 | texture.magFilfer = THREE.NearestFilter; 88 | }); 89 | }, 90 | 91 | tick: function (time) { 92 | var startTime = this.startTime = this.startTime || time; 93 | var elapsedTime = time - this.startTime; 94 | var spriteIndex = Math.floor((elapsedTime / 500) % 3); 95 | this.shader.uniforms.uSpriteIndex.value = spriteIndex; 96 | } 97 | }); -------------------------------------------------------------------------------- /basics/quad/components/uv-values.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('uv-values', { 2 | init: function () { 3 | this.topLeftUVEl = document.querySelector('#topLeftUV'); 4 | this.topRightUVEl = document.querySelector('#topRightUV'); 5 | this.bottomLeftUVSpriteEl = document.querySelector('#bottomLeftUV'); 6 | this.bottomRightUVSpriteEl = document.querySelector('#bottomRightUV'); 7 | }, 8 | 9 | tick: function (time) { 10 | var startTime = this.startTime = this.startTime || time; 11 | var elapsedTime = time - this.startTime; 12 | var spriteIndex = Math.floor((elapsedTime / 500) % 3); 13 | if (spriteIndex === 0) { 14 | this.topLeftUVEl.setAttribute('text', 'value', '0, 1'); 15 | this.topRightUVEl.setAttribute('text', 'value', '0.33, 1'); 16 | this.bottomLeftUVSpriteEl.setAttribute('text', 'value', '0, 0'); 17 | this.bottomRightUVSpriteEl.setAttribute('text', 'value', '0.33, 0'); 18 | } 19 | if (spriteIndex === 1) { 20 | this.topLeftUVEl.setAttribute('text', 'value', '0.33, 1'); 21 | this.topRightUVEl.setAttribute('text', 'value', '0.66, 1'); 22 | this.bottomLeftUVSpriteEl.setAttribute('text', 'value', '0.33, 0'); 23 | this.bottomRightUVSpriteEl.setAttribute('text', 'value', '0.66, 0'); 24 | } 25 | if (spriteIndex === 2) { 26 | this.topLeftUVEl.setAttribute('text', 'value', '0.66, 1'); 27 | this.topRightUVEl.setAttribute('text', 'value', '1, 1'); 28 | this.bottomLeftUVSpriteEl.setAttribute('text', 'value', '0.66, 0'); 29 | this.bottomRightUVSpriteEl.setAttribute('text', 'value', '1, 0'); 30 | } 31 | } 32 | }); -------------------------------------------------------------------------------- /basics/quad/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Quad • A-Frame 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |

Quad Geometry

32 |

The humble quad. A rectangle composed of two triangles. A building block for many effects.

33 |
34 |
35 | 36 | 37 | 38 | 39 |
40 | 41 | 42 |
43 |
44 |

Textured Quad

45 |

To create effects quads will be shaded, textured and animated. This is a quad with a texture applied from a sprite sheet. The uv values change to select the desired portion of the image and create the illusion of an animated character.

46 |

To UV coordinates of the current frame are displayed in the corners of the Quad

47 |
48 |
49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | 75 | 76 |
77 |
78 |

Textured Quad

79 |

There are many software packages that can simulate complex animations and generate a corresponding sprite sheet.

80 |

The current frame is highlighted in the sprite sheet as the explosion animation plays.

81 |
82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
99 | 100 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A-Frame FX Examples 5 | 6 | 7 | 119 | 120 | 121 |
122 |

A-Frame FX

123 | 129 |
130 | 131 |
132 | 133 |

Basics

134 | 135 | 139 | 140 |

Wind Waker

141 | 142 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /js/components/info-message.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | AFRAME.registerComponent('info-message', { 3 | schema: { 4 | htmlSrc: {type: 'selector'}, 5 | startOpened: {default: false}, 6 | width: {default: 400}, 7 | height: {default: 320} 8 | }, 9 | init: function () { 10 | var sceneEl = this.el.sceneEl; 11 | var messageEl = this.messageEl = document.createElement('div'); 12 | var startOpened = this.data.startOpened; 13 | this.toggleInfoMessage = this.toggleInfoMessage.bind(this); 14 | 15 | messageEl.classList.add('a-info-message'); 16 | messageEl.setAttribute('aframe-injected', ''); 17 | 18 | var closeButtonEl = this.closeButtonEl = document.createElement('button'); 19 | closeButtonEl.innerHTML = 'X'; 20 | closeButtonEl.classList.add('a-close-button-info'); 21 | closeButtonEl.onclick = this.toggleInfoMessage; 22 | 23 | this.createInfoButton(this.toggleInfoMessage); 24 | 25 | this.addStyles(); 26 | sceneEl.appendChild(messageEl); 27 | 28 | this.messageEl.style.display = startOpened ? '' : 'none'; 29 | this.infoButton.style.display = startOpened ? 'none' : ''; 30 | messageEl.addEventListener('click', function (evt) { evt.stopPropagation(); }); 31 | }, 32 | 33 | update: function () { 34 | var messageEl = this.messageEl; 35 | messageEl.innerHTML = this.data.htmlSrc.data; 36 | messageEl.appendChild(this.closeButtonEl); 37 | }, 38 | 39 | addStyles: function () { 40 | var css = 41 | '.a-info-message{border-radius: 10px; position: absolute; width: ' + this.data.width + 'px;' + 42 | 'height: ' + this.data.height + 'px; background-color: white; border: 3px solid rgba(0,0,0,0.65);' + 43 | 'bottom: 22px; left: 22px; color: rgb(51, 51, 51); padding: 20px 15px 0 15px;' + 44 | 'font-size: 11pt; line-height: 20pt; z-index: 9999}' + 45 | 46 | '.a-info-message a{border-bottom: 1px solid rgba(53,196,232,.15); color: #1497b8;' + 47 | 'position: relative; text-decoration: none; transition: .05s ease;}' + 48 | 49 | '@media only screen and (min-width: 1200px) {' + 50 | '.a-info-message {font-size: 12pt}}' + 51 | 52 | '@media only screen and (max-width: 600px) {' + 53 | '.a-info-message {left: 20px; right: 20px; bottom: 60px; width: auto}}' + 54 | 55 | '@media only screen and (max-height: 600px) {' + 56 | '.a-info-message {left: 20px; bottom: 20px; height: 250px}}' + 57 | 58 | '.a-close-button-info{width: 25px; height: 25px; padding: 0;' + 59 | 'top: 10px; right: 10px; position: absolute; color: white;' + 60 | 'font-size: 12px; line-height: 12px; border: none; background-color: #ef2d5e;' + 61 | 'border-radius: 5px; font-weight: medium}' + 62 | 63 | '.a-close-button-info:hover{background-color: #b32146; color: white}' + 64 | '.a-info-message-container {position: absolute; left: 100px; bottom: 20px;}' + 65 | '.a-info-message-button {background: rgba(0, 0, 0, 0.35) ' + this.infoMessageButtonDataURI + ' 50% 50% no-repeat;}' + 66 | '.a-info-message-button {background-size: 92% 90%; border: 0; bottom: 0; cursor: pointer; min-width: 78px; min-height: 34px; padding-right: 0; padding-top: 0; position: absolute; right: 0; transition: background-color .05s ease; -webkit-transition: background-color .05s ease; z-index: 9999; border-radius: 8px; touch-action: manipulation;}' + 67 | '.a-info-message-button:active, .a-info-message-button:hover {background-color: #ef2d5e;}'; 68 | var style = document.createElement('style'); 69 | 70 | if (style.styleSheet) { 71 | style.styleSheet.cssText = css; 72 | } else { 73 | style.appendChild(document.createTextNode(css)); 74 | } 75 | 76 | document.getElementsByTagName('head')[0].appendChild(style); 77 | }, 78 | 79 | toggleInfoMessage: function () { 80 | var display = this.messageEl.style.display; 81 | this.infoButton.style.display = display; 82 | display = display === 'none' ? '' : 'none'; 83 | this.messageEl.style.display = display; 84 | if (display === 'none') { 85 | this.el.emit('infomessageclosed'); 86 | } else { 87 | this.el.emit('infomessageopened'); 88 | } 89 | }, 90 | 91 | createInfoButton: function (onClick) { 92 | var infoButton; 93 | var wrapper; 94 | 95 | // Create elements. 96 | wrapper = document.createElement('div'); 97 | wrapper.classList.add('a-info-message-container'); 98 | this.infoButton = infoButton = document.createElement('button'); 99 | infoButton.className = 'a-info-message-button'; 100 | infoButton.setAttribute('title', 'Information about this experience'); 101 | // Insert elements. 102 | wrapper.appendChild(infoButton); 103 | infoButton.addEventListener('click', function (evt) { 104 | onClick(); 105 | evt.stopPropagation(); 106 | }); 107 | this.el.sceneEl.appendChild(wrapper); 108 | }, 109 | 110 | infoMessageButtonDataURI: 'url(data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfMyIgZGF0YS1uYW1lPSJMYXllciAzIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTAiIGhlaWdodD0iNjIiIHZpZXdCb3g9IjAgMCAxNTAgNjIiPjxwYXRoIGQ9Ik0xMzgsMEgxMkExMi4wMzUsMTIuMDM1LDAsMCwwLDAsMTJWNTBBMTIuMDM1LDEyLjAzNSwwLDAsMCwxMiw2MkgxMzhhMTIuMDM1LDEyLjAzNSwwLDAsMCwxMi0xMlYxMkExMi4wMzUsMTIuMDM1LDAsMCwwLDEzOCwwWk0zNi4yMTIsNDYuMDQzSDI5di0zMGg3LjIxMlptMzAuODQzLDBINTkuODY0TDQ5LjIzMiwyNy4zNzVWNDYuMDQzSDQydi0zMGg3LjIzMkw1OS44NDMsMzQuNzExVjE2LjA0M2g3LjIxMlpNOTMuMDcsMjEuNjI3SDgwLjIzNHY2LjlIOTEuOXY1LjU2M0g4MC4yMzRWNDYuMDQzSDczdi0zMEg5My4wN1pNMTIzLjQsMzEuNjY1YTE3LjgsMTcuOCwwLDAsMS0xLjYzNyw3LjgxMiwxMi4xMDgsMTIuMTA4LDAsMCwxLTQuNjUyLDUuMjMyLDEyLjk1MSwxMi45NTEsMCwwLDEtNi44NywxLjgzNCwxMy4xMzgsMTMuMTM4LDAsMCwxLTYuODM4LTEuNzcyLDEyLjA4MSwxMi4wODEsMCwwLDEtNC42NTItNS4wNjZBMTcuMjgxLDE3LjI4MSwwLDAsMSw5NywzMi4xNDF2LTEuN2ExNy44NTksMTcuODU5LDAsMCwxLDEuNjI3LTcuODIyLDEyLjA2NywxMi4wNjcsMCwwLDEsNC42NjItNS4yMzMsMTMuOCwxMy44LDAsMCwxLDEzLjc0OS0uMDIxLDEyLjI4OSwxMi4yODksMCwwLDEsNC42NzMsNS4xOTEsMTcuMzYyLDE3LjM2MiwwLDAsMSwxLjY4OSw3LjcxOVoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTEwLjIsMjEuMjQxcS01LjQ3MSwwLTUuNzgyLDguMjA2bC0uMDIsMi4yMThhMTMuMDQ3LDEzLjA0NywwLDAsMCwxLjQ3MSw2LjgxNyw0LjgxMSw0LjgxMSwwLDAsMCw0LjM3MiwyLjM4Myw0Ljc1Miw0Ljc1MiwwLDAsMCw0LjI0OC0yLjM0MUExMi42OTEsMTIuNjkxLDAsMCwwLDExNiwzMS43ODlWMzAuNHEwLTQuNS0xLjUtNi44MjhBNC44MjEsNC44MjEsMCwwLDAsMTEwLjIsMjEuMjQxWiIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg==)' 111 | 112 | }); 113 | -------------------------------------------------------------------------------- /js/components/slideshow.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('slideshow', { 2 | schema: { 3 | slides: {type: 'array'}, 4 | firstSlideIndex: {default: 0}, 5 | loadAlwaysFirstSlide: {default: true}, 6 | enabled: {default: true}, 7 | vrEnabled: {default: true} 8 | }, 9 | 10 | init: function () { 11 | var slideEl; 12 | var slideTextEl; 13 | var data = this.data; 14 | 15 | this.slideEls = []; 16 | this.addStyles(); 17 | 18 | for (var i = 0; i < data.slides.length; ++i) { 19 | slideEl = document.querySelector(data.slides[i]); 20 | this.slideEls.push(slideEl); 21 | slideEl.object3D.visible = false; 22 | slideTextEl = slideEl.querySelector('.slide'); 23 | if (slideTextEl) { slideTextEl.classList.add('hidden'); } 24 | } 25 | 26 | this.onKeyDown = this.onKeyDown.bind(this); 27 | this.onKeyUp = this.onKeyUp.bind(this); 28 | this.showSlide = this.showSlide.bind(this); 29 | 30 | 31 | if (data.slides.length === 1) { 32 | this.setHash(data.firstSlideIndex); 33 | this.showSlide(); 34 | return; 35 | } 36 | 37 | this.createControlButtons(); 38 | 39 | if (data.loadAlwaysFirstSlide) { this.setHash(data.firstSlideIndex); } 40 | this.showSlide(); 41 | 42 | window.addEventListener('keydown', this.onKeyDown); 43 | window.addEventListener('keyup', this.onKeyUp); 44 | window.addEventListener('hashchange', this.showSlide); 45 | }, 46 | 47 | setBackground: function (slideEl) { 48 | var sceneEl = this.el.sceneEl; 49 | var counterTextEl = this.counterTextEl; 50 | var backgroundColor = slideEl.getAttribute('background-color'); 51 | var slide = slideEl.querySelector('.slide'); 52 | 53 | if (slideEl.querySelector('.dark')) { 54 | sceneEl.setAttribute('background', 'color', 'black'); 55 | counterTextEl.style.color = 'white'; 56 | } else { 57 | sceneEl.setAttribute('background', 'color', 'white'); 58 | counterTextEl.style.color = ''; 59 | } 60 | 61 | if (slide) { 62 | if (backgroundColor) { 63 | slide.style.backgroundColor = backgroundColor; 64 | slide.style.color = 'white'; 65 | counterTextEl.style.color = 'white'; 66 | slide.style.borderRight = '1px solid white'; 67 | //slide.style.textShadow = '1px 1px black'; 68 | sceneEl.setAttribute('background', 'color', backgroundColor); 69 | } else { 70 | slide.style.color = ''; 71 | slide.style.borderRight = ''; 72 | slide.style.textShadow = ''; 73 | slide.style.backgroundColor = ''; 74 | } 75 | } 76 | }, 77 | 78 | update: function () { 79 | /*if (this.data.enabled) { 80 | this.startSlideShow(); 81 | } else { 82 | this.stopSlideShow(); 83 | }*/ 84 | }, 85 | 86 | startSlideShow: function () { 87 | this.controlsContainerEl.classList.remove('hidden'); 88 | }, 89 | 90 | stopSlideShow: function () { 91 | this.controlsContainerEl.classList.add('hidden'); 92 | }, 93 | 94 | createControlButtons: function (onClick) { 95 | var nextButtonEl; 96 | var previousButtonEl; 97 | var controlsContainerEl; 98 | var counterEl; 99 | var self = this; 100 | 101 | // Create elements. 102 | controlsContainerEl = this.controlsContainerEl = document.createElement('div'); 103 | controlsContainerEl.classList.add('a-slideshow-container'); 104 | 105 | this.previousButtonEl = previousButtonEl = document.createElement('button'); 106 | previousButtonEl.classList.add('a-slideshow-button'); 107 | previousButtonEl.classList.add('a-slideshow-button-previous'); 108 | previousButtonEl.classList.add('previous'); 109 | if (!this.data.enabled) { previousButtonEl.classList.add('hidden'); } 110 | previousButtonEl.setAttribute('title', 'Previous Slide'); 111 | 112 | counterEl = this.counterEl = document.createElement('div'); 113 | counterEl.classList.add('a-slideshow-counter'); 114 | this.counterTextEl = document.createElement('span'); 115 | this.counterEl.appendChild(this.counterTextEl); 116 | 117 | this.nextButtonEl = nextButtonEl = document.createElement('button'); 118 | nextButtonEl.classList.add('a-slideshow-button'); 119 | nextButtonEl.classList.add('a-slideshow-button-next'); 120 | nextButtonEl.classList.add('next'); 121 | if (!this.data.enabled) { nextButtonEl.classList.add('hidden'); } 122 | nextButtonEl.setAttribute('title', 'Next Slide'); 123 | 124 | this.portraitMessageEl = portraitMessageEl = document.createElement('div'); 125 | portraitMessageEl.classList.add('a-slideshow-portrait-message'); 126 | portraitMessageEl.innerHTML = 'Turn your device for slideshow'; 127 | if (!this.data.enabled) { portraitMessageEl.classList.add('hidden'); } 128 | 129 | // Insert elements. 130 | controlsContainerEl.appendChild(previousButtonEl); 131 | controlsContainerEl.appendChild(counterEl); 132 | controlsContainerEl.appendChild(nextButtonEl); 133 | controlsContainerEl.appendChild(portraitMessageEl); 134 | 135 | nextButtonEl.addEventListener('click', function (evt) { 136 | self.loadNextSlide(); 137 | evt.stopPropagation(); 138 | }); 139 | 140 | previousButtonEl.addEventListener('click', function (evt) { 141 | self.loadNextSlide(); 142 | evt.stopPropagation(); 143 | }); 144 | this.el.sceneEl.appendChild(controlsContainerEl); 145 | }, 146 | 147 | addStyles: function () { 148 | var infoMessageNextButtonDataURI = 'url(data:image/svg+xml;base64,PHN2ZyBpZD0iTGF5ZXJfNCIgZGF0YS1uYW1lPSJMYXllciA0IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNTAgNjIiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojZmZmO308L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0xMzgsMEgxMkExMiwxMiwwLDAsMCwwLDEyVjUwQTEyLDEyLDAsMCwwLDEyLDYySDEzOGExMiwxMiwwLDAsMCwxMi0xMlYxMkExMiwxMiwwLDAsMCwxMzgsMFpNNDkuMTksNDZINDJMMzEuNDUsMjcuNDJWNDZIMjQuMjZWMTYuMTRoNy4xOUw0MiwzNC43MlYxNi4xNGg3LjE3Wk03NC4zNiwyMS43SDYwLjk0VjI4SDcyLjI2djUuMzRINjAuOTR2Ny4xM0g3NC4zMlY0Nkg1My43NVYxNi4xNEg3NC4zNlptMTksMjQuMy01LTEwLjExTDgzLjM2LDQ2SDc1LjA3bDguNTYtMTVMNzUuMywxNi4xNGg4LjJsNC44OCw5LjkzLDQuODgtOS45M2g4LjIzTDkzLjE0LDMxLDEwMS43MSw0NlptMzQuMDgtMjQuM2gtOVY0NmgtNy4yVjIxLjdoLTguOFYxNi4xNGgyNVoiLz48L3N2Zz4=)'; 149 | var infoMessagePrevButtonDataURI = 'url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyNS4wLjEsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCINCgkgdmlld0JveD0iMCAwIDE1MCA2MiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTUwIDYyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KCS5zdDB7ZGlzcGxheTpub25lO30NCgkuc3Qxe2Rpc3BsYXk6aW5saW5lO30NCgkuc3Qye2ZpbGw6bm9uZTt9DQoJLnN0M3tmaWxsOiNGRkZGRkY7fQ0KPC9zdHlsZT4NCjxnIGlkPSJCYWNrZ3JvdW5kIiBjbGFzcz0ic3QwIj4NCgk8cmVjdCBjbGFzcz0ic3QxIiB3aWR0aD0iMTUwIiBoZWlnaHQ9IjYyIi8+DQo8L2c+DQo8ZyBpZD0iQlVUVE9OIiBjbGFzcz0ic3QwIj4NCgk8ZyBjbGFzcz0ic3QxIj4NCgkJPGc+DQoJCQk8cG9seWdvbiBjbGFzcz0ic3QyIiBwb2ludHM9IjU5LjgsMzQuNyA0OS4yLDE2IDQyLDE2IDQyLDQ2IDQ5LjIsNDYgNDkuMiwyNy40IDU5LjksNDYgNjcuMSw0NiA2Ny4xLDE2IDU5LjgsMTYgCQkJIi8+DQoJCQk8cG9seWdvbiBjbGFzcz0ic3QyIiBwb2ludHM9IjczLDQ2IDgwLjIsNDYgODAuMiwzNC4xIDkxLjksMzQuMSA5MS45LDI4LjUgODAuMiwyOC41IDgwLjIsMjEuNiA5My4xLDIxLjYgOTMuMSwxNiA3MywxNiAJCQkiLz4NCgkJCTxyZWN0IHg9IjI5IiB5PSIxNiIgY2xhc3M9InN0MiIgd2lkdGg9IjcuMiIgaGVpZ2h0PSIzMCIvPg0KCQkJPHBhdGggY2xhc3M9InN0MiIgZD0iTTExNywxNy40Yy0yLTEuMi00LjMtMS44LTYuOC0xLjhjLTIuNiwwLTQuOSwwLjYtNi45LDEuOGMtMiwxLjItMy42LDMtNC43LDUuMmMtMS4xLDIuMy0xLjYsNC45LTEuNiw3LjgNCgkJCQl2MS43YzAuMSwyLjgsMC42LDUuNCwxLjgsNy42YzEuMSwyLjIsMi43LDMuOSw0LjcsNS4xczQuMywxLjgsNi44LDEuOHM0LjktMC42LDYuOS0xLjhjMi0xLjIsMy42LTMsNC43LTUuMg0KCQkJCWMxLjEtMi4zLDEuNi00LjksMS42LTcuOHYtMS40YzAtMi45LTAuNi01LjUtMS43LTcuN0MxMjAuNiwyMC4zLDExOSwxOC42LDExNywxNy40eiIvPg0KCQkJPGc+DQoJCQkJPHJlY3QgeD0iMjkiIHk9IjE2IiBjbGFzcz0ic3QyIiB3aWR0aD0iNy4yIiBoZWlnaHQ9IjMwIi8+DQoJCQkJPHBvbHlnb24gY2xhc3M9InN0MiIgcG9pbnRzPSI3Myw0NiA4MC4yLDQ2IDgwLjIsMzQuMSA5MS45LDM0LjEgOTEuOSwyOC41IDgwLjIsMjguNSA4MC4yLDIxLjYgOTMuMSwyMS42IDkzLjEsMTYgNzMsMTYgCQkJCSIvPg0KCQkJCTxwb2x5Z29uIGNsYXNzPSJzdDIiIHBvaW50cz0iNTkuOCwzNC43IDQ5LjIsMTYgNDIsMTYgNDIsNDYgNDkuMiw0NiA0OS4yLDI3LjQgNTkuOSw0NiA2Ny4xLDQ2IDY3LjEsMTYgNTkuOCwxNiAJCQkJIi8+DQoJCQkJPHBhdGggY2xhc3M9InN0MiIgZD0iTTExNywxNy40Yy0yLTEuMi00LjMtMS44LTYuOC0xLjhjLTIuNiwwLTQuOSwwLjYtNi45LDEuOGMtMiwxLjItMy42LDMtNC43LDUuMmMtMS4xLDIuMy0xLjYsNC45LTEuNiw3LjgNCgkJCQkJdjEuN2MwLjEsMi44LDAuNiw1LjQsMS44LDcuNmMxLjEsMi4yLDIuNywzLjksNC43LDUuMXM0LjMsMS44LDYuOCwxLjhzNC45LTAuNiw2LjktMS44YzItMS4yLDMuNi0zLDQuNy01LjINCgkJCQkJYzEuMS0yLjMsMS42LTQuOSwxLjYtNy44di0xLjRjMC0yLjktMC42LTUuNS0xLjctNy43QzEyMC42LDIwLjMsMTE5LDE4LjYsMTE3LDE3LjR6Ii8+DQoJCQkJPHBhdGggY2xhc3M9InN0MyIgZD0iTTEzOCwwSDEyQzUuNCwwLDAsNS40LDAsMTJ2MzhjMCw2LjYsNS40LDEyLDEyLDEyaDEyNmM2LjYsMCwxMi01LjQsMTItMTJWMTJDMTUwLDUuNCwxNDQuNiwwLDEzOCwweg0KCQkJCQkgTTM2LjIsNDZIMjlWMTZoNy4yVjQ2eiBNNjcuMSw0NmgtNy4yTDQ5LjIsMjcuNFY0Nkg0MlYxNmg3LjJsMTAuNiwxOC43VjE2aDcuMlY0NnogTTkzLjEsMjEuNkg4MC4ydjYuOWgxMS43djUuNkg4MC4ydjEySDczDQoJCQkJCVYxNmgyMC4xVjIxLjZ6IE0xMjMuNCwzMS43YzAsMi45LTAuNSw1LjUtMS42LDcuOGMtMS4xLDIuMy0yLjYsNC00LjcsNS4yYy0yLDEuMi00LjMsMS44LTYuOSwxLjhzLTQuOC0wLjYtNi44LTEuOA0KCQkJCQlzLTMuNS0yLjktNC43LTUuMWMtMS4xLTIuMi0xLjctNC43LTEuOC03LjZ2LTEuN2MwLTMsMC41LTUuNiwxLjYtNy44YzEuMS0yLjMsMi42LTQsNC43LTUuMmMyLTEuMiw0LjMtMS44LDYuOS0xLjgNCgkJCQkJYzIuNiwwLDQuOCwwLjYsNi44LDEuOGMyLDEuMiwzLjYsMi45LDQuNyw1LjJjMS4xLDIuMiwxLjcsNC44LDEuNyw3LjdWMzEuN3oiLz4NCgkJCTwvZz4NCgkJPC9nPg0KCQk8cGF0aCBjbGFzcz0ic3QzIiBkPSJNMTEwLjIsMjEuMmMtMy42LDAtNS42LDIuNy01LjgsOC4ybDAsMi4yYzAsMywwLjUsNS4yLDEuNSw2LjhzMi40LDIuNCw0LjQsMi40YzEuOCwwLDMuMy0wLjgsNC4yLTIuMw0KCQkJYzEtMS42LDEuNS0zLjgsMS41LTYuN3YtMS40YzAtMy0wLjUtNS4zLTEuNS02LjhDMTEzLjUsMjIsMTEyLjEsMjEuMiwxMTAuMiwyMS4yeiIvPg0KCTwvZz4NCjwvZz4NCjxnIGlkPSJMYXllcl80IiBjbGFzcz0ic3QwIj4NCgk8ZyBjbGFzcz0ic3QxIj4NCgkJPGc+DQoJCQk8cGF0aCBjbGFzcz0ic3QzIiBkPSJNMTM4LDBIMTJDNS40LDAsMCw1LjQsMCwxMnYzOGMwLDYuNiw1LjQsMTIsMTIsMTJoMTI2YzYuNiwwLDEyLTUuNCwxMi0xMlYxMkMxNTAsNS40LDE0NC42LDAsMTM4LDB6DQoJCQkJIE00OS4yLDQ2SDQyTDMxLjUsMjcuNFY0NmgtNy4yVjE2LjFoNy4yTDQyLDM0LjdWMTYuMWg3LjJWNDZ6IE03NC40LDIxLjdINjAuOVYyOGgxMS4zdjUuM0g2MC45djcuMWgxMy40VjQ2SDUzLjdWMTYuMWgyMC42DQoJCQkJVjIxLjd6IE05My40LDQ2bC01LTEwLjFsLTUsMTAuMWgtOC4zbDguNi0xNS4xbC04LjMtMTQuOGg4LjJsNC45LDkuOWw0LjktOS45aDguMmwtOC4zLDE0LjhsOC42LDE1LjFIOTMuNHogTTEyNy41LDIxLjdoLTlWNDYNCgkJCQloLTcuMlYyMS43aC04Ljh2LTUuNmgyNVYyMS43eiIvPg0KCQk8L2c+DQoJPC9nPg0KPC9nPg0KPGcgaWQ9IkxheWVyXzRfY29weSI+DQoJPGc+DQoJCTxnPg0KCQkJPHBhdGggY2xhc3M9InN0MyIgZD0iTTM1LjMsMjEuN2gtNC44djguNmg0LjdjMS40LDAsMi41LTAuMywzLjItMXMxLjEtMS43LDEuMS0zYzAtMS40LTAuNC0yLjUtMS4xLTMuM1MzNi42LDIxLjcsMzUuMywyMS43eg0KCQkJCSBNMTM4LDBIMTJDNS40LDAsMCw1LjQsMCwxMnYzOGMwLDYuNiw1LjQsMTIsMTIsMTJoMTI2YzYuNiwwLDEyLTUuNCwxMi0xMlYxMkMxNTAsNS40LDE0NC42LDAsMTM4LDB6IE00My42LDMzLjMNCgkJCQljLTIuMSwxLjctNC45LDIuNi04LjYsMi42aC00LjZWNDZoLTcuMlYxNi4xaDExLjljMi4zLDAsNC4zLDAuNCw2LjEsMS4zczMuMSwyLDQuMSwzLjZjMSwxLjYsMS40LDMuMywxLjQsNS4zDQoJCQkJQzQ2LjgsMjkuMiw0NS43LDMxLjUsNDMuNiwzMy4zeiBNNzQuNyw0Nkg2N2wtNS4zLTEwLjZoLTMuOVY0NmgtNy4yVjE2LjFoMTEuOGMzLjUsMCw2LjMsMC44LDguMywyLjRzMywzLjgsMyw2LjcNCgkJCQljMCwyLjEtMC40LDMuOC0xLjMsNS4yYy0wLjgsMS40LTIuMiwyLjUtMy45LDMuM2w2LjIsMTIuMVY0NnogTTk4LjYsMjEuN0g4NS4yVjI4aDExLjN2NS4zSDg1LjJ2Ny4xaDEzLjRWNDZINzhWMTYuMWgyMC42VjIxLjd6DQoJCQkJIE0xMTcsNDZoLTcuOWwtMTAtMjkuOWg4bDUuOSwyMS43bDUuOS0yMS43aDhMMTE3LDQ2eiBNNjIuMywyMS43aC00LjZ2OC4yaDQuNmMxLjQsMCwyLjQtMC40LDMuMS0xLjFjMC43LTAuNywxLTEuNywxLTMNCgkJCQljMC0xLjMtMC4zLTIuMy0xLTNDNjQuNywyMiw2My43LDIxLjcsNjIuMywyMS43eiIvPg0KCQk8L2c+DQoJPC9nPg0KPC9nPg0KPGcgaWQ9IkZvbnQiIGNsYXNzPSJzdDAiPg0KCTx0ZXh0IHRyYW5zZm9ybT0ibWF0cml4KDEgMCAwIDEgMjkgLTI0KSIgY2xhc3M9InN0MSIgc3R5bGU9ImZvbnQtZmFtaWx5OidSb2JvdG8tQmxhY2snOyBmb250LXNpemU6NDIuMzc1cHg7Ij5JTkZPPC90ZXh0Pg0KPC9nPg0KPC9zdmc+DQo=)'; 150 | var css = 151 | '.slide {font-family: Helvetica; border-right: 1px solid rgb(70, 70, 70); color: #2d2d2d;' + 152 | 'z-index: 9999; position: absolute; width: 30%;' + 153 | 'display: inline-block; top: 10%; bottom: 10%; padding: 20px;' + 154 | 'font-size: 15pt; }' + 155 | '.slide.hidden { display: none; }' + 156 | '.slide div {position: relative; top: 50%;' + 157 | 'transform: translateY(-50%); text-align: center; padding: 20px}' + 158 | '.slide h1 {font-size: 20pt; marging: 50%; text-align: right;}' + 159 | '.slide p {font-size: 12pt; text-align: justify;}' + 160 | '.slide.dark {background-color: black; color: white}' + 161 | '.slide.dark a{color: #ea80fc}' + 162 | 163 | '.a-slideshow-container {display:flex; position: absolute; left: calc(50% - 105px); bottom: 20px; width: 210px;}' + 164 | //'.a-slideshow-container .hidden {display: none}' + 165 | '.a-slideshow-button-previous {background: rgba(0, 0, 0, 0.35) ' + infoMessagePrevButtonDataURI + ' 50% 50% no-repeat;}' + 166 | '.a-slideshow-button-next {background: rgba(0, 0, 0, 0.35) ' + infoMessageNextButtonDataURI + ' 50% 50% no-repeat;}' + 167 | '.a-slideshow-button {background-size: 92% 90%; border: 0; bottom: 0; cursor: pointer; min-width: 78px; min-height: 34px; padding-right: 0; padding-top: 0; transition: background-color .05s ease; -webkit-transition: background-color .05s ease; z-index: 9999; border-radius: 8px; touch-action: manipulation;}' + 168 | '.a-slideshow-button.next {right: -60px;}' + 169 | '.a-slideshow-button.previous {right: 60px;}' + 170 | '.a-slideshow-button .hidden {display: none}' + 171 | '.a-slideshow-button:active, .a-slideshow-button:hover, .hover {background-color: #ef2d5e;}' + 172 | '.a-slideshow-counter {align: auto; height: 34px; text-align: center; min-width: 50px; color: #2d2d2d; display: inline-block;}' + 173 | '.a-slideshow-counter span {line-height: 34px; display: inline-block; vertical-align: middle;}' + 174 | '.a-slideshow-portrait-message {display: none}' + 175 | 176 | '@media only screen and (max-height: 600px) {' + 177 | '.slide h1 {font-size: 12pt;}' + 178 | '.slide p {font-size: 8pt;}' + 179 | '}' + 180 | 181 | '@media (orientation: portrait) { ' + 182 | '.a-slideshow-portrait-message {display: inline-block; text-align: center; width: 250px; border: 3px solid rgb(51, 51, 51); line-height: 27px; height: 27px; margin-bottom: 2px; border-radius: 10px; background-color: white; }' + 183 | '.a-slideshow-container {right: 20px; left: auto; width: 250px}' + 184 | '.a-slideshow-button {display: none}' + 185 | '.a-slideshow-counter {display: none}' + 186 | '.slide {display: none}' + 187 | '}'; 188 | var style = document.createElement('style'); 189 | 190 | if (style.styleSheet) { 191 | style.styleSheet.cssText = css; 192 | } else { 193 | style.appendChild(document.createTextNode(css)); 194 | } 195 | 196 | document.getElementsByTagName('head')[0].appendChild(style); 197 | }, 198 | 199 | 200 | onKeyDown: function (evt) { 201 | var keyCode = evt.keyCode; 202 | 203 | // Left or right keys. 204 | if (keyCode !== 37 && keyCode !== 39) { return; } 205 | if (keyCode === 37) { 206 | this.previousButtonEl.classList.add('hover'); 207 | this.loadPreviousSlide(); 208 | } 209 | if (keyCode === 39) { 210 | this.nextButtonEl.classList.add('hover'); 211 | this.loadNextSlide(); 212 | } 213 | }, 214 | 215 | onKeyUp: function (evt) { 216 | var keyCode = evt.keyCode; 217 | 218 | // Left or right keys. 219 | if (keyCode !== 37 && keyCode !== 39) { return; } 220 | if (keyCode === 37) { this.previousButtonEl.classList.remove('hover'); } 221 | if (keyCode === 39) { this.nextButtonEl.classList.remove('hover'); } 222 | }, 223 | 224 | loadNextSlide: function () { 225 | var currentSlideIndex = this.currentSlideIndex; 226 | if (currentSlideIndex === (this.data.slides.length - 1)) { 227 | currentSlideIndex = 0; 228 | } else { 229 | currentSlideIndex++; 230 | } 231 | this.setHash(currentSlideIndex); 232 | }, 233 | 234 | loadPreviousSlide: function () { 235 | var currentSlideIndex = this.currentSlideIndex; 236 | if (currentSlideIndex === 0) { 237 | currentSlideIndex = this.data.slides.length - 1; 238 | } else { 239 | currentSlideIndex--; 240 | } 241 | this.setHash(currentSlideIndex); 242 | }, 243 | 244 | setHash: function (slideIndex) { 245 | window.location.hash = slideIndex; 246 | }, 247 | 248 | showSlide: function () { 249 | var slideTextEl; 250 | var currentUrl = document.URL; 251 | var slideIndex = currentUrl.split('#')[1]; 252 | var cameraSlideEl; 253 | var counterTextEl = this.counterTextEl; 254 | var currentSlideEl; 255 | 256 | slideIndex = this.currentSlideIndex = Number.parseInt(slideIndex) || 0; 257 | cameraSlideEl = this.slideEls[slideIndex].querySelector("[camera]"); 258 | if (cameraSlideEl) { cameraSlideEl.setAttribute('camera', 'active', true); } 259 | currentSlideEl = this.slideEls[slideIndex]; 260 | currentSlideEl.play(); 261 | currentSlideEl.object3D.visible = true; 262 | this.setBackground(currentSlideEl); 263 | 264 | slideTextEl = currentSlideEl.querySelector('.slide'); 265 | if (slideTextEl) { slideTextEl.classList.remove('hidden'); } 266 | for (var i = 0; i < this.data.slides.length; i++) { 267 | if (i === slideIndex) { continue; } 268 | cameraSlideEl = this.slideEls[i].querySelector("[camera]"); 269 | if (cameraSlideEl) { cameraSlideEl.setAttribute('camera', 'active', false); } 270 | this.slideEls[i].pause(); 271 | this.slideEls[i].object3D.visible = false; 272 | slideTextEl = this.slideEls[i].querySelector('.slide') 273 | if (slideTextEl) { slideTextEl.classList.add('hidden'); } 274 | } 275 | 276 | if (counterTextEl) { counterTextEl.innerHTML = slideIndex + 1 + '/' + this.data.slides.length; } 277 | this.el.setAttribute('vr-mode-ui', 'enabled', this.data.vrEnabled && this.currentSlideIndex === this.data.firstSlideIndex); 278 | } 279 | }); -------------------------------------------------------------------------------- /js/systems/wireframe.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerSystem('wireframe', { 2 | unindexBufferGeometry: function (bufferGeometry) { 3 | // un-indices the geometry, copying all attributes like position and uv 4 | const index = bufferGeometry.getIndex(); 5 | if (!index) return; // already un-indexed 6 | 7 | const indexArray = index.array; 8 | const triangleCount = indexArray.length / 3; 9 | 10 | const attributes = bufferGeometry.attributes; 11 | const newAttribData = Object.keys(attributes).map(key => { 12 | return { 13 | key: key, 14 | array: [], 15 | attribute: bufferGeometry.getAttribute(key) 16 | }; 17 | }); 18 | 19 | for (let i = 0; i < triangleCount; i++) { 20 | // indices into attributes 21 | const a = indexArray[i * 3 + 0]; 22 | const b = indexArray[i * 3 + 1]; 23 | const c = indexArray[i * 3 + 2]; 24 | const indices = [ a, b, c ]; 25 | 26 | // for each attribute, put vertex into unindexed list 27 | newAttribData.forEach(data => { 28 | const attrib = data.attribute; 29 | const dim = attrib.itemSize; 30 | // add [a, b, c] vertices 31 | for (let i = 0; i < indices.length; i++) { 32 | const index = indices[i]; 33 | for (let d = 0; d < dim; d++) { 34 | const v = attrib.array[index * dim + d]; 35 | data.array.push(v); 36 | } 37 | } 38 | }); 39 | } 40 | index.array = null; 41 | bufferGeometry.setIndex(null); 42 | 43 | // now copy over new data 44 | newAttribData.forEach(data => { 45 | const oldBuffer = data.attribute; 46 | const newArray = new data.attribute.array.constructor(data.array); 47 | const newBuffer = new THREE.BufferAttribute(newArray, oldBuffer.itemSize, oldBuffer.normalized); 48 | bufferGeometry.setAttribute(data.key, newBuffer); 49 | }); 50 | }, 51 | 52 | calculateBarycenters: function(geometry) { 53 | var attrib = geometry.getIndex() || geometry.getAttribute('position'); 54 | var count = attrib.count / 3; 55 | var barycenters = this.barycenters = []; 56 | for (var i = 0; i < count; ++i) { 57 | barycenters.push( 58 | 1, 0, 0, 59 | 0, 1, 0, 60 | 0, 0, 1 61 | ); 62 | }; 63 | geometry.setAttribute('barycentric', new THREE.Float32BufferAttribute(barycenters, 3)); 64 | } 65 | }); -------------------------------------------------------------------------------- /js/vendor/RGBELoader.js: -------------------------------------------------------------------------------- 1 | // https://github.com/mrdoob/three.js/issues/5552 2 | // http://en.wikipedia.org/wiki/RGBE_image_format 3 | 4 | THREE.RGBELoader = function ( manager ) { 5 | 6 | THREE.DataTextureLoader.call( this, manager ); 7 | 8 | this.type = THREE.UnsignedByteType; 9 | 10 | }; 11 | 12 | THREE.RGBELoader.prototype = Object.assign( Object.create( THREE.DataTextureLoader.prototype ), { 13 | 14 | constructor: THREE.RGBELoader, 15 | 16 | // adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html 17 | 18 | parse: function ( buffer ) { 19 | 20 | var 21 | /* return codes for rgbe routines */ 22 | //RGBE_RETURN_SUCCESS = 0, 23 | RGBE_RETURN_FAILURE = - 1, 24 | 25 | /* default error routine. change this to change error handling */ 26 | rgbe_read_error = 1, 27 | rgbe_write_error = 2, 28 | rgbe_format_error = 3, 29 | rgbe_memory_error = 4, 30 | rgbe_error = function ( rgbe_error_code, msg ) { 31 | 32 | switch ( rgbe_error_code ) { 33 | 34 | case rgbe_read_error: console.error( "RGBELoader Read Error: " + ( msg || '' ) ); 35 | break; 36 | case rgbe_write_error: console.error( "RGBELoader Write Error: " + ( msg || '' ) ); 37 | break; 38 | case rgbe_format_error: console.error( "RGBELoader Bad File Format: " + ( msg || '' ) ); 39 | break; 40 | default: 41 | case rgbe_memory_error: console.error( "RGBELoader: Error: " + ( msg || '' ) ); 42 | 43 | } 44 | 45 | return RGBE_RETURN_FAILURE; 46 | 47 | }, 48 | 49 | /* offsets to red, green, and blue components in a data (float) pixel */ 50 | //RGBE_DATA_RED = 0, 51 | //RGBE_DATA_GREEN = 1, 52 | //RGBE_DATA_BLUE = 2, 53 | 54 | /* number of floats per pixel, use 4 since stored in rgba image format */ 55 | //RGBE_DATA_SIZE = 4, 56 | 57 | /* flags indicating which fields in an rgbe_header_info are valid */ 58 | RGBE_VALID_PROGRAMTYPE = 1, 59 | RGBE_VALID_FORMAT = 2, 60 | RGBE_VALID_DIMENSIONS = 4, 61 | 62 | NEWLINE = "\n", 63 | 64 | fgets = function ( buffer, lineLimit, consume ) { 65 | 66 | lineLimit = ! lineLimit ? 1024 : lineLimit; 67 | var p = buffer.pos, 68 | i = - 1, len = 0, s = '', chunkSize = 128, 69 | chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ) 70 | ; 71 | while ( ( 0 > ( i = chunk.indexOf( NEWLINE ) ) ) && ( len < lineLimit ) && ( p < buffer.byteLength ) ) { 72 | 73 | s += chunk; len += chunk.length; 74 | p += chunkSize; 75 | chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ); 76 | 77 | } 78 | 79 | if ( - 1 < i ) { 80 | 81 | /*for (i=l-1; i>=0; i--) { 82 | byteCode = m.charCodeAt(i); 83 | if (byteCode > 0x7f && byteCode <= 0x7ff) byteLen++; 84 | else if (byteCode > 0x7ff && byteCode <= 0xffff) byteLen += 2; 85 | if (byteCode >= 0xDC00 && byteCode <= 0xDFFF) i--; //trail surrogate 86 | }*/ 87 | if ( false !== consume ) buffer.pos += len + i + 1; 88 | return s + chunk.slice( 0, i ); 89 | 90 | } 91 | 92 | return false; 93 | 94 | }, 95 | 96 | /* minimal header reading. modify if you want to parse more information */ 97 | RGBE_ReadHeader = function ( buffer ) { 98 | 99 | var line, match, 100 | 101 | // regexes to parse header info fields 102 | magic_token_re = /^#\?(\S+)$/, 103 | gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/, 104 | exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/, 105 | format_re = /^\s*FORMAT=(\S+)\s*$/, 106 | dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/, 107 | 108 | // RGBE format header struct 109 | header = { 110 | 111 | valid: 0, /* indicate which fields are valid */ 112 | 113 | string: '', /* the actual header string */ 114 | 115 | comments: '', /* comments found in header */ 116 | 117 | programtype: 'RGBE', /* listed at beginning of file to identify it after "#?". defaults to "RGBE" */ 118 | 119 | format: '', /* RGBE format, default 32-bit_rle_rgbe */ 120 | 121 | gamma: 1.0, /* image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) */ 122 | 123 | exposure: 1.0, /* a value of 1.0 in an image corresponds to watts/steradian/m^2. defaults to 1.0 */ 124 | 125 | width: 0, height: 0 /* image dimensions, width/height */ 126 | 127 | }; 128 | 129 | if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) { 130 | 131 | return rgbe_error( rgbe_read_error, "no header found" ); 132 | 133 | } 134 | 135 | /* if you want to require the magic token then uncomment the next line */ 136 | if ( ! ( match = line.match( magic_token_re ) ) ) { 137 | 138 | return rgbe_error( rgbe_format_error, "bad initial token" ); 139 | 140 | } 141 | 142 | header.valid |= RGBE_VALID_PROGRAMTYPE; 143 | header.programtype = match[ 1 ]; 144 | header.string += line + "\n"; 145 | 146 | while ( true ) { 147 | 148 | line = fgets( buffer ); 149 | if ( false === line ) break; 150 | header.string += line + "\n"; 151 | 152 | if ( '#' === line.charAt( 0 ) ) { 153 | 154 | header.comments += line + "\n"; 155 | continue; // comment line 156 | 157 | } 158 | 159 | if ( match = line.match( gamma_re ) ) { 160 | 161 | header.gamma = parseFloat( match[ 1 ], 10 ); 162 | 163 | } 164 | 165 | if ( match = line.match( exposure_re ) ) { 166 | 167 | header.exposure = parseFloat( match[ 1 ], 10 ); 168 | 169 | } 170 | 171 | if ( match = line.match( format_re ) ) { 172 | 173 | header.valid |= RGBE_VALID_FORMAT; 174 | header.format = match[ 1 ];//'32-bit_rle_rgbe'; 175 | 176 | } 177 | 178 | if ( match = line.match( dimensions_re ) ) { 179 | 180 | header.valid |= RGBE_VALID_DIMENSIONS; 181 | header.height = parseInt( match[ 1 ], 10 ); 182 | header.width = parseInt( match[ 2 ], 10 ); 183 | 184 | } 185 | 186 | if ( ( header.valid & RGBE_VALID_FORMAT ) && ( header.valid & RGBE_VALID_DIMENSIONS ) ) break; 187 | 188 | } 189 | 190 | if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) { 191 | 192 | return rgbe_error( rgbe_format_error, "missing format specifier" ); 193 | 194 | } 195 | 196 | if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) { 197 | 198 | return rgbe_error( rgbe_format_error, "missing image size specifier" ); 199 | 200 | } 201 | 202 | return header; 203 | 204 | }, 205 | 206 | RGBE_ReadPixels_RLE = function ( buffer, w, h ) { 207 | 208 | var data_rgba, offset, pos, count, byteValue, 209 | scanline_buffer, ptr, ptr_end, i, l, off, isEncodedRun, 210 | scanline_width = w, num_scanlines = h, rgbeStart 211 | ; 212 | 213 | if ( 214 | // run length encoding is not allowed so read flat 215 | ( ( scanline_width < 8 ) || ( scanline_width > 0x7fff ) ) || 216 | // this file is not run length encoded 217 | ( ( 2 !== buffer[ 0 ] ) || ( 2 !== buffer[ 1 ] ) || ( buffer[ 2 ] & 0x80 ) ) 218 | ) { 219 | 220 | // return the flat buffer 221 | return new Uint8Array( buffer ); 222 | 223 | } 224 | 225 | if ( scanline_width !== ( ( buffer[ 2 ] << 8 ) | buffer[ 3 ] ) ) { 226 | 227 | return rgbe_error( rgbe_format_error, "wrong scanline width" ); 228 | 229 | } 230 | 231 | data_rgba = new Uint8Array( 4 * w * h ); 232 | 233 | if ( ! data_rgba.length ) { 234 | 235 | return rgbe_error( rgbe_memory_error, "unable to allocate buffer space" ); 236 | 237 | } 238 | 239 | offset = 0; pos = 0; ptr_end = 4 * scanline_width; 240 | rgbeStart = new Uint8Array( 4 ); 241 | scanline_buffer = new Uint8Array( ptr_end ); 242 | 243 | // read in each successive scanline 244 | while ( ( num_scanlines > 0 ) && ( pos < buffer.byteLength ) ) { 245 | 246 | if ( pos + 4 > buffer.byteLength ) { 247 | 248 | return rgbe_error( rgbe_read_error ); 249 | 250 | } 251 | 252 | rgbeStart[ 0 ] = buffer[ pos ++ ]; 253 | rgbeStart[ 1 ] = buffer[ pos ++ ]; 254 | rgbeStart[ 2 ] = buffer[ pos ++ ]; 255 | rgbeStart[ 3 ] = buffer[ pos ++ ]; 256 | 257 | if ( ( 2 != rgbeStart[ 0 ] ) || ( 2 != rgbeStart[ 1 ] ) || ( ( ( rgbeStart[ 2 ] << 8 ) | rgbeStart[ 3 ] ) != scanline_width ) ) { 258 | 259 | return rgbe_error( rgbe_format_error, "bad rgbe scanline format" ); 260 | 261 | } 262 | 263 | // read each of the four channels for the scanline into the buffer 264 | // first red, then green, then blue, then exponent 265 | ptr = 0; 266 | while ( ( ptr < ptr_end ) && ( pos < buffer.byteLength ) ) { 267 | 268 | count = buffer[ pos ++ ]; 269 | isEncodedRun = count > 128; 270 | if ( isEncodedRun ) count -= 128; 271 | 272 | if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) { 273 | 274 | return rgbe_error( rgbe_format_error, "bad scanline data" ); 275 | 276 | } 277 | 278 | if ( isEncodedRun ) { 279 | 280 | // a (encoded) run of the same value 281 | byteValue = buffer[ pos ++ ]; 282 | for ( i = 0; i < count; i ++ ) { 283 | 284 | scanline_buffer[ ptr ++ ] = byteValue; 285 | 286 | } 287 | //ptr += count; 288 | 289 | } else { 290 | 291 | // a literal-run 292 | scanline_buffer.set( buffer.subarray( pos, pos + count ), ptr ); 293 | ptr += count; pos += count; 294 | 295 | } 296 | 297 | } 298 | 299 | 300 | // now convert data from buffer into rgba 301 | // first red, then green, then blue, then exponent (alpha) 302 | l = scanline_width; //scanline_buffer.byteLength; 303 | for ( i = 0; i < l; i ++ ) { 304 | 305 | off = 0; 306 | data_rgba[ offset ] = scanline_buffer[ i + off ]; 307 | off += scanline_width; //1; 308 | data_rgba[ offset + 1 ] = scanline_buffer[ i + off ]; 309 | off += scanline_width; //1; 310 | data_rgba[ offset + 2 ] = scanline_buffer[ i + off ]; 311 | off += scanline_width; //1; 312 | data_rgba[ offset + 3 ] = scanline_buffer[ i + off ]; 313 | offset += 4; 314 | 315 | } 316 | 317 | num_scanlines --; 318 | 319 | } 320 | 321 | return data_rgba; 322 | 323 | }; 324 | 325 | var RGBEByteToRGBFloat = function ( sourceArray, sourceOffset, destArray, destOffset ) { 326 | 327 | var e = sourceArray[ sourceOffset + 3 ]; 328 | var scale = Math.pow( 2.0, e - 128.0 ) / 255.0; 329 | 330 | destArray[ destOffset + 0 ] = sourceArray[ sourceOffset + 0 ] * scale; 331 | destArray[ destOffset + 1 ] = sourceArray[ sourceOffset + 1 ] * scale; 332 | destArray[ destOffset + 2 ] = sourceArray[ sourceOffset + 2 ] * scale; 333 | 334 | }; 335 | 336 | var RGBEByteToRGBHalf = function ( sourceArray, sourceOffset, destArray, destOffset ) { 337 | 338 | var e = sourceArray[ sourceOffset + 3 ]; 339 | var scale = Math.pow( 2.0, e - 128.0 ) / 255.0; 340 | 341 | destArray[ destOffset + 0 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 0 ] * scale ); 342 | destArray[ destOffset + 1 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 1 ] * scale ); 343 | destArray[ destOffset + 2 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 2 ] * scale ); 344 | 345 | }; 346 | 347 | var byteArray = new Uint8Array( buffer ); 348 | byteArray.pos = 0; 349 | var rgbe_header_info = RGBE_ReadHeader( byteArray ); 350 | 351 | if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) { 352 | 353 | var w = rgbe_header_info.width, 354 | h = rgbe_header_info.height, 355 | image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h ); 356 | 357 | if ( RGBE_RETURN_FAILURE !== image_rgba_data ) { 358 | 359 | switch ( this.type ) { 360 | 361 | case THREE.UnsignedByteType: 362 | 363 | var data = image_rgba_data; 364 | var format = THREE.RGBEFormat; // handled as THREE.RGBAFormat in shaders 365 | var type = THREE.UnsignedByteType; 366 | break; 367 | 368 | case THREE.FloatType: 369 | 370 | var numElements = ( image_rgba_data.length / 4 ) * 3; 371 | var floatArray = new Float32Array( numElements ); 372 | 373 | for ( var j = 0; j < numElements; j ++ ) { 374 | 375 | RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 3 ); 376 | 377 | } 378 | 379 | var data = floatArray; 380 | var format = THREE.RGBFormat; 381 | var type = THREE.FloatType; 382 | break; 383 | 384 | case THREE.HalfFloatType: 385 | 386 | var numElements = ( image_rgba_data.length / 4 ) * 3; 387 | var halfArray = new Uint16Array( numElements ); 388 | 389 | for ( var j = 0; j < numElements; j ++ ) { 390 | 391 | RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 3 ); 392 | 393 | } 394 | 395 | var data = halfArray; 396 | var format = THREE.RGBFormat; 397 | var type = THREE.HalfFloatType; 398 | break; 399 | 400 | default: 401 | 402 | console.error( 'THREE.RGBELoader: unsupported type: ', this.type ); 403 | break; 404 | 405 | } 406 | 407 | return { 408 | width: w, height: h, 409 | data: data, 410 | header: rgbe_header_info.string, 411 | gamma: rgbe_header_info.gamma, 412 | exposure: rgbe_header_info.exposure, 413 | format: format, 414 | type: type 415 | }; 416 | 417 | } 418 | 419 | } 420 | 421 | return null; 422 | 423 | }, 424 | 425 | setDataType: function ( value ) { 426 | 427 | this.type = value; 428 | return this; 429 | 430 | }, 431 | 432 | load: function ( url, onLoad, onProgress, onError ) { 433 | 434 | function onLoadCallback( texture, texData ) { 435 | 436 | switch ( texture.type ) { 437 | 438 | case THREE.UnsignedByteType: 439 | 440 | texture.encoding = THREE.RGBEEncoding; 441 | texture.minFilter = THREE.NearestFilter; 442 | texture.magFilter = THREE.NearestFilter; 443 | texture.generateMipmaps = false; 444 | texture.flipY = true; 445 | break; 446 | 447 | case THREE.FloatType: 448 | 449 | texture.encoding = THREE.LinearEncoding; 450 | texture.minFilter = THREE.LinearFilter; 451 | texture.magFilter = THREE.LinearFilter; 452 | texture.generateMipmaps = false; 453 | texture.flipY = true; 454 | break; 455 | 456 | case THREE.HalfFloatType: 457 | 458 | texture.encoding = THREE.LinearEncoding; 459 | texture.minFilter = THREE.LinearFilter; 460 | texture.magFilter = THREE.LinearFilter; 461 | texture.generateMipmaps = false; 462 | texture.flipY = true; 463 | break; 464 | 465 | } 466 | 467 | if ( onLoad ) onLoad( texture, texData ); 468 | 469 | } 470 | 471 | return THREE.DataTextureLoader.prototype.load.call( this, url, onLoadCallback, onProgress, onError ); 472 | 473 | } 474 | 475 | } ); -------------------------------------------------------------------------------- /wind-waker/fire-dynamic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wind Waker Wind FX • A-Frame 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /wind-waker/fire-static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wind Waker Wind FX • A-Frame 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /wind-waker/fire-static/main.js: -------------------------------------------------------------------------------- 1 | var fireVertexShader = ` 2 | attribute vec4 orientation; 3 | attribute vec3 offset; 4 | attribute vec2 scale; 5 | attribute float life; 6 | attribute float random; 7 | 8 | varying vec2 vUv; 9 | varying float vRandom; 10 | varying float vAlpha; 11 | 12 | float range(float oldValue, float oldMin, float oldMax, float newMin, float newMax) { 13 | float oldRange = oldMax - oldMin; 14 | float newRange = newMax - newMin; 15 | return (((oldValue - oldMin) * newRange) / oldRange) + newMin; 16 | } 17 | 18 | // From Inigo Quilez http://www.iquilezles.org/www/articles/functions/functions.htm 19 | float pcurve(float x, float a, float b) { 20 | float k = pow(a + b, a + b) / (pow(a, a) * pow(b, b)); 21 | return k * pow(x, a) * pow(1.0 - x, b); 22 | } 23 | 24 | void main() { 25 | vUv = uv; 26 | vRandom = random; 27 | 28 | vAlpha = pcurve(life, 1.0, 2.0); 29 | 30 | vec3 pos = position; 31 | 32 | pos.xy *= scale * vec2(range(pow(life, 1.5), 0.0, 1.0, 1.0, 0.6), range(pow(life, 1.5), 0.0, 1.0, 0.6, 1.2)); 33 | 34 | vec4 or = orientation; 35 | vec3 vcV = cross(or.xyz, pos); 36 | pos = vcV * (2.0 * or.w) + (cross(or.xyz, vcV) * 2.0 + pos); 37 | 38 | gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); 39 | } 40 | `; 41 | 42 | var fireFragmentShader = ` 43 | uniform sampler2D uMap; 44 | uniform vec3 uColor1; 45 | uniform vec3 uColor2; 46 | uniform float uTime; 47 | 48 | varying vec2 vUv; 49 | varying float vAlpha; 50 | varying float vRandom; 51 | 52 | void main() { 53 | vec2 uv = vUv; 54 | 55 | float spriteLength = 10.0; 56 | uv.x /= spriteLength; 57 | float spriteIndex = mod(uTime * 0.1 + vRandom * 2.0, 1.0); 58 | uv.x += floor(spriteIndex * spriteLength) / spriteLength; 59 | 60 | vec4 map = texture2D(uMap, uv); 61 | 62 | gl_FragColor.rgb = mix(uColor2, uColor1, map.r); 63 | gl_FragColor.a = vAlpha * map.a; 64 | } 65 | `; 66 | 67 | var embersVertexShader = ` 68 | attribute float size; 69 | attribute float life; 70 | attribute vec3 offset; 71 | 72 | varying float vAlpha; 73 | 74 | // From Inigo Quilez http://www.iquilezles.org/www/articles/functions/functions.htm 75 | float impulse(float k, float x) { 76 | float h = k * x; 77 | return h * exp(1.0 - h); 78 | } 79 | 80 | void main() { 81 | vAlpha = impulse(6.28, life); 82 | 83 | vec3 pos = position; 84 | pos += offset * vec3(life * 0.7 + 0.3, life * 0.9 + 0.1, life * 0.7 + 0.3); 85 | 86 | vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0); 87 | gl_PointSize = size * (80.0 / length(mvPosition.xyz)); 88 | gl_Position = projectionMatrix * mvPosition; 89 | } 90 | `; 91 | 92 | var embersFragmentShader = ` 93 | uniform sampler2D uMap; 94 | uniform vec3 uColor; 95 | 96 | varying float vAlpha; 97 | 98 | void main() { 99 | vec2 uv = vec2(gl_PointCoord.x, 1.0 - gl_PointCoord.y); 100 | vec4 mask = texture2D(uMap, uv); 101 | 102 | gl_FragColor.rgb = uColor; 103 | gl_FragColor.a = mask.a * vAlpha * 0.8; 104 | } 105 | `; 106 | 107 | var hazeVertexShader = ` 108 | attribute vec3 base; 109 | attribute vec3 offset; 110 | attribute vec4 orientation; 111 | attribute vec2 scale; 112 | attribute float life; 113 | 114 | varying float vAlpha; 115 | varying vec2 vUv; 116 | 117 | // From Inigo Quilez http://www.iquilezles.org/www/articles/functions/functions.htm 118 | float impulse(float k, float x) { 119 | float h = k * x; 120 | return h * exp(1.0 - h); 121 | } 122 | 123 | float pcurve(float x, float a, float b) { 124 | float k = pow(a + b, a + b) / (pow(a, a) * pow(b, b)); 125 | return k * pow(x, a) * pow(1.0 - x, b); 126 | } 127 | 128 | void main() { 129 | vUv = uv; 130 | vAlpha = pcurve(life, 1.0, 2.0); 131 | 132 | vec3 pos = position; 133 | 134 | pos.xy *= scale * (life * 0.7 + 0.3); 135 | 136 | vec4 or = orientation; 137 | vec3 vcV = cross(or.xyz, pos); 138 | pos = vcV * (2.0 * or.w) + (cross(or.xyz, vcV) * 2.0 + pos); 139 | 140 | pos += base; 141 | pos += offset * vec3(life * 0.7 + 0.3, life * 0.9 + 0.1, life * 0.7 + 0.3); 142 | 143 | gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);; 144 | } 145 | `; 146 | 147 | var hazeFragmentShader = ` 148 | uniform sampler2D uMap; 149 | uniform sampler2D uMask; 150 | uniform vec2 uResolution; 151 | 152 | varying float vAlpha; 153 | varying vec2 vUv; 154 | 155 | void main() { 156 | vec2 uv = gl_FragCoord.xy / uResolution; 157 | vec2 mask = texture2D(uMask, vUv).ra - vec2(0.5); 158 | uv -= mask * 0.1; 159 | vec4 tex = texture2D(uMap, uv); 160 | 161 | gl_FragColor.rgb = tex.rgb; 162 | gl_FragColor.a = vAlpha * 0.5; 163 | } 164 | `; 165 | 166 | (function() { 167 | var _renderer, _scene, _camera, _controls, _rtt; 168 | var _width, _height; 169 | 170 | window.onload = init; 171 | 172 | function init() { 173 | initWorld(); 174 | initScene(); 175 | } 176 | 177 | //=====// Utils //========================================// 178 | 179 | function random(min, max, precision) { 180 | var p = Math.pow(10, precision); 181 | return Math.round((min + Math.random() * (max - min)) * p) / p; 182 | } 183 | 184 | //=====// World //========================================// 185 | 186 | function initWorld() { 187 | _renderer = new THREE.WebGLRenderer(); 188 | _renderer.setPixelRatio(2); 189 | _renderer.setSize(window.innerWidth, window.innerHeight); 190 | _renderer.setClearColor(0x000000); 191 | document.body.appendChild(_renderer.domElement); 192 | 193 | _scene = new THREE.Scene(); 194 | 195 | _camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); 196 | _camera.position.set(0, 0, 4); 197 | _camera.target = new THREE.Vector3(); 198 | _camera.lookAt(_camera.target); 199 | 200 | _controls = new THREE.OrbitControls(_camera); 201 | _controls.enableDamping = true; 202 | _controls.dampingFactor = 0.1; 203 | _controls.rotateSpeed = 0.1; 204 | 205 | window.addEventListener('resize', resize, false); 206 | resize(); 207 | requestAnimationFrame(render); 208 | } 209 | 210 | function resize() { 211 | _width = window.innerWidth; 212 | _height = window.innerHeight; 213 | _renderer.setSize(_width, _height); 214 | _camera.aspect = _width / _height; 215 | _camera.updateProjectionMatrix(); 216 | resetRT(); 217 | } 218 | 219 | function resetRT() { 220 | var _parameters = {minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, stencilBuffer: false}; 221 | if (_rtt) _rtt.dispose(); 222 | _rtt = new THREE.WebGLRenderTarget(_width * 0.5, _height * 0.5, _parameters); 223 | } 224 | 225 | function render() { 226 | requestAnimationFrame(render); 227 | if (_controls) _controls.update(); 228 | _renderer.render(_scene, _camera); 229 | } 230 | 231 | //=====// Scene //========================================// 232 | 233 | function initScene() { 234 | initBackground(); 235 | initFire(); 236 | initEmbers(); 237 | initHaze(); 238 | } 239 | 240 | function initBackground() { 241 | var background = new THREE.Mesh( 242 | new THREE.BoxBufferGeometry(4, 4, 4), 243 | new THREE.MeshBasicMaterial({side: THREE.BackSide}) 244 | ); 245 | _scene.add(background); 246 | 247 | var textureLoader = new THREE.TextureLoader(); 248 | textureLoader.load('https://cinemont.com/tutorials/zelda/rock.jpg', t => {background.material.map = t, background.material.needsUpdate = true}); 249 | } 250 | 251 | //=====// Fire //========================================// 252 | 253 | function initFire() { 254 | var _geometry, _shader, _mesh, _group; 255 | var _num = 8; 256 | 257 | (function() { 258 | initGeometry(); 259 | initInstances(); 260 | initShader(); 261 | initMesh(); 262 | requestAnimationFrame(loop); 263 | })(); 264 | 265 | function initGeometry() { 266 | _geometry = new THREE.InstancedBufferGeometry(); 267 | _geometry.maxInstancedCount = _num; 268 | 269 | var shape = new THREE.PlaneBufferGeometry(1, 1); 270 | shape.translate(0, 0.4, 0); 271 | var data = shape.attributes; 272 | 273 | _geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(data.position.array), 3)); 274 | _geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(data.uv.array), 2)); 275 | _geometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(data.normal.array), 3)); 276 | _geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(shape.index.array), 1)); 277 | shape.dispose(); 278 | } 279 | 280 | function initInstances() { 281 | var orientation = new THREE.InstancedBufferAttribute(new Float32Array(_num * 4), 4); 282 | var randoms = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1); 283 | var scale = new THREE.InstancedBufferAttribute(new Float32Array(_num * 2), 2); 284 | var life = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1); 285 | 286 | for (let i = 0; i < _num; i++) { 287 | orientation.setXYZW(i, 0, 0, 0, 1); 288 | life.setX(i, i / _num + 1); 289 | } 290 | 291 | _geometry.addAttribute('orientation', orientation); 292 | _geometry.addAttribute('scale', scale); 293 | _geometry.addAttribute('life', life); 294 | _geometry.addAttribute('random', randoms); 295 | } 296 | 297 | function initShader() { 298 | var uniforms = { 299 | uMap: {type: 't', value: null}, 300 | uColor1: {type: 'c', value: new THREE.Color(0x961800)}, // red 301 | uColor2: {type: 'c', value: new THREE.Color(0x4b5828)}, // yellow 302 | uTime: {type: 'f', value: 0}, 303 | }; 304 | 305 | _shader = new THREE.ShaderMaterial({ 306 | uniforms: uniforms, 307 | vertexShader: fireVertexShader, 308 | fragmentShader: fireFragmentShader, 309 | blending: THREE.AdditiveBlending, 310 | transparent: true, 311 | depthWrite: false, 312 | }); 313 | 314 | var textureLoader = new THREE.TextureLoader(); 315 | textureLoader.load('https://cinemont.com/tutorials/zelda/flame.png', t => _shader.uniforms.uMap.value = t); 316 | } 317 | 318 | function initMesh() { 319 | _group = new THREE.Group(); 320 | _mesh = new THREE.Mesh(_geometry, _shader); 321 | _mesh.frustumCulled = false; 322 | _group.add(_mesh); 323 | _scene.add(_group); 324 | } 325 | 326 | function loop(e) { 327 | requestAnimationFrame(loop); 328 | _shader.uniforms.uTime.value = e * 0.001; 329 | 330 | _group.quaternion.copy(_camera.quaternion); 331 | 332 | var life = _geometry.attributes.life; 333 | var scale = _geometry.attributes.scale; 334 | var randoms = _geometry.attributes.random; 335 | 336 | for (let i = 0; i < _num; i++) { 337 | var value = life.array[i]; 338 | value += 0.025; 339 | 340 | if (value > 1) { 341 | value -= 1; 342 | 343 | scale.setXY(i, random(0.8, 1.2, 3), random(0.8, 1.2, 3)); 344 | randoms.setX(i, random(0, 1, 3)); 345 | } 346 | 347 | life.setX(i, value); 348 | } 349 | life.needsUpdate = true; 350 | scale.needsUpdate = true; 351 | randoms.needsUpdate = true; 352 | } 353 | } 354 | 355 | //=====// Embers //========================================// 356 | 357 | function initEmbers() { 358 | var _geometry, _shader, _points; 359 | var _num = 8; 360 | 361 | (function() { 362 | initGeometry(); 363 | initShader(); 364 | initMesh(); 365 | requestAnimationFrame(loop); 366 | })(); 367 | 368 | function initGeometry() { 369 | _geometry = new THREE.BufferGeometry(); 370 | _geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(_num * 3), 3)); 371 | _geometry.addAttribute('offset', new THREE.BufferAttribute(new Float32Array(_num * 3), 3)); 372 | _geometry.addAttribute('size', new THREE.BufferAttribute(new Float32Array(_num), 1)); 373 | _geometry.addAttribute('life', new THREE.BufferAttribute(new Float32Array(_num), 1)); 374 | 375 | for (let i = 0; i < _num; i++) { 376 | _geometry.attributes.life.setX(i, random(0, 1, 3) + 1); 377 | } 378 | } 379 | 380 | function initShader() { 381 | var uniforms = { 382 | uMap: {type: 't', value: null}, 383 | uColor: {type: 'c', value: new THREE.Color(0xffe61e)}, 384 | }; 385 | 386 | _shader = new THREE.ShaderMaterial({ 387 | uniforms: uniforms, 388 | vertexShader: embersVertexShader, 389 | fragmentShader: embersFragmentShader, 390 | blending: THREE.AdditiveBlending, 391 | transparent: true, 392 | depthTest: false, 393 | }); 394 | 395 | var textureLoader = new THREE.TextureLoader(); 396 | textureLoader.load('https://cinemont.com/tutorials/zelda/ember.png', t => _shader.uniforms.uMap.value = t); 397 | } 398 | 399 | function initMesh() { 400 | _points = new THREE.Points(_geometry, _shader); 401 | _points.frustumCulled = false; 402 | _scene.add(_points); 403 | } 404 | 405 | function loop() { 406 | requestAnimationFrame(loop); 407 | var life = _geometry.attributes.life; 408 | var position = _geometry.attributes.position; 409 | var size = _geometry.attributes.size; 410 | var offset = _geometry.attributes.offset; 411 | for (let i = 0; i < _num; i++) { 412 | var value = life.array[i]; 413 | value += 0.02; 414 | 415 | if (value > 1) { 416 | value -= 1; 417 | 418 | offset.setXYZ(i, 419 | random(-0.2, 0.2, 3), 420 | random(0.7, 1.2, 3), 421 | random(-0.2, 0.2, 3) 422 | ); 423 | size.setX(i, random(0.2, 0.5, 3)); 424 | } 425 | 426 | life.setX(i, value); 427 | } 428 | 429 | life.needsUpdate = true; 430 | position.needsUpdate = true; 431 | size.needsUpdate = true; 432 | offset.needsUpdate = true; 433 | } 434 | } 435 | 436 | //=====// Haze //========================================// 437 | 438 | function initHaze() { 439 | var _geometry, _shader, _mesh; 440 | 441 | var _num = 4; 442 | 443 | var _z = new THREE.Vector3(0, 0, 1); 444 | var _quat = new THREE.Quaternion(); 445 | var _quat2 = new THREE.Quaternion(); 446 | 447 | (function() { 448 | initGeometry(); 449 | initInstances(); 450 | initShader(); 451 | initMesh(); 452 | window.addEventListener('resize', resizeHaze, false); 453 | resizeHaze(); 454 | requestAnimationFrame(loop); 455 | })(); 456 | 457 | function initGeometry() { 458 | _geometry = new THREE.InstancedBufferGeometry(); 459 | _geometry.maxInstancedCount = _num; 460 | 461 | var shape = new THREE.PlaneBufferGeometry(2, 2); 462 | var data = shape.attributes; 463 | 464 | _geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(data.position.array), 3)); 465 | _geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(data.uv.array), 2)); 466 | _geometry.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(data.normal.array), 3)); 467 | _geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(shape.index.array), 1)); 468 | shape.dispose(); 469 | } 470 | 471 | function initInstances() { 472 | var base = new THREE.InstancedBufferAttribute(new Float32Array(_num * 3), 3); 473 | var offset = new THREE.InstancedBufferAttribute(new Float32Array(_num * 3), 3); 474 | var orientation = new THREE.InstancedBufferAttribute(new Float32Array(_num * 4), 4); 475 | var scale = new THREE.InstancedBufferAttribute(new Float32Array(_num * 2), 2); 476 | var rotation = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1); 477 | var life = new THREE.InstancedBufferAttribute(new Float32Array(_num), 1); 478 | 479 | for (let i = 0; i < _num; i++) { 480 | orientation.setXYZW(i, 0, 0, 0, 1); 481 | life.setX(i, i / _num + 1); 482 | } 483 | 484 | _geometry.addAttribute('base', base); 485 | _geometry.addAttribute('offset', offset); 486 | _geometry.addAttribute('orientation', orientation); 487 | _geometry.addAttribute('scale', scale); 488 | _geometry.addAttribute('rotation', rotation); 489 | _geometry.addAttribute('life', life); 490 | } 491 | 492 | function initShader() { 493 | let dpr = _renderer.getPixelRatio(); 494 | var uniforms = { 495 | uMap: {type: 't', value: null}, 496 | uMask: {type: 't', value: null}, 497 | uResolution: {type: 'v2', value: new THREE.Vector2(_width * dpr, _height * dpr)}, 498 | }; 499 | 500 | _shader = new THREE.ShaderMaterial({ 501 | uniforms: uniforms, 502 | vertexShader: hazeVertexShader, 503 | fragmentShader: hazeFragmentShader, 504 | transparent: true, 505 | depthTest: false, 506 | }); 507 | 508 | var textureLoader = new THREE.TextureLoader(); 509 | textureLoader.load('https://cinemont.com/tutorials/zelda/haze.png', t => _shader.uniforms.uMask.value = t); 510 | } 511 | 512 | function initMesh() { 513 | _mesh = new THREE.Mesh(_geometry, _shader); 514 | _mesh.frustumCulled = false; 515 | _scene.add(_mesh); 516 | } 517 | 518 | function resizeHaze() { 519 | let dpr = _renderer.getPixelRatio(); 520 | _shader.uniforms.uMap.value = _rtt.texture; 521 | _shader.uniforms.uResolution.value.set(_width * dpr, _height * dpr); 522 | } 523 | 524 | function loop(e) { 525 | requestAnimationFrame(loop); 526 | 527 | _mesh.visible = false; 528 | _renderer.render(_scene, _camera, _rtt); 529 | _mesh.visible = true; 530 | 531 | var life = _geometry.attributes.life; 532 | var base = _geometry.attributes.base; 533 | var offset = _geometry.attributes.offset; 534 | var scale = _geometry.attributes.scale; 535 | var orientation = _geometry.attributes.orientation; 536 | var rotation = _geometry.attributes.rotation; 537 | for (let i = 0; i < _num; i++) { 538 | var value = life.array[i]; 539 | value += 0.008; 540 | 541 | if (value > 1) { 542 | value -= 1; 543 | 544 | rotation.setX(i, random(0, 3.14, 3)); 545 | 546 | offset.setXYZ(i, 547 | random(-0.2, 0.2, 3), 548 | random(2.5, 3.0, 3), 549 | 0 550 | ); 551 | scale.setXY(i, random(0.6, 1.2, 3), random(0.6, 1.2, 3)); 552 | } 553 | 554 | _quat.copy(_camera.quaternion); 555 | _quat2.setFromAxisAngle(_z, rotation.array[i]); 556 | _quat.multiply(_quat2); 557 | orientation.setXYZW(i, _quat.x, _quat.y, _quat.z, _quat.w); 558 | 559 | life.setX(i, value); 560 | } 561 | 562 | life.needsUpdate = true; 563 | base.needsUpdate = true; 564 | scale.needsUpdate = true; 565 | offset.needsUpdate = true; 566 | orientation.needsUpdate = true; 567 | } 568 | } 569 | 570 | })(); -------------------------------------------------------------------------------- /wind-waker/water/assets/clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/wind-waker/water/assets/clouds.png -------------------------------------------------------------------------------- /wind-waker/water/assets/island.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/wind-waker/water/assets/island.glb -------------------------------------------------------------------------------- /wind-waker/water/assets/king-of-red-lions.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/wind-waker/water/assets/king-of-red-lions.glb -------------------------------------------------------------------------------- /wind-waker/water/assets/message.html: -------------------------------------------------------------------------------- 1 |

Based on Watter effect blog post by Ada Rose Cannon and Wind Waker Ocean breakdown by Nathan Gordon

2 | 3 |

Clouds backdrop created in Photoshop following Howard Pinsky tutorial

4 | 5 |

6 | Island model and King of the Red Lions model 7 |

-------------------------------------------------------------------------------- /wind-waker/water/assets/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmarcos/aframe-fx/1cd366a6a1f59c58434c7452781e2e793ddd2a0c/wind-waker/water/assets/water.png -------------------------------------------------------------------------------- /wind-waker/water/components/tile.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('tile', { 2 | vertexShader:` 3 | varying vec2 vUv; 4 | void main() { 5 | vUv = uv; 6 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 7 | } 8 | `, 9 | 10 | fragmentShader:` 11 | uniform vec3 uColor; 12 | uniform sampler2D uMap; 13 | varying vec2 vUv; 14 | 15 | void main() { 16 | vec4 tex1 = texture2D(uMap, vUv * 1.0); 17 | vec4 tex2 = texture2D(uMap, vUv * 1.0 + vec2(0.2)); 18 | 19 | gl_FragColor = vec4(uColor + vec3(tex1.a * 0.9 - tex2.a * 0.02), 1.0); 20 | }`, 21 | 22 | init: function () { 23 | var geometry = this.geometry = new THREE.PlaneBufferGeometry(1, 1, 1, 1); 24 | geometry.rotateX(-Math.PI / 2); 25 | this.initShader(); 26 | this.mesh = new THREE.Mesh(geometry, this.shader); 27 | this.el.setObject3D('mesh', this.mesh); 28 | this.el.object3D.rotation.set(Math.PI / 2, 0, 0); 29 | }, 30 | 31 | initShader: function () { 32 | var uniforms = { 33 | uColor: {type: 'f', value: new THREE.Color('#0051da')}, 34 | uMap: {type: 't', value: null} 35 | }; 36 | 37 | var shader = this.shader = new THREE.ShaderMaterial({ 38 | uniforms: uniforms, 39 | vertexShader: this.vertexShader, 40 | fragmentShader: this.fragmentShader, 41 | side: THREE.DoubleSide, 42 | }); 43 | 44 | var textureLoader = new THREE.TextureLoader(); 45 | textureLoader.load('assets/water.png', function (texture) { 46 | shader.uniforms.uMap.value = texture; 47 | texture.wrapS = texture.wrapT = THREE.REPEAT_WRAPPING; 48 | }); 49 | } 50 | }); -------------------------------------------------------------------------------- /wind-waker/water/components/tiles-animation.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('tiles-animation', { 2 | init: function () { 3 | this.topTileEl = document.querySelector('.tile.top'); 4 | this.bottomTileEl = document.querySelector('.tile.bottom'); 5 | this.leftTileEl = document.querySelector('.tile.left'); 6 | this.rightTileEl = document.querySelector('.tile.right'); 7 | }, 8 | 9 | tick: function (time) { 10 | var seconds = Math.floor(time / 500) % 5; 11 | 12 | this.topTileEl.object3D.visible = seconds !== 0; 13 | this.rightTileEl.object3D.visible = seconds !== 0 && seconds !== 1; 14 | this.bottomTileEl.object3D.visible = seconds !== 0 && seconds !== 1 && seconds !== 2; 15 | this.leftTileEl.object3D.visible = seconds === 4; 16 | }, 17 | }); -------------------------------------------------------------------------------- /wind-waker/water/components/vignette-background.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('vignette-background', { 2 | vertexShader:` 3 | attribute vec3 position; 4 | uniform mat4 modelViewMatrix; 5 | uniform mat4 projectionMatrix; 6 | varying vec2 vUv; 7 | void main() { 8 | gl_Position = vec4(position, 1.0); 9 | vUv = vec2(position.x, position.y) * 0.5 + 0.5; 10 | } 11 | `, 12 | 13 | fragmentShader:` 14 | precision mediump float; 15 | 16 | uniform vec3 color1; 17 | uniform vec3 color2; 18 | uniform float aspect; 19 | uniform vec2 offset; 20 | uniform vec2 scale; 21 | uniform float noiseAlpha; 22 | uniform bool aspectCorrection; 23 | uniform float grainScale; 24 | uniform float grainTime; 25 | uniform vec2 smooth; 26 | 27 | vec3 blend(vec3 base, vec3 blend) { 28 | return mix( 29 | sqrt(base) * (2.0 * blend - 1.0) + 2.0 * base * (1.0 - blend), 30 | 2.0 * base * blend + base * base * (1.0 - 2.0 * blend), 31 | step(base, vec3(0.5)) 32 | ); 33 | } 34 | 35 | vec3 mod289(vec3 x) 36 | { 37 | return x - floor(x * (1.0 / 289.0)) * 289.0; 38 | } 39 | 40 | vec4 mod289(vec4 x) 41 | { 42 | return x - floor(x * (1.0 / 289.0)) * 289.0; 43 | } 44 | 45 | vec4 permute(vec4 x) 46 | { 47 | return mod289(((x*34.0)+1.0)*x); 48 | } 49 | 50 | vec4 taylorInvSqrt(vec4 r) 51 | { 52 | return 1.79284291400159 - 0.85373472095314 * r; 53 | } 54 | 55 | vec3 fade(vec3 t) { 56 | return t*t*t*(t*(t*6.0-15.0)+10.0); 57 | } 58 | 59 | float snoise(vec3 v) 60 | { 61 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 62 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 63 | 64 | // First corner 65 | vec3 i = floor(v + dot(v, C.yyy) ); 66 | vec3 x0 = v - i + dot(i, C.xxx) ; 67 | 68 | // Other corners 69 | vec3 g = step(x0.yzx, x0.xyz); 70 | vec3 l = 1.0 - g; 71 | vec3 i1 = min( g.xyz, l.zxy ); 72 | vec3 i2 = max( g.xyz, l.zxy ); 73 | 74 | // x0 = x0 - 0.0 + 0.0 * C.xxx; 75 | // x1 = x0 - i1 + 1.0 * C.xxx; 76 | // x2 = x0 - i2 + 2.0 * C.xxx; 77 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 78 | vec3 x1 = x0 - i1 + C.xxx; 79 | vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y 80 | vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y 81 | 82 | // Permutations 83 | i = mod289(i); 84 | vec4 p = permute( permute( permute( 85 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 86 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 87 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 88 | 89 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 90 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 91 | float n_ = 0.142857142857; // 1.0/7.0 92 | vec3 ns = n_ * D.wyz - D.xzx; 93 | 94 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) 95 | 96 | vec4 x_ = floor(j * ns.z); 97 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 98 | 99 | vec4 x = x_ *ns.x + ns.yyyy; 100 | vec4 y = y_ *ns.x + ns.yyyy; 101 | vec4 h = 1.0 - abs(x) - abs(y); 102 | 103 | vec4 b0 = vec4( x.xy, y.xy ); 104 | vec4 b1 = vec4( x.zw, y.zw ); 105 | 106 | //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; 107 | //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; 108 | vec4 s0 = floor(b0)*2.0 + 1.0; 109 | vec4 s1 = floor(b1)*2.0 + 1.0; 110 | vec4 sh = -step(h, vec4(0.0)); 111 | 112 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 113 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 114 | 115 | vec3 p0 = vec3(a0.xy,h.x); 116 | vec3 p1 = vec3(a0.zw,h.y); 117 | vec3 p2 = vec3(a1.xy,h.z); 118 | vec3 p3 = vec3(a1.zw,h.w); 119 | 120 | //Normalise gradients 121 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 122 | p0 *= norm.x; 123 | p1 *= norm.y; 124 | p2 *= norm.z; 125 | p3 *= norm.w; 126 | 127 | // Mix final noise value 128 | vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 129 | m = m * m; 130 | return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 131 | dot(p2,x2), dot(p3,x3) ) ); 132 | } 133 | 134 | // Classic Perlin noise, periodic variant 135 | float pnoise(vec3 P, vec3 rep) 136 | { 137 | vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period 138 | vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period 139 | Pi0 = mod289(Pi0); 140 | Pi1 = mod289(Pi1); 141 | vec3 Pf0 = fract(P); // Fractional part for interpolation 142 | vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0 143 | vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x); 144 | vec4 iy = vec4(Pi0.yy, Pi1.yy); 145 | vec4 iz0 = Pi0.zzzz; 146 | vec4 iz1 = Pi1.zzzz; 147 | 148 | vec4 ixy = permute(permute(ix) + iy); 149 | vec4 ixy0 = permute(ixy + iz0); 150 | vec4 ixy1 = permute(ixy + iz1); 151 | 152 | vec4 gx0 = ixy0 * (1.0 / 7.0); 153 | vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5; 154 | gx0 = fract(gx0); 155 | vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0); 156 | vec4 sz0 = step(gz0, vec4(0.0)); 157 | gx0 -= sz0 * (step(0.0, gx0) - 0.5); 158 | gy0 -= sz0 * (step(0.0, gy0) - 0.5); 159 | 160 | vec4 gx1 = ixy1 * (1.0 / 7.0); 161 | vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5; 162 | gx1 = fract(gx1); 163 | vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1); 164 | vec4 sz1 = step(gz1, vec4(0.0)); 165 | gx1 -= sz1 * (step(0.0, gx1) - 0.5); 166 | gy1 -= sz1 * (step(0.0, gy1) - 0.5); 167 | 168 | vec3 g000 = vec3(gx0.x,gy0.x,gz0.x); 169 | vec3 g100 = vec3(gx0.y,gy0.y,gz0.y); 170 | vec3 g010 = vec3(gx0.z,gy0.z,gz0.z); 171 | vec3 g110 = vec3(gx0.w,gy0.w,gz0.w); 172 | vec3 g001 = vec3(gx1.x,gy1.x,gz1.x); 173 | vec3 g101 = vec3(gx1.y,gy1.y,gz1.y); 174 | vec3 g011 = vec3(gx1.z,gy1.z,gz1.z); 175 | vec3 g111 = vec3(gx1.w,gy1.w,gz1.w); 176 | 177 | vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110))); 178 | g000 *= norm0.x; 179 | g010 *= norm0.y; 180 | g100 *= norm0.z; 181 | g110 *= norm0.w; 182 | vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111))); 183 | g001 *= norm1.x; 184 | g011 *= norm1.y; 185 | g101 *= norm1.z; 186 | g111 *= norm1.w; 187 | 188 | float n000 = dot(g000, Pf0); 189 | float n100 = dot(g100, vec3(Pf1.x, Pf0.yz)); 190 | float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z)); 191 | float n110 = dot(g110, vec3(Pf1.xy, Pf0.z)); 192 | float n001 = dot(g001, vec3(Pf0.xy, Pf1.z)); 193 | float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z)); 194 | float n011 = dot(g011, vec3(Pf0.x, Pf1.yz)); 195 | float n111 = dot(g111, Pf1); 196 | 197 | vec3 fade_xyz = fade(Pf0); 198 | vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z); 199 | vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y); 200 | float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 201 | return 2.2 * n_xyz; 202 | } 203 | 204 | float grain(vec2 texCoord, vec2 resolution, float frame, float multiplier) { 205 | vec2 mult = texCoord * resolution; 206 | float offset = snoise(vec3(mult / multiplier, frame)); 207 | float n1 = pnoise(vec3(mult, offset), vec3(1.0/texCoord * resolution, 1.0)); 208 | return n1 / 2.0 + 0.5; 209 | } 210 | 211 | float grain(vec2 texCoord, vec2 resolution, float frame) { 212 | return grain(texCoord, resolution, frame, 2.5); 213 | } 214 | 215 | float grain(vec2 texCoord, vec2 resolution) { 216 | return grain(texCoord, resolution, 0.0); 217 | } 218 | 219 | varying vec2 vUv; 220 | 221 | void main() { 222 | vec2 q = vec2(vUv - 0.5); 223 | if (aspectCorrection) { 224 | q.x *= aspect; 225 | } 226 | q /= scale; 227 | q -= offset; 228 | float dst = length(q); 229 | dst = smoothstep(smooth.x, smooth.y, dst); 230 | vec3 color = mix(color1, color2, dst); 231 | 232 | if (noiseAlpha > 0.0 && grainScale > 0.0) { 233 | float gSize = 1.0 / grainScale; 234 | float g = grain(vUv, vec2(gSize * aspect, gSize), grainTime); 235 | vec3 noiseColor = blend(color, vec3(g)); 236 | gl_FragColor.rgb = mix(color, noiseColor, noiseAlpha); 237 | } else { 238 | gl_FragColor.rgb = color; 239 | } 240 | gl_FragColor.a = 1.0; 241 | } 242 | `, 243 | 244 | init: function () { 245 | var geometry = this.geometry = new THREE.PlaneGeometry(2, 2, 1); 246 | var material = new THREE.RawShaderMaterial({ 247 | vertexShader: this.vertexShader, 248 | fragmentShader: this.fragmentShader, 249 | side: THREE.DoubleSide, 250 | uniforms: { 251 | aspectCorrection: { type: 'i', value: false }, 252 | aspect: { type: 'f', value: 1 }, 253 | grainScale: { type: 'f', value: 0.000000005 }, 254 | grainTime: { type: 'f', value: 0.2 }, 255 | noiseAlpha: { type: 'f', value: 0.25 }, 256 | offset: { type: 'v2', value: new THREE.Vector2(0, 0) }, 257 | scale: { type: 'v2', value: new THREE.Vector2(1.5, 1.5) }, 258 | smooth: { type: 'v2', value: new THREE.Vector2(0.0, 1.0) }, 259 | color1: { type: 'c', value: new THREE.Color('#fff') }, 260 | color2: { type: 'c', value: new THREE.Color('#283844') } 261 | }, 262 | depthTest: false 263 | }); 264 | 265 | this.mesh = new THREE.Mesh(geometry, material) 266 | //this.el.setObject3D('mesh', this.mesh); 267 | } 268 | }); -------------------------------------------------------------------------------- /wind-waker/water/components/water-v1.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('water-v1', { 2 | vertexShader:` 3 | varying vec2 vUv; 4 | 5 | attribute vec3 barycentric; 6 | varying vec3 vDistanceBarycenter; 7 | 8 | void main() { 9 | vUv = uv; 10 | vDistanceBarycenter = barycentric; 11 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 12 | } 13 | `, 14 | 15 | fragmentShader:` 16 | #if __VERSION__ == 100 17 | #extension GL_OES_standard_derivatives : enable 18 | #endif 19 | 20 | uniform vec3 uColor; 21 | uniform sampler2D uMap; 22 | uniform float uTime; 23 | 24 | varying vec2 vUv; 25 | varying vec3 vDistanceBarycenter; 26 | 27 | // This is like 28 | float aastep (float threshold, float dist) { 29 | float afwidth = fwidth(dist) * 0.5; 30 | return smoothstep(threshold - afwidth, threshold + afwidth, dist); 31 | } 32 | 33 | void main() { 34 | vec3 blue = uColor; 35 | float thickness = 0.015; 36 | float time = uTime / 50.0; 37 | 38 | vec2 uv = vUv * 10.0; 39 | float tileIndex = floor(10.0 - uv.y) * 10.0 + floor(uv.x); 40 | float currentTile = mod(time, 100.0); 41 | 42 | vec4 tex1 = texture2D(uMap, uv * 1.0); 43 | vec4 tex2 = texture2D(uMap, uv * 1.0 + vec2(0.2)); 44 | 45 | // this will be our signed distance for the wireframe edge 46 | float d = min(min(vDistanceBarycenter.x, vDistanceBarycenter.y), vDistanceBarycenter.z); 47 | // compute the anti-aliased stroke edge 48 | float edge = 1.0 - aastep(thickness, d); 49 | // now compute the final color of the mesh 50 | vec4 lineColor = vec4(0.1, 0.1, 0.1, 1.0); 51 | vec4 fillColor = vec4(1.0, 1.0, 1.0, 1.0); 52 | if (tileIndex > currentTile) { 53 | gl_FragColor = vec4(mix(fillColor, lineColor, edge)); 54 | } else { 55 | gl_FragColor = vec4(blue + vec3(tex1.a * 0.9 - tex2.a * 0.02), 1.0); 56 | } 57 | }`, 58 | 59 | init: function () { 60 | var unindexBufferGeometry = this.el.sceneEl.systems.wireframe.unindexBufferGeometry; 61 | var geometry = this.geometry = new THREE.PlaneBufferGeometry(1, 1, 10, 10); 62 | geometry.rotateX(-Math.PI / 2); 63 | unindexBufferGeometry(geometry); 64 | this.initShader(); 65 | this.mesh = new THREE.Mesh(geometry, this.shader); 66 | this.el.setObject3D('mesh', this.mesh) 67 | }, 68 | 69 | play: function () { 70 | this.startTime = undefined; 71 | }, 72 | 73 | initShader: function () { 74 | var calculateBarycenters = this.el.sceneEl.systems.wireframe.calculateBarycenters; 75 | var baryCenters = calculateBarycenters(this.geometry); 76 | var uniforms = { 77 | uMap: {type: 't', value: null}, 78 | uTime: {type: 'f', value: 0}, 79 | uColor: {type: 'f', value: new THREE.Color('#0051da') 80 | }}; 81 | 82 | var shader = this.shader = new THREE.ShaderMaterial({ 83 | uniforms: uniforms, 84 | vertexShader: this.vertexShader, 85 | fragmentShader: this.fragmentShader, 86 | side: THREE.DoubleSide, 87 | transparent: true 88 | }); 89 | 90 | var textureLoader = new THREE.TextureLoader(); 91 | textureLoader.load('assets/water.png', function (texture) { 92 | shader.uniforms.uMap.value = texture; 93 | texture.wrapS = texture.wrapT = THREE.REPEAT_WRAPPING; 94 | }); 95 | }, 96 | 97 | tick: function (time) { 98 | var startTime = this.startTime = this.startTime || time; 99 | this.shader.uniforms.uTime.value = time - startTime; 100 | } 101 | }); -------------------------------------------------------------------------------- /wind-waker/water/components/water-v2.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('water-v2', { 2 | vertexShader:` 3 | #define SCALE 10.0 4 | 5 | varying vec2 vUv; 6 | 7 | attribute vec3 barycentric; 8 | varying vec3 vDistanceBarycenter; 9 | 10 | void main() { 11 | vUv = uv; 12 | vDistanceBarycenter = barycentric; 13 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 14 | } 15 | `, 16 | 17 | fragmentShader:` 18 | #if __VERSION__ == 100 19 | #extension GL_OES_standard_derivatives : enable 20 | #endif 21 | 22 | uniform vec3 uColor; 23 | uniform sampler2D uMap; 24 | uniform float uTime; 25 | 26 | varying vec2 vUv; 27 | varying vec3 vDistanceBarycenter; 28 | 29 | // This is like 30 | float aastep (float threshold, float dist) { 31 | float afwidth = fwidth(dist) * 0.5; 32 | return smoothstep(threshold - afwidth, threshold + afwidth, dist); 33 | } 34 | 35 | void main() { 36 | vec3 blue = uColor; 37 | float thickness = 0.015; 38 | float time = uTime / 1000.0; 39 | 40 | vec2 uv = vUv * 10.0; 41 | 42 | uv = vUv * 10.0 + vec2(time * -0.05); 43 | 44 | uv.y += 0.01 * (sin(uv.x * 3.5 + time * 0.35) + sin(uv.x * 4.8 + time * 1.05) + sin(uv.x * 7.3 + time * 0.45)) / 3.0; 45 | uv.x += 0.12 * (sin(uv.y * 4.0 + time * 0.5) + sin(uv.y * 6.8 + time * 0.75) + sin(uv.y * 11.3 + time * 0.2)) / 3.0; 46 | uv.y += 0.12 * (sin(uv.x * 4.2 + time * 0.64) + sin(uv.x * 6.3 + time * 1.65) + sin(uv.x * 8.2 + time * 0.45)) / 3.0; 47 | 48 | vec4 tex1 = texture2D(uMap, uv * 1.0); 49 | vec4 tex2 = texture2D(uMap, uv * 1.0 + vec2(0.2)); 50 | 51 | gl_FragColor = vec4(blue + vec3(tex1.a * 0.9 - tex2.a * 0.02), 1.0); 52 | }`, 53 | 54 | init: function () { 55 | var unindexBufferGeometry = this.el.sceneEl.systems.wireframe.unindexBufferGeometry; 56 | var geometry = this.geometry = new THREE.PlaneBufferGeometry(1, 1, 10, 10); 57 | geometry.rotateX(-Math.PI / 2); 58 | unindexBufferGeometry(geometry); 59 | this.initShader(); 60 | this.mesh = new THREE.Mesh(geometry, this.shader); 61 | this.el.setObject3D('mesh', this.mesh) 62 | }, 63 | 64 | play: function () { 65 | this.startTime = undefined; 66 | }, 67 | 68 | initShader: function () { 69 | var calculateBarycenters = this.el.sceneEl.systems.wireframe.calculateBarycenters; 70 | var baryCenters = calculateBarycenters(this.geometry); 71 | var uniforms = { 72 | uMap: {type: 't', value: null}, 73 | uTime: {type: 'f', value: 0}, 74 | uColor: {type: 'f', value: new THREE.Color('#0051da') 75 | }}; 76 | 77 | var shader = this.shader = new THREE.ShaderMaterial({ 78 | uniforms: uniforms, 79 | vertexShader: this.vertexShader, 80 | fragmentShader: this.fragmentShader, 81 | side: THREE.DoubleSide, 82 | transparent: true 83 | }); 84 | 85 | var textureLoader = new THREE.TextureLoader(); 86 | textureLoader.load('assets/water.png', function (texture) { 87 | shader.uniforms.uMap.value = texture; 88 | texture.wrapS = texture.wrapT = THREE.REPEAT_WRAPPING; 89 | }); 90 | }, 91 | 92 | tick: function (time) { 93 | var startTime = this.startTime = this.startTime || time; 94 | this.shader.uniforms.uTime.value = time - startTime; 95 | } 96 | }); -------------------------------------------------------------------------------- /wind-waker/water/components/water-v3.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('water-v3', { 2 | vertexShader:` 3 | #define SCALE 0.2 4 | 5 | attribute vec3 barycentric; 6 | varying vec3 vDistanceBarycenter; 7 | 8 | uniform float uTime; 9 | varying vec2 vUv; 10 | 11 | float calculateSurface(float x, float z) { 12 | float time = uTime / 1000.0; 13 | float y = 0.0; 14 | y += (sin(x * 1.0 / SCALE + time * 1.0) + sin(x * 2.3 / SCALE + time * 1.5) + sin(x * 3.3 / SCALE + time * 0.4)) / 150.0; 15 | y += (sin(z * 0.2 / SCALE + time * 1.8) + sin(z * 1.8 / SCALE + time * 1.8) + sin(z * 2.8 / SCALE + time * 0.8)) / 150.0; 16 | 17 | return y; 18 | } 19 | 20 | void main() { 21 | vDistanceBarycenter = barycentric; 22 | vec3 animatedPosition = position; 23 | 24 | float strength = 1.0; 25 | animatedPosition.y += strength * calculateSurface(position.x, position.z); 26 | animatedPosition.y -= strength * calculateSurface(0.0, 0.0); 27 | 28 | vUv = uv; 29 | 30 | gl_Position = projectionMatrix * modelViewMatrix * vec4(animatedPosition, 1.0); 31 | } 32 | `, 33 | 34 | fragmentShader:` 35 | #if __VERSION__ == 100 36 | #extension GL_OES_standard_derivatives : enable 37 | #endif 38 | 39 | uniform sampler2D uMap; 40 | uniform float uTime; 41 | 42 | varying vec2 vUv; 43 | uniform vec3 uColor; 44 | varying vec3 vDistanceBarycenter; 45 | 46 | // This is like 47 | float aastep (float threshold, float dist) { 48 | float afwidth = fwidth(dist) * 0.5; 49 | return smoothstep(threshold - afwidth, threshold + afwidth, dist); 50 | } 51 | 52 | void main() { 53 | vec3 blue = uColor; 54 | float thickness = 0.015; 55 | float time = uTime / 50.0; 56 | float uvTime = uTime / 1000.0; 57 | 58 | vec2 uv = vUv * 10.0; 59 | 60 | uv.y += 0.01 * (sin(uv.x * 3.5 + uvTime * 0.35) + sin(uv.x * 4.8 + uvTime * 1.05) + sin(uv.x * 7.3 + uvTime * 0.45)) / 3.0; 61 | uv.x += 0.12 * (sin(uv.y * 4.0 + uvTime * 0.5) + sin(uv.y * 6.8 + uvTime * 0.75) + sin(uv.y * 11.3 + uvTime * 0.2)) / 3.0; 62 | uv.y += 0.12 * (sin(uv.x * 4.2 + uvTime * 0.64) + sin(uv.x * 6.3 + uvTime * 1.65) + sin(uv.x * 8.2 + uvTime * 0.45)) / 3.0; 63 | 64 | float tileIndex = floor(10.0 - uv.y) * 10.0 + floor(uv.x); 65 | float currentTile = mod(time, 100.0); 66 | 67 | vec4 tex1 = texture2D(uMap, uv * 1.0); 68 | vec4 tex2 = texture2D(uMap, uv * 1.0 + vec2(0.2)); 69 | 70 | // this will be our signed distance for the wireframe edge 71 | float d = min(min(vDistanceBarycenter.x, vDistanceBarycenter.y), vDistanceBarycenter.z); 72 | // compute the anti-aliased stroke edge 73 | float edge = 1.0 - aastep(thickness, d); 74 | // now compute the final color of the mesh 75 | vec4 lineColor = vec4(0.1, 0.1, 0.1, 1.0); 76 | vec4 fillColor = vec4(1.0, 1.0, 1.0, 1.0); 77 | 78 | if (tileIndex > currentTile) { 79 | gl_FragColor = vec4(mix(fillColor, lineColor, edge)); 80 | } else { 81 | gl_FragColor = vec4(blue + vec3(tex1.a * 0.9 - tex2.a * 0.02), 1.0); 82 | } 83 | }`, 84 | 85 | init: function () { 86 | var unindexBufferGeometry = this.el.sceneEl.systems.wireframe.unindexBufferGeometry; 87 | var geometry = this.geometry = new THREE.PlaneBufferGeometry(1, 1, 10, 10); 88 | geometry.rotateX(-Math.PI / 2); 89 | unindexBufferGeometry(geometry); 90 | this.initShader(); 91 | this.mesh = new THREE.Mesh(geometry, this.shader); 92 | this.el.setObject3D('mesh', this.mesh) 93 | }, 94 | 95 | play: function () { 96 | this.startTime = undefined; 97 | }, 98 | 99 | initShader: function () { 100 | var calculateBarycenters = this.el.sceneEl.systems.wireframe.calculateBarycenters; 101 | var baryCenters = calculateBarycenters(this.geometry); 102 | var uniforms = { 103 | uMap: {type: 't', value: null}, 104 | uTime: {type: 'f', value: 0}, 105 | uColor: {type: 'f', value: new THREE.Color('#0051da')} 106 | }; 107 | 108 | var shader = this.shader = new THREE.ShaderMaterial({ 109 | uniforms: uniforms, 110 | vertexShader: this.vertexShader, 111 | fragmentShader: this.fragmentShader, 112 | side: THREE.DoubleSide, 113 | transparent: true 114 | }); 115 | 116 | var textureLoader = new THREE.TextureLoader(); 117 | textureLoader.load('assets/water.png', function (texture) { 118 | shader.uniforms.uMap.value = texture; 119 | texture.wrapS = texture.wrapT = THREE.REPEAT_WRAPPING; 120 | }); 121 | }, 122 | 123 | tick: function (time) { 124 | var startTime = this.startTime = this.startTime || time; 125 | this.shader.uniforms.uTime.value = time - startTime; 126 | } 127 | }); -------------------------------------------------------------------------------- /wind-waker/water/components/water.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('water', { 2 | vertexShader :` 3 | #define SCALE 12.0 4 | 5 | varying vec2 vUv; 6 | uniform float uTime; 7 | 8 | float calculateSurface(float x, float z) { 9 | float time = uTime / 1000.0; 10 | float y = 0.0; 11 | 12 | y += (sin(x * 1.0 / SCALE + time * 1.0) + sin(x * 2.3 / SCALE + time * 1.5) + sin(x * 3.3 / SCALE + time * 0.4)) / 40.0; 13 | y += (sin(z * 0.2 / SCALE + time * 1.8) + sin(z * 1.8 / SCALE + time * 1.8) + sin(z * 2.8 / SCALE + time * 0.8)) / 40.0; 14 | 15 | return y; 16 | } 17 | 18 | void main() { 19 | vUv = uv; 20 | vec3 pos = position; 21 | bool animation = true; 22 | 23 | float strength = 1.0; 24 | if (animation) { 25 | pos.y += strength * calculateSurface(pos.x, pos.z); 26 | pos.y -= strength * calculateSurface(0.0, 0.0); 27 | } 28 | 29 | gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); 30 | }`, 31 | 32 | fragmentShader : ` 33 | varying vec2 vUv; 34 | 35 | uniform sampler2D uMap; 36 | uniform float uTime; 37 | uniform vec3 uColor; 38 | 39 | uniform vec3 fogColor; 40 | uniform float fogNear; 41 | uniform float fogFar; 42 | 43 | void main() { 44 | bool antialias = true; 45 | bool wireframe = false; 46 | bool animation = true; 47 | float time = uTime / 5000.0; 48 | 49 | vec2 uv = vUv * 20.0; 50 | 51 | if (animation) { 52 | uv += vec2(time * -0.05); 53 | uv.y += 0.01 * (sin(uv.x * 3.5 + time * 0.35) + sin(uv.x * 4.8 + time * 1.05) + sin(uv.x * 7.3 + time * 0.45)) / 3.0; 54 | uv.x += 0.12 * (sin(uv.y * 4.0 + time * 0.5) + sin(uv.y * 6.8 + time * 0.75) + sin(uv.y * 11.3 + time * 0.2)) / 3.0; 55 | uv.y += 0.12 * (sin(uv.x * 4.2 + time * 0.64) + sin(uv.x * 6.3 + time * 1.65) + sin(uv.x * 8.2 + time * 0.45)) / 3.0; 56 | } 57 | 58 | vec4 tex1 = texture2D(uMap, uv * 1.0); 59 | vec4 tex2 = texture2D(uMap, uv * 1.0 + vec2(0.2)); 60 | 61 | vec3 blue = uColor; 62 | float thickness = 0.0025; 63 | 64 | float depth = gl_FragCoord.z / gl_FragCoord.w; 65 | float fogFactor = smoothstep(fogNear, fogFar, depth); 66 | 67 | gl_FragColor = vec4(blue + vec3(tex1.a * 0.9 - tex2.a * 0.02), 1.0); 68 | gl_FragColor.rgb = mix(gl_FragColor.rgb, fogColor, fogFactor); 69 | }`, 70 | 71 | init: function () { 72 | var geometry = this.geometry = new THREE.PlaneBufferGeometry(50, 50, 20, 20); 73 | geometry.rotateX(-Math.PI / 2); 74 | this.initShader(); 75 | this.mesh = new THREE.Mesh(geometry, this.shader); 76 | this.el.setObject3D('mesh', this.mesh) 77 | }, 78 | 79 | play: function () { 80 | this.startTime = undefined; 81 | }, 82 | 83 | initShader: function () { 84 | var uniforms = { 85 | uMap: {type: 't', value: null}, 86 | uTime: {type: 'f', value: 0}, 87 | uColor: {type: 'f', value: new THREE.Color('#0065a7')}, 88 | fogColor: { type: "c", value: 'red' }, 89 | fogNear: { type: "f", value: 1 }, 90 | fogFar: { type: "f", value: 100 } 91 | }; 92 | 93 | var shader = this.shader = new THREE.ShaderMaterial({ 94 | uniforms: uniforms, 95 | vertexShader: this.vertexShader, 96 | fragmentShader: this.fragmentShader, 97 | side: THREE.DoubleSide, 98 | transparent: true, 99 | fog: false 100 | }); 101 | 102 | var textureLoader = new THREE.TextureLoader(); 103 | textureLoader.load('assets/water.png', function (texture) { 104 | shader.uniforms.uMap.value = texture; 105 | texture.wrapS = texture.wrapT = THREE.REPEAT_WRAPPING; 106 | }); 107 | }, 108 | 109 | tick: function (time) { 110 | var scene = this.el.sceneEl.object3D; 111 | var target = new THREE.Vector3(0, -5, 0); 112 | var startTime = this.startTime = this.startTime || time; 113 | this.shader.uniforms.uTime.value = time - startTime; 114 | 115 | this.shader.uniforms.fogColor.value = scene.fog.color; 116 | this.shader.uniforms.fogNear.value = scene.fog.near; 117 | this.shader.uniforms.fogFar.value = scene.fog.far; 118 | } 119 | }); -------------------------------------------------------------------------------- /wind-waker/water/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wind Waker Ocean FX • A-Frame 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
36 |

Tilable Texture

37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | 50 | 51 |
52 |

Plane Geometry

53 |
54 | 55 | 56 | 59 | 60 |
61 | 62 | 63 |
64 |

Animate Texture UVs

65 |
66 | 67 | 68 | 71 | 72 |
73 | 74 | 75 |
76 |

Animate Vertices

77 |
78 | 79 | 80 | 81 | 84 | 85 | 86 |
87 | 88 | 89 | 94 | 95 | 97 | 102 | 103 | 108 | 109 | 110 | 111 |
112 | 113 | -------------------------------------------------------------------------------- /wind-waker/wind/aframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wind Waker Wind FX • A-Frame 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 |
21 |

Spiral Geometry

22 |
23 | 24 | 25 | 26 | 27 |
28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /wind-waker/wind/components/wind-v1.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('wind-v1', { 2 | vertexShader:` 3 | varying vec2 vUv; 4 | 5 | attribute vec3 barycentric; 6 | varying vec3 vDistanceBarycenter; 7 | 8 | void main() { 9 | vUv = uv; 10 | vDistanceBarycenter = barycentric; 11 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 12 | } 13 | `, 14 | 15 | fragmentShader:` 16 | uniform float uTime; 17 | 18 | varying vec2 vUv; 19 | varying vec3 vDistanceBarycenter; 20 | 21 | // This is like 22 | float aastep (float threshold, float dist) { 23 | float afwidth = fwidth(dist) * 0.5; 24 | return smoothstep(threshold - afwidth, threshold + afwidth, dist); 25 | } 26 | 27 | void main() { 28 | float thickness = 0.015; 29 | // this will be our signed distance for the wireframe edge 30 | float d = min(min(vDistanceBarycenter.x, vDistanceBarycenter.y), vDistanceBarycenter.z); 31 | // compute the anti-aliased stroke edge 32 | float edge = 1.0 - aastep(thickness, d); 33 | // now compute the final color of the mesh 34 | vec4 lineColor = vec4(0.1, 0.1, 0.1, 1.0); 35 | vec4 fillColor = vec4(1.0, 1.0, 1.0, 1.0); 36 | gl_FragColor = vec4(mix(fillColor, lineColor, edge)); 37 | }`, 38 | 39 | init: function () { 40 | var mesh; 41 | this.initGeometry(); 42 | this.initShader(); 43 | 44 | mesh = new THREE.Mesh(this.geometry, this.shader); 45 | this.el.setObject3D('mesh', mesh); 46 | }, 47 | 48 | createSpiral: function(pointsNumber) { 49 | var points = []; 50 | var r = 8; 51 | var a = 0; 52 | for (var i = 0; i < pointsNumber; i++) { 53 | var p = (1 - i / pointsNumber); 54 | r -= Math.pow(p, 2) * 0.187; 55 | a += 0.3 - (r / 6) * 0.2; 56 | 57 | /*points.push(new THREE.Vector3( 58 | r * Math.sin(a), 59 | Math.pow(p, 2.5) * 2, 60 | r * Math.cos(a) 61 | ));*/ 62 | 63 | points.push(new THREE.Vector3( 64 | r * Math.sin(a), 65 | 1.0, 66 | r * Math.cos(a) 67 | )); 68 | console.log("POINT " + r * Math.sin(a) + " " + Math.pow(p, 2.5) * 2 + " " + r * Math.cos(a)); 69 | } 70 | return points; 71 | }, 72 | 73 | initGeometry: function() { 74 | var points = this.createSpiral(120); 75 | // Create the flat geometry 76 | var geometry = this.geometry = new THREE.BufferGeometry(); 77 | 78 | // create two times as many vertices as points, as we're going to push them in opposing directions to create a ribbon 79 | geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(points.length * 3 * 2), 3)); 80 | geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(points.length * 2 * 2), 2)); 81 | geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(points.length * 6), 1)); 82 | 83 | points.forEach((b, i) => { 84 | var o = 0.1; 85 | 86 | geometry.attributes.position.setXYZ(i * 2 + 0, b.x, b.y + o, b.z); 87 | geometry.attributes.position.setXYZ(i * 2 + 1, b.x, b.y - o, b.z); 88 | 89 | geometry.attributes.uv.setXY(i * 2 + 0, i / (points.length - 1), 0); 90 | geometry.attributes.uv.setXY(i * 2 + 1, i / (points.length - 1), 1); 91 | 92 | if (i < points.length - 1) { 93 | geometry.index.setX(i * 6 + 0, i * 2); 94 | geometry.index.setX(i * 6 + 1, i * 2 + 1); 95 | geometry.index.setX(i * 6 + 2, i * 2 + 2); 96 | 97 | geometry.index.setX(i * 6 + 0 + 3, i * 2 + 1); 98 | geometry.index.setX(i * 6 + 1 + 3, i * 2 + 3); 99 | geometry.index.setX(i * 6 + 2 + 3, i * 2 + 2); 100 | } 101 | }); 102 | 103 | var calculateBarycenters = this.el.sceneEl.systems.wireframe.calculateBarycenters; 104 | calculateBarycenters(this.geometry); 105 | }, 106 | 107 | initShader: function() { 108 | var uniforms = { 109 | uTime: {type: 'f', value: Math.random() * 3}, 110 | }; 111 | 112 | var shader = this.shader = new THREE.ShaderMaterial({ 113 | uniforms: uniforms, 114 | vertexShader: this.vertexShader, 115 | fragmentShader: this.fragmentShader, 116 | side: THREE.DoubleSide, 117 | transparent: true, 118 | depthTest: false, 119 | }); 120 | shader.speed = Math.random() * 0.4 + 0.8; 121 | } 122 | }) -------------------------------------------------------------------------------- /wind-waker/wind/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wind Waker Wind FX • A-Frame 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /wind-waker/wind/main.js: -------------------------------------------------------------------------------- 1 | const vertexShader = ` 2 | varying vec2 vUv; 3 | 4 | void main() { 5 | vUv = uv; 6 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 7 | } 8 | `; 9 | 10 | const fragmentShader = ` 11 | varying vec2 vUv; 12 | 13 | uniform float uTime; 14 | 15 | void main() { 16 | float len = 0.15; 17 | float falloff = 0.1; 18 | float p = mod(uTime * 0.25, 1.0); 19 | float alpha = smoothstep(len, len - falloff, abs(vUv.x - p)); 20 | float width = smoothstep(len * 2.0, 0.0, abs(vUv.x - p)) * 0.5; 21 | alpha *= smoothstep(width, width - 0.3, abs(vUv.y - 0.5)); 22 | 23 | alpha *= smoothstep(0.5, 0.3, abs(p - 0.5) * (1.0 + len)); 24 | 25 | gl_FragColor.rgb = vec3(1.0); 26 | gl_FragColor.a = alpha; 27 | // gl_FragColor.a += 0.1; 28 | } 29 | `; 30 | 31 | { 32 | let _renderer, _scene, _camera, _controls; 33 | let _geometry; 34 | let _shaders = []; 35 | 36 | window.onload = init; 37 | 38 | function init() { 39 | initWorld(); 40 | initScene(); 41 | } 42 | 43 | //=====// World //========================================// 44 | 45 | function initWorld() { 46 | _renderer = new THREE.WebGLRenderer(); 47 | _renderer.setPixelRatio(2); 48 | _renderer.setSize(window.innerWidth, window.innerHeight); 49 | _renderer.setClearColor(0x2e2f27); 50 | document.body.appendChild(_renderer.domElement); 51 | 52 | _scene = new THREE.Scene(); 53 | 54 | _camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); 55 | _camera.position.set(0, 4, 15); 56 | _camera.target = new THREE.Vector3(0, 1, 0); 57 | 58 | _controls = new THREE.OrbitControls(_camera, document.body); 59 | _controls.target = _camera.target; 60 | _controls.enableDamping = true; 61 | _controls.dampingFactor = 0.1; 62 | _controls.rotateSpeed = 0.1; 63 | 64 | window.addEventListener('resize', resize, false); 65 | resize(); 66 | requestAnimationFrame(render); 67 | } 68 | 69 | function resize() { 70 | _renderer.setSize(window.innerWidth, window.innerHeight); 71 | _camera.aspect = window.innerWidth / window.innerHeight; 72 | _camera.updateProjectionMatrix(); 73 | } 74 | 75 | function render() { 76 | requestAnimationFrame(render); 77 | if (_controls) _controls.update(); 78 | _renderer.render(_scene, _camera); 79 | } 80 | 81 | //=====// Scene //========================================// 82 | 83 | function initScene() { 84 | initGeometry(); 85 | for (let i = 0; i < 6; i++) initMesh(); 86 | requestAnimationFrame(loop); 87 | } 88 | 89 | function createSpiral() { 90 | let points = []; 91 | let r = 8; 92 | let a = 0; 93 | for (let i = 0; i < 120; i++) { 94 | let p = (1 - i / 120); 95 | r -= Math.pow(p, 2) * 0.187; 96 | a += 0.3 - (r / 6) * 0.2; 97 | 98 | points.push(new THREE.Vector3( 99 | r * Math.sin(a), 100 | Math.pow(p, 2.5) * 2, 101 | r * Math.cos(a) 102 | )); 103 | } 104 | return points; 105 | } 106 | 107 | function initGeometry() { 108 | const points = createSpiral(); 109 | 110 | // Create the flat geometry 111 | const geometry = new THREE.BufferGeometry(); 112 | 113 | // create two times as many vertices as points, as we're going to push them in opposing directions to create a ribbon 114 | geometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(points.length * 3 * 2), 3)); 115 | geometry.addAttribute('uv', new THREE.BufferAttribute(new Float32Array(points.length * 2 * 2), 2)); 116 | geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(points.length * 6), 1)); 117 | 118 | points.forEach((b, i) => { 119 | let o = 0.1; 120 | 121 | geometry.attributes.position.setXYZ(i * 2 + 0, b.x, b.y + o, b.z); 122 | geometry.attributes.position.setXYZ(i * 2 + 1, b.x, b.y - o, b.z); 123 | 124 | geometry.attributes.uv.setXY(i * 2 + 0, i / (points.length - 1), 0); 125 | geometry.attributes.uv.setXY(i * 2 + 1, i / (points.length - 1), 1); 126 | 127 | if (i < points.length - 1) { 128 | geometry.index.setX(i * 6 + 0, i * 2); 129 | geometry.index.setX(i * 6 + 1, i * 2 + 1); 130 | geometry.index.setX(i * 6 + 2, i * 2 + 2); 131 | 132 | geometry.index.setX(i * 6 + 0 + 3, i * 2 + 1); 133 | geometry.index.setX(i * 6 + 1 + 3, i * 2 + 3); 134 | geometry.index.setX(i * 6 + 2 + 3, i * 2 + 2); 135 | } 136 | }); 137 | 138 | _geometry = geometry; 139 | } 140 | 141 | function initMesh() { 142 | const uniforms = { 143 | uTime: {type: 'f', value: Math.random() * 3}, 144 | }; 145 | 146 | let shader = new THREE.ShaderMaterial({ 147 | uniforms: uniforms, 148 | vertexShader: vertexShader, 149 | fragmentShader: fragmentShader, 150 | side: THREE.DoubleSide, 151 | transparent: true, 152 | depthTest: false, 153 | }); 154 | shader.speed = Math.random() * 0.4 + 0.8; 155 | _shaders.push(shader); 156 | 157 | let mesh = new THREE.Mesh(_geometry, shader); 158 | mesh.rotation.y = Math.random() * 10; 159 | mesh.scale.setScalar(0.5 + Math.random()); 160 | mesh.scale.y = Math.random() * 0.2 + 0.9; 161 | mesh.position.y = Math.random(); 162 | _scene.add(mesh); 163 | } 164 | 165 | let lastTime = 0; 166 | function loop(e) { 167 | requestAnimationFrame(loop); 168 | _scene.rotation.y += 0.02; 169 | 170 | let delta = e - lastTime; 171 | _shaders.forEach(shader => { 172 | shader.uniforms.uTime.value += delta * 0.001 * shader.speed; 173 | }); 174 | lastTime = e; 175 | } 176 | } --------------------------------------------------------------------------------