繼續玩味之前寫的音樂頻譜作品,將原來在Canvas標簽上的 作圖利用Three.js讓它通過WebGL呈現,這樣就打造出了一個全立體感的頻譜效果了。
項目詳情及源碼
項目GitHub地址:https://github.com/Wayou/3D_Audio_Spectrum_VIsualizer/tree/master
在線演示地址:http://wayou.github.io/3D_Audio_Spectrum_VIsualizer
如果你想的話,可以從這里下載示例音樂:http://pan.baidu.com/s/1eQqqSfS
Note:
- 可以直接點擊'play default' 播放自帶的音樂,神探夏洛克插曲,如果你也看了的話,聽著應該會有感的
- 支持文件拖拽進行播放,將音頻文件拖拽到頁面即可
- 也可以通過文件上傳按鈕選擇一個音頻文件進行播放
- 鼠標拖拽可以移動鏡頭變換視野
- 鼠標滾輪可以進行縮放
-
右上角的控制面板可以進行一些外觀及鏡頭上的設置,可以自己探索玩玩
利用Three.js呈現
關于音頻處理方面的邏輯基本和之前介紹HTML5 Audio API那篇博客里講的差不多,差別只在這個項目里面將頻譜的展示從2d的canvas換成3d的WebGL進行展示,使用的是Three.js。所以只簡單介紹關于3d場景方面的構建,具體實現可以訪問項目GitHub頁面下載源碼。
構建躍動的柱條
每根綠色柱條是一個CubeGeometry,柱條上面的蓋子也是CubeGeometry,只是長度更短而以,同時使用的是白色。
//創建綠色柱條的形狀
var cubeGeometry = new THREE.CubeGeometry(MWIDTH, 1, MTHICKNESS);
//創建綠色柱條的材質
var cubeMaterial = new THREE.MeshPhongMaterial({
color: 0x01FF00,
ambient: 0x01FF00,
specular: 0x01FF00,
shininess: 20,
reflectivity: 5.5
});
//創建白色蓋子的形狀
var capGeometry = new THREE.CubeGeometry(MWIDTH, 0.5, MTHICKNESS);
//創建白色蓋子的材質
var capMaterial = new THREE.MeshPhongMaterial({
color: 0xffffff,
ambient: 0x01FF00,
specular: 0x01FF00,
shininess: 20,
reflectivity: 5.5
});
上面只是創建了形狀及材質,需要將這兩者組合在一起形成一個模型,才是我們看到的實際物體。下面通過一個循環創建了一字排開的柱條和對應的蓋子,然后添加到場景中。
//創建一字排開的柱條和蓋子,并添加到場景中
for (var i = METERNUM - 1; i >= 0; i--) {
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = -45 + (MWIDTH + GAP) * i;
cube.position.y = -1;
cube.position.z = 0.5;
cube.castShadow = true;
cube.name = 'cube' + i;
scene.add(cube);
var cap = new THREE.Mesh(capGeometry, capMaterial);
cap.position.x = -45 + (MWIDTH + GAP) * i;
cap.position.y = 0.5;
cap.position.z = 0.5;
cap.castShadow = true;
cap.name = 'cap' + i;
scene.add(cap);
};
注意到我們為每個物體指定了名稱以方便之后獲取該物體。
添加動畫
動畫部分同時是使用requestAnimation,根據傳入的音頻分析器(analyser)的數據來更新每根柱條的長度。
var renderAnimation = function() {
if (analyser) {
//從音頻分析器中獲取數據
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
var step = Math.round(array.length / METERNUM);
//更新每根柱條的高度
for (var i = 0; i < METERNUM; i++) {
var value = array[i * step] / 4;
value = value < 1 ? 1 : value;
var meter = scene.getObjectByName('cube' + i, true);
meter.scale.y = value;
}
};
//重新渲染畫面
render.render(scene, camera);
requestAnimationFrame(renderAnimation);
};
requestAnimationFrame(renderAnimation);
對于白色蓋子的處理稍微不同,因為它是緩慢下落的,不能使用及時送達的音頻數據來更新它。實現的方式是每次動畫更新中檢查當前柱條的高度與前一時刻蓋子的高度,看誰大,如果柱條更高,則蓋子使用新的高度,否則蓋子高度減1,這樣就實現了緩落的效果。
var renderAnimation = function() {
if (analyser) {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
var step = Math.round(array.length / METERNUM);
for (var i = 0; i < METERNUM; i++) {
var value = array[i * step] / 4;
value = value < 1 ? 1 : value;
var meter = scene.getObjectByName('cube' + i, true),
cap = scene.getObjectByName('cap' + i, true);
meter.scale.y = value;
//計算柱條邊沿尺寸以獲得高度
meter.geometry.computeBoundingBox();
height = (meter.geometry.boundingBox.max.y - meter.geometry.boundingBox.min.y) * value;
//將柱條高度與蓋子高度進行比較
if (height / 2 > cap.position.y) {
cap.position.y = height / 2;
} else {
cap.position.y -= controls.dropSpeed;
};
}
};
//重新渲染畫面
render.render(scene, camera);
requestAnimationFrame(renderAnimation);
};
requestAnimationFrame(renderAnimation);
鏡頭控制
鏡頭的控制使用的是與Three.js搭配的一個插件ObitControls.js,如果你下載了Three.js的源碼可以在里面找到。只需獲取一個鼠標拖動的前后時間差,然后在動畫循環中調用插件進行畫面更新即可。
var orbitControls = new THREE.OrbitControls(camera);
orbitControls.minDistance = 50;
orbitControls.maxDistance = 200;
orbitControls.maxPolarAngle = 1.5;
var renderAnimation = function() {
var delta = clock.getDelta();
orbitControls.update(delta);
if (analyser) {
var array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(array);
var step = Math.round(array.length / METERNUM);
for (var i = 0; i < METERNUM; i++) {
var value = array[i * step] / 4;
value = value < 1 ? 1 : value;
var meter = scene.getObjectByName('cube' + i, true),
cap = scene.getObjectByName('cap' + i, true);
meter.scale.y = value;
//計算柱條邊沿尺寸以獲得高度
meter.geometry.computeBoundingBox();
height = (meter.geometry.boundingBox.max.y - meter.geometry.boundingBox.min.y) * value;
//將柱條高度與蓋子高度進行比較
if (height / 2 > cap.position.y) {
cap.position.y = height / 2;
} else {
cap.position.y -= controls.dropSpeed;
};
}
};
//重新渲染畫面
render.render(scene, camera);
requestAnimationFrame(renderAnimation);
};
requestAnimationFrame(renderAnimation);
注意到在實例化一個ObitControls后,進行了一些角度和鏡頭伸縮方面的設置,限制了用戶把畫面翻轉到平面的底部,也保證了鏡頭在伸縮時不會太遠及太近。
參數控制
右上角的控制面板可以進行畫面的一些參數更改,使用的是谷歌員工創建的一個插件dat.gui.js 。
首先需要定義一個包含全部需要控制的參數的對象:
var controls = new function() {
this.capColor = 0xFFFFFF;
this.barColor = 0x01FF00;
this.ambientColor = 0x0c0c0c;
this.dropSpeed = 0.1;
this.autoRotate = false;
};
然后實例化一個控制器,將這個對象及相應參數進行綁定:
var gui = new dat.GUI();
//添加蓋子下降速度的控制
gui.add(controls, 'dropSpeed', 0.1, 0.5);
//蓋子顏色控制
gui.addColor(controls, 'capColor').onChange(function(e) {
scene.children.forEach(function(child) {
if (child.name.indexOf('cap') > -1) {
child.material.color.setStyle(e);
child.material.ambient = new THREE.Color(e)
child.material.emissive = new THREE.Color(e)
child.material.needsUpdate = true;
}
});
});
//柱條顏色控制
gui.addColor(controls, 'barColor').onChange(function(e) {
scene.children.forEach(function(child) {
if (child.name.indexOf('cube') > -1) {
child.material.color.setStyle(e);
child.material.ambient = new THREE.Color(e)
child.material.emissive = new THREE.Color(e)
child.material.needsUpdate = true;
}
});
});
//鏡頭自動移動控制
gui.add(controls, 'autoRotate').onChange(function(e) {
orbitControls.autoRotate = e;
});
總結
完成了主要功能,但沒達到我預期的效果,我想的是把柱條做成發光的,調研了一下,需要用更復雜的材質,同時也不能用WebGL來渲染畫面了,性能是一方面,同時也還沒研究得那么深入,所以就先出了這個版本先。以后或許弄個水波效果。
REFERENCE
Offical Documentation: http://threejs.org/docs/
A Demo: http://srchea.com/blog/2013/05/experimenting-with-web-audio-api-three-js-webgl/
Another Example: https://github.com/arirusso/three-audio-spectrum
A Working Demo: http://badassjs.com/post/27056714305/plucked-html5-audio-editor-and-threeaudio-js
Dat GUI plugin: https://code.google.com/p/dat-gui/
文章列表