Three.js + HTML5 Audio API 打造3D音乐频谱,Let’s ROCK!

简介: 继续玩味之前写的音乐频谱作品,将原来在Canvas标签上的 作图利用Three.js让它通过WebGL呈现,这样就打造出了一个全立体感的频谱效果了。 项目详情及源码 项目GitHub地址:https://github.

继续玩味之前写的音乐频谱作品,将原来在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/

目录
相关文章
|
13天前
|
人工智能 程序员 UED
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【01】完成新年倒计时页面-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
|
11天前
|
前端开发 JavaScript
【02】v1.0.1更新增加倒计时完成后的放烟花页面-优化播放器-优化结构目录-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【02】v1.0.1更新增加倒计时完成后的放烟花页面-优化播放器-优化结构目录-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
【02】v1.0.1更新增加倒计时完成后的放烟花页面-优化播放器-优化结构目录-蛇年新年快乐倒计时领取礼物放烟花html代码优雅草科技央千澈写采用html5+div+CSS+JavaScript-优雅草卓伊凡-做一条关于新年的代码分享给你们-为了C站的分拼一下子
|
14天前
html+js+css实现的建筑方块立体数字时钟源码
html+js+css实现的建筑方块立体数字时钟源码
63 33
|
15天前
|
移动开发 HTML5
HTML5 SVG实现可爱的小鸟卡通动画3D特效
HTML5 SVG实现可爱的小鸟卡通动画
53 29
|
9天前
|
JavaScript 前端开发 安全
盘点原生JS中目前最没用的几个功能API
在JavaScript的发展历程中,许多功能与API曾风光无限,但随着技术进步和语言演化,部分功能逐渐被淘汰或被更高效的替代方案取代。例如,`with`语句使代码作用域复杂、可读性差;`void`操作符功能冗余且影响可读性;`eval`函数存在严重安全风险和性能问题;`unescape`和`escape`函数已被`decodeURIComponent`和`encodeURIComponent`取代;`arguments`对象则被ES6的剩余参数语法替代。这些变化体现了JavaScript不断优化的趋势,开发者应紧跟技术步伐,学习新技能,适应新技术环境。
34 10
|
2月前
一个好看的小时钟html+js+css源码
一个好看的小时钟html+js+css源码
111 24
|
3月前
|
JSON 缓存 JavaScript
深入浅出:使用Node.js构建RESTful API
在这个数字时代,API已成为软件开发的基石之一。本文旨在引导初学者通过Node.js和Express框架快速搭建一个功能完备的RESTful API。我们将从零开始,逐步深入,不仅涉及代码编写,还包括设计原则、最佳实践及调试技巧。无论你是初探后端开发,还是希望扩展你的技术栈,这篇文章都将是你的理想指南。
|
2月前
|
JSON JavaScript 前端开发
深入浅出Node.js:从零开始构建RESTful API
在数字化时代的浪潮中,后端开发作为连接用户与数据的桥梁,扮演着至关重要的角色。本文将引导您步入Node.js的奇妙世界,通过实践操作,掌握如何使用这一强大的JavaScript运行时环境构建高效、可扩展的RESTful API。我们将一同探索Express框架的使用,学习如何设计API端点,处理数据请求,并实现身份验证机制,最终部署我们的成果到云服务器上。无论您是初学者还是有一定基础的开发者,这篇文章都将为您打开一扇通往后端开发深层知识的大门。
61 12
|
2月前
|
Web App开发 移动开发 HTML5
html5 + Three.js 3D风雪封印在棱镜中的梅花鹿动效源码
html5 + Three.js 3D风雪封印在棱镜中的梅花鹿动效源码。画面中心是悬浮于空的梅花鹿,其四周由白色线段组成了一个6边形将中心的梅花鹿包裹其中。四周漂浮的白雪随着多边形的转动而同步旋转。建议使用支持HTML5与css3效果较好的火狐(Firefox)或谷歌(Chrome)等浏览器预览本源码。
95 2
|
2月前
|
移动开发 HTML5
HTML5 3D地球仪可按经纬坐标定位特效
这是一个基于HTML5的3D地球仪动画,地球仪不仅可以自动自西向东旋转,而且还可以旋转到指定经纬度坐标。另外,还有一个控制面板,可以控制地球是否自转、光晕是否显示,以及地理缩放。你也可以通过拖拽鼠标来改变地球仪的视角,可以将它移至南北极的视角,也可以移至赤道的视角,非常方便。需要的朋友可下载试试!
75 2