Threejs实现音乐节奏表演

简介: 这篇文章详细介绍了如何使用Three.js结合音频频谱分析来创建一个随着音乐节奏变化的3D视觉表演,提供了实现音乐可视化效果的具体步骤和代码示例。

昨天看到一个网站是2维的柱形图随着音乐节奏起伏,我突然想到二维形状可以起伏,三维应该也可以,最终原理应该都是将音乐频谱分解为数据,通过数据的切换不断地改变图形的形状来实现,因为找了分析音乐频谱的源码,解析完,再通过threejs的动画实现了3D场景下的模型随着音乐的节奏而变化,下面做详细步骤的说明:

这只是做个简单的demo,主要是有了音乐的数据,后面的效果可以任意发挥,首先需要新建一个场景,还是之前的老样子,创建场景,相机,灯光,渲染器等,代码如下:

initScene(){
      scene = new THREE.Scene();
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(200,400,200);
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(300,300,600)
      scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(600,200,600)
      scene.add(directionalLight2);
    },
initFloor(){
      let floorGeometry = new THREE.BoxGeometry( 200,1,200);
      let floorMaterial = new THREE.MeshBasicMaterial({color:'#03133E'});
      let floor = new THREE.Mesh( floorGeometry, floorMaterial );
      floor.position.set(0,2,0)
      scene.add(floor)
    },
initRenderer(){
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.container = document.getElementById("container")
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setClearColor('#AAAAAA', 1.0);
      this.container.appendChild(this.renderer.domElement);
    },
    initControl(){
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.maxPolarAngle = Math.PI / 2.2;      // // 最大角度
    },
initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
}

然后在地板上放小方块,如果直接循环放置会放在同一个位置重叠,因为这里要在循环里控制每个小方块之间的距离,大概意思就是每个小方块长宽高为10,每个小方块间隔1,且每铺满11个小方块为一行后就再换一行重头平铺,每个方块的高度设置为0,水平位置为循环中计算出来的位置,最终全部平铺开来,每个都加入到场景中等会用音乐的节奏来控制小方块的跳动,但是经过测试太多的小方块会导致卡顿,(因为模型和修改模型位置和材质都会消耗性能,且因为方块独立跳动没办法进行合并),这里选择用121个小方块,做成11*11的方阵。

initBox(){
      let x = -60; let z = -60;
      for (let i = 0; i < 121; i++) {
        if(i%11 === 0){
          x = x + 11
          z = -60
        }
        z = z + 11
        const geometry = new THREE.BoxGeometry(10, 10, 10);
        const material = new THREE.MeshPhongMaterial({
          color: 0x00ff00
        });
        let boxMesh = new THREE.Mesh(geometry, material);
        this.boxMeshs.push(boxMesh)
        this.boxMeshs[i].position.set(x, 0, z);
        scene.add(this.boxMeshs[i]);
      }
    },

然后就需要去加载音乐了,音乐是设置在html中的audio标签,通过js获取标签元素来获取到连接的音乐资源,通过加载到的音乐分析,并获取他的音乐节奏,这部分我直接贴出代码,就不做详细解释了,如果有问题可以私信我。

initRound(){
      const AudioContext = window.AudioContext || window.webkitAudioContext;
      let audioCtx = new AudioContext();
      if(!this.source){
        this.source = audioCtx.createMediaElementSource(this.myAudio);//通过audio标签获取播放源
        this.analyser = audioCtx.createAnalyser(); // 创建AnalyserNode 用于分析音频频谱的节点
        this.source.connect(this.analyser);
        this.analyser.connect(audioCtx.destination);
        let bufferLength = this.analyser.frequencyBinCount;
        this.dataArray = new Uint8Array(bufferLength);
      }
    },

此时的场景如下:

Threejs实现音乐节奏表演_Threejs

下面就是关键的步骤,通过不断更新的音乐节奏来更新小方块的位置,这里是在渲染动画里去实现,在循环中不断通过this.analyser.getByteFrequencyData(this.dataArray);获取到新的音乐数据,然后根据dataArray的值设置方块的位置,这里是直接设置方块的所在高度,为了更好看的效果还加了颜色设置,通过不同高度切换不同的RGB颜色,并赋值给对应的小方块材质,

initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
      if(this.analyser){
        this.analyser.getByteFrequencyData(this.dataArray);
        for (let i = 0; i < this.dataArray.length-1; i++) {
          if(this.boxMeshs[i]){
            let x = this.boxMeshs[i].position.x
            let z = this.boxMeshs[i].position.z
            this.boxMeshs[i].position.set(x,this.dataArray[i],z)
            this.boxMeshs[i].geometry.parameters.width = this.dataArray[i]
            this.boxMeshs[i].material.color = new THREE.Color(0.8,this.dataArray[i],this.dataArray[i]);
            let r =0, g = 0, b  = 0;
            if(this.dataArray[i]<85){
              r = g = b = this.dataArray[i] * 3
            }else if(this.dataArray[i]<170){
              r = g = (this.dataArray[i] - 85) * 2
              b = 255
            }else{
              r = (this.dataArray[i] - 170) * 3
              g = b = 255
            }
            this.boxMeshs[i].material.color = new THREE.Color('rgb('+r+', '+g+', '+b+')')
          }
        }
      }
    },

基本的代码就这些了,下面附上完整代码

<template>
  <div>
    <div style="position: absolute;top:10px;left:40%;z-index: 100">
      <audio id='myAudio' controls>
        <source src="/static/video/music.mp3" type="audio/mp3">
      </audio>
    </div>
    <div id="container"></div>
  </div>
</template>

<script>
import * as THREE from 'three'
import {OrbitControls} from "three/addons/controls/OrbitControls";

let scene;
export default {
  name: "agv-single",
  data() {
    return{
      camera:null,
      cameraCurve:null,
      renderer:null,
      container:null,
      controls:null,

      myAudio:null,
      analyser:null,
      dataArray:[],
      boxMeshs:[],
      source:null,
    }
  },
  methods:{
    initScene(){
      scene = new THREE.Scene();
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(200,400,200);
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(300,300,600)
      scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(600,200,600)
      scene.add(directionalLight2);
    },
    initFloor(){
      let floorGeometry = new THREE.BoxGeometry( 200,1,200);
      let floorMaterial = new THREE.MeshBasicMaterial({color:'#03133E'});
      let floor = new THREE.Mesh( floorGeometry, floorMaterial );
      floor.position.set(0,2,0)
      scene.add(floor)
    },
    initRound(){
      const AudioContext = window.AudioContext || window.webkitAudioContext;
      let audioCtx = new AudioContext();
      if(!this.source){
        this.source = audioCtx.createMediaElementSource(this.myAudio);//通过audio标签获取播放源
        this.analyser = audioCtx.createAnalyser(); // 创建AnalyserNode 用于分析音频频谱的节点
        this.source.connect(this.analyser);
        this.analyser.connect(audioCtx.destination);
        let bufferLength = this.analyser.frequencyBinCount;
        this.dataArray = new Uint8Array(bufferLength);
      }
    },
    initBox(){
      let x = -60; let z = -60;
      for (let i = 0; i < 121; i++) {
        if(i%11 === 0){
          x = x + 11
          z = -60
        }
        z = z + 11
        const geometry = new THREE.BoxGeometry(10, 10, 10);
        const material = new THREE.MeshPhongMaterial({
          color: 0x00ff00
        });
        let boxMesh = new THREE.Mesh(geometry, material);
        this.boxMeshs.push(boxMesh)
        this.boxMeshs[i].position.set(x, 0, z);
        scene.add(this.boxMeshs[i]);
      }
    },
    initRenderer(){
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.container = document.getElementById("container")
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setClearColor('#AAAAAA', 1.0);
      this.container.appendChild(this.renderer.domElement);
    },
    initControl(){
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.maxPolarAngle = Math.PI / 2.2;      // // 最大角度
    },
    initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
      if(this.analyser){
        this.analyser.getByteFrequencyData(this.dataArray);
        for (let i = 0; i < this.dataArray.length-1; i++) {
          if(this.boxMeshs[i]){
            let x = this.boxMeshs[i].position.x
            let z = this.boxMeshs[i].position.z
            this.boxMeshs[i].position.set(x,this.dataArray[i],z)
            this.boxMeshs[i].geometry.parameters.width = this.dataArray[i]
            this.boxMeshs[i].material.color = new THREE.Color(0.8,this.dataArray[i],this.dataArray[i]);
            let r =0, g = 0, b  = 0;
            if(this.dataArray[i]<85){
              r = g = b = this.dataArray[i] * 3
            }else if(this.dataArray[i]<170){
              r = g = (this.dataArray[i] - 85) * 2
              b = 255
            }else{
              r = (this.dataArray[i] - 170) * 3
              g = b = 255
            }
            this.boxMeshs[i].material.color = new THREE.Color('rgb('+r+', '+g+', '+b+')')
          }
        }
      }
    },
    initPage(){
      this.initScene();
      this.initCamera();
      this.initLight();
      this.initFloor();
      this.initRenderer();
      this.initControl();
      this.initBox();
      this.initAnimate();
    }
  },
  mounted() {
    this.myAudio = document.querySelector('#myAudio');
    this.myAudio.addEventListener('play', () => {
      this.initRound();
    })
    this.initPage()
  }
}
</script>

