HTML
xxxxxxxxxx
1
<div id="container"></div>
2
3
<button id="start">start</button>
4
5
<script type="x-shader/x-vertex" id="vertexshader">
6
uniform float amplitude;
7
attribute vec3 customColor;
8
attribute vec3 displacement;
9
varying vec3 vNormal;
10
varying vec3 vColor;
11
void main() {
12
vNormal = normal;
13
vColor = customColor;
14
vec3 newPosition = position + normal * amplitude * displacement;
15
gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );
16
}
17
</script>
18
19
<script type="x-shader/x-fragment" id="fragmentshader">
20
varying vec3 vNormal;
21
varying vec3 vColor;
22
void main() {
23
const float ambient = 0.6;
24
vec3 light = vec3( 1.0 );
25
light = normalize( light );
26
float directional = max( dot( vNormal, light ), 0.0 );
27
gl_FragColor = vec4( ( directional + ambient ) * vColor, 1.0 );
28
}
29
</script>
30
CSS
xxxxxxxxxx
1
body {
2
margin: 0px;
3
overflow: hidden;
4
background: rgb(201,14,157); /* Old browsers */
5
background: -moz-linear-gradient(top, rgba(201,14,157,1) 0%, rgba(64,11,102,1) 52%, rgba(22,32,135,1) 100%); /* FF3.6-15 */
6
background: -webkit-linear-gradient(top, rgba(201,14,157,1) 0%,rgba(64,11,102,1) 52%,rgba(22,32,135,1) 100%); /* Chrome10-25,Safari5.1-6 */
7
background: linear-gradient(to bottom, rgba(201,14,157,1) 0%,rgba(64,11,102,1) 52%,rgba(22,32,135,1) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
8
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#c90e9d', endColorstr='#162087',GradientType=0 ); /* IE6-9 */
9
}
10
11
button {
12
background: black;
13
color: white;
14
border-radius: 4px;
15
padding: 10px 20px 8px;
16
position: fixed;
17
top: 40px;
18
left: calc(50vw - 50px);
19
font-size: 11px;
20
text-transform: uppercase;
21
letter-spacing: 0.2em;
22
border: 1px solid #555;
23
cursor: pointer;
24
}
25
26
audio {
27
position: fixed;
28
z-index: 3000;
29
top: 10px;
30
left: 10px;
31
opacity: 0.7;
32
}
JS
xxxxxxxxxx
1
// heavily commented for those trying to learn
2
const initCanvasAudio = name => {
3
// create the new audio
4
const audio = new Audio();
5
audio.src = "https://s3-us-west-2.amazonaws.com/s.cdpn.io/28963/song18.mp3";
6
audio.controls = true;
7
audio.autoplay = true;
8
audio.crossOrigin = "anonymous";
9
document.body.appendChild(audio);
10
11
// wire it up
12
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
13
const source = audioCtx.createMediaElementSource(audio);
14
const volumeControl = audioCtx.createGain();
15
source.connect(audioCtx.destination);
16
source.connect(volumeControl);
17
const analyzer = audioCtx.createAnalyser();
18
volumeControl.connect(analyzer);
19
analyzer.connect(audioCtx.destination);
20
21
//connect the volume adjustments from the user
22
volumeControl.gain.value = audio.volume;
23
24
// now we start with the three initialization
25
let renderer, scene, camera, stats, controls, mesh, uniforms;
26
let width = window.innerWidth,
27
height = window.innerHeight;
28
29
//have to kick off init and the animation
30
init();
31
animate();
32
33
function init() {
34
// create the camera and hook up orbit controls
35
camera = new THREE.PerspectiveCamera(40, width / height, 1, 10000);
36
camera.position.set(0, 0, 100);
37
controls = new THREE.OrbitControls(camera);
38
controls.autoRotate = true;
39
40
// create the scene
41
scene = new THREE.Scene();
42
//scene.background = new THREE.Color(0x300064);
43
44
// create the geometry
45
let geometry = new THREE.TorusKnotGeometry(20, 0.8, 67, 18, 15, 12);
46
geometry.center();
47
let tessellateModifier = new THREE.TessellateModifier(8);
48
for (let i = 0; i < 6; i++) {
49
tessellateModifier.modify(geometry);
50
}
51
geometry = new THREE.BufferGeometry().fromGeometry(geometry);
52
let numFaces = geometry.attributes.position.count / 3;
53
54
//map the colors, fragments
55
let colors = new Float32Array(numFaces * 3 * 3);
56
let displacement = new Float32Array(numFaces * 3 * 3);
57
let color = new THREE.Color();
58
for (let f = 0; f < numFaces; f++) {
59
let index = 9 * f;
60
let h = 0.2 * Math.random();
61
let s = 0.5 + 0.5 * Math.random();
62
let l = 0.5 + 0.5 * Math.random();
63
color.setHSL(h, s, l);
64
let d = 10 * (0.5 - Math.random());
65
for (let i = 0; i < 3; i++) {
66
colors[index + 5 * i] = color.r;
67
colors[index + 8 * i + 1] = color.g;
68
colors[index + 2 * i + 2] = color.b;
69
displacement[index + 3 * i] = d;
70
displacement[index + 3 * i + 1] = d;
71
displacement[index + 3 * i + 2] = d;
72
}
73
}
74
75
// add them to the geometry
76
geometry.addAttribute("customColor", new THREE.BufferAttribute(colors, 3));
77
geometry.addAttribute(
78
"displacement",
79
new THREE.BufferAttribute(displacement, 3)
80
);
81
82
// attach the shader material you see in the html
83
uniforms = {
84
amplitude: { value: 0.0 }
85
};
86
const shaderMaterial = new THREE.ShaderMaterial({
87
uniforms: uniforms,
88
vertexShader: document.getElementById("vertexshader").textContent,
89
fragmentShader: document.getElementById("fragmentshader").textContent
90
});
91
92
// create the mesh (this is where the geometry and material are added to the scene)
93
mesh = new THREE.Mesh(geometry, shaderMaterial);
94
scene.add(mesh);
95
96
// renderer
97
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
98
renderer.setPixelRatio(window.devicePixelRatio);
99
renderer.setSize(width, height);
100
renderer.autoClear = false;
101
renderer.setClearColor(0x000000, 0.0);
102
const container = document.getElementById("container");
103
container.appendChild(renderer.domElement);
104
window.addEventListener("resize", onWindowResize, false);
105
}
106
107
// make it still work when you resize the screen
108
function onWindowResize() {
109
camera.aspect = window.innerWidth / window.innerHeight;
110
camera.updateProjectionMatrix();
111
controls.update();
112
renderer.setSize(window.innerWidth, window.innerHeight);
113
}
114
115
// rAF and get the frequency data from the audio all the time so we can use it to update the amplitude
116
function animate() {
117
var freqData = new Uint8Array(analyzer.frequencyBinCount);
118
analyzer.getByteFrequencyData(freqData);
119
requestAnimationFrame(animate);
120
render(freqData);
121
}
122
123
//render the sucker
124
function render(freqData) {
125
// this is what makes the shader pop. This line of code feeds the audio in
126
uniforms.amplitude.value = numscale(freqData[0], 0, 300, -2, 2);
127
// we have to update the orbit controls anytime we render
128
controls.update();
129
renderer.render(scene, camera);
130
}
131
};
132
133
// chrome needs sound kicked off by the user now
134
const button = document.querySelector("button");
135
button.addEventListener("click", event => {
136
initCanvasAudio();
137
button.remove();
138
});
139
140
// helper function to map scales
141
const numscale = (num, in_min, in_max, out_min, out_max) => {
142
return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
143
};
144
Console errors: 0