<style scoped>
#container{
  position: absolute;
  width:100%;
  height:100%;
  overflow: hidden;
}

</style>

效果图如下

Threejs实现音乐节奏表演_数据_02

相关文章
|
Kubernetes Cloud Native 开发工具
Argo与Flux在云原生GitOps实践上的能力对比与分析
随着云原生技术的普及和落地,越来越多的云原生应用被部署到生产环境中,由于云原生应用通常都是基于云的分布式部署模式,且每个应用可能是由多个功能组件互相调用来一起提供完整的服务的,每个组件都有自己独立的迭代流程和计划。在这种情况下,功能组件越多,意味着应用的发布管理越复杂,如果没有一个好的方案或者系统来管理复杂应用的发布上线的话,业务面临的风险也是非常大的。开源社区在复杂应用发布管理方面逐渐开始发力,
5171 1
Argo与Flux在云原生GitOps实践上的能力对比与分析
|
存储 Android开发 Python
使用 MitmProxy 自动抓取微信公众号阅读数、点赞和再看数据(一)
某天接到一个需要抓取某某微信公众的所有历史文章的阅读数、点赞和再看数据的需求。 为了解放双手,就用 Python 代码撸一个,选择 MitmProxy 代理作为抓包工具,因为它可以使用 Python 代码监听抓取到的 url,用于自动获取 cookie 等场景。
2136 0
使用 MitmProxy 自动抓取微信公众号阅读数、点赞和再看数据(一)
|
11月前
|
存储 Java easyexcel
招行面试:100万级别数据的Excel,如何秒级导入到数据库?
本文由40岁老架构师尼恩撰写,分享了应对招商银行Java后端面试绝命12题的经验。文章详细介绍了如何通过系统化准备,在面试中展示强大的技术实力。针对百万级数据的Excel导入难题,尼恩推荐使用阿里巴巴开源的EasyExcel框架,并结合高性能分片读取、Disruptor队列缓冲和高并发批量写入的架构方案,实现高效的数据处理。此外,文章还提供了完整的代码示例和配置说明,帮助读者快速掌握相关技能。建议读者参考《尼恩Java面试宝典PDF》进行系统化刷题,提升面试竞争力。关注公众号【技术自由圈】可获取更多技术资源和指导。
ThreeJs给物体添加贴图
这篇文章详细说明了在Three.js中如何给3D物体添加贴图,并展示了实现局部贴图的技术和方法。
631 1
ThreeJs给物体添加贴图
|
机器学习/深度学习 决策智能
深度学习中的对抗性训练
在这篇技术性文章中,我们将深入探讨深度学习中的对抗性训练。这种训练方法通过引入对抗性样本来提高模型的鲁棒性和泛化能力。文章将从对抗性训练的基本概念、原理以及实现方法等方面进行详细介绍,并结合实际案例分析其在实际应用中的效果和挑战。通过对这一主题的探讨,希望能够为读者提供有益的技术参考和启示。
645 1
|
前端开发
吃豆人来袭:CSS打造经典游戏动画,让网页设计更有趣!
吃豆人来袭:CSS打造经典游戏动画,让网页设计更有趣!
Threejs制作大海效果
这篇文章详细介绍了使用Three.js制作大海效果的技术细节,包括创建水面模型、应用波纹纹理以及实现动态波浪效果的方法。
377 0
|
API
解决http下navigator.clipboard为undefined问题
总之,要解决 `navigator.clipboard`为 `undefined`的问题,你需要确保遵循浏览器的安全策略,使用HTTPS,获得用户授权,并在受信任的上下文中使用clipboard API。此外,还要确保你的代码在支持该API的浏览器上运行。如果问题仍然存在,可能需要进一步调查特定浏览器和环境的问题。
2204 3
|
机器学习/深度学习 数据采集 数据可视化
探索性数据分析(EDA)
探索性数据分析(EDA)
842 0
|
Python
【从零学习python 】42.Python中的内置属性和方法
【从零学习python 】42.Python中的内置属性和方法
125 